feat(auth): шаг 0.6 — TTL access 15 минут + хэширование refresh/reset-токенов в БД
Some checks are pending
Branch Check / Type Check & Build (push) Waiting to run
Some checks are pending
Branch Check / Type Check & Build (push) Waiting to run
- server/utils/jwt.ts: дефолт JWT_ACCESS_EXPIRES 30d → 15m (env сохранено), hashToken() (sha256 hex) - migrations/0082_token_hashes.sql: *_hash колонки в user_sessions/users, DELETE FROM user_sessions (глобальный разлогин), legacy plain-колонки сохранены, но не пишутся - storage: lookup/отзыв сессий и reset-токенов по хэшу; markSessionReplaced по id сессии - auth.service: ротация в rotateFamilySession(), grace-period ротирует активную сессию вместо возврата plain-токена - auth.core.routes: forgot/reset-password пишут/ищут sha256-хэш, plain только в письме - tests/token-security.test.ts: 12 тестов (выпуск по хэшу, ротация, reuse detection, grace, reset) - .env.example, swagger, IMPLEMENTATION_LOG.md обновлены Проверки: npm run check чисто, vitest 118/118, lint 0 errors, build собирается
This commit is contained in:
@@ -7,7 +7,7 @@ import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth
|
||||
import { validateRequest } from "../middleware/validation.middleware";
|
||||
import { registerOrganizationSchema, loginSchema, registerByInvitationSchema } from "@shared/schema";
|
||||
import { hashPassword, verifyPassword } from "../utils/password";
|
||||
import { generateTokens } from "../utils/jwt";
|
||||
import { hashToken } from "../utils/jwt";
|
||||
import { emailService } from "../services/email.service";
|
||||
import { notificationService } from "../services/notification.service";
|
||||
import { authLimiter, refreshLimiter } from "./shared";
|
||||
@@ -339,8 +339,10 @@ export function registerAuthCoreRoutes(router: Router): void {
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
const expires = new Date(Date.now() + 60 * 60 * 1000);
|
||||
|
||||
// Шаг 0.6: в БД сохраняется только sha256-хэш токена, plain — только в письме.
|
||||
await storage.updateUser(user.id, {
|
||||
resetPasswordToken: token,
|
||||
resetPasswordToken: null,
|
||||
resetPasswordTokenHash: hashToken(token),
|
||||
resetPasswordExpires: expires,
|
||||
});
|
||||
|
||||
@@ -381,7 +383,7 @@ export function registerAuthCoreRoutes(router: Router): void {
|
||||
}
|
||||
|
||||
if (!user.resetPasswordExpires || new Date() > new Date(user.resetPasswordExpires)) {
|
||||
await storage.updateUser(user.id, { resetPasswordToken: null, resetPasswordExpires: null });
|
||||
await storage.updateUser(user.id, { resetPasswordToken: null, resetPasswordTokenHash: null, resetPasswordExpires: null });
|
||||
return res.status(400).json({ success: false, error: 'Срок действия ссылки истёк. Запросите новую.' });
|
||||
}
|
||||
|
||||
@@ -389,6 +391,7 @@ export function registerAuthCoreRoutes(router: Router): void {
|
||||
await storage.updateUser(user.id, {
|
||||
passwordHash,
|
||||
resetPasswordToken: null,
|
||||
resetPasswordTokenHash: null,
|
||||
resetPasswordExpires: null,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user