feat: add refresh, logout methods
This commit is contained in:
parent
2b861c220a
commit
cce2d83101
@ -32,6 +32,7 @@
|
|||||||
"@teacinema/contracts": "^1.0.0",
|
"@teacinema/contracts": "^1.0.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.3",
|
"class-validator": "^0.14.3",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
@ -42,6 +43,7 @@
|
|||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
"@teacinema/core": "1.0.7",
|
"@teacinema/core": "1.0.7",
|
||||||
|
"@types/cookie-parser": "^1.4.10",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Logger } from '@nestjs/common'
|
|||||||
import { ConfigService } from '@nestjs/config'
|
import { ConfigService } from '@nestjs/config'
|
||||||
import { NestFactory } from '@nestjs/core'
|
import { NestFactory } from '@nestjs/core'
|
||||||
import { SwaggerModule } from '@nestjs/swagger'
|
import { SwaggerModule } from '@nestjs/swagger'
|
||||||
|
import cookieParser from 'cookie-parser'
|
||||||
|
|
||||||
import { AppModule } from './core/app.module'
|
import { AppModule } from './core/app.module'
|
||||||
import { getCorsConfig, swaggerConfig, validationPipe } from './core/config'
|
import { getCorsConfig, swaggerConfig, validationPipe } from './core/config'
|
||||||
@ -13,6 +14,8 @@ async function bootstrap() {
|
|||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const config = app.get(ConfigService)
|
const config = app.get(ConfigService)
|
||||||
|
|
||||||
|
app.use(cookieParser(config.getOrThrow<string>('COOKIES_SECRET')))
|
||||||
|
|
||||||
app.useGlobalPipes(validationPipe)
|
app.useGlobalPipes(validationPipe)
|
||||||
app.useGlobalFilters(new GrpcExceptionFilter())
|
app.useGlobalFilters(new GrpcExceptionFilter())
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
Req,
|
||||||
|
Res
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
import { ApiOperation } from '@nestjs/swagger'
|
import { ApiOperation } from '@nestjs/swagger'
|
||||||
|
import type { Request, Response } from 'express'
|
||||||
|
import { lastValueFrom } from 'rxjs'
|
||||||
|
|
||||||
import { AuthClientGrpc } from './auth.grpc'
|
import { AuthClientGrpc } from './auth.grpc'
|
||||||
import { SendOtpRequestDto, VerifyOtpRequestDto } from './dto'
|
import { SendOtpRequestDto, VerifyOtpRequestDto } from './dto'
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly client: AuthClientGrpc) {}
|
constructor(
|
||||||
|
private readonly client: AuthClientGrpc,
|
||||||
|
private readonly config: ConfigService
|
||||||
|
) {}
|
||||||
|
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'Send OTP',
|
summary: 'Send OTP',
|
||||||
@ -24,7 +38,60 @@ export class AuthController {
|
|||||||
})
|
})
|
||||||
@Post('otp/verify')
|
@Post('otp/verify')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
public verifyOtp(@Body() dto: VerifyOtpRequestDto) {
|
public async verifyOtp(
|
||||||
return this.client.verifyOtp(dto)
|
@Body() dto: VerifyOtpRequestDto,
|
||||||
|
@Res({ passthrough: true }) res: Response
|
||||||
|
) {
|
||||||
|
const { accessToken, refreshToken } = await lastValueFrom(
|
||||||
|
this.client.verifyOtp(dto)
|
||||||
|
)
|
||||||
|
|
||||||
|
res.cookie('refreshToken', refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
domain: this.config.getOrThrow('COOKIES_DOMAIN'),
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 30 * 24 * 60 * 60 * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
return { accessToken }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Refresh',
|
||||||
|
description: 'Refresh access token'
|
||||||
|
})
|
||||||
|
@Post('refresh')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public async refresh(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Res({ passthrough: true }) res: Response
|
||||||
|
) {
|
||||||
|
const refreshToken = req.cookies?.refreshToken as string
|
||||||
|
|
||||||
|
const { accessToken, refreshToken: newRefreshToken } = await lastValueFrom(
|
||||||
|
this.client.refresh({ refreshToken })
|
||||||
|
)
|
||||||
|
|
||||||
|
res.cookie('refreshToken', newRefreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
domain: this.config.getOrThrow('COOKIES_DOMAIN'),
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 30 * 24 * 60 * 60 * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
return { accessToken }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Logout',
|
||||||
|
description: 'Logout user'
|
||||||
|
})
|
||||||
|
@Post('logout')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public logout(@Res({ passthrough: true }) res: Response) {
|
||||||
|
res.clearCookie('refreshToken')
|
||||||
|
return { ok: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
|
|||||||
import type { ClientGrpc } from '@nestjs/microservices'
|
import type { ClientGrpc } from '@nestjs/microservices'
|
||||||
import {
|
import {
|
||||||
AuthServiceClient,
|
AuthServiceClient,
|
||||||
|
RefreshRequest,
|
||||||
SendOtpRequest,
|
SendOtpRequest,
|
||||||
VerifyOtpRequest
|
VerifyOtpRequest
|
||||||
} from '@teacinema/contracts/gen/auth'
|
} from '@teacinema/contracts/gen/auth'
|
||||||
@ -23,4 +24,8 @@ export class AuthClientGrpc implements OnModuleInit {
|
|||||||
public verifyOtp(request: VerifyOtpRequest) {
|
public verifyOtp(request: VerifyOtpRequest) {
|
||||||
return this.authService.verifyOtp(request)
|
return this.authService.verifyOtp(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public refresh(request: RefreshRequest) {
|
||||||
|
return this.authService.refresh(request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { IdentifierValidator } from '../../../../shared/validators'
|
|||||||
export class SendOtpRequestDto {
|
export class SendOtpRequestDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'User phone or email',
|
description: 'User phone or email',
|
||||||
example: '+791234567890'
|
example: '+79123456789'
|
||||||
})
|
})
|
||||||
@Validate(IdentifierValidator)
|
@Validate(IdentifierValidator)
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { IdentifierValidator } from '../../../../shared/validators'
|
|||||||
export class VerifyOtpRequestDto {
|
export class VerifyOtpRequestDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'User phone or email',
|
description: 'User phone or email',
|
||||||
example: '+791234567890'
|
example: '+79123456789'
|
||||||
})
|
})
|
||||||
@Validate(IdentifierValidator)
|
@Validate(IdentifierValidator)
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
26
yarn.lock
26
yarn.lock
@ -1198,9 +1198,9 @@
|
|||||||
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
||||||
|
|
||||||
"@teacinema/contracts@^1.0.0":
|
"@teacinema/contracts@^1.0.0":
|
||||||
version "1.0.4"
|
version "1.0.5"
|
||||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.4/contracts-1.0.4.tgz#178dc6392aae52c4dc2b60bb1470a0a2885a8715"
|
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.5/contracts-1.0.5.tgz#f6db65f58ba3621e81e886f54d6ec9e674c4785b"
|
||||||
integrity sha512-3jHP4NAoH9munaN2Iegvg0X6KN/SR1rwZU7KpbuBtlcTJqsLljTaHwG+XKjEL84Y9b+sF73jGGbg/w2Y8IBQmw==
|
integrity sha512-paFa/zVWKzDNuH04EyGPlG981YEX7Xt42gRwpfZwACGa5CCM5lehT7xKU4h9p/l9IebrOtTPDwJdQn/D41o3Gw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nestjs/microservices" "^11.1.12"
|
"@nestjs/microservices" "^11.1.12"
|
||||||
protoc "33.4.0"
|
protoc "33.4.0"
|
||||||
@ -1314,6 +1314,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/cookie-parser@^1.4.10":
|
||||||
|
version "1.4.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.10.tgz#a045272a383a30597a01955d4f9c790018f214e4"
|
||||||
|
integrity sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==
|
||||||
|
|
||||||
"@types/cookiejar@^2.1.5":
|
"@types/cookiejar@^2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
|
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
|
||||||
@ -2375,12 +2380,25 @@ convert-source-map@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||||
|
|
||||||
|
cookie-parser@^1.4.7:
|
||||||
|
version "1.4.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26"
|
||||||
|
integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==
|
||||||
|
dependencies:
|
||||||
|
cookie "0.7.2"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||||
|
|
||||||
cookie-signature@^1.2.1, cookie-signature@^1.2.2:
|
cookie-signature@^1.2.1, cookie-signature@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
|
||||||
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
||||||
|
|
||||||
cookie@^0.7.1:
|
cookie@0.7.2, cookie@^0.7.1:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
||||||
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user