feat: implement session to project

master
shancheas 2023-02-09 17:29:32 +07:00
parent e54dddfd89
commit 64788600b2
9 changed files with 147 additions and 0 deletions

View File

@ -0,0 +1,6 @@
export const USER_SESSIONS = 'USER_SESSIONS';
export const ACCESS_CONTROL = 'ACCESS_CONTROL';
export const JWT_SECRET =
process.env.JWT_SECRET ?? 'B9A8Y92wZwbGBHOcUaHykeQ6mNNKeTFt';
export const JWT_EXPIRED = process.env.JWT_EXPIRED ?? '12h';

View File

@ -0,0 +1,4 @@
export type JWTToken = {
exp: number;
iat: number;
};

View File

@ -0,0 +1,6 @@
export interface UsersSession {
id: number;
username: string;
name: string;
roles: string[];
}

View File

@ -0,0 +1,31 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { SessionService } from '../..';
import { Response, Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class RefreshTokenInterceptor implements NestInterceptor {
constructor(protected readonly session: SessionService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
const authorization = request.headers['authorization'];
if (authorization) {
const [, token] = authorization.split(' ');
const refreshToken = this.session.refreshToken(token);
if (refreshToken) {
response.setHeader('ex-refresh-token', refreshToken);
response.setHeader('Access-Control-Expose-Headers', 'ex-refresh-token');
}
}
return next.handle();
}
}

View File

@ -0,0 +1,26 @@
import { Inject, Injectable, Request, Scope } from '@nestjs/common';
import { UsersSession } from '../entities/user-sessions.interface';
import { REQUEST } from '@nestjs/core';
import { SessionService } from '../services/session.service';
@Injectable({ scope: Scope.REQUEST })
export class UserProvider {
constructor(
@Inject(REQUEST) private readonly request: Request,
private readonly session: SessionService,
) {}
get user(): UsersSession {
/**
* There is no Token validation here
* Because, the token should be available and active here
*
* If this function throw an error
* rather you trying to call user from function that use `@Unprotected` decorator
* or you forget to set scope to `Scope.REQUEST` from app.module.
*
* Please check the token validation at JWTGuard (core/domain/jwt.guard.ts)
*/
const [, token] = this.request.headers['authorization'].split(' ');
return this.session.verifyToken(token);
}
}

View File

@ -0,0 +1,23 @@
import { Injectable, Scope } from '@nestjs/common';
import { UsersSession } from '../entities/user-sessions.interface';
import { JwtService } from '@nestjs/jwt';
import { JWTToken } from '../..';
import { isTokenNearExpired } from '../utils/jwt.helpers';
@Injectable({ scope: Scope.REQUEST })
export class SessionService {
constructor(private readonly jwt: JwtService) {}
createAccessToken(session: UsersSession): string {
return this.jwt.sign(session);
}
verifyToken(token: string): UsersSession & JWTToken {
return this.jwt.verify<UsersSession & JWTToken>(token);
}
refreshToken(token: string): string | undefined {
const { exp, iat, ...user } = this.verifyToken(token);
const isNearExp = isTokenNearExpired(exp, iat);
return isNearExp ? this.createAccessToken(user) : null;
}
}

View File

@ -0,0 +1,22 @@
/**
*
* @param exp JWT Token expire time
* @param iat JWT Token create time
* @param expTimeTolerance this constant tell when the token near expire or not
*
* this function will return true when the rest time (exp - now)
* is less than expire duration (exp - iat) * expTimeTolerance
* otherwise will return false
*/
export function isTokenNearExpired(
exp: number,
iat: number,
expTimeTolerance = 0.2,
): boolean {
const now = new Date().getTime() / 1000;
const expDuration = exp - iat;
const toleranceDuration = expDuration * expTimeTolerance;
const restDuration = exp - now;
return restDuration < toleranceDuration;
}

View File

@ -0,0 +1,11 @@
export * from './constants';
export * from './domain/providers/user';
export * from './domain/entities/user-sessions.interface';
export * from './domain/entities/jwt.interface';
export * from './domain/services/session.service';
export * from './domain/interceptors/refresh-token.interceptor';
export * from './session.module';

View File

@ -0,0 +1,18 @@
import { Global, Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { JWT_EXPIRED, JWT_SECRET } from '../../auth/constants';
import { UserProvider } from './domain/providers/user';
import { SessionService } from './domain/services/session.service';
@Global()
@Module({
imports: [
JwtModule.register({
secret: JWT_SECRET,
signOptions: { expiresIn: JWT_EXPIRED },
}),
],
providers: [SessionService, UserProvider],
exports: [SessionService, UserProvider],
})
export class SessionModule {}