From: PhiTux Date: Tue, 22 Jul 2025 15:31:44 +0000 (+0200) Subject: backup codes now working X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=4ced48ec0679c9baa438662abd4c55a2f96a5b95;p=DailyTxT.git backup codes now working --- diff --git a/backend/handlers/users.go b/backend/handlers/users.go index 645cad7..648d520 100644 --- a/backend/handlers/users.go +++ b/backend/handlers/users.go @@ -45,8 +45,6 @@ func Login(w http.ResponseWriter, r *http.Request) { // Find user var userID int - var hashedPassword string - var salt string found := false var username string @@ -61,12 +59,6 @@ func Login(w http.ResponseWriter, r *http.Request) { if id, ok := user["user_id"].(float64); ok { userID = int(id) } - if pwd, ok := user["password"].(string); ok { - hashedPassword = pwd - } - if s, ok := user["salt"].(string); ok { - salt = s - } break } } @@ -191,23 +183,19 @@ func Login(w http.ResponseWriter, r *http.Request) { return } - // Verify password - if !utils.VerifyPassword(req.Password, hashedPassword) { - utils.Logger.Printf("Login failed. Password for user '%s' is incorrect", req.Username) - http.Error(w, "User/Password combination not found", http.StatusNotFound) - return - } - - // Get intermediate key - derivedKey, err := utils.DeriveKeyFromPassword(req.Password, salt) + derivedKey, availableBackupCodes, err := utils.CheckPasswordForUser(userID, req.Password) if err != nil { + utils.Logger.Printf("Error checking password for user '%s': %v", req.Username, err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return + } else if derivedKey == "" { + utils.Logger.Printf("Login failed. Password for user '%s' is incorrect", req.Username) + http.Error(w, "User/Password combination not found", http.StatusNotFound) + return } - derivedKeyBase64 := base64.StdEncoding.EncodeToString(derivedKey) // Create JWT token - token, err := utils.GenerateToken(userID, req.Username, derivedKeyBase64) + token, err := utils.GenerateToken(userID, username, derivedKey) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return @@ -225,8 +213,9 @@ func Login(w http.ResponseWriter, r *http.Request) { // Return success utils.JSONResponse(w, http.StatusOK, map[string]any{ - "migration_started": false, - "username": username, + "migration_started": false, + "username": username, + "available_backup_codes": availableBackupCodes, }) } @@ -660,7 +649,7 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) { } // Get derived key from context - derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string) + _, ok = r.Context().Value(utils.DerivedKeyKey).(string) if !ok { utils.JSONResponse(w, http.StatusUnauthorized, map[string]any{ "success": false, @@ -679,6 +668,23 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) { return } + derivedKey, availableBackupCodes, err := utils.CheckPasswordForUser(userID, req.OldPassword) + if err != nil { + utils.JSONResponse(w, http.StatusInternalServerError, map[string]any{ + "success": false, + "message": fmt.Sprintf("Error checking old password: %v", err), + }) + return + } else if len(derivedKey) == 0 { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": "Old password is incorrect", + "password_incorrect": true, + "available_backup_codes": availableBackupCodes, + }) + return + } + // Get user data users, err := utils.GetUsers() if err != nil { @@ -718,24 +724,6 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) { return } - currentPassword, ok := user["password"].(string) - if !ok { - utils.JSONResponse(w, http.StatusInternalServerError, map[string]any{ - "success": false, - "message": "Current hashed password not found for user", - }) - return - } - - if !utils.VerifyPassword(req.OldPassword, currentPassword) { - utils.JSONResponse(w, http.StatusOK, map[string]any{ - "success": false, - "message": "Old password is incorrect", - "password_incorrect": true, - }) - return - } - newHashedPassword, err := utils.HashPassword(req.NewPassword) if err != nil { utils.JSONResponse(w, http.StatusInternalServerError, map[string]any{ @@ -813,6 +801,9 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) { user["salt"] = saltBase64 user["enc_enc_key"] = encEncKey + // Remove backup codes if they exist + user["backup_codes"] = []any{} + // Update users data for i, u := range usersList { if uMap, ok := u.(map[string]any); ok && uMap["user_id"] == userID { @@ -882,58 +873,30 @@ func DeleteAccount(w http.ResponseWriter, r *http.Request) { return } - // Check if password is correct - users, err := utils.GetUsers() - if err != nil { - utils.JSONResponse(w, http.StatusOK, map[string]any{ - "success": false, - "message": fmt.Sprintf("Error retrieving users: %v", err), - }) - return - } - usersList, ok := users["users"].([]any) - if !ok { + derived_key, _, err := utils.CheckPasswordForUser(userID, req.Password) + if err != nil || len(derived_key) == 0 { utils.JSONResponse(w, http.StatusOK, map[string]any{ - "success": false, - "message": "Users data is not in the correct format", + "success": false, + "message": "Error checking password", + "password_incorrect": true, }) return } - var user map[string]any - for _, u := range usersList { - uMap, ok := u.(map[string]any) - if !ok { - continue - } - if id, ok := uMap["user_id"].(float64); ok && int(id) == userID { - user = uMap - break - } - } - - if user == nil { + // Get User data + users, err := utils.GetUsers() + if err != nil { utils.JSONResponse(w, http.StatusOK, map[string]any{ "success": false, - "message": "User not found", + "message": fmt.Sprintf("Error retrieving users: %v", err), }) return } - - password, ok := user["password"].(string) + usersList, ok := users["users"].([]any) if !ok { utils.JSONResponse(w, http.StatusOK, map[string]any{ "success": false, - "message": "Current hashed password not found for user", - }) - return - } - - if !utils.VerifyPassword(req.Password, password) { - utils.JSONResponse(w, http.StatusOK, map[string]any{ - "success": false, - "message": "Password is incorrect", - "password_incorrect": true, + "message": "Users data is not in the correct format", }) return } @@ -1004,34 +967,18 @@ func CreateBackupCodes(w http.ResponseWriter, r *http.Request) { } // Check if password is correct - correct, backupCodes, err := utils.CheckPasswordForUser(userID, req.Password) - if err != nil { + derivedKey, backup_codes, err := utils.CheckPasswordForUser(userID, req.Password) + if err != nil || len(derivedKey) == 0 { utils.Logger.Printf("Error checking password for user %d: %v", userID, err) utils.JSONResponse(w, http.StatusOK, map[string]any{ "success": false, - "message": err, - }) - return - } else if !correct { - utils.JSONResponse(w, http.StatusOK, map[string]any{ - "success": false, - "message": "Password is incorrect", - "password_incorrect": true, + "message": "Error checking password", }) return } - // otherwise, we have the correct password - // Get derived key from context - derivedKey, ok := r.Context().Value(utils.DerivedKeyKey).(string) - if !ok { - utils.JSONResponse(w, http.StatusUnauthorized, map[string]any{ - "success": false, - "message": "User not authenticated", - }) - return - } + // otherwise, we have the correct password // Generate backup codes codes, codeData, err := utils.GenerateBackupCodes(derivedKey) @@ -1052,9 +999,14 @@ func CreateBackupCodes(w http.ResponseWriter, r *http.Request) { return } + available_backup_codes := len(codes) + if backup_codes == -1 { + available_backup_codes = -1 + } + utils.JSONResponse(w, http.StatusOK, map[string]any{ "success": true, "backup_codes": codes, - "available_backup_codes": backupCodes, + "available_backup_codes": available_backup_codes, }) } diff --git a/backend/utils/security.go b/backend/utils/security.go index da535fe..8a3642c 100644 --- a/backend/utils/security.go +++ b/backend/utils/security.go @@ -409,20 +409,20 @@ func GetEncryptionKey(userID int, derivedKey string) (string, error) { return "", fmt.Errorf("user not found") } -// CheckPasswordForUser checks if the provided password matches the user's password OR on of his backup codes -// Returns true if the password matches, false otherwise -// Return the amount of backup codes available for the user (-1 if password does not match) -func CheckPasswordForUser(userID int, password string) (bool, int, error) { +// CheckPasswordForUser checks if the provided password matches the user's password OR on of his backup codes. +// Returns the derivedKey, if successfully validating password, otherwise empty string +// Return the amount of backup codes available for the user (-1 if password does not match or if backup code was NOT used). +func CheckPasswordForUser(userID int, password string) (string, int, error) { // Get users users, err := GetUsers() if err != nil { - return false, -1, fmt.Errorf("error retrieving users: %v", err) + return "", -1, fmt.Errorf("error retrieving users: %v", err) } // Find user usersList, ok := users["users"].([]any) if !ok { - return false, -1, fmt.Errorf("users.json is not in the correct format") + return "", -1, fmt.Errorf("users.json is not in the correct format") } for _, u := range usersList { @@ -434,34 +434,64 @@ func CheckPasswordForUser(userID int, password string) (bool, int, error) { if id, ok := user["user_id"].(float64); ok && int(id) == userID { passwordHash, ok := user["password"].(string) if !ok { - return false, -1, fmt.Errorf("user data is not in the correct format") + return "", -1, fmt.Errorf("user data is not in the correct format") } if VerifyPassword(password, passwordHash) { - return true, -1, nil + // Calculate derived key + derKey, err := DeriveKeyFromPassword(password, user["salt"].(string)) + if err != nil { + return "", -1, fmt.Errorf("error deriving key from password: %v", err) + } + + return base64.StdEncoding.EncodeToString(derKey), -1, nil } // Check backup codes backupCodes, ok := user["backup_codes"].([]any) if !ok { - return false, -1, fmt.Errorf("user backup codes are not in the correct format") + return "", -1, nil } - for _, code := range backupCodes { - codeStr, ok := code.(string) + for i, code := range backupCodes { + codeStr, ok := code.(map[string]any)["password"].(string) if !ok { + Logger.Printf("Invalid backup code format for user %d: %v", userID, code) continue // Skip invalid codes } - if VerifyPassword(password, codeStr) { - return true, -1, nil + + if !VerifyPassword(password, codeStr) { + continue + } + + // Password matched the code! Remove backup code + backupCodes = append(backupCodes[:i], backupCodes[i+1:]...) + + // Update user data + user["backup_codes"] = backupCodes + if err := WriteUsers(users); err != nil { + return "", -1, fmt.Errorf("error saving updated user data: %v", err) } + + // Calculate derived key + tempKey, err := DeriveKeyFromPassword(password, code.(map[string]any)["salt"].(string)) + if err != nil { + return "", -1, fmt.Errorf("error deriving key from password: %v", err) + } + + derKey, err := DecryptText(code.(map[string]any)["enc_derived_key"].(string), base64.URLEncoding.EncodeToString(tempKey)) + if err != nil { + return "", -1, fmt.Errorf("error decrypting derived key: %v", err) + } + + return derKey, len(backupCodes), nil } - return false, -1, nil // Password does not match + return "", -1, nil } } - return false, -1, fmt.Errorf("user not found") + return "", -1, nil } func CreatePasswordString() string { diff --git a/frontend/src/routes/(authed)/+layout.svelte b/frontend/src/routes/(authed)/+layout.svelte index a233d56..849d1a7 100644 --- a/frontend/src/routes/(authed)/+layout.svelte +++ b/frontend/src/routes/(authed)/+layout.svelte @@ -548,9 +548,6 @@ .then((response) => { if (response.data.success) { backupCodes = response.data.backup_codes; - } else if (response.data.password_incorrect) { - console.error('Error creating backup codes: Password incorrect'); - showBackupCodesPasswordIncorrect = true; } else { console.error('Error creating backup codes'); console.error(response.data); @@ -624,7 +621,7 @@ - @@ -1200,23 +1197,19 @@ > Backup-Codes generieren {#if isGeneratingBackupCodes} - -
+
Loading...
{/if} - {#if showBackupCodesPasswordIncorrect} - - {/if} {#if backupCodes.length > 0}
Deine Backup-Codes:
- Notiere dir die Codes, können nach dem Schließen dieses Fenstern nicht erneut angezeigt - werden! +

+ Notiere dir die Codes, sie können nach dem Schließen dieses Fenstern nicht + erneut angezeigt werden! +

diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 9ffc4a5..827e109 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -10,6 +10,7 @@ import { API_URL } from '$lib/APIurl.js'; import trianglify from 'trianglify'; import { alwaysShowSidenav } from '$lib/helpers.js'; + import * as bootstrap from 'bootstrap'; let { children } = $props(); let inDuration = 150; @@ -20,8 +21,20 @@ return config; }); + let available_backup_codes = $state(0); + axios.interceptors.response.use( (response) => { + if (response.data && response.data.available_backup_codes >= 0) { + available_backup_codes = response.data.available_backup_codes; + // show toast + if (available_backup_codes < 6) { + let toast = new bootstrap.Toast( + document.getElementById('toastAvailableBackupCodesWarning') + ); + toast.show(); + } + } return response; }, (error) => { @@ -110,6 +123,22 @@
{/key} + +
+ +