authkit initial

This commit is contained in:
juancwu 2026-04-26 01:36:53 +00:00
commit 134393fbca
43 changed files with 5188 additions and 1 deletions

69
service_reset.go Normal file
View file

@ -0,0 +1,69 @@
package authkit
import (
"context"
"errors"
"git.juancwu.dev/juancwu/errx"
)
// RequestPasswordReset mints a single-use password-reset token for the user
// behind email and returns the plaintext for the caller to deliver via email.
// Returns ErrUserNotFound when the email isn't registered (per project
// policy of distinct errors over anti-enumeration).
func (a *Auth) RequestPasswordReset(ctx context.Context, email string) (string, error) {
const op = "authkit.Auth.RequestPasswordReset"
u, err := a.deps.Users.GetUserByEmail(ctx, normalizeEmail(email))
if err != nil {
return "", errx.Wrap(op, err)
}
plaintext, hash, err := mintSecret(prefixPasswordRset, a.cfg.Random)
if err != nil {
return "", errx.Wrap(op, err)
}
now := a.now()
t := &Token{
Hash: hash,
Kind: TokenPasswordReset,
UserID: u.ID,
CreatedAt: now,
ExpiresAt: now.Add(a.cfg.PasswordResetTTL),
}
if err := a.deps.Tokens.CreateToken(ctx, t); err != nil {
return "", errx.Wrap(op, err)
}
return plaintext, nil
}
// ConfirmPasswordReset consumes the reset token, sets the new password,
// bumps the user's session_version, and revokes outstanding sessions so the
// reset constitutes a global logout.
func (a *Auth) ConfirmPasswordReset(ctx context.Context, plaintextToken, newPassword string) error {
const op = "authkit.Auth.ConfirmPasswordReset"
hash, ok := parseSecret(prefixPasswordRset, plaintextToken)
if !ok {
return errx.Wrap(op, ErrTokenInvalid)
}
now := a.now()
t, err := a.deps.Tokens.ConsumeToken(ctx, TokenPasswordReset, hash, now)
if err != nil {
return errx.Wrap(op, err)
}
newHash, err := a.deps.Hasher.Hash(newPassword)
if err != nil {
return errx.Wrap(op, err)
}
if err := a.deps.Users.SetPassword(ctx, t.UserID, newHash); err != nil {
if errors.Is(err, ErrUserNotFound) {
return errx.Wrap(op, ErrUserNotFound)
}
return errx.Wrap(op, err)
}
if _, err := a.deps.Users.BumpSessionVersion(ctx, t.UserID); err != nil {
return errx.Wrap(op, err)
}
if err := a.deps.Sessions.DeleteUserSessions(ctx, t.UserID); err != nil {
return errx.Wrap(op, err)
}
return nil
}