npm package: @skroz/profile-api
Description: GraphQL-резолверы и сервисы для аутентификации пользователей. Построена на type-graphql + TypeORM + Redis.
Latest version: 1.0.35

# @skroz/profile-api

GraphQL-резолверы и сервисы для аутентификации пользователей. Построена на type-graphql + TypeORM + Redis.

## Архитектура

Библиотека предоставляет **фабрики резолверов** — функции, которые принимают зависимости и возвращают класс резолвера для регистрации в GraphQL-схеме. Это позволяет переиспользовать логику в разных приложениях с разными User-сущностями.

## Билд

```bash
cd libs/utils/packages/profile-api
yarn build
```

## Экспорты

### Типы

- **`AuthUser`** — интерфейс пользователя: `id`, `email`, `name`, `avatar`, `password`, `isEmailConfirmed`, `isBanned`, `isTempPassword`, `urlSlug`, `telegramId`, `isEmailNotificationEnabled`, `isTelegramNotificationEnabled`, `lastSeenAt`, `save()`
- **`ProfileDbAdapter`** — интерфейс БД-адаптера, который нужно реализовать: `findUserByEmail`, `findUserById`, `findUserByTelegramId`, `createUser`, `isEmailTaken`, `findUserByProviderId`, `updateUserProviderId`
- **`ProfileAuthConfig`** — конфиг токенов/лимитов: `resendEmailLimitSeconds`, `confirmationTokenLifetimeMinutes`, `recoveryTokenLifetimeMinutes`
- **`EmailConfig`** — extends ProfileAuthConfig + настройки email: `domain`, `websiteUrl`, `primaryBrandColor`, `logoUrl`, `fromEmailUsername`, `templateDir`, `isOnlineSeconds`, `isOnlineRecentlySeconds`
- **`ProfileEmailTemplate`** — enum шаблонов писем: `CONFIRM_EMAIL`, `FORGOT_PASSWORD`, `TEMP_PASSWORD`, `GENERIC_NOTIFICATION`
- **`ProfileLocales`** — структура переводов для auth-ошибок и email-шаблонов
- **`ProfileContext<TUser>`** — GraphQL-контекст: `user?`, `req`, `res`, `t`

### TypeOrmProfileAdapter

Готовая реализация `ProfileDbAdapter` поверх TypeORM.

```ts
import { TypeOrmProfileAdapter } from '@skroz/profile-api';

const db = new TypeOrmProfileAdapter(() =>
  getConnection().getRepository(User)
);
```

Провайдер-ID ищет по полю `${provider}Id` (т.е. `googleId`, `vkId`, `telegramId` и т.д.) — эти поля должны быть в сущности User.

### TypeOrmBaseUser

Базовая TypeORM-сущность со всеми полями `AuthUser`. Можно наследовать:

```ts
import { TypeOrmBaseUser } from '@skroz/profile-api';

@Entity()
export class User extends TypeOrmBaseUser {
  // дополнительные поля
}
```

### ProfileAuthService

Основной сервис. Принимает `(db, email, redis, config)`.

```ts
import { ProfileAuthService } from '@skroz/profile-api';

const authService = new ProfileAuthService(db, emailService, redis, {
  resendEmailLimitSeconds: 60,
  confirmationTokenLifetimeMinutes: 60,
  recoveryTokenLifetimeMinutes: 30,
});
```

Публичные поля: `db`, `email`, `redis`, `config`.
Методы: `hashPassword`, `verifyPassword`, `setTokenToRedis`, `removeTokenFromRedis`, `getUserByToken`, `sendLink`.

### ProfileEmailService

Отправка писем (confirm email, forgot password, temp password). Передаётся в `ProfileAuthService`.

---

## Резолверы

Все резолверы создаются через фабричные функции. `authService` в deps может быть как экземпляром, так и функцией `(ctx) => ProfileAuthService` (для per-request сервисов).

### createAuthResolver(deps)

Auth-мутации: `register`, `login`, `logout`, `confirmEmail`, `sendToken`, `recoverPassword`.

