"Passwort vergessen?" appears on the login screen when SMTP and BASE_URL are configured. The request always gets the same answer and sends the mail in the background (at most every 2 minutes per account). The link holds a random token valid once for one hour; only its SHA-256 is stored. Setting the new password invalidates all open links and ends every session. The app removes the token from the address bar and offers a way back for expired links. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
52 lines
1.7 KiB
Go
52 lines
1.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestPasswordResetToken(t *testing.T) {
|
|
store, err := NewStore(filepath.Join(t.TempDir(), "reset.db"))
|
|
if err != nil {
|
|
t.Fatalf("NewStore: %v", err)
|
|
}
|
|
defer store.Close()
|
|
u, _ := store.CreateUser("anna@example.com", "old", "Anna", "member")
|
|
|
|
raw, err := store.CreatePasswordResetToken(u.ID)
|
|
if err != nil || len(raw) != 64 {
|
|
t.Fatalf("token: %q %v", raw, err)
|
|
}
|
|
var stored string
|
|
_ = store.db.QueryRow(`SELECT token_hash FROM password_reset_tokens`).Scan(&stored)
|
|
if stored == raw {
|
|
t.Fatalf("only the hash may be stored")
|
|
}
|
|
if _, err := store.CreatePasswordResetToken(u.ID); err != ErrResetTooSoon {
|
|
t.Errorf("second request within the cooldown must be refused, got %v", err)
|
|
}
|
|
|
|
before, _ := store.GetUserByID(u.ID)
|
|
if _, err := store.ResetPasswordWithToken("falsch", "new"); err != ErrResetTokenInvalid {
|
|
t.Errorf("unknown token must fail")
|
|
}
|
|
if id, err := store.ResetPasswordWithToken(raw, "new"); err != nil || id != u.ID {
|
|
t.Fatalf("reset: %v", err)
|
|
}
|
|
after, _ := store.GetUserByID(u.ID)
|
|
if after.PasswordHash != "new" || after.TokenVersion != before.TokenVersion+1 {
|
|
t.Errorf("password not changed or sessions not ended: %+v", after)
|
|
}
|
|
if _, err := store.ResetPasswordWithToken(raw, "again"); err != ErrResetTokenInvalid {
|
|
t.Errorf("a token works only once")
|
|
}
|
|
|
|
// Expired tokens fail
|
|
_, _ = store.db.Exec(`DELETE FROM password_reset_tokens`)
|
|
raw2, _ := store.CreatePasswordResetToken(u.ID)
|
|
_, _ = store.db.Exec(`UPDATE password_reset_tokens SET expires_at = '2000-01-01T00:00:00Z'`)
|
|
if _, err := store.ResetPasswordWithToken(raw2, "x"); err != ErrResetTokenInvalid {
|
|
t.Errorf("expired token must fail")
|
|
}
|
|
}
|