Перейти к содержимому

Auth API — аутентификация

Слой: libs/apis/providers/auth-api · Приложение: apps/auth-api

Auth API — отдельное NestJS-приложение, отвечающее за аутентификацию и управление сессиями пользователей платформы. Сервис реализует раздельную («двойную») аутентификацию для двух типов пользователей — customer (клиент) и employee (сотрудник), — каждый со своим набором JWT-секретов и стратегий. На вход/refresh выдаётся пара токенов access + refresh (refresh кладётся в httpOnly-cookie), привязанная к серверной сессии и к fingerprint устройства. Помимо входа и обновления токенов сервис обеспечивает регистрацию, верификацию e-mail по коду и сброс пароля по коду (коды хранятся в Redis), а также «универсальный» эндпоинт /me, распознающий тип пользователя по токену. В кодовой базе присутствует также третий тип — admin (admin-auth + jwt-admin), устроенный по той же модели.

ФичаПутьЧто делает
customer-authlibs/apis/providers/auth-api/features/customer-authВход/регистрация/выход/me клиента, refresh-токен, верификация e-mail, сброс пароля; passport-стратегии customer.
employee-authlibs/apis/providers/auth-api/features/employee-authТо же для сотрудника (структурно зеркалит customer-auth).
user-authlibs/apis/providers/auth-api/features/user-authУниверсальный GET /auth/me с UniversalAccessTokenGuard, распознающим customer/employee/admin токен.
sessionlibs/apis/providers/auth-api/features/sessionCRUD сессий в Postgres через Prisma (service + repository).
tokenlibs/apis/providers/auth-api/features/tokenГенерация/поиск/удаление JWT-записей, per-type сервисы (customer/employee/admin), cron-очистка.
data-accesslibs/apis/providers/auth-api/data-accessDTO запросов/ответов (sign-in/up/out, reset-password, verification, me, session, token).
admin-authlibs/apis/providers/auth-api/features/admin-authАутентификация администратора (вне основного фокуса; та же модель access+refresh).

Почему customer и employee раздельны. Это два независимых контура с разными JWT-секретами и разным временем жизни (JWT_CUSTOMER_* vs JWT_EMPLOYEE_*), разными passport-стратегиями и разными cookie. Клиент не может войти по «сотрудничьему» токену и наоборот: при sign-in проверяется наличие соответствующей связи у пользователя (user.customer / user.employee), а access-стратегия валидирует токен только своим секретом. Один и тот же User теоретически может иметь и профиль customer, и employee, но токены и сессии для них подписываются/проверяются раздельно.

Пара токенов. На каждый вход генерируется access- и refresh-JWT (Token{Customer,Employee}Service.generate*TokenPair). Payload — { userId, roles, customerId|employeeId }, каждому токену присваивается свой jti (uuid). Access извлекается из заголовка Authorization: Bearer; refresh — из cookie CustomerRefreshToken / EmployeeRefreshToken (или из Authorization, fallback — см. extract*RefreshTokenFromCookieOrAuthHeader). Cookie ставится с опциями httpOnly, secure, sameSite: 'none', maxAge = refreshExpiresIn * 1000.

Где хранятся сессии и токены — Postgres/Prisma, не Redis. Метаданные пары токенов пишутся в таблицу tokens (модель Token: accessTokenId, accessTokenExpiredAt, refreshTokenId, refreshTokenExpiredAt, userId), а сессия — в таблицу sessions (модель Session: userId, tokenId (unique), fingerprint, ip, с уникальным ключом @@unique([userId, fingerprint])). JWT в БД не хранятся целиком — хранятся только их идентификаторы (jti) и сроки, по которым проверяется «живость» токена. Redis используется только для временных кодов верификации e-mail и сброса пароля (TTL 15 мин, resend-lock 2 мин).

Fingerprint. Обязательный заголовок X-Fingerprint (декоратор HeaderFingerprint, кидает BadRequestException при отсутствии) идентифицирует устройство. Пара (userId, fingerprint) уникальна на уровне сессии: повторный вход с того же устройства не плодит сессии, а переиспользует существующую — её tokenId переставляется на новую запись токена, старый токен удаляется. Многие эндпоинты также требуют заголовок X-Session-Id (HeaderSessionId).

