43 lines
2.7 KiB
SQL
43 lines
2.7 KiB
SQL
-- 0007_password_reset — сброс пароля по одноразовому токену.
|
||
--
|
||
-- SMTP в проекте нет по решению: токен выдаёт админ
|
||
-- (POST /users/:id/password-reset) и передаёт его пользователю сам —
|
||
-- любым каналом, кроме почты внутри приложения. Пользователь меняет
|
||
-- пароль через POST /auth/reset-password.
|
||
--
|
||
-- В таблице лежит ТОЛЬКО sha256 токена: утекшая база не даёт
|
||
-- переиспользовать выпущенные токены. Свойство «увидел — значит чужой»:
|
||
-- зная хеш, токен не восстановить (256 бит поискового пространства).
|
||
--
|
||
-- Правила, проверяемые в SQL, а не в хендлерах:
|
||
-- * токен одноразовый: WHERE used_at IS NULL в самом UPDATE;
|
||
-- * срок: WHERE expires_at > now(), срок всегда позже выпуска (CHECK);
|
||
-- * у пользователя действует ровно один свежий токен — выпуск нового
|
||
-- гасит предыдущие (WITH в CreateResetToken);
|
||
-- * вместе с паролем закрываются refresh-сессии (в коде, сразу после
|
||
-- успешного UPDATE — смена пароля без разлогина бессмысленна).
|
||
--
|
||
-- Строки живут после использования (история), чистка — по желанию:
|
||
-- просроченные токены бесполезны, таблица растёт медленно.
|
||
--
|
||
-- Применённый файл не редактировать (checksum в schema_migrations).
|
||
|
||
CREATE TABLE password_reset_tokens (
|
||
id bigserial PRIMARY KEY,
|
||
user_id uuid NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
||
-- sha256 токена в hex; сам токен в базе не хранится
|
||
token_hash text NOT NULL UNIQUE,
|
||
created_at timestamptz NOT NULL DEFAULT now(),
|
||
expires_at timestamptz NOT NULL,
|
||
-- NULL — ещё не использован; момент смены пароля
|
||
used_at timestamptz,
|
||
|
||
CONSTRAINT password_reset_tokens_expiry
|
||
CHECK (expires_at > created_at)
|
||
);
|
||
|
||
-- Выпуск нового токена гасит старые конкретного пользователя,
|
||
-- поэтому выборка «незакрытые токены пользователя» должна быть быстрой.
|
||
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
|
||
ON password_reset_tokens (user_id, created_at);
|