feat: implement session to project
parent
e54dddfd89
commit
64788600b2
|
@ -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';
|
|
@ -0,0 +1,4 @@
|
|||
export type JWTToken = {
|
||||
exp: number;
|
||||
iat: number;
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export interface UsersSession {
|
||||
id: number;
|
||||
username: string;
|
||||
name: string;
|
||||
roles: string[];
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
|
@ -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 {}
|
Loading…
Reference in New Issue