Проверка на каждом запросе. Access-стратегии (Customer/EmployeeAccessTokenStrategy) после верификации подписи дополнительно проверяют, что токен с таким jti ещё существует в БД (findOneByAccessTokenId) и что пользователь не заблокирован (user.isBlocked). Guard’ы (Customer/EmployeeAccessTokenGuard, *RefreshTokenGuard) — тонкие обёртки над passport AuthGuard(strategyName) из apis/shared.

Universal-guard (user-auth). UniversalAccessTokenGuard не использует passport-стратегию: он последовательно пробует верифицировать Bearer-токен секретом customer → employee → admin, при успехе проверяет наличие токена в БД и кладёт payload в request.user. Это позволяет одному эндпоинту /auth/me обслуживать любой тип пользователя.

sequenceDiagram
participant C as Клиент
participant Ctrl as CustomerAuthController
participant Svc as CustomerAuthService
participant TCS as TokenCustomerService
participant DB as Postgres (Prisma)
Note over C,DB: Sign-in
C->>Ctrl: POST /v1/customers/auth/sign-in (email,password, X-Fingerprint)
Ctrl->>Svc: signIn(dto, {ip, fingerprint})
Svc->>DB: findOneByEmail (+customer, roles)
Svc->>Svc: bcrypt.compare(password)
Svc->>TCS: generateCustomerTokenPair(payload)
TCS-->>Svc: {accessToken, refreshToken, jti/exp}
Svc->>DB: token.create(...) сохранить jti/exp
Svc->>DB: session findUnique(userId+fingerprint)
alt сессия есть
Svc->>DB: session.update(tokenId) + token.deleteMany(старый)
else нет
Svc->>DB: session.create(ip, fingerprint, tokenId)
end
Svc-->>Ctrl: {accessToken, refreshToken, session, user}
Ctrl-->>C: body{token(access), session, user} + Set-Cookie refresh
Note over C,DB: Refresh
C->>Ctrl: GET /v1/customers/auth/refresh/token (cookie refresh, X-Session-Id, X-Fingerprint)
Ctrl->>Svc: refreshToken({sessionId, fingerprint, refreshToken})
Svc->>Svc: decode(refresh) → userId, jti
Svc->>DB: session findUnique(id+userId+fingerprint, include token)
Svc->>Svc: jti === session.token.refreshTokenId ?
Svc->>TCS: generateCustomerTokenPair(payload)
Svc->>DB: token.update(session.tokenId, новые jti/exp)
Svc-->>Ctrl: {accessToken, refreshToken}
Ctrl-->>C: body{token(access)} + Set-Cookie новый refresh

Глобальные настройки (apps/auth-api/src/main.ts): префикс api, URI-versioning (/api/v1/...), CORS с credentials, cookie-parser, глобальный ValidationPipe({ transform: true, transformOptions.strategy: 'excludeAll' }) и глобальный PrismaExceptionFilter. Swagger — на /api. Порт берётся из AppConfigService. Ниже пути указаны без префикса api и версии (все контроллеры — version: '1', т.е. реально /api/v1/...).

МетодПутьGuardОписаниеDTO
GET/customers/auth/meCustomerAccessTokenGuardПрофиль текущего клиента + сессия; сверяет сессию по userId+fingerprint и accessTokenId.CustomerAuthMeResponseDto
POST/customers/auth/sign-inВход по email/паролю, выдача access (в теле) + refresh (в cookie), создание/обновление сессии.CustomerAuthSignInDtoCustomerAuthSignInResponseDto
POST/customers/auth/sign-upРегистрация клиента (создаёт Customer + User).CustomerAuthSignUpDtoCustomerAuthSignUpResponseDto
GET/customers/auth/sign-outCustomerAccessTokenGuardУдаляет токены сессии, чистит cookie (204).— (заголовки X-Session-Id, X-Fingerprint)
GET/customers/auth/refresh/tokenCustomerRefreshTokenGuardОбновление пары токенов по refresh из cookie/Authorization.— → { token }
GET/customers/auth/verification/email/checkCustomerAccessTokenGuardВозвращает { verified: isEmailVerified }.
GET/customers/auth/verification/email/codeCustomerAccessTokenGuardГенерирует и шлёт код на e-mail (Redis TTL 15 мин, resend-lock 2 мин).
POST/customers/auth/verification/email/code/checkCustomerAccessTokenGuardПроверяет код, помечает e-mail верифицированным.CustomerVerificationEmailCodeCheckDto
POST/customers/auth/reset-password/meCustomerRefreshTokenGuardСмена пароля авторизованным клиентом (по сессии), уведомление на e-mail.CustomerAuthResetPasswordDto
POST/customers/auth/reset-password/email/code/sendОтправка кода сброса пароля на e-mail.CustomerAuthResetPasswordEmailCodeSendDto
POST/customers/auth/reset-password/email/codeПроверка кода и установка нового пароля.CustomerAuthResetPasswordEmailCodeCheckDto

