Compare commits

...

3 Commits

Author SHA1 Message Date
vchikalkin
f7ab0dc2d7 apps/api: account: remove db props from token payload 2024-01-16 12:31:25 +03:00
vchikalkin
f19f7af6a0 apps/api: use refresh-token from headers 2024-01-16 12:23:54 +03:00
vchikalkin
8470a72d8d apps/api: fix refresh token 2024-01-15 14:55:25 +03:00
6 changed files with 39 additions and 18 deletions

3
.env
View File

@ -20,4 +20,5 @@ LDAP_ATTRIBUTE=
API_SECRET= API_SECRET=
API_TOKEN_TTL= API_TOKEN_TTL=
API_CACHE_TTL= API_CACHE_TTL=
COOKIE_TOKEN_NAME=token COOKIE_TOKEN_NAME=token
COOKIE_TOKEN_MAX_AGE=

View File

@ -48,7 +48,7 @@ export class AccountService {
const payload: TokenPayload = { const payload: TokenPayload = {
username: login, username: login,
...omit(account.toJSON(), ['password']), ...omit(account.toJSON(), ['password', '_id', '__v']),
}; };
return this.jwtService.sign(payload); return this.jwtService.sign(payload);

View File

@ -3,29 +3,46 @@ import { env } from './config/env';
import { Controller, Get, HttpStatus, Req, Res } from '@nestjs/common'; import { Controller, Get, HttpStatus, Req, Res } from '@nestjs/common';
import { ApiExcludeController } from '@nestjs/swagger'; import { ApiExcludeController } from '@nestjs/swagger';
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { cookieOptions } from 'src/config/cookie';
@Controller() @Controller()
@ApiExcludeController() @ApiExcludeController()
export class AppController { export class AppController {
constructor(private readonly appService: AppService) {} constructor(private readonly appService: AppService) {}
@Get('auth') @Get('auth')
public async auth(@Req() req: FastifyRequest, @Res() reply: FastifyReply) { public async auth(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
const token = req.cookies[env.COOKIE_TOKEN_NAME] || req.headers?.authorization?.split(' ')[1];
if (!token) return reply.status(HttpStatus.UNAUTHORIZED).send();
try { try {
const token = req.cookies[env.COOKIE_TOKEN_NAME] || req.headers.authorization.split(' ')[1]; return this.handleDefaultCheck(req, reply, token);
} catch (error) {
const _err = error as Error;
const isTokenExpired = _err.name?.toLocaleLowerCase().includes('expired');
const refreshToken = req.headers['refresh-token'] === '1';
this.appService.checkToken(token); if (isTokenExpired && refreshToken) return this.handleExpiredToken(req, reply, token);
reply.header('Authorization', `Bearer ${token}`); return this.handleError(req, reply);
return reply.send();
} catch {
// if (error.name === 'TokenExpiredError') {
// const newToken = this.appService.refreshToken(token);
// return reply.setCookie(env.COOKIE_TOKEN_NAME, newToken, cookieOptions).send();
// }
return reply.status(HttpStatus.UNAUTHORIZED).send();
} }
} }
private handleDefaultCheck(req: FastifyRequest, reply: FastifyReply, token: string) {
this.appService.checkToken(token);
reply.header('Authorization', `Bearer ${token}`);
return reply.send();
}
private handleExpiredToken(req: FastifyRequest, reply: FastifyReply, token: string) {
const newToken = this.appService.refreshToken(token);
reply.header('Authorization', `Bearer ${newToken}`);
return reply.setCookie(env.COOKIE_TOKEN_NAME, newToken, cookieOptions).send();
}
private handleError(req: FastifyRequest, reply: FastifyReply) {
return reply.status(HttpStatus.UNAUTHORIZED).send();
}
} }

View File

@ -3,7 +3,7 @@ import { env } from 'src/config/env';
export const cookieOptions: CookieSerializeOptions = { export const cookieOptions: CookieSerializeOptions = {
httpOnly: true, httpOnly: true,
maxAge: env.API_TOKEN_TTL, maxAge: env.COOKIE_TOKEN_MAX_AGE,
path: '/', path: '/',
secure: true, secure: true,
}; };

View File

@ -5,6 +5,7 @@ const envSchema = z.object({
API_PORT: z.number().optional().default(3001), API_PORT: z.number().optional().default(3001),
API_SECRET: z.string(), API_SECRET: z.string(),
API_TOKEN_TTL: z.string().transform((val) => Number.parseInt(val, 10)), API_TOKEN_TTL: z.string().transform((val) => Number.parseInt(val, 10)),
COOKIE_TOKEN_MAX_AGE: z.string().transform((val) => Number.parseInt(val, 10)),
COOKIE_TOKEN_NAME: z.string().default('token'), COOKIE_TOKEN_NAME: z.string().default('token'),
LDAP_ATTRIBUTE: z.string(), LDAP_ATTRIBUTE: z.string(),
LDAP_BASE: z.string(), LDAP_BASE: z.string(),

View File

@ -11,12 +11,14 @@ export class Account {
@ApiResponseProperty() @ApiResponseProperty()
@ApiProperty() @ApiProperty()
@Prop({ index: { unique: true }, required: true }) @Prop({ index: { unique: true }, required: true })
username: string; public username: string;
@ApiResponseProperty() @ApiResponseProperty()
@ApiProperty() @ApiProperty()
@Prop({ required: true }) @Prop({ required: true })
password: string; public password: string;
readonly [key: string]: unknown;
} }
export const AccountSchema = SchemaFactory.createForClass(Account); export const AccountSchema = SchemaFactory.createForClass(Account);