// Find user
var userID int
- var hashedPassword string
- var salt string
found := false
var username string
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
}
}
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
// Return success
utils.JSONResponse(w, http.StatusOK, map[string]any{
- "migration_started": false,
- "username": username,
+ "migration_started": false,
+ "username": username,
+ "available_backup_codes": availableBackupCodes,
})
}
}
// 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,
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 {
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{
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 {
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
}
}
// 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)
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,
})
}
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 {
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 {
.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);
<button class="btn btn-outline-secondary me-2" onclick={openSettingsModal}
><Fa icon={faSliders} /></button
>
- <button class="btn btn-outline-secondary" onclick={logout(null)}
+ <button class="btn btn-outline-secondary" onclick={() => logout(null)}
><Fa icon={faRightFromBracket} /></button
>
</div>
>
Backup-Codes generieren
{#if isGeneratingBackupCodes}
- <!-- svelte-ignore a11y_no_static_element_interactions -->
- <div class="spinner-border" role="status">
+ <div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
{/if}
</button>
</form>
- {#if showBackupCodesPasswordIncorrect}
- <div class="alert alert-danger mt-2" role="alert" transition:slide>
- Das eingegebene Passwort ist falsch!
- </div>
- {/if}
{#if backupCodes.length > 0}
<div class="alert alert-success alert-dismissible mt-3" transition:slide>
<h6>Deine Backup-Codes:</h6>
- Notiere dir die Codes, können nach dem Schließen dieses Fenstern nicht erneut angezeigt
- werden!
+ <p>
+ Notiere dir die Codes, sie können nach dem Schließen dieses Fenstern nicht
+ erneut angezeigt werden!
+ </p>
<button class="btn btn-secondary my-2" onclick={copyBackupCodes}>
<Fa icon={codesCopiedSuccess ? faCheck : faCopy} />
Codes kopieren
{/if}
{#if showBackupCodesError}
<div class="alert alert-danger mt-2" role="alert" transition:slide>
- Fehler beim Erstellen der Backup-Codes!
+ Fehler beim Erstellen der Backup-Codes! Vielleicht stimmt das Passwort nicht?
</div>
{/if}
</div>