Полностью зеркалит customer-auth (те же методы/DTO-набор, свои EmployeeAccessTokenGuard/EmployeeRefreshTokenGuard, cookie EmployeeRefreshToken).

МетодПутьGuardОписаниеDTO
GET/employees/auth/meEmployeeAccessTokenGuardПрофиль сотрудника + сессия.EmployeeAuthMeResponseDto
POST/employees/auth/sign-inВход сотрудника, access + refresh-cookie.EmployeeAuthSignInDtoEmployeeAuthSignInResponseDto
POST/employees/auth/sign-upРегистрация сотрудника (Employee + User).EmployeeAuthSignUpDtoEmployeeAuthSignUpResponseDto
GET/employees/auth/sign-outEmployeeAccessTokenGuardУдаление токенов сессии + очистка cookie (204).
GET/employees/auth/refresh/tokenEmployeeRefreshTokenGuardОбновление пары токенов.— → { token }
GET/employees/auth/verification/email/checkEmployeeAccessTokenGuardСтатус верификации e-mail.
GET/employees/auth/verification/email/codeEmployeeAccessTokenGuardОтправка кода верификации.
POST/employees/auth/verification/email/code/checkEmployeeAccessTokenGuardПроверка кода верификации.EmployeeVerificationEmailCodeCheckDto
POST/employees/auth/reset-password/meEmployeeRefreshTokenGuardСмена пароля по сессии.EmployeeAuthResetPasswordDto
POST/employees/auth/reset-password/email/code/sendОтправка кода сброса пароля.EmployeeAuthResetPasswordEmailCodeSendDto
POST/employees/auth/reset-password/email/codeПроверка кода и смена пароля.EmployeeAuthResetPasswordEmailCodeCheckDto
МетодПутьGuardОписаниеDTO
GET/auth/meUniversalAccessTokenGuardУниверсальный me: по customerId/employeeId в токене возвращает соответствующий профиль + роли + сессию.UserAuthResponseDto
  • TokenService (token.service.ts) — базовый сервис поверх TokenRepository (Prisma, таблица tokens). Умеет: decodeOne (JWT decode без проверки подписи), saveOne, updateOneById, deleteManyBy, findOneByAccessTokenId, findOneByRefreshTokenId, prepareUserOnRoleToToken (маппинг ролей в массив строк). Экспортирует типы TokenPayload и TokenDecodePayload (+ iat/exp/jti).
  • TokenCustomerService / TokenEmployeeService / TokenAdminService — тонкие генераторы пары токенов через @nestjs/jwt JwtService.signAsync, каждый со своим секретом и expiresIn из соответствующего config-сервиса; присваивают jti = uuid(), затем декодируют, чтобы вернуть accessTokenId/exp, refreshTokenId/exp.
  • Платформенная роль в admin-токене. TokenPayload несёт необязательный claim platformRole (ADMIN | OPERATOR | ARBITER) — по нему guard’ы OperatorGuard, AdministratorGuard и ArbiterGuard в admin-api различают три рабочих места. Значение выводится из UserOnRole хелпером resolvePlatformRole (features/token, helpers/platform-role.helper.ts), а не подставляется литералом, и перевыводится при каждом выпуске пары, включая refresh: иначе claim исчезал бы после первой ротации, а снятая роль продолжала бы жить в токене до истечения срока.
  • Одна платформенная роль на пользователя. AdminAuthService.login пускает любую из трёх ролей, но при двух платформенных ролях отвечает 403: совмещение запрещено решением №19 (оператор «по совместительству администратор» видел бы деньги). То же ограничение стоит в базе частичным уникальным индексом users_on_roles_single_platform_role, так что обойти его через прямую запись в таблицу тоже нельзя.
  • TokenJobService (token-job.service.ts) — cron-задача @Cron(CronExpression.EVERY_DAY_AT_3AM): удаляет из БД токены с истёкшим refreshTokenExpiredAt < now. ScheduleModule.forRoot({}) подключён в TokenModule.
  • SessionService / SessionRepository — CRUD над таблицей sessions (Prisma): saveOne, updateOneById, findMany, findOneBy (findUnique). Ключевые уникальные ограничения: token_id unique и @@unique([userId, fingerprint]).
  • Что где хранится. Postgres/Prisma: записи Token (jti + сроки) и Session (fingerprint, ip, ссылка на токен). Redis (RedisClientService): только коды e-mail-верификации и сброса пароля с TTL — ключи CUSTOMER_/EMPLOYEE_VERIFICATION_CODE, *_RESET_PASSWORD_CODE и парные *_RESEND_CODE (TTL 900с / lock 120с).
  • Транзакции. Sign-in и refresh помечены @Transactional() (nestjs-cls + TransactionalAdapterPrisma); репозитории работают через TransactionHost.tx, так что создание токена, обновление сессии и удаление старого токена атомарны.
  • configs: apis/configs/auth-api/jwt-customer, jwt-employee, jwt-admin (секреты и время жизни access/refresh, env-переменные ниже); apis/configs/shared/app (AppConfigService — порт приложения).
  • utils: apis/utils/prisma-client (PrismaClientModule/Service — доступ к БД для сессий/токенов); redis-client (RedisClientService — коды верификации/сброса).
  • shared (apis/shared): guard’ы (Customer/EmployeeAccessTokenGuard, *RefreshTokenGuard), декораторы (HeaderFingerprint, HeaderSessionId, GetTokenPayload), хелперы (getAuthCookieOptions, extract*TokenFromCookieOrAuthHeader, generatePasswordHashBy, getRandomCode), константы имён стратегий/cookie, MapperService (маппинг сущностей в Response-DTO), PrismaExceptionFilter.
  • provider user-api: features/user (UserService), features/customer (CustomerService), features/employee (EmployeeService), data-access (CustomerCreateDto, EmployeeCreateDto) — учётные данные и профили.
  • email-sender (email-sender-api-features-sender): EmailSenderService — отправка писем верификации регистрации, кода смены пароля и уведомления о смене пароля (шаблоны в apps/auth-api/src/assets/email-templates/{ru,en}).
  • инфраструктура: nestjs-cls + @nestjs-cls/transactional (транзакции, request-id), @nestjs/schedule (cron), @nestjs/passport + passport-jwt.
