feat(db/m7-user-mgmt): migration 0024 + self-service + SMTP queries (U3)
Adds users.email (optional, lowercase-unique via partial index), smtp_config singleton, and password_resets tokens. Plus the queries U3-T2 / T3 / T4 use: - ChangeUserPassword: self-service password change. HTTP layer verifies the current password before this fires. - UpdateUserProfile: set display_name + email together. - RegenerateApiToken: invalidate old API token. - GetUserByEmail: case-insensitive lookup for forgot-password. - GetSMTPConfig + UpdateSMTPConfig: admin SMTP settings CRUD. - CreatePasswordReset / GetPasswordReset / UsePasswordReset (:execrows for atomic claim) / DeleteExpiredPasswordResets (cron-style cleanup, not yet wired). Email column is nullable; users without email have admin-reset as their only password-recovery path. The lower() unique index allows many NULLs and prevents case-insensitive duplicates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
-- name: CreatePasswordReset :exec
|
||||
INSERT INTO password_resets (token, user_id, expires_at)
|
||||
VALUES ($1, $2, $3);
|
||||
|
||||
-- name: GetPasswordReset :one
|
||||
SELECT * FROM password_resets WHERE token = $1;
|
||||
|
||||
-- name: UsePasswordReset :execrows
|
||||
-- Atomic claim: marks the token used only if currently unused
|
||||
-- and not expired. Returns rows-affected so the caller can
|
||||
-- distinguish "successful claim" (rows=1) from "already used /
|
||||
-- expired / nonexistent" (rows=0).
|
||||
UPDATE password_resets
|
||||
SET used_at = now()
|
||||
WHERE token = $1
|
||||
AND used_at IS NULL
|
||||
AND expires_at > now();
|
||||
|
||||
-- name: DeleteExpiredPasswordResets :exec
|
||||
-- Cleanup: drop tokens that have been expired more than 7 days.
|
||||
-- Not wired in U3; useful to have for a future cron sweep.
|
||||
DELETE FROM password_resets WHERE expires_at < now() - interval '7 days';
|
||||
@@ -0,0 +1,15 @@
|
||||
-- name: GetSMTPConfig :one
|
||||
SELECT * FROM smtp_config WHERE id = true;
|
||||
|
||||
-- name: UpdateSMTPConfig :exec
|
||||
UPDATE smtp_config
|
||||
SET enabled = $1,
|
||||
host = $2,
|
||||
port = $3,
|
||||
username = $4,
|
||||
password = $5,
|
||||
from_address = $6,
|
||||
from_name = $7,
|
||||
use_tls = $8,
|
||||
updated_at = now()
|
||||
WHERE id = true;
|
||||
@@ -114,3 +114,31 @@ UPDATE users
|
||||
SET auto_approve_requests = $2
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: ChangeUserPassword :exec
|
||||
-- Self-service password change. Caller (HTTP handler) verifies the
|
||||
-- current password before calling this. Distinct from
|
||||
-- ResetUserPassword which is admin-driven (no current-password
|
||||
-- check).
|
||||
UPDATE users SET password_hash = $2 WHERE id = $1;
|
||||
|
||||
-- name: UpdateUserProfile :one
|
||||
-- Self-service: set display name and/or email. Both fields are
|
||||
-- nullable in the DB; pass NULL ptr to clear, non-NULL ptr to set.
|
||||
UPDATE users
|
||||
SET display_name = $2,
|
||||
email = $3
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: RegenerateApiToken :one
|
||||
-- Self-service: caller wants a new API token. Used by the /settings
|
||||
-- API Token card's "Regenerate" button.
|
||||
UPDATE users SET api_token = $2 WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetUserByEmail :one
|
||||
-- Used by forgot-password lookup. Lowercase comparison both sides
|
||||
-- to keep the unique index happy and to make the lookup
|
||||
-- case-insensitive.
|
||||
SELECT * FROM users WHERE lower(email) = lower($1);
|
||||
|
||||
Reference in New Issue
Block a user