-
Notifications
You must be signed in to change notification settings - Fork 0
Role based auth backend #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
34edd92
2011b66
bb180d4
1a95f15
8587514
8f251c8
89677e3
267b6eb
44f537d
a5a7852
20a26ba
88f8d3c
5421fe6
6aea768
caa33e9
390b380
f1bad91
f7621f5
1331bbb
d69b3c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,17 @@ | ||
| import { Module } from '@nestjs/common'; | ||
| import { forwardRef, Module } from '@nestjs/common'; | ||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||
| import { Allocation } from './allocations.entity'; | ||
| import { AllocationsController } from './allocations.controller'; | ||
| import { AllocationsService } from './allocations.service'; | ||
| import { AuthService } from '../auth/auth.service'; | ||
| import { JwtStrategy } from '../auth/jwt.strategy'; | ||
| import { AuthModule } from '../auth/auth.module'; | ||
|
|
||
| @Module({ | ||
| imports: [TypeOrmModule.forFeature([Allocation])], | ||
| imports: [ | ||
| TypeOrmModule.forFeature([Allocation]), | ||
| forwardRef(() => AuthModule), | ||
| ], | ||
| controllers: [AllocationsController], | ||
| providers: [AllocationsService, AuthService, JwtStrategy], | ||
| providers: [AllocationsService], | ||
| exports: [AllocationsService], | ||
| }) | ||
| export class AllocationModule {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,17 @@ | ||
| import { Module } from '@nestjs/common'; | ||
| import { Module, forwardRef } from '@nestjs/common'; | ||
| import { PassportModule } from '@nestjs/passport'; | ||
|
|
||
| import { AuthController } from './auth.controller'; | ||
| import { AuthService } from './auth.service'; | ||
| import { JwtStrategy } from './jwt.strategy'; | ||
| import { UsersModule } from '../users/users.module'; | ||
|
|
||
| @Module({ | ||
| imports: [UsersModule, PassportModule.register({ defaultStrategy: 'jwt' })], | ||
| imports: [ | ||
| forwardRef(() => UsersModule), | ||
| PassportModule.register({ defaultStrategy: 'jwt' }), | ||
| ], | ||
| controllers: [AuthController], | ||
| providers: [AuthService, JwtStrategy], | ||
| exports: [AuthService, JwtStrategy], | ||
| }) | ||
| export class AuthModule {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,22 @@ | ||
| import { Injectable } from '@nestjs/common'; | ||
| import { Injectable, UnauthorizedException } from '@nestjs/common'; | ||
| import { PassportStrategy } from '@nestjs/passport'; | ||
| import { passportJwtSecret } from 'jwks-rsa'; | ||
| import { ExtractJwt, Strategy } from 'passport-jwt'; | ||
|
|
||
| import { UsersService } from '../users/users.service'; | ||
| import CognitoAuthConfig from './aws-exports'; | ||
| import { AuthService } from './auth.service'; | ||
|
|
||
| @Injectable() | ||
| export class JwtStrategy extends PassportStrategy(Strategy) { | ||
| constructor() { | ||
| constructor( | ||
| private usersService: UsersService, | ||
| ) { | ||
| const cognitoAuthority = `https://cognito-idp.${CognitoAuthConfig.region}.amazonaws.com/${CognitoAuthConfig.userPoolId}`; | ||
|
|
||
| super({ | ||
| jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), | ||
| ignoreExpiration: false, | ||
| _audience: CognitoAuthConfig.clientId, | ||
| _audience: CognitoAuthConfig.userPoolClientId, | ||
| issuer: cognitoAuthority, | ||
| algorithms: ['RS256'], | ||
| secretOrKeyProvider: passportJwtSecret({ | ||
|
|
@@ -26,6 +29,8 @@ export class JwtStrategy extends PassportStrategy(Strategy) { | |
| } | ||
|
|
||
| async validate(payload) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we leave a comment here to clarify that when this function is called, we know the payload parameter represents a valid signed JWT (so the user's sub is actually what they say it is) because the Passport JWT strategy takes care of that for us? Given the name |
||
| return { idUser: payload.sub, email: payload.email }; | ||
| const dbUser = await this.usersService.findUserByCognitoId(payload.sub); | ||
| console.log('Database user retrieved:', dbUser); | ||
| return dbUser; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { SetMetadata } from '@nestjs/common'; | ||
| import { Role } from '../users/types'; | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we document this decorator to explain what it does and how it interacts with the roles guard? This will be confusing for people not familiar with how decorators and guards work in Nest |
||
| export const ROLES_KEY = 'roles'; | ||
| export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; | ||
| import { Reflector } from '@nestjs/core'; | ||
| import { Role } from '../users/types'; | ||
| import { ROLES_KEY } from './roles.decorator'; | ||
|
|
||
| @Injectable() | ||
| export class RolesGuard implements CanActivate { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than adding this to every controller separately, can we set it up as a global guard (as long as we make sure to return true if a route doesn't have any roles metadata set)?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we document this to explain what it does and how it interacts with the roles decorator (including the behavior from |
||
| constructor(private reflector: Reflector) {} | ||
|
|
||
| canActivate(context: ExecutionContext): boolean { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a comment clarifying that if this returns false, Nest automatically throws a |
||
| const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [ | ||
| context.getHandler(), | ||
| context.getClass(), | ||
| ]); | ||
|
|
||
| if (!requiredRoles) { | ||
| return true; | ||
| } | ||
|
|
||
| const { user } = context.switchToHttp().getRequest(); | ||
|
|
||
| return requiredRoles.some((role) => user.role === role); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ import { RemoveMultipleVolunteerTypes1764811878152 } from '../migrations/1764811 | |
| import { RemoveUnusedStatuses1764816885341 } from '../migrations/1764816885341-RemoveUnusedStatuses'; | ||
| import { UpdatePantryFields1763762628431 } from '../migrations/1763762628431-UpdatePantryFields'; | ||
| import { PopulateDummyData1768501812134 } from '../migrations/1768501812134-populateDummyData'; | ||
| import { AddUserPoolId1769189327767 } from '../migrations/1769189327767-AddUserPoolId'; | ||
|
|
||
| const config = { | ||
| type: 'postgres', | ||
|
|
@@ -46,8 +47,8 @@ const config = { | |
| ReviseTables1737522923066, | ||
| UpdateUserRole1737816745912, | ||
| UpdatePantriesTable1737906317154, | ||
| UpdateDonations1738697216020, | ||
| UpdateDonationColTypes1741708808976, | ||
| UpdateDonations1738697216020, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this intentional? |
||
| UpdatePantriesTable1738172265266, | ||
| UpdatePantriesTable1739056029076, | ||
| AssignmentsPantryIdNotUnique1758384669652, | ||
|
|
@@ -67,6 +68,7 @@ const config = { | |
| RemoveMultipleVolunteerTypes1764811878152, | ||
| RemoveUnusedStatuses1764816885341, | ||
| PopulateDummyData1768501812134, | ||
| AddUserPoolId1769189327767, | ||
| ], | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,19 @@ | ||
| import { Module } from '@nestjs/common'; | ||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||
| import { JwtStrategy } from '../auth/jwt.strategy'; | ||
| import { AuthService } from '../auth/auth.service'; | ||
| import { Donation } from './donations.entity'; | ||
| import { DonationService } from './donations.service'; | ||
| import { DonationsController } from './donations.controller'; | ||
| import { ManufacturerModule } from '../foodManufacturers/manufacturer.module'; | ||
| import { AuthModule } from '../auth/auth.module'; | ||
| import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; | ||
|
|
||
| @Module({ | ||
| imports: [ | ||
| TypeOrmModule.forFeature([Donation, FoodManufacturer]), | ||
| ManufacturerModule, | ||
| AuthModule, | ||
| ], | ||
| controllers: [DonationsController], | ||
| providers: [DonationService, AuthService, JwtStrategy], | ||
| providers: [DonationService], | ||
| }) | ||
| export class DonationModule {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| import { Module } from '@nestjs/common'; | ||
| import { TypeOrmModule } from '@nestjs/typeorm'; | ||
| import { FoodManufacturer } from './manufacturer.entity'; | ||
| import { AuthModule } from '../auth/auth.module'; | ||
|
|
||
| @Module({ | ||
| imports: [TypeOrmModule.forFeature([FoodManufacturer])], | ||
| imports: [TypeOrmModule.forFeature([FoodManufacturer]), AuthModule], | ||
| }) | ||
| export class ManufacturerModule {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a
typespackage we can add to get better typing here