Auth Service
Сервис аутентификации, KYC и комплаенс
Auth Service
Auth Service — это центральный сервис платформы, отвечающий за управление идентичностью пользователей, KYC-проверки, контроль лимитов и санкционные проверки.
Обзор
Сервис обеспечивает:
- Аутентификацию и авторизацию пользователей
- KYC-процессы (Know Your Customer)
- Контроль лимитов операций
- Санкционные проверки по спискам
- Управление ролями и разрешениями
Архитектура
Доменные области
auth-service/
├── domains/
│ ├── authz/ # Авторизация (JWT, RBAC)
│ │ ├── application/
│ │ │ └── use-cases/
│ │ │ ├── validate-token.use-case.ts
│ │ │ └── check-permissions.use-case.ts
│ │ └── infrastructure/
│ │ └── better-auth/
│ └── identity/ # Идентификация и KYC
│ ├── application/
│ │ └── use-cases/
│ │ ├── get-user-profile.use-case.ts
│ │ ├── submit-kyc.use-case.ts
│ │ ├── review-kyc.use-case.ts
│ │ ├── check-limits.use-case.ts
│ │ ├── check-sanctions.use-case.ts
│ │ ├── block-user.use-case.ts
│ │ └── unblock-user.use-case.ts
│ └── infrastructure/
│ └── sanctions/
└── infrastructure/
├── db/ # PostgreSQL
├── kafka/ # Kafka events
└── audit/ # Audit loggingAPI (gRPC Connect)
IdentityService
GetUserProfile
Получение профиля пользователя.
message GetUserProfileRequest {
RequestContext context = 1;
string user_id = 2;
}
message GetUserProfileResponse {
UserProfile profile = 1;
}Пример:
const response = await identityClient.getUserProfile(
{ requestId, correlationId, idempotencyKey },
userId
);SubmitKyc
Отправка KYC-заявки на проверку.
message SubmitKycRequest {
RequestContext context = 1;
SubmitKycPayload payload = 2;
}
message SubmitKycPayload {
string user_id = 1;
string full_name = 2;
string citizenship = 3;
Address address = 4;
repeated DocumentReference documents = 5;
string date_of_birth = 6;
string passport_number = 7;
}Процесс:
- Пользователь загружает документы (паспорт, селфи)
- Данные отправляются в СМЭВ для верификации
- Создается KYC case со статусом
OPEN - Оператор проверяет и утверждает/отклоняет
ReviewKyc
Рассмотрение KYC-заявки оператором.
message ReviewKycRequest {
RequestContext context = 1;
string case_id = 2;
KycCaseStatus status = 3; // APPROVED, REJECTED, BLOCKED
string reason = 4;
KycStatus target_kyc_level = 5; // BASIC, STANDARD, ADVANCED
}Статусы KYC case:
OPEN— заявка созданаIN_REVIEW— на рассмотренииAPPROVED— одобреноREJECTED— отклоненоBLOCKED— заблокировано
CheckLimits
Проверка лимитов перед операцией.
message CheckLimitsRequest {
RequestContext context = 1;
string user_id = 2;
LimitType limit_type = 3; // DEPOSIT, WITHDRAWAL, TRADING
Money amount = 4;
OperationType operation_type = 5;
}
message CheckLimitsResponse {
bool allowed = 1;
string reason = 2;
}Типы лимитов:
DEPOSIT— лимит на ввод средствWITHDRAWAL— лимит на вывод средствEXTERNAL— лимит на внешние операцииTRADING— лимит на торговлю
Периоды:
DAILY— дневнойWEEKLY— недельныйMONTHLY— месячный
CheckSanctions
Проверка пользователя по санкционным спискам.
message CheckSanctionsRequest {
RequestContext context = 1;
string user_id = 2;
SubmitKycPayload payload = 3;
}
message CheckSanctionsResponse {
SanctionsCheck check = 1;
}Процесс:
- Проверка по спискам OFAC, EU, UN
- Проверка по российским спискам
- Проверка PEP (Politically Exposed Persons)
- Возврат статуса и совпадений
BlockUser / UnblockUser
Блокировка и разблокировка пользователя.
message BlockUserRequest {
RequestContext context = 1;
string user_id = 2;
string reason = 3;
}AuthZService
ValidateToken
Валидация JWT токена.
message ValidateTokenRequest {
string access_token = 1;
}
message ValidateTokenResponse {
bool valid = 1;
string user_id = 2;
repeated string roles = 3;
repeated string scopes = 4;
}CheckPermissions
Проверка прав доступа.
message CheckPermissionsRequest {
string user_id = 1;
string resource = 2;
repeated string required_roles = 3;
repeated string required_scopes = 4;
}События Kafka
Identity Events
Топик: maniton.identity.events.v1
UserCreatedEvent
message UserCreatedEvent {
RequestContext context = 1;
string user_id = 2;
string email = 3;
string phone = 4;
}KycStatusChangedEvent
message KycStatusChangedEvent {
RequestContext context = 1;
string user_id = 2;
KycStatus old_status = 3;
KycStatus new_status = 4;
}UserBlockedEvent
message UserBlockedEvent {
RequestContext context = 1;
string user_id = 2;
string reason = 3;
}Use Cases
GetUserProfileUseCase
Получение профиля пользователя с фильтрацией по KYC статусу.
async execute(userId: string): Promise<UserProfile> {
const profile = await this.userRepository.findById(userId);
if (!profile) {
throw new NotFoundException('User not found');
}
return this.mapper.toProto(profile);
}SubmitKycUseCase
Создание KYC case и отправка на проверку.
async execute(
context: RequestContext,
payload: SubmitKycPayload
): Promise<KycCase> {
// 1. Валидация данных
this.validatePayload(payload);
// 2. Создание KYC case
const kycCase = new KycCase(
crypto.randomUUID(),
payload.userId,
KycCaseStatus.OPEN,
RiskLevel.LOW,
payload
);
// 3. Отправка в СМЭВ
await this.smevClient.verify(payload);
// 4. Сохранение
await this.kycCaseRepository.save(kycCase);
// 5. Публикация события
await this.eventPublisher.publish({
type: 'KycSubmitted',
payload: kycCase,
});
return kycCase;
}CheckLimitsUseCase
Проверка лимитов с учетом периода и типа операции.
async execute(
context: RequestContext,
userId: string,
limitType: LimitType,
amount: Money,
operationType: OperationType
): Promise<{ allowed: boolean; reason?: string }> {
// 1. Получение лимитов пользователя
const limits = await this.limitRepository.findByUserAndType(userId, limitType);
// 2. Проверка каждого периода
for (const limit of limits) {
const used = await this.getUsedAmount(userId, limit.period, operationType);
const total = used + amount.amount.value;
if (BigInt(total) > BigInt(limit.maxAmount.amount.value)) {
return {
allowed: false,
reason: `Limit exceeded for ${limit.period}: ${total} > ${limit.maxAmount.amount.value}`,
};
}
}
return { allowed: true };
}Безопасность
JWT Токены
- Access Token: 15 минут
- Refresh Token: 30 дней
- Алгоритм: RS256 (RSA Signature with SHA-256)
- Ключи: Хранятся в HSM
RBAC
Роли:
USER— обычный пользовательADMIN— администраторOPERATOR— оператор KYCAUDITOR— аудитор
Разрешения:
KYC_APPROVE— одобрение KYCKYC_REJECT— отклонение KYCUSERS_BLOCK— блокировка пользователейUSERS_UNBLOCK— разблокировка пользователейLIMITS_VIEW— просмотр лимитовLIMITS_EDIT— редактирование лимитов
Санкционные проверки
Источники:
- OFAC (США)
- EU Sanctions (Евросоюз)
- UN Sanctions (ООН)
- Российские списки (ФСФН, ЦБ РФ)
Алгоритм:
- Нормализация данных (ФИО, дата рождения)
- Фаззи-матчинг (Levenshtein distance)
- Проверка PEP
- Проверка связей (семья, партнеры)
Интеграции
СМЭВ (Система Межведомственного Электронного Взаимодействия)
Проверка паспортных данных и документов через государственные сервисы.
interface SmevClient {
verifyPassport(passportNumber: string): Promise<PassportInfo>;
verifyInn(inn: string): Promise<InnInfo>;
verifySnils(snils: string): Promise<SnilsInfo>;
}Better Auth
Интеграция с Better Auth для аутентификации.
import { auth } from '@infrastructure/better-auth/better-auth.config';
export const authConfig = auth({
providers: [
{
id: 'phone',
name: 'Phone',
type: 'credential',
},
],
session: {
expiresIn: 15 * 60, // 15 минут
},
});Мониторинг
Метрики
auth_requests_total— общее количество запросовauth_requests_duration_seconds— время обработкиkyc_checks_total— количество KYC проверокkyc_checks_duration_seconds— время KYC проверкиlimits_checks_total— количество проверок лимитовsanctions_checks_total— количество санкционных проверок
Логи
this.logger.log('KYC submitted', {
userId: payload.userId,
caseId: kycCase.caseId,
riskLevel: kycCase.riskLevel,
});Тестирование
# Unit тесты
bun test --filter auth-service
# Интеграционные тесты
bun test:integration --filter auth-service
# E2E тесты
cd apps/tests/playwright
bun run test --grep "Auth"Troubleshooting
Проблема: KYC проверка зависает
Решение: Проверьте соединение с СМЭВ API.
# Проверка доступности СМЭВ
curl https://smev.gosuslugi.ru/portal/apiПроблема: Лимиты не работают
Решение: Проверьте конфигурацию лимитов в базе данных.
SELECT * FROM user_limits WHERE user_id = '<user-id>';Проблема: Санкционные проверки медленные
Решение: Добавьте кэширование результатов.
const cached = await this.cache.get(`sanctions:${userId}`);
if (cached) return cached;