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-auth | libs/apis/providers/auth-api/features/customer-auth | Вход/регистрация/выход/me клиента, refresh-токен, верификация e-mail, сброс пароля; passport-стратегии customer. |
| employee-auth | libs/apis/providers/auth-api/features/employee-auth | То же для сотрудника (структурно зеркалит customer-auth). |
| user-auth | libs/apis/providers/auth-api/features/user-auth | Универсальный GET /auth/me с UniversalAccessTokenGuard, распознающим customer/employee/admin токен. |
| session | libs/apis/providers/auth-api/features/session | CRUD сессий в Postgres через Prisma (service + repository). |
| token | libs/apis/providers/auth-api/features/token | Генерация/поиск/удаление JWT-записей, per-type сервисы (customer/employee/admin), cron-очистка. |
| data-access | libs/apis/providers/auth-api/data-access | DTO запросов/ответов (sign-in/up/out, reset-password, verification, me, session, token). |
| admin-auth | libs/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 новый refreshHTTP API
Заголовок раздела «HTTP API»Глобальные настройки (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/...).
customer-auth
Заголовок раздела «customer-auth»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
| GET | /customers/auth/me | CustomerAccessTokenGuard | Профиль текущего клиента + сессия; сверяет сессию по userId+fingerprint и accessTokenId. | → CustomerAuthMeResponseDto |
| POST | /customers/auth/sign-in | — | Вход по email/паролю, выдача access (в теле) + refresh (в cookie), создание/обновление сессии. | CustomerAuthSignInDto → CustomerAuthSignInResponseDto |
| POST | /customers/auth/sign-up | — | Регистрация клиента (создаёт Customer + User). | CustomerAuthSignUpDto → CustomerAuthSignUpResponseDto |
| GET | /customers/auth/sign-out | CustomerAccessTokenGuard | Удаляет токены сессии, чистит cookie (204). | — (заголовки X-Session-Id, X-Fingerprint) |
| GET | /customers/auth/refresh/token | CustomerRefreshTokenGuard | Обновление пары токенов по refresh из cookie/Authorization. | — → { token } |
| GET | /customers/auth/verification/email/check | CustomerAccessTokenGuard | Возвращает { verified: isEmailVerified }. | — |
| GET | /customers/auth/verification/email/code | CustomerAccessTokenGuard | Генерирует и шлёт код на e-mail (Redis TTL 15 мин, resend-lock 2 мин). | — |
| POST | /customers/auth/verification/email/code/check | CustomerAccessTokenGuard | Проверяет код, помечает e-mail верифицированным. | CustomerVerificationEmailCodeCheckDto |
| POST | /customers/auth/reset-password/me | CustomerRefreshTokenGuard | Смена пароля авторизованным клиентом (по сессии), уведомление на e-mail. | CustomerAuthResetPasswordDto |
| POST | /customers/auth/reset-password/email/code/send | — | Отправка кода сброса пароля на e-mail. | CustomerAuthResetPasswordEmailCodeSendDto |
| POST | /customers/auth/reset-password/email/code | — | Проверка кода и установка нового пароля. | CustomerAuthResetPasswordEmailCodeCheckDto |
employee-auth
Заголовок раздела «employee-auth»Полностью зеркалит customer-auth (те же методы/DTO-набор, свои
EmployeeAccessTokenGuard/EmployeeRefreshTokenGuard, cookie EmployeeRefreshToken).
| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
| GET | /employees/auth/me | EmployeeAccessTokenGuard | Профиль сотрудника + сессия. | → EmployeeAuthMeResponseDto |
| POST | /employees/auth/sign-in | — | Вход сотрудника, access + refresh-cookie. | EmployeeAuthSignInDto → EmployeeAuthSignInResponseDto |
| POST | /employees/auth/sign-up | — | Регистрация сотрудника (Employee + User). | EmployeeAuthSignUpDto → EmployeeAuthSignUpResponseDto |
| GET | /employees/auth/sign-out | EmployeeAccessTokenGuard | Удаление токенов сессии + очистка cookie (204). | — |
| GET | /employees/auth/refresh/token | EmployeeRefreshTokenGuard | Обновление пары токенов. | — → { token } |
| GET | /employees/auth/verification/email/check | EmployeeAccessTokenGuard | Статус верификации e-mail. | — |
| GET | /employees/auth/verification/email/code | EmployeeAccessTokenGuard | Отправка кода верификации. | — |
| POST | /employees/auth/verification/email/code/check | EmployeeAccessTokenGuard | Проверка кода верификации. | EmployeeVerificationEmailCodeCheckDto |
| POST | /employees/auth/reset-password/me | EmployeeRefreshTokenGuard | Смена пароля по сессии. | EmployeeAuthResetPasswordDto |
| POST | /employees/auth/reset-password/email/code/send | — | Отправка кода сброса пароля. | EmployeeAuthResetPasswordEmailCodeSendDto |
| POST | /employees/auth/reset-password/email/code | — | Проверка кода и смена пароля. | EmployeeAuthResetPasswordEmailCodeCheckDto |
user-auth
Заголовок раздела «user-auth»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
| GET | /auth/me | UniversalAccessTokenGuard | Универсальный 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/jwtJwtService.signAsync, каждый со своим секретом иexpiresInиз соответствующего config-сервиса; присваиваютjti = uuid(), затем декодируют, чтобы вернутьaccessTokenId/exp,refreshTokenId/exp.- Платформенная роль в admin-токене.
TokenPayloadнесёт необязательный claimplatformRole(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_idunique и@@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.
Env-переменные конфигов
Заголовок раздела «Env-переменные конфигов»| 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.ts | Bootstrap: префикс 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.ts | Passport-стратегия проверки access-токена + существования в БД + isBlocked. |
.../customer-auth/src/lib/strategies/customer-refresh-token.strategy.ts | Passport-стратегия 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.ts | Cron-очистка просроченных токенов (3:00 ежедневно). |
.../data-access/src/lib/dtos/*.dto.ts | DTO запросов/ответов; 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), сервисом (@Transactionalsign-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 о добавлении прочих сессий/токенов в бан — точка для реализации инвалидации всех устройств.