Added UI for mfa and other profile settings
This commit is contained in:
@@ -59,7 +59,6 @@ func APIRegister(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
log.Println("POST [api/register] " + r.RemoteAddr + ": Successfully created user")
|
||||
}
|
||||
|
||||
func APILogin(w http.ResponseWriter, r *http.Request) {
|
||||
var creds struct {
|
||||
Username string `json:"username"`
|
||||
@@ -110,7 +109,6 @@ func APILogin(w http.ResponseWriter, r *http.Request) {
|
||||
issueLoginSession(w, r, user)
|
||||
log.Println("POST [api/login] " + r.RemoteAddr + ": Successfully logged in")
|
||||
}
|
||||
|
||||
func APILoginTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
TwoFactorToken string `json:"two_factor_token"`
|
||||
@@ -165,6 +163,117 @@ func APILoginTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("POST [api/login/2fa] " + r.RemoteAddr + ": Successfully logged in with 2FA")
|
||||
}
|
||||
|
||||
func AccountUpdateUsername(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
log.Println("POST [api/account/username] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := strings.TrimSpace(req.Username)
|
||||
if username == "" || req.Password == "" {
|
||||
http.Error(w, "Username and password required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
claims := r.Context().Value(auth.UserContextKey).(*auth.Claims)
|
||||
user, err := storage.GetUserById(claims.UserID)
|
||||
if err != nil {
|
||||
log.Println("POST [api/account/username] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !auth.CheckPasswordHash(req.Password, user.Password) {
|
||||
http.Error(w, "Invalid password", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err := storage.UpdateUserUsername(user.ID, username); err != nil {
|
||||
log.Println("POST [api/account/username] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "Username already exists or could not be saved", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"username": strings.ToLower(username),
|
||||
})
|
||||
log.Println("POST [api/account/username] " + r.RemoteAddr + ": Updated username")
|
||||
}
|
||||
func AccountUpdatePassword(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
CurrentPassword string `json:"current_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
log.Println("POST [api/account/password] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.CurrentPassword == "" || req.NewPassword == "" {
|
||||
http.Error(w, "Current and new password required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(req.NewPassword) > 72 {
|
||||
http.Error(w, "Password exceeds the maximum allowed length of 72 characters", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
if req.CurrentPassword == req.NewPassword {
|
||||
http.Error(w, "New password must be different", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
claims := r.Context().Value(auth.UserContextKey).(*auth.Claims)
|
||||
user, err := storage.GetUserById(claims.UserID)
|
||||
if err != nil {
|
||||
log.Println("POST [api/account/password] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !auth.CheckPasswordHash(req.CurrentPassword, user.Password) {
|
||||
http.Error(w, "Invalid password", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
hashed, err := auth.HashPassword(req.NewPassword)
|
||||
if err != nil {
|
||||
log.Println("POST [api/account/password] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "Could not hash password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := storage.UpdateUserPassword(user.ID, hashed); err != nil {
|
||||
log.Println("POST [api/account/password] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "Could not update password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := storage.RevokeAllRefreshTokensForUser(user.ID); err != nil {
|
||||
log.Println("POST [api/account/password] " + r.RemoteAddr + ": " + err.Error())
|
||||
http.Error(w, "Could not revoke old sessions", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = hashed
|
||||
issueLoginSession(w, r, user)
|
||||
log.Println("POST [api/account/password] " + r.RemoteAddr + ": Updated password")
|
||||
}
|
||||
func TwoFactorSetup(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -222,7 +331,6 @@ func TwoFactorSetup(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
log.Println("POST [api/2fa/setup] " + r.RemoteAddr + ": Created 2FA setup challenge")
|
||||
}
|
||||
|
||||
func TwoFactorEnable(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -275,7 +383,6 @@ func TwoFactorEnable(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
log.Println("POST [api/2fa/enable] " + r.RemoteAddr + ": Enabled 2FA and generated recovery codes")
|
||||
}
|
||||
|
||||
func TwoFactorDisable(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -326,7 +433,6 @@ func TwoFactorDisable(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{"two_factor_enabled": false})
|
||||
log.Println("POST [api/2fa/disable] " + r.RemoteAddr + ": Disabled 2FA")
|
||||
}
|
||||
|
||||
func TwoFactorRegenerateRecoveryCodes(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -396,7 +502,6 @@ func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
clearAuthCookies(w)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func TestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims, _ := utils.IsLoggedIn(w, r)
|
||||
|
||||
@@ -412,7 +517,6 @@ func TestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
log.Println("GET [api/ping] " + r.RemoteAddr + ": Successfully tested connection")
|
||||
}
|
||||
|
||||
func RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
@@ -456,7 +560,6 @@ func RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
issueLoginSession(w, r, user)
|
||||
log.Println("POST [api/refresh] " + r.RemoteAddr + ": Successfully refreshed token")
|
||||
}
|
||||
|
||||
func UserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
log.Println("GET [api/userinfo] " + r.RemoteAddr + ": Method " + r.Method + " not allowed")
|
||||
@@ -525,7 +628,6 @@ func UserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
log.Println("GET [api/userinfo] " + r.RemoteAddr + ": Successfully retrieved user info of " + user.Username + " (" + user.ID + ")")
|
||||
}
|
||||
|
||||
func issueLoginSession(w http.ResponseWriter, r *http.Request, user models.User) {
|
||||
secret := []byte(os.Getenv("JWT_SECRET"))
|
||||
if len(secret) == 0 {
|
||||
@@ -599,7 +701,6 @@ func generateRecoveryCodes(count int) ([]string, []string, error) {
|
||||
|
||||
return codes, hashes, nil
|
||||
}
|
||||
|
||||
func generateRecoveryCode() (string, error) {
|
||||
bytes := make([]byte, 10)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
@@ -609,7 +710,6 @@ func generateRecoveryCode() (string, error) {
|
||||
raw := hex.EncodeToString(bytes)
|
||||
return raw[0:5] + "-" + raw[5:10] + "-" + raw[10:15] + "-" + raw[15:20], nil
|
||||
}
|
||||
|
||||
func normalizeRecoveryCode(code string) string {
|
||||
code = strings.TrimSpace(code)
|
||||
code = strings.ReplaceAll(code, "-", "")
|
||||
@@ -637,7 +737,6 @@ func setAuthCookies(w http.ResponseWriter, accessToken, refreshToken string) {
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
||||
func clearAuthCookies(w http.ResponseWriter) {
|
||||
for _, name := range []string{"access_token", "refresh_token"} {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
|
||||
Reference in New Issue
Block a user