Skip to content

Commit

Permalink
feat: Add RBAC
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed Dec 25, 2023
1 parent 78c5108 commit b4cb14f
Show file tree
Hide file tree
Showing 34 changed files with 461 additions and 366 deletions.
6 changes: 3 additions & 3 deletions apps/api/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export default {
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }]
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/api',
};
coverageDirectory: '../../coverage/apps/api'
}
6 changes: 6 additions & 0 deletions apps/api/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"prettier:fix": {
"command": "pnpx prettier -w .",
"options": {
"cwd": "apps/api"
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
Expand Down
22 changes: 11 additions & 11 deletions apps/api/src/app/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Test, TestingModule } from '@nestjs/testing'

import { AppController } from './app.controller';
import { AppController } from './app.controller'

describe('AppController', () => {
let app: TestingModule;
let app: TestingModule

beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [],
}).compile();
});
providers: []
}).compile()
})

describe('healthCheck', () => {
it('should return "Hello API"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.health()).toEqual('UP');
});
});
});
const appController = app.get<AppController>(AppController)
expect(appController.health()).toEqual('UP')
})
})
})
6 changes: 4 additions & 2 deletions apps/api/src/app/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Controller, Get } from '@nestjs/common';
import { Controller, Get } from '@nestjs/common'
import { Public } from '../decorators/public.decorator'

@Controller()
export class AppController {
constructor() {}

@Get('health')
@Public()
health(): string {
return 'UP';
return 'UP'
}
}
29 changes: 18 additions & 11 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { SupabaseModule } from '../supabase/supabase.module';
import { ConfigModule } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { AuthModule } from '../auth/auth.module';
import { PrismaModule } from '../prisma/prisma.module';
import { CommonModule } from '../common/common.module';
import { ResendModule } from '../resend/resend.module';
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { SupabaseModule } from '../supabase/supabase.module'
import { ConfigModule } from '@nestjs/config'
import { PassportModule } from '@nestjs/passport'
import { AuthModule } from '../auth/auth.module'
import { PrismaModule } from '../prisma/prisma.module'
import { CommonModule } from '../common/common.module'
import { ResendModule } from '../resend/resend.module'
import { APP_GUARD } from '@nestjs/core'
import { AuthGuard } from '../auth/auth.guard'

@Module({
controllers: [AppController],
imports: [
ConfigModule.forRoot({
isGlobal: true,
isGlobal: true
}),
PassportModule,
SupabaseModule,
Expand All @@ -22,6 +24,11 @@ import { ResendModule } from '../resend/resend.module';
ResendModule,
SupabaseModule
],
providers: [],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard
}
]
})
export class AppModule {}
7 changes: 7 additions & 0 deletions apps/api/src/auth/admin.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AdminGuard } from './admin.guard'

describe('AdminGuard', () => {
it('should be defined', () => {
expect(new AdminGuard()).toBeDefined()
})
})
13 changes: 13 additions & 0 deletions apps/api/src/auth/admin.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { User } from '@prisma/client'
import { Observable } from 'rxjs'

@Injectable()
export class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest()
const user: User = request.user

return user.isAdmin
}
}
32 changes: 15 additions & 17 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { Controller, Param, Post, Query } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserAuthenticatedResponse } from './auth.types';
import { Controller, Param, Post, Query } from '@nestjs/common'
import { AuthService } from './auth.service'
import { UserAuthenticatedResponse } from './auth.types'
import { Public } from '../decorators/public.decorator'

@Controller('auth')
export class AuthController {
constructor(
private authService: AuthService
) {}
constructor(private authService: AuthService) {}

@Post('send-otp/:email')
async sendOtp(@Param('email') email: string): Promise<void> {
await this.authService.sendOtp(email);
}
@Public()
@Post('send-otp/:email')
async sendOtp(@Param('email') email: string): Promise<void> {
await this.authService.sendOtp(email)
}

@Post('validate-otp')
async validateOtp(
@Query('email') email: string,
@Query('otp') otp: string)
: Promise<UserAuthenticatedResponse> {
return await this.authService.validateOtp(email, otp);
}
@Public()
@Post('validate-otp')
async validateOtp(@Query('email') email: string, @Query('otp') otp: string): Promise<UserAuthenticatedResponse> {
return await this.authService.validateOtp(email, otp)
}
}
7 changes: 7 additions & 0 deletions apps/api/src/auth/auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AuthGuard } from './auth.guard'

describe('AuthGuard', () => {
it('should be defined', () => {
expect(new AuthGuard(null, null, null)).toBeDefined()
})
})
45 changes: 45 additions & 0 deletions apps/api/src/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { Request } from 'express'
import { PrimsaRepository } from '../prisma/prisma.repository'
import { Reflector } from '@nestjs/core'
import { IS_PUBLIC_KEY } from '../decorators/public.decorator'

@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private repository: PrimsaRepository,
private reflector: Reflector
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass()
])
if (isPublic) {
return true
}

const request = context.switchToHttp().getRequest()
const token = this.extractTokenFromHeader(request)
if (!token) {
throw new UnauthorizedException()
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET
})
request['user'] = await this.repository.findUserById(payload.id)
} catch {
throw new UnauthorizedException()
}
return true
}

private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? []
return type === 'Bearer' ? token : undefined
}
}
14 changes: 7 additions & 7 deletions apps/api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { Module } from '@nestjs/common'
import { AuthService } from './auth.service'
import { AuthController } from './auth.controller'
import { JwtModule } from '@nestjs/jwt'

@Module({
imports: [
JwtModule.register({
global: true,
secret: process.env.JWT_SECRET,
signOptions: {
expiresIn: '1d' ,
signOptions: {
expiresIn: '1d',
issuer: 'keyshade.xyz',
algorithm: 'HS256',
algorithm: 'HS256'
}
})
],
Expand Down
30 changes: 15 additions & 15 deletions apps/api/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { PrimsaRepository } from '../prisma/prisma.repository';
import { TestResend } from '../resend/services/test.resend';
import { RESEND_SERVICE } from '../resend/services/resend.service.interface';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import { Test, TestingModule } from '@nestjs/testing'
import { AuthService } from './auth.service'
import { PrimsaRepository } from '../prisma/prisma.repository'
import { TestResend } from '../resend/services/test.resend'
import { RESEND_SERVICE } from '../resend/services/resend.service.interface'
import { JwtService } from '@nestjs/jwt'
import { PrismaService } from '../prisma/prisma.service'

describe('AuthService', () => {
let service: AuthService;
let service: AuthService

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -17,13 +17,13 @@ describe('AuthService', () => {
{ provide: RESEND_SERVICE, useClass: TestResend },
JwtService,
PrismaService
],
}).compile();
]
}).compile()

service = module.get<AuthService>(AuthService);
});
service = module.get<AuthService>(AuthService)
})

it('should be defined', () => {
expect(service).toBeDefined();
});
});
expect(service).toBeDefined()
})
})
Loading

0 comments on commit b4cb14f

Please sign in to comment.