feat: add refresh, logout methods

This commit is contained in:
Sergey Krylov 2026-02-04 06:20:27 +03:00
parent 2b861c220a
commit cce2d83101
7 changed files with 105 additions and 10 deletions

View File

@ -32,6 +32,7 @@
"@teacinema/contracts": "^1.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"cookie-parser": "^1.4.7",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
@ -42,6 +43,7 @@
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@teacinema/core": "1.0.7",
"@types/cookie-parser": "^1.4.10",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",

View File

@ -2,6 +2,7 @@ import { Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { NestFactory } from '@nestjs/core'
import { SwaggerModule } from '@nestjs/swagger'
import cookieParser from 'cookie-parser'
import { AppModule } from './core/app.module'
import { getCorsConfig, swaggerConfig, validationPipe } from './core/config'
@ -13,6 +14,8 @@ async function bootstrap() {
const logger = new Logger()
const config = app.get(ConfigService)
app.use(cookieParser(config.getOrThrow<string>('COOKIES_SECRET')))
app.useGlobalPipes(validationPipe)
app.useGlobalFilters(new GrpcExceptionFilter())

View File

@ -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 type { Request, Response } from 'express'
import { lastValueFrom } from 'rxjs'
import { AuthClientGrpc } from './auth.grpc'
import { SendOtpRequestDto, VerifyOtpRequestDto } from './dto'
@Controller('auth')
export class AuthController {
constructor(private readonly client: AuthClientGrpc) {}
constructor(
private readonly client: AuthClientGrpc,
private readonly config: ConfigService
) {}
@ApiOperation({
summary: 'Send OTP',
@ -24,7 +38,60 @@ export class AuthController {
})
@Post('otp/verify')
@HttpCode(HttpStatus.OK)
public verifyOtp(@Body() dto: VerifyOtpRequestDto) {
return this.client.verifyOtp(dto)
public async verifyOtp(
@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 }
}
}

View File

@ -2,6 +2,7 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
import type { ClientGrpc } from '@nestjs/microservices'
import {
AuthServiceClient,
RefreshRequest,
SendOtpRequest,
VerifyOtpRequest
} from '@teacinema/contracts/gen/auth'
@ -23,4 +24,8 @@ export class AuthClientGrpc implements OnModuleInit {
public verifyOtp(request: VerifyOtpRequest) {
return this.authService.verifyOtp(request)
}
public refresh(request: RefreshRequest) {
return this.authService.refresh(request)
}
}

View File

@ -6,7 +6,7 @@ import { IdentifierValidator } from '../../../../shared/validators'
export class SendOtpRequestDto {
@ApiProperty({
description: 'User phone or email',
example: '+791234567890'
example: '+79123456789'
})
@Validate(IdentifierValidator)
@IsString()

View File

@ -13,7 +13,7 @@ import { IdentifierValidator } from '../../../../shared/validators'
export class VerifyOtpRequestDto {
@ApiProperty({
description: 'User phone or email',
example: '+791234567890'
example: '+79123456789'
})
@Validate(IdentifierValidator)
@IsString()

View File

@ -1198,9 +1198,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"
@ -1314,6 +1314,11 @@
dependencies:
"@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":
version "2.1.5"
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"
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:
version "1.2.2"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
cookie@^0.7.1:
cookie@0.7.2, cookie@^0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==