```ts
import { createAuthResolver } from '@skroz/profile-api';

const AuthResolver = createAuthResolver({
  authService,           // ProfileAuthService | (ctx) => ProfileAuthService
  userType: User,        // GraphQL ObjectType
  onUserCreated: async (user, ctx) => { /* ... */ },
  onLogin: async (user, ctx) => { /* ... */ },
  onLogout: async (user, ctx) => { /* ... */ },
  onEmailConfirmed: async (user, ctx) => { /* ... */ },
  onPasswordRecovered: async (user, ctx) => { /* ... */ },
  logTelegramBot: { sendError: async (msg) => { /* ... */ } },
});
```

### createProfileResolver(deps)

Мутации профиля (только для авторизованных): `updateEmail`, `updatePassword`, `updateProfile`, `toggleEmailNotification`.

```ts
import { createProfileResolver } from '@skroz/profile-api';

const ProfileResolver = createProfileResolver({ authService, userType: User });
```

### createOauthResolver(deps)

OAuth-авторизация и вход через Telegram-бот.

Мутации:
- `oauthLogin(input: OauthLoginInput)` — вход через Google/VK/Яндекс/Mail/Apple/Telegram-виджет
- `generateTelegramAuthToken` → `{ token, url }` — генерирует токен и deep link для бота
- `confirmTelegramAuthToken(token)` → `{ confirmed, expired }` — polling со стороны фронта

```ts
import { createOauthResolver, GoogleOauth, VKOauth } from '@skroz/profile-api';

const OauthResolver = createOauthResolver({
  authService,
  userType: User,
  providers: {
    google: new GoogleOauth(clientId, clientSecret, redirectUri),
    vk: new VKOauth(clientId, clientSecret, redirectUri),
    // ya, mail, apple — аналогично
  },
  telegramBotToken: process.env.TELEGRAM_BOT_TOKEN, // для Telegram Login Widget
  telegramBotName: 'MyBot',  // имя бота без @, для deep link авторизации
  redis: getRedis(),          // клиент с методами get/setex/del
  onUserCreated: async (user, ctx) => { /* ... */ },
  onLogin: async (user, ctx) => { /* ... */ },
});
```

---

## confirmTelegramBotAuth

Вспомогательная функция для **Telegram-бота** (не для GraphQL-резолвера).

Когда пользователь переходит по deep link `/start tgauth_<token>`, бот вызывает эту функцию — она записывает `telegramUserId` в Redis, и фронт получает `confirmed: true` при следующем polling.

```ts
import { confirmTelegramBotAuth } from '@skroz/profile-api';

// В обработчике команды /start в Telegram-боте:
if (text.startsWith('/start tgauth_')) {
  const token = text.replace('/start tgauth_', '');
  const confirmed = await confirmTelegramBotAuth(redis, token, user.id);
  if (confirmed) {
    // отправить пользователю сообщение об успешной авторизации
  }
}
```

Возвращает `true` если токен найден и подтверждён, `false` если истёк или не существует.
Использует тот же Redis-префикс (`skroz:profile:tgbotauth`) и TTL (300 сек), что и `generateTelegramAuthToken`.

---

## OAuth-провайдеры

```ts
import { GoogleOauth, VKOauth, YandexOauth, MailOauth, AppleOauth } from '@skroz/profile-api';
```

Каждый провайдер реализует интерфейс `OAuthProvider`:

```ts
interface OAuthProvider {
  exchangeCode(code: string): Promise<OAuthProfile>;
  readonly trustedEmail: boolean; // true = email верифицирован провайдером
}

interface OAuthProfile {
  providerId: string;
  email?: string | null;
  name?: string | null;
  avatarUrl?: string | null;
}
```

`trustedEmail: true` у Google, VK, Яндекс, Mail, Apple — при входе через них `isEmailConfirmed` и `isEmailNotificationEnabled` автоматически выставляются в `true`.

---

## DTO

GraphQL input/output классы для использования в схеме:

`AuthInput`, `UpdateEmailInput`, `UpdatePasswordInput`, `UpdateProfileInput`,
`ConfirmEmailInput`, `RecoverPasswordInput`, `SendTokenInput`, `SendTokenPayload`,
`OauthLoginInput`, `TelegramAuthData`