-- 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);