ConfigНазначениеПеременные
jwt-customerСекреты и TTL токенов клиентаJWT_CUSTOMER_ACCESS_SECRET_KEY, JWT_CUSTOMER_ACCESS_EXPIRES_IN (по умолч. 3600с), JWT_CUSTOMER_REFRESH_SECRET_KEY, JWT_CUSTOMER_REFRESH_EXPIRES_IN (7200с)
jwt-employeeСекреты и TTL токенов сотрудникаJWT_EMPLOYEE_ACCESS_SECRET_KEY, JWT_EMPLOYEE_ACCESS_EXPIRES_IN (3600с), JWT_EMPLOYEE_REFRESH_SECRET_KEY, JWT_EMPLOYEE_REFRESH_EXPIRES_IN (7200с)
jwt-adminСекреты/TTL токенов админа + список доменовJWT_ADMIN_ACCESS_SECRET_KEY, JWT_ADMIN_ACCESS_EXPIRES_IN (3600с), JWT_ADMIN_REFRESH_SECRET_KEY, JWT_ADMIN_REFRESH_EXPIRES_IN (7200с), ADMIN_ALLOWED_DOMAINS
ПутьРоль
apps/auth-api/src/main.tsBootstrap: префикс api, URI-versioning, CORS, cookie-parser, ValidationPipe, PrismaExceptionFilter, Swagger, порт из AppConfigService.
apps/auth-api/src/app/app.module.tsКорневой модуль: ClsModule + транзакции, подключает Customer/Employee/User/Admin-Auth.
.../customer-auth/src/lib/customer-auth.controller.tsМаршруты me/sign-in/sign-up/sign-out клиента.
.../customer-auth/src/lib/customer-auth.service.tsЛогика входа/регистрации/выхода, работа с сессией и токенами (@Transactional).
.../customer-auth/src/lib/customer-auth-refresh-token.{controller,service}.tsОбновление пары токенов.
.../customer-auth/src/lib/customer-auth-verification.{controller,service}.tsВерификация e-mail по коду (Redis).
.../customer-auth/src/lib/customer-auth-reset-password.{controller,service}.tsСброс/смена пароля (Redis + email-sender).
.../customer-auth/src/lib/strategies/customer-access-token.strategy.tsPassport-стратегия проверки access-токена + существования в БД + isBlocked.
.../customer-auth/src/lib/strategies/customer-refresh-token.strategy.tsPassport-стратегия refresh-токена (extract из cookie/header).
.../customer-auth/src/lib/customer-auth.constants.tsКлючи и TTL Redis-кодов.
.../employee-auth/**Зеркало customer-auth для сотрудника.
.../user-auth/src/lib/user-auth.controller.tsУниверсальный GET /auth/me.
.../user-auth/src/lib/user-auth.service.tsВетвление по customerId/employeeId, сбор профиля+ролей+сессии.
.../user-auth/src/lib/guards/universal-access-token.guard.tsМульти-секретная проверка токена (customer→employee→admin).
.../session/src/lib/session.{service,repository,type}.tsРабота с таблицей sessions (Prisma).
.../token/src/lib/token.{service,repository}.tsРабота с таблицей tokens, decode JWT.
.../token/src/lib/token-{customer,employee,admin}.service.tsГенераторы пары токенов per-type.
.../token/src/lib/token-job.service.tsCron-очистка просроченных токенов (3:00 ежедневно).
.../data-access/src/lib/dtos/*.dto.tsDTO запросов/ответов; session.dto.ts, token.dto.ts — create/update.
apps/core-api/prisma/schema.prisma (Token, Session)Схема БД для токенов и сессий.
libs/apis/shared/src/lib/{guards,decorators,helpers,constants}Guard’ы, декораторы, cookie/token-хелперы, имена стратегий.
libs/apis/configs/auth-api/jwt-{customer,employee,admin}/**Секреты и TTL JWT, env-валидация.
  • Новый тип пользователя (по образцу customer/employee): (1) добавить config jwt-<type> (env-секреты/TTL, *ConfigModule/Service); (2) добавить Token<Type>Service в TokenModule (генерация пары токенов); (3) создать фичу <type>-auth с контроллерами (sign-in/up/out, refresh, verification, reset-password), сервисом (@Transactional sign-in по образцу CustomerAuthService), access/refresh passport-стратегиями и константами имён в apis/shared; (4) подключить модуль в AppModule; (5) при необходимости добавить ветку в UniversalAccessTokenGuard и в UserAuthService.me.
  • Новый способ входа (например OAuth/по коду): добавить endpoint в соответствующий *AuthController, реализовать логику в сервисе и в конце переиспользовать Token*Service.generate*TokenPair + SessionService + TokenService.saveOne — т.е. подключиться к общей выдаче сессии/токенов.
  • Новый эндпоинт внутри существующей фичи: добавить метод в контроллер с нужным guard’ом (*AccessTokenGuard/*RefreshTokenGuard), достать payload через @GetTokenPayload() и метаданные через @HeaderFingerprint()/@HeaderSessionId(), описать DTO в data-access@Expose() — активна стратегия excludeAll), вернуть ответ через MapperService.toResponse(...).
  • Ротация/бан сессий. В сервисах сброса пароля есть TODO о добавлении прочих сессий/токенов в бан — точка для реализации инвалидации всех устройств.