feat: add passport
This commit is contained in:
parent
e294b8355f
commit
b22a787eff
@ -34,6 +34,7 @@
|
||||
"@prisma/client": "^7.3.0",
|
||||
"@teacinema/common": "^1.0.0",
|
||||
"@teacinema/contracts": "^1.0.0",
|
||||
"@teacinema/passport": "^1.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.3",
|
||||
"dotenv-expand": "^12.0.3",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConfigModule } from '@nestjs/config'
|
||||
|
||||
import { dabataseEnv, grpcEnv, redisEnv } from './config'
|
||||
import { dabataseEnv, grpcEnv, passportEnv, redisEnv } from './config'
|
||||
import { PrismaModule } from './infra/prisma/prisma.module'
|
||||
import { RedisModule } from './infra/redis/redis.module'
|
||||
import { AuthModule } from './modules/auth/auth.module'
|
||||
@ -11,7 +11,7 @@ import { OtpModule } from './modules/otp/otp.module'
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [dabataseEnv, grpcEnv, redisEnv]
|
||||
load: [dabataseEnv, grpcEnv, redisEnv, passportEnv]
|
||||
}),
|
||||
PrismaModule,
|
||||
RedisModule,
|
||||
|
||||
1
src/config/env/index.ts
vendored
1
src/config/env/index.ts
vendored
@ -1,3 +1,4 @@
|
||||
export * from './grpc.env'
|
||||
export * from './database.env'
|
||||
export * from './redis.env'
|
||||
export * from './passport.env'
|
||||
|
||||
16
src/config/env/passport.env.ts
vendored
Normal file
16
src/config/env/passport.env.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import { registerAs } from '@nestjs/config'
|
||||
|
||||
import { validateEnv } from '@/shared/utils'
|
||||
|
||||
import { PassportConfig } from '../interfaces/passport.interface'
|
||||
import { PassportValidator } from '../validators'
|
||||
|
||||
export const passportEnv = registerAs<PassportConfig>('passport', () => {
|
||||
validateEnv(process.env, PassportValidator)
|
||||
|
||||
return {
|
||||
secretKey: process.env.PASSPORT_SECRET_KEY,
|
||||
accessTtl: parseInt(process.env.PASSPORT_ACCESS_TTL),
|
||||
refreshTtl: parseInt(process.env.PASSPORT_REFRESH_TTL)
|
||||
}
|
||||
})
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './env'
|
||||
export * from './interfaces'
|
||||
export * from './loaders'
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { DatabaseConfig } from './database.interface'
|
||||
import { GrpcConfig } from './grpc.interface'
|
||||
import { PassportConfig } from './passport.interface'
|
||||
import { RedisConfig } from './redis.interface'
|
||||
|
||||
export interface AllConfigs {
|
||||
database: DatabaseConfig
|
||||
grpc: GrpcConfig
|
||||
redis: RedisConfig
|
||||
passport: PassportConfig
|
||||
}
|
||||
|
||||
5
src/config/interfaces/passport.interface.ts
Normal file
5
src/config/interfaces/passport.interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface PassportConfig {
|
||||
secretKey: string
|
||||
accessTtl: number
|
||||
refreshTtl: number
|
||||
}
|
||||
1
src/config/loaders/index.ts
Normal file
1
src/config/loaders/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './passport.config.loader'
|
||||
12
src/config/loaders/passport.config.loader.ts
Normal file
12
src/config/loaders/passport.config.loader.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { PassportOptions } from '@teacinema/passport'
|
||||
|
||||
import { AllConfigs } from '@/config'
|
||||
|
||||
export function getPassportConfig(
|
||||
configService: ConfigService<AllConfigs>
|
||||
): PassportOptions {
|
||||
return {
|
||||
secretKey: configService.get('passport.secretKey', { infer: true })
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './grpc.validator'
|
||||
export * from './database.validator'
|
||||
export * from './redis.validator'
|
||||
export * from './passport.validator'
|
||||
|
||||
14
src/config/validators/passport.validator.ts
Normal file
14
src/config/validators/passport.validator.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { IsInt, IsNumber, IsString } from 'class-validator'
|
||||
|
||||
export class PassportValidator {
|
||||
@IsString()
|
||||
public PASSPORT_SECRET_KEY: string
|
||||
|
||||
@IsInt()
|
||||
@IsNumber()
|
||||
public PASSPORT_ACCESS_TTL: number
|
||||
|
||||
@IsInt()
|
||||
@IsNumber()
|
||||
public PASSPORT_REFRESH_TTL: number
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { Controller } from '@nestjs/common'
|
||||
import { GrpcMethod } from '@nestjs/microservices'
|
||||
import { Controller } from '@nestjs/common';
|
||||
import { GrpcMethod } from '@nestjs/microservices';
|
||||
import type {
|
||||
RefreshRequest,
|
||||
RefreshResponse,
|
||||
SendOtpRequest,
|
||||
SendOtpResponse,
|
||||
VerifyOtpRequest,
|
||||
@ -9,6 +11,10 @@ import type {
|
||||
|
||||
import { AuthService } from './auth.service'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Controller()
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
@ -22,4 +28,9 @@ export class AuthController {
|
||||
public VerifyOtp(data: VerifyOtpRequest): Promise<VerifyOtpResponse> {
|
||||
return this.authService.verifyOtp(data)
|
||||
}
|
||||
|
||||
@GrpcMethod('AuthService', 'Refresh')
|
||||
public refresh(data: RefreshRequest): RefreshResponse {
|
||||
return this.authService.refresh(data)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { PassportModule } from '@teacinema/passport'
|
||||
|
||||
import { getPassportConfig } from '@/config'
|
||||
import { OtpService } from '@/modules/otp/otp.service'
|
||||
|
||||
import { AuthController } from './auth.controller'
|
||||
@ -7,6 +10,12 @@ import { AuthRepository } from './auth.repository'
|
||||
import { AuthService } from './auth.service'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule.registerAsync({
|
||||
useFactory: getPassportConfig,
|
||||
inject: [ConfigService]
|
||||
})
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, AuthRepository, OtpService]
|
||||
})
|
||||
|
||||
@ -1,24 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { RpcException } from '@nestjs/microservices'
|
||||
import { Account } from '@prisma/generated/client'
|
||||
import { RpcStatus } from '@teacinema/common'
|
||||
import {
|
||||
type RefreshRequest,
|
||||
type RefreshResponse,
|
||||
SendOtpRequest,
|
||||
SendOtpResponse,
|
||||
VerifyOtpRequest,
|
||||
VerifyOtpResponse
|
||||
} from '@teacinema/contracts/gen/auth'
|
||||
import { PassportService } from '@teacinema/passport'
|
||||
|
||||
import { AllConfigs } from '@/config'
|
||||
import { OtpService } from '@/modules/otp/otp.service'
|
||||
|
||||
import { AuthRepository } from './auth.repository'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly ACCESS_TOKEN_TTL: number
|
||||
private readonly REFRESH_TOKEN_TTL: number
|
||||
|
||||
public constructor(
|
||||
private readonly configService: ConfigService<AllConfigs>,
|
||||
private readonly authRepository: AuthRepository,
|
||||
private readonly otpService: OtpService
|
||||
) {}
|
||||
private readonly otpService: OtpService,
|
||||
private readonly passportService: PassportService
|
||||
) {
|
||||
this.ACCESS_TOKEN_TTL = configService.get('passport.accessTtl', {
|
||||
infer: true
|
||||
})
|
||||
this.REFRESH_TOKEN_TTL = configService.get('passport.refreshTtl', {
|
||||
infer: true
|
||||
})
|
||||
}
|
||||
|
||||
public async sendOtp(data: SendOtpRequest): Promise<SendOtpResponse> {
|
||||
const { identifier, type } = data
|
||||
@ -80,6 +97,38 @@ export class AuthService {
|
||||
})
|
||||
}
|
||||
|
||||
return { accessToken: '123456', refreshToken: '123456' }
|
||||
return this.generateTokens(account.id)
|
||||
}
|
||||
|
||||
private generateTokens(userId: Account['id']) {
|
||||
const payload = {
|
||||
sub: userId,
|
||||
iat: Date.now(),
|
||||
exp: Date.now() + this.ACCESS_TOKEN_TTL
|
||||
}
|
||||
const accessToken = this.passportService.generate(
|
||||
payload.sub,
|
||||
this.ACCESS_TOKEN_TTL
|
||||
)
|
||||
const refreshToken = this.passportService.generate(
|
||||
payload.sub,
|
||||
this.REFRESH_TOKEN_TTL
|
||||
)
|
||||
|
||||
return { accessToken, refreshToken }
|
||||
}
|
||||
|
||||
public refresh(data: RefreshRequest): RefreshResponse {
|
||||
const { refreshToken } = data
|
||||
const result = this.passportService.verify(refreshToken)
|
||||
|
||||
if (!result.valid) {
|
||||
throw new RpcException({
|
||||
code: RpcStatus.UNAUTHENTICATED,
|
||||
details: result.reason
|
||||
})
|
||||
}
|
||||
|
||||
return this.generateTokens(result.userId);
|
||||
}
|
||||
}
|
||||
|
||||
20
yarn.lock
20
yarn.lock
@ -1055,7 +1055,7 @@
|
||||
webpack "5.104.1"
|
||||
webpack-node-externals "3.0.0"
|
||||
|
||||
"@nestjs/common@^11.0.1":
|
||||
"@nestjs/common@^11.0.1", "@nestjs/common@^11.1.12":
|
||||
version "11.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.1.12.tgz#bd28d34fa6bf676671c70be8148b5405ccd3d422"
|
||||
integrity sha512-v6U3O01YohHO+IE3EIFXuRuu3VJILWzyMmSYZXpyBbnp0hk0mFyHxK2w3dF4I5WnbwiRbWlEXdeXFvPQ7qaZzw==
|
||||
@ -1075,7 +1075,7 @@
|
||||
dotenv-expand "12.0.1"
|
||||
lodash "4.17.21"
|
||||
|
||||
"@nestjs/core@^11.0.1":
|
||||
"@nestjs/core@^11.0.1", "@nestjs/core@^11.1.12":
|
||||
version "11.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-11.1.12.tgz#cdbff023cc87ed071aebafeacb209bc5997da1e4"
|
||||
integrity sha512-97DzTYMf5RtGAVvX1cjwpKRiCUpkeQ9CCzSAenqkAhOmNVVFaApbhuw+xrDt13rsCa2hHVOYPrV4dBgOYMJjsA==
|
||||
@ -1360,9 +1360,9 @@
|
||||
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
||||
|
||||
"@teacinema/contracts@^1.0.0":
|
||||
version "1.0.4"
|
||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.4/contracts-1.0.4.tgz#178dc6392aae52c4dc2b60bb1470a0a2885a8715"
|
||||
integrity sha512-3jHP4NAoH9munaN2Iegvg0X6KN/SR1rwZU7KpbuBtlcTJqsLljTaHwG+XKjEL84Y9b+sF73jGGbg/w2Y8IBQmw==
|
||||
version "1.0.5"
|
||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.5/contracts-1.0.5.tgz#f6db65f58ba3621e81e886f54d6ec9e674c4785b"
|
||||
integrity sha512-paFa/zVWKzDNuH04EyGPlG981YEX7Xt42gRwpfZwACGa5CCM5lehT7xKU4h9p/l9IebrOtTPDwJdQn/D41o3Gw==
|
||||
dependencies:
|
||||
"@nestjs/microservices" "^11.1.12"
|
||||
protoc "33.4.0"
|
||||
@ -1376,6 +1376,16 @@
|
||||
dependencies:
|
||||
"@trivago/prettier-plugin-sort-imports" "^5.2.2"
|
||||
|
||||
"@teacinema/passport@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fpassport/-/1.0.0/passport-1.0.0.tgz#c848d9fb089a975111bf19280cd4ca7b0118b73c"
|
||||
integrity sha512-aPDPmaAkXiHKTkBxGR08DB3ih3cAZSF91CZaRBLXpQ7/NvfVLcOGmvk9jaAL5vbExtwT/OOQVKEMb0nIfVAzzg==
|
||||
dependencies:
|
||||
"@nestjs/common" "^11.1.12"
|
||||
"@nestjs/core" "^11.1.12"
|
||||
reflect-metadata "^0.2.2"
|
||||
rxjs "^7.8.2"
|
||||
|
||||
"@tokenizer/inflate@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.4.1.tgz#fa6cdb8366151b3cc8426bf9755c1ea03a2fba08"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user