feat: add telegram authorization

This commit is contained in:
Sergey Krylov 2026-02-20 06:41:17 +03:00
parent 0341b2eafe
commit 0d184020d9
8 changed files with 104 additions and 32 deletions

View File

@ -5,17 +5,10 @@ import { TokenService } from '@/modules/token/token.service'
import { UserRepository } from '@/shared/utils/repositories' import { UserRepository } from '@/shared/utils/repositories'
import { AuthController } from './auth.controller' import { AuthController } from './auth.controller'
import { AuthRepository } from './auth.repository'
import { AuthService } from './auth.service' import { AuthService } from './auth.service'
@Module({ @Module({
controllers: [AuthController], controllers: [AuthController],
providers: [ providers: [AuthService, UserRepository, OtpService, TokenService]
AuthService,
AuthRepository,
UserRepository,
OtpService,
TokenService
]
}) })
export class AuthModule {} export class AuthModule {}

View File

@ -1,13 +0,0 @@
import { Injectable } from '@nestjs/common'
import { AccountCreateInput } from '@prisma/generated/models/Account'
import { PrismaService } from '@/infra/prisma/prisma.service'
@Injectable()
export class AuthRepository {
public constructor(private readonly prismaService: PrismaService) {}
public createAccount(data: AccountCreateInput) {
return this.prismaService.account.create({ data })
}
}

View File

@ -16,12 +16,9 @@ import { UserRepository } from '@/shared/utils/repositories'
import { TokenService } from '../token/token.service' import { TokenService } from '../token/token.service'
import { AuthRepository } from './auth.repository'
@Injectable() @Injectable()
export class AuthService { export class AuthService {
public constructor( public constructor(
private readonly authRepository: AuthRepository,
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private readonly otpService: OtpService, private readonly otpService: OtpService,
private readonly tokenService: TokenService private readonly tokenService: TokenService
@ -41,7 +38,7 @@ export class AuthService {
} }
if (!account) { if (!account) {
account = await this.authRepository.createAccount({ account = await this.userRepository.createAccount({
email: isEmailType ? identifier : undefined, email: isEmailType ? identifier : undefined,
phone: isPhoneType ? identifier : undefined phone: isPhoneType ? identifier : undefined
}) })

View File

@ -1,6 +1,10 @@
import { Controller } from '@nestjs/common' import { Controller } from '@nestjs/common'
import { GrpcMethod } from '@nestjs/microservices' import { GrpcMethod } from '@nestjs/microservices'
import type { import type {
TelegramCompleteRequest,
TelegramCompleteResponse,
TelegramConsumeRequest,
TelegramConsumeResponse,
TelegramInitResponse, TelegramInitResponse,
TelegramVerifyRequest, TelegramVerifyRequest,
TelegramVerifyResponse TelegramVerifyResponse
@ -21,4 +25,18 @@ export class TelegramController {
public verify(data: TelegramVerifyRequest): Promise<TelegramVerifyResponse> { public verify(data: TelegramVerifyRequest): Promise<TelegramVerifyResponse> {
return this.telegramService.verify(data) return this.telegramService.verify(data)
} }
@GrpcMethod('AuthService', 'TelegramComplete')
public complete(
data: TelegramCompleteRequest
): Promise<TelegramCompleteResponse> {
return this.telegramService.complete(data)
}
@GrpcMethod('AuthService', 'TelegramConsume')
public consumeSession(
data: TelegramConsumeRequest
): Promise<TelegramConsumeResponse> {
return this.telegramService.consumeSession(data)
}
} }

View File

@ -2,12 +2,13 @@ import { Module } from '@nestjs/common'
import { TelegramRepository } from '@/modules/telegram/telegram.repository' import { TelegramRepository } from '@/modules/telegram/telegram.repository'
import { TokenService } from '@/modules/token/token.service' import { TokenService } from '@/modules/token/token.service'
import { UserRepository } from '@/shared/utils/repositories'
import { TelegramController } from './telegram.controller' import { TelegramController } from './telegram.controller'
import { TelegramService } from './telegram.service' import { TelegramService } from './telegram.service'
@Module({ @Module({
controllers: [TelegramController], controllers: [TelegramController],
providers: [TelegramService, TelegramRepository, TokenService] providers: [TelegramService, TelegramRepository, TokenService, UserRepository]
}) })
export class TelegramModule {} export class TelegramModule {}

View File

@ -3,6 +3,10 @@ import { ConfigService } from '@nestjs/config'
import { RpcException } from '@nestjs/microservices' import { RpcException } from '@nestjs/microservices'
import { RpcStatus } from '@teacinema/common' import { RpcStatus } from '@teacinema/common'
import { import {
TelegramCompleteRequest,
TelegramCompleteResponse,
TelegramConsumeRequest,
TelegramConsumeResponse,
TelegramInitResponse, TelegramInitResponse,
TelegramVerifyRequest TelegramVerifyRequest
} from '@teacinema/contracts/gen/auth' } from '@teacinema/contracts/gen/auth'
@ -11,6 +15,7 @@ import { createHash, createHmac, randomBytes } from 'node:crypto'
import { AllConfigs } from '@/config' import { AllConfigs } from '@/config'
import { RedisService } from '@/infra/redis/redis.service' import { RedisService } from '@/infra/redis/redis.service'
import { TokenService } from '@/modules/token/token.service' import { TokenService } from '@/modules/token/token.service'
import { UserRepository } from '@/shared/utils/repositories'
import { TelegramRepository } from './telegram.repository' import { TelegramRepository } from './telegram.repository'
@ -25,7 +30,8 @@ export class TelegramService {
private readonly redisService: RedisService, private readonly redisService: RedisService,
private readonly configService: ConfigService<AllConfigs>, private readonly configService: ConfigService<AllConfigs>,
private readonly telegramRepository: TelegramRepository, private readonly telegramRepository: TelegramRepository,
private readonly tokenService: TokenService private readonly tokenService: TokenService,
private readonly userRepository: UserRepository
) { ) {
this.BOT_ID = configService.get('telegram.telegramBotId', { infer: true }) this.BOT_ID = configService.get('telegram.telegramBotId', { infer: true })
this.BOT_TOKEN = configService.get('telegram.telegramBotToken', { this.BOT_TOKEN = configService.get('telegram.telegramBotToken', {
@ -94,7 +100,7 @@ export class TelegramService {
const dataCheckString = dataCheckArr.join('\n') const dataCheckString = dataCheckArr.join('\n')
const secretKey = createHash('sha256') const secretKey = createHash('sha256')
.update(`${this.BOT_ID}:${this.BOT_TOKEN}`) .update(`${this.BOT_ID}:${this.BOT_TOKEN}`)
.digest('hex') .digest()
const hmac = createHmac('sha256', secretKey) const hmac = createHmac('sha256', secretKey)
.update(dataCheckString) .update(dataCheckString)
@ -102,4 +108,67 @@ export class TelegramService {
return hmac === hash return hmac === hash
} }
public async complete(
data: TelegramCompleteRequest
): Promise<TelegramCompleteResponse> {
const { phone, sessionId } = data
const raw = await this.redisService.get(`telegram_session:${sessionId}`)
if (!raw) {
throw new RpcException({
code: RpcStatus.NOT_FOUND,
message: 'Session not found'
})
}
const { telegramId }: Record<'telegramId' | 'username', string> =
JSON.parse(raw)
let user = await this.userRepository.findByPhone(phone)
if (!user) {
user = await this.userRepository.createAccount({
phone,
telegramId
})
}
await this.userRepository.updateAccount(user.id, {
telegramId,
isPhoneVerified: true
})
const tokens = this.tokenService.generateTokens(user.id)
await this.redisService.set(
`telegram_tokens:${sessionId}`,
JSON.stringify(tokens),
'EX',
120
)
await this.redisService.del(`telegram_session:${sessionId}`)
return { sessionId }
}
public async consumeSession(
data: TelegramConsumeRequest
): Promise<TelegramConsumeResponse> {
const { sessionId } = data
const redisKey = `telegram_tokens:${sessionId}`
const raw = await this.redisService.get(redisKey)
if (!raw) {
throw new RpcException({
code: RpcStatus.NOT_FOUND,
message: 'Session not found'
})
}
const tokens: Record<'accessToken' | 'refreshToken', string> =
JSON.parse(raw)
await this.redisService.del(redisKey)
return tokens
}
} }

View File

@ -1,6 +1,9 @@
import { Injectable } from '@nestjs/common' import { Injectable } from '@nestjs/common'
import { Account } from '@prisma/generated/client' import { Account } from '@prisma/generated/client'
import { AccountUpdateInput } from '@prisma/generated/models/Account' import {
AccountCreateInput,
AccountUpdateInput
} from '@prisma/generated/models/Account'
import { PrismaService } from '@/infra/prisma/prisma.service' import { PrismaService } from '@/infra/prisma/prisma.service'
@ -26,4 +29,8 @@ export class UserRepository {
data data
}) })
} }
public createAccount(data: AccountCreateInput) {
return this.prismaService.account.create({ data })
}
} }

View File

@ -1383,9 +1383,9 @@
integrity sha512-q3DURJbSk3k8MNWFIYaSM4LEcBgPbWa+HJmBz/nzYT4kuYitJVSXxpZ97kr0Ea+81AZwAU3JhQKlkK0SBWUi0A== integrity sha512-q3DURJbSk3k8MNWFIYaSM4LEcBgPbWa+HJmBz/nzYT4kuYitJVSXxpZ97kr0Ea+81AZwAU3JhQKlkK0SBWUi0A==
"@teacinema/contracts@^1.0.0": "@teacinema/contracts@^1.0.0":
version "1.0.8" version "1.0.9"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.8/contracts-1.0.8.tgz#4e31c3a370b0808629d9c93900c5f27958ef82e6" resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.9/contracts-1.0.9.tgz#14b136d0c08552147b0de637a7af6d340d538527"
integrity sha512-xyPyifbVwWelgo4MsvhhRl//To+b0Tnp8DtANCzwLBUhwPj3ZG19QgfPB7Ih7PsBU5TD6F80aNlITDxJbFgkFQ== integrity sha512-9BwWvXaDKgRcef6+1ep9vUDWBdOndqheeTX5mXK5OOvLXbLOlzYoqAUoT0ZwaRwOCD+Je/LDrqcdti6eoss71Q==
dependencies: dependencies:
"@nestjs/microservices" "^11.1.12" "@nestjs/microservices" "^11.1.12"
protoc "33.4.0" protoc "33.4.0"