Compare commits
No commits in common. "4a0cad44bea693ef1f1139832619de8f17ce1b61" and "d9074b4365d8ae957dd8a25e711edd869495dc88" have entirely different histories.
4a0cad44be
...
d9074b4365
@ -11,8 +11,6 @@ services:
|
|||||||
- .env.production.local
|
- .env.production.local
|
||||||
expose:
|
expose:
|
||||||
- '9101'
|
- '9101'
|
||||||
volumes:
|
|
||||||
- ../logs/auth:/var/log/services/auth
|
|
||||||
networks:
|
networks:
|
||||||
- teacinema
|
- teacinema
|
||||||
|
|
||||||
|
|||||||
@ -30,11 +30,6 @@
|
|||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/microservices": "^11.1.12",
|
"@nestjs/microservices": "^11.1.12",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@opentelemetry/auto-instrumentations-node": "^0.72.0",
|
|
||||||
"@opentelemetry/exporter-trace-otlp-grpc": "^0.214.0",
|
|
||||||
"@opentelemetry/resources": "^2.6.1",
|
|
||||||
"@opentelemetry/sdk-node": "^0.214.0",
|
|
||||||
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
||||||
"@prisma/adapter-pg": "^7.3.0",
|
"@prisma/adapter-pg": "^7.3.0",
|
||||||
"@prisma/client": "^7.3.0",
|
"@prisma/client": "^7.3.0",
|
||||||
"@teacinema/common": "^1.0.0",
|
"@teacinema/common": "^1.0.0",
|
||||||
@ -47,9 +42,6 @@
|
|||||||
"class-validator": "^0.14.3",
|
"class-validator": "^0.14.3",
|
||||||
"dotenv-expand": "^12.0.3",
|
"dotenv-expand": "^12.0.3",
|
||||||
"ioredis": "^5.9.2",
|
"ioredis": "^5.9.2",
|
||||||
"nestjs-pino": "^4.6.1",
|
|
||||||
"pino": "^10.3.1",
|
|
||||||
"pino-http": "^11.0.0",
|
|
||||||
"prisma": "^7.3.0",
|
"prisma": "^7.3.0",
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { ConfigModule } from '@nestjs/config'
|
import { ConfigModule } from '@nestjs/config'
|
||||||
import { LoggerModule } from 'nestjs-pino'
|
|
||||||
import pino from 'pino'
|
|
||||||
|
|
||||||
import { ObservabilityModule } from '@/observability/observability.module'
|
import { ObservabilityModule } from '@/observability/observability.module'
|
||||||
|
|
||||||
@ -23,23 +21,6 @@ import { TelegramModule } from './modules/telegram/telegram.module'
|
|||||||
import { TokenModule } from './modules/token/token.module'
|
import { TokenModule } from './modules/token/token.module'
|
||||||
import { UsersModule } from './modules/users/users.module'
|
import { UsersModule } from './modules/users/users.module'
|
||||||
|
|
||||||
const transport = pino.transport({
|
|
||||||
target: 'pino/file',
|
|
||||||
options: {
|
|
||||||
destination: '/var/log/services/auth/auth.log',
|
|
||||||
mkdir: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const logger = pino(
|
|
||||||
{
|
|
||||||
level: process.env.LOG_LEVEL || 'info',
|
|
||||||
messageKey: 'msg',
|
|
||||||
base: { service: 'auth' }
|
|
||||||
},
|
|
||||||
transport
|
|
||||||
)
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
@ -51,9 +32,6 @@ const logger = pino(
|
|||||||
],
|
],
|
||||||
load: [dabataseEnv, grpcEnv, redisEnv, passportEnv, telegramEnv, rmqEnv]
|
load: [dabataseEnv, grpcEnv, redisEnv, passportEnv, telegramEnv, rmqEnv]
|
||||||
}),
|
}),
|
||||||
LoggerModule.forRoot({
|
|
||||||
pinoHttp: { logger }
|
|
||||||
}),
|
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
RedisModule,
|
RedisModule,
|
||||||
ObservabilityModule,
|
ObservabilityModule,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { NestFactory } from '@nestjs/core'
|
|||||||
import { createGrpcServer } from '@/infra/grpc/grpc.server'
|
import { createGrpcServer } from '@/infra/grpc/grpc.server'
|
||||||
|
|
||||||
import { AppModule } from './app.module'
|
import { AppModule } from './app.module'
|
||||||
import './observability/tracing'
|
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule)
|
const app = await NestFactory.create(AppModule)
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
VerifyOtpRequest,
|
VerifyOtpRequest,
|
||||||
VerifyOtpResponse
|
VerifyOtpResponse
|
||||||
} from '@teacinema/contracts/gen/auth'
|
} from '@teacinema/contracts/gen/auth'
|
||||||
import { PinoLogger } from 'nestjs-pino'
|
|
||||||
|
|
||||||
import { MessagingService } from '@/infra/messaging/messaging.service'
|
import { MessagingService } from '@/infra/messaging/messaging.service'
|
||||||
import { OtpService } from '@/modules/otp/otp.service'
|
import { OtpService } from '@/modules/otp/otp.service'
|
||||||
@ -23,22 +22,17 @@ import { UsersClientGrpc } from '../users/users.grpc'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly logger: PinoLogger,
|
|
||||||
private readonly userRepository: UserRepository,
|
private readonly userRepository: UserRepository,
|
||||||
private readonly otpService: OtpService,
|
private readonly otpService: OtpService,
|
||||||
private readonly tokenService: TokenService,
|
private readonly tokenService: TokenService,
|
||||||
private readonly messagingService: MessagingService,
|
private readonly messagingService: MessagingService,
|
||||||
private readonly usersClient: UsersClientGrpc,
|
private readonly usersClient: UsersClientGrpc,
|
||||||
private readonly configService: ConfigService
|
private readonly configService: ConfigService
|
||||||
) {
|
) {}
|
||||||
this.logger.setContext(AuthService.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sendOtp(data: SendOtpRequest): Promise<SendOtpResponse> {
|
public async sendOtp(data: SendOtpRequest): Promise<SendOtpResponse> {
|
||||||
const { identifier, type } = data
|
const { identifier, type } = data
|
||||||
|
|
||||||
this.logger.debug(`OTP request received from ${identifier} of type ${type}`)
|
|
||||||
|
|
||||||
let account: Account | null = null
|
let account: Account | null = null
|
||||||
const isPhoneType = type === 'phone'
|
const isPhoneType = type === 'phone'
|
||||||
const isEmailType = type === 'email'
|
const isEmailType = type === 'email'
|
||||||
@ -50,9 +44,6 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
this.logger.warn(
|
|
||||||
`Account not found, creating new account for ${identifier} `
|
|
||||||
)
|
|
||||||
account = await this.userRepository.createAccount({
|
account = await this.userRepository.createAccount({
|
||||||
email: isEmailType ? identifier : undefined,
|
email: isEmailType ? identifier : undefined,
|
||||||
phone: isPhoneType ? identifier : undefined
|
phone: isPhoneType ? identifier : undefined
|
||||||
@ -69,16 +60,12 @@ export class AuthService {
|
|||||||
type: type as 'email' | 'phone'
|
type: type as 'email' | 'phone'
|
||||||
})
|
})
|
||||||
|
|
||||||
this.logger.info(`OTP successfully send to ${identifier} of type ${type}`)
|
|
||||||
|
|
||||||
return { ok: true }
|
return { ok: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
public async verifyOtp(data: VerifyOtpRequest): Promise<VerifyOtpResponse> {
|
public async verifyOtp(data: VerifyOtpRequest): Promise<VerifyOtpResponse> {
|
||||||
const { identifier, code, type } = data
|
const { identifier, code, type } = data
|
||||||
|
|
||||||
this.logger.debug(`OTP verify for ${identifier} of type ${type}`)
|
|
||||||
|
|
||||||
await this.otpService.verify(identifier, code, type as 'email' | 'phone')
|
await this.otpService.verify(identifier, code, type as 'email' | 'phone')
|
||||||
|
|
||||||
const isPhoneType = type === 'phone'
|
const isPhoneType = type === 'phone'
|
||||||
@ -89,9 +76,6 @@ export class AuthService {
|
|||||||
: await this.userRepository.findByEmail(identifier)
|
: await this.userRepository.findByEmail(identifier)
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
this.logger.warn(
|
|
||||||
`Account not found, create new account for ${identifier}`
|
|
||||||
)
|
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcStatus.NOT_FOUND,
|
code: RpcStatus.NOT_FOUND,
|
||||||
details: 'Account not found'
|
details: 'Account not found'
|
||||||
@ -110,10 +94,6 @@ export class AuthService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
`OTP verified successfully for ${identifier} of type ${type}`
|
|
||||||
)
|
|
||||||
|
|
||||||
this.usersClient.create({ id: account.id }).subscribe()
|
this.usersClient.create({ id: account.id }).subscribe()
|
||||||
|
|
||||||
return this.tokenService.generateTokens(account.id)
|
return this.tokenService.generateTokens(account.id)
|
||||||
@ -121,15 +101,9 @@ export class AuthService {
|
|||||||
|
|
||||||
public refresh(data: RefreshRequest): RefreshResponse {
|
public refresh(data: RefreshRequest): RefreshResponse {
|
||||||
const { refreshToken } = data
|
const { refreshToken } = data
|
||||||
this.logger.debug(`Refresh token request received`)
|
|
||||||
const result = this.tokenService.verify(refreshToken)
|
const result = this.tokenService.verify(refreshToken)
|
||||||
|
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
this.logger.error(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ok
|
|
||||||
// @ts-expect-error
|
|
||||||
`Invalid refresh token reason: ${result.reason || 'Unknown'}`
|
|
||||||
)
|
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcStatus.UNAUTHENTICATED,
|
code: RpcStatus.UNAUTHENTICATED,
|
||||||
// @ts-expect-error -- TODO: fixme
|
// @ts-expect-error -- TODO: fixme
|
||||||
@ -137,10 +111,6 @@ export class AuthService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
`Refresh token verified successfully for user ${result.userId}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return this.tokenService.generateTokens(result.userId)
|
return this.tokenService.generateTokens(result.userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,17 @@
|
|||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { RpcException } from '@nestjs/microservices'
|
import { RpcException } from '@nestjs/microservices'
|
||||||
import { RpcStatus } from '@teacinema/common'
|
import { RpcStatus } from '@teacinema/common'
|
||||||
import { PinoLogger } from 'nestjs-pino'
|
|
||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'node:crypto'
|
||||||
|
|
||||||
import { RedisService } from '@/infra/redis/redis.service'
|
import { RedisService } from '@/infra/redis/redis.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OtpService {
|
export class OtpService {
|
||||||
public constructor(
|
public constructor(private readonly redisService: RedisService) {}
|
||||||
private readonly logger: PinoLogger,
|
|
||||||
private readonly redisService: RedisService
|
|
||||||
) {
|
|
||||||
this.logger.setContext(OtpService.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async send(indentifier: string, type: 'email' | 'phone') {
|
public async send(indentifier: string, type: 'email' | 'phone') {
|
||||||
const { code, hash } = this.generateCode()
|
const { code, hash } = this.generateCode()
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
`OTP send to ${indentifier} of type ${type}, code ${code}, hash ${hash}`
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.redisService.set(
|
await this.redisService.set(
|
||||||
`otp:${type}:${indentifier}`,
|
`otp:${type}:${indentifier}`,
|
||||||
hash,
|
hash,
|
||||||
@ -29,8 +19,6 @@ export class OtpService {
|
|||||||
60 * 5
|
60 * 5
|
||||||
)
|
)
|
||||||
|
|
||||||
this.logger.debug(`OTP stored to Redis: ${indentifier}`)
|
|
||||||
|
|
||||||
return { code, hash }
|
return { code, hash }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,8 +54,6 @@ export class OtpService {
|
|||||||
const code = Math.floor(100_000 + Math.random() * 900_000)
|
const code = Math.floor(100_000 + Math.random() * 900_000)
|
||||||
const hash = createHash('sha256').update(String(code)).digest('hex')
|
const hash = createHash('sha256').update(String(code)).digest('hex')
|
||||||
|
|
||||||
this.logger.debug(`Generated OTP code: ${code}`)
|
|
||||||
|
|
||||||
return { code, hash }
|
return { code, hash }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
||||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'
|
|
||||||
import { resourceFromAttributes } from '@opentelemetry/resources'
|
|
||||||
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
||||||
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
|
|
||||||
|
|
||||||
const traceExporter = new OTLPTraceExporter({
|
|
||||||
url: 'http://jaeger:4317'
|
|
||||||
})
|
|
||||||
|
|
||||||
const otelSdk = new NodeSDK({
|
|
||||||
traceExporter,
|
|
||||||
resource: resourceFromAttributes({
|
|
||||||
[ATTR_SERVICE_NAME]: 'auth-service'
|
|
||||||
}),
|
|
||||||
instrumentations: [
|
|
||||||
getNodeAutoInstrumentations({
|
|
||||||
'@opentelemetry/instrumentation-grpc': { enabled: true },
|
|
||||||
'@opentelemetry/instrumentation-http': { enabled: true },
|
|
||||||
'@opentelemetry/instrumentation-nestjs-core': { enabled: true },
|
|
||||||
'@opentelemetry/instrumentation-redis': { enabled: true },
|
|
||||||
'@opentelemetry/instrumentation-pg': { enabled: true }
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
otelSdk.start()
|
|
||||||
Loading…
x
Reference in New Issue
Block a user