Skip to content

Commit

Permalink
Implement Guard for all authentication method
Browse files Browse the repository at this point in the history
  • Loading branch information
zenkiet committed Sep 21, 2023
1 parent 78f749a commit 65d1c61
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 3 deletions.
23 changes: 23 additions & 0 deletions server/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Nest Framework",
"args": ["${workspaceFolder}/src/main.ts"],
"runtimeArgs": [
"--nolazy",
"-r",
"ts-node/register",
"-r",
"tsconfig-paths/register"
],
"sourceMaps": true,
"envFile": "${workspaceFolder}/.env",
"cwd": "${workspaceRoot}",
"console": "integratedTerminal",
"protocol": "inspector"
}
]
}
8 changes: 8 additions & 0 deletions server/src/iam/authentication/authentication.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,24 @@ import { AuthenticationService } from './services/authentication.service';
import { AuthenticationController } from './controllers/authentication.controller';
import jwtConfig from '../config/jwt.config/jwt.config';
import { AccessTokenStrategy } from './strategies/access-token/access-token.strategy';
import { AccessTokenGuard } from './guards/access-token/access-token.guard';
import { AuthenticationGuard } from './guards/authentication/authentication.guard';
import { APP_GUARD } from '@nestjs/core';

@Module({
providers: [
{
provide: HashingService,
useClass: BcryptService,
},
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
AuthenticationService,
PrismaService,
AccessTokenStrategy,
AccessTokenGuard,
],
imports: [
PassportModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { AuthenticationService } from '../services/authentication.service';
import { SignUpDto } from '../dto/sign-up.dto/sign-up.dto';
import { SignInDto } from '../dto/sign-in.dto/sign-in.dto';
import { AccessTokenStrategy } from '../strategies/access-token/access-token.strategy';
import { Auth } from '../decorators/auth/auth.decorator';
import { AuthType } from '../enums/auth-type.enum';

@Auth(AuthType.None)
@Controller('authentication')
export class AuthenticationController {
constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ExecutionContext, createParamDecorator } from '@nestjs/common';
import { ActiveUserData } from '../../interfaces/active-user-data.interface';
import { REQUEST_USER_KEY } from 'src/iam/constants/iam.contant';

export const ActiveUser = createParamDecorator(
(field: keyof ActiveUserData | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user: ActiveUserData | undefined = request[REQUEST_USER_KEY];
return field ? user && user?.[field] : user;
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SetMetadata } from '@nestjs/common';
import { AuthType } from '../../enums/auth-type.enum';

export const AUTH_TYPE_KEY = 'authType';

export const Auth = (...authTypes: AuthType[]) =>
SetMetadata(AUTH_TYPE_KEY, authTypes);
4 changes: 4 additions & 0 deletions server/src/iam/authentication/enums/auth-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum AuthType {
Bearer,
None,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AccessTokenGuard } from './access-token.guard';

describe('AccessTokenGuard', () => {
it('should be defined', () => {
expect(new AccessTokenGuard()).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { ActiveUserData } from '../../interfaces/active-user-data.interface';

@Injectable()
export class AccessTokenGuard extends AuthGuard('jwt') implements CanActivate {
constructor() {
super();
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return super.canActivate(context);
}

handleRequest(err: any, user: any, info: any) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException('No access token found');
}
return user;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AuthenticationGuard } from './authentication.guard';

describe('AuthenticationGuard', () => {
it('should be defined', () => {
expect(new AuthenticationGuard()).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AccessTokenGuard } from './../access-token/access-token.guard';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { AuthType } from '../../enums/auth-type.enum';
import { Reflector } from '@nestjs/core';
import { AUTH_TYPE_KEY } from '../../decorators/auth/auth.decorator';

@Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly accessTokenGuard: AccessTokenGuard,
) {}
private static readonly defaultAuthType = AuthType.Bearer;

private readonly authTypeGuardMap: Record<
AuthType,
CanActivate | CanActivate[]
> = {
[AuthType.None]: { canActivate: () => true },
[AuthType.Bearer]: this.accessTokenGuard,
};

async canActivate(context: ExecutionContext): Promise<boolean> {
const authTypes = this.reflector.getAllAndOverride<AuthType[]>(
AUTH_TYPE_KEY,
[context.getHandler(), context.getClass()],
) ?? [AuthenticationGuard.defaultAuthType];

const guards = authTypes
.map((type: any) => this.authTypeGuardMap[type])
.flat();

const guardPromises = guards.map((guard: any) =>
guard.canActivate(context),
);

const results = await Promise.allSettled(guardPromises); //? return 'rejected' or 'fulfilled'

const rejected = results.find((result: any) => {
return result.status === 'rejected';
});

if (rejected) {
throw rejected['reason'];
}

return results.some(
(result: any) => result.status === 'fulfilled' && result.value,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface ActiveUser {
export interface ActiveUserData {
sub: number;
email: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { randomUUID } from 'crypto';
import jwtConfig from '../../config/jwt.config/jwt.config';
import { ConfigType } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { ActiveUser } from '../interfaces/active-user.interface';
import { ActiveUserData } from '../interfaces/active-user-data.interface';

@Injectable()
export class AuthenticationService {
Expand Down Expand Up @@ -83,7 +83,7 @@ export class AuthenticationService {

const [accessToken, refreshToken] = await Promise.all([
//* accessToken
this.signToken<Partial<ActiveUser>>(
this.signToken<Partial<ActiveUserData>>(
user.id,
this.jwtConfiguration.accessTokenTtl,
payload,
Expand Down
1 change: 1 addition & 0 deletions server/src/iam/constants/iam.contant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const REQUEST_USER_KEY = 'user';

0 comments on commit 65d1c61

Please sign in to comment.