43 lines
2.7 KiB
MySQL
43 lines
2.7 KiB
MySQL
|
|
-- 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);
|