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