forked from Code-4-Community/scaffolding
-
Notifications
You must be signed in to change notification settings - Fork 0
[SSF-117]: Backend endpoints user gated #89
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
Open
dburkhart07
wants to merge
30
commits into
main
Choose a base branch
from
ddb/SSF-117-backend-endpoints-user-gated
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+557
−107
Open
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
34edd92
Initial JWT Auth for backend
dburkhart07 2011b66
Added guards for jwt auth
dburkhart07 bb180d4
Updated auth
dburkhart07 1a95f15
Tried fixing JWT Strategy
dburkhart07 8587514
Updated auth
dburkhart07 8f251c8
Finished general authentication for both frontend and backend pages
dburkhart07 89677e3
Final commit for this branch
dburkhart07 267b6eb
Revisions made with Sam!!!
dburkhart07 44f537d
Resolved merge conflicts
dburkhart07 a5a7852
Resolved merge conflicts
dburkhart07 20a26ba
Another main merge
dburkhart07 88f8d3c
Fixed all errors with modules
dburkhart07 5421fe6
prettier
dburkhart07 6aea768
Fixed module importing
dburkhart07 caa33e9
prettier
dburkhart07 390b380
Added back in donation migration
dburkhart07 f1bad91
Full implementation of backend role-based auth
dburkhart07 f7621f5
prettier
dburkhart07 1331bbb
[SSF 17] - environment variables updates (#44)
dburkhart07 d69b3c8
Fixed user flow to use a cognito id hardcoded into the database
dburkhart07 75c3f95
Messy first attempt. Working for single service validation
dburkhart07 36b5c88
Messy first attempt. Working for single service validation
dburkhart07 c40096d
Messy first attempt. Working for single service validation
dburkhart07 6a02f87
Messy first attempt. Working for single service validation
dburkhart07 27ca72c
Working version, precleanup
dburkhart07 8958137
prettier
dburkhart07 9a1aee6
Added documentation to make things clearer
dburkhart07 3d9da87
Merged in main
dburkhart07 4d81c5a
prettier
dburkhart07 b907c10
Merged main
dburkhart07 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,20 @@ | ||
| 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 +27,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { | |
| } | ||
|
|
||
| async validate(payload) { | ||
| return { idUser: payload.sub, email: payload.email }; | ||
| const dbUser = await this.usersService.findUserByCognitoId(payload.sub); | ||
|
Member
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. since find user by cognito id throws an exception i think we should add some sort of error handling here |
||
| return dbUser; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { SetMetadata, Type } from '@nestjs/common'; | ||
|
|
||
| // Resolver function type to get the owner user ID for a given entity ID | ||
| export type OwnerIdResolver = (params: { | ||
| entityId: number; | ||
| services: ServiceRegistry; | ||
| }) => Promise<number | null>; | ||
|
|
||
| // Registry of services that can be easily resolved | ||
| // Eliminates the issues with circular dependencies | ||
| // allowing the lambdas to resolve only the services they need | ||
| export interface ServiceRegistry { | ||
| get<T>(serviceClass: Type<T>): T; | ||
| } | ||
|
|
||
| // Configuration for ownership check | ||
| export interface OwnershipConfig { | ||
| idParam: string; | ||
| resolver: OwnerIdResolver; | ||
| } | ||
|
|
||
| export const OWNERSHIP_CHECK_KEY = 'ownership_check'; | ||
|
|
||
| export const CheckOwnership = (config: OwnershipConfig) => | ||
| SetMetadata(OWNERSHIP_CHECK_KEY, config); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import { | ||
| Injectable, | ||
| CanActivate, | ||
| ExecutionContext, | ||
| ForbiddenException, | ||
| NotFoundException, | ||
| Type, | ||
| } from '@nestjs/common'; | ||
| import { Reflector } from '@nestjs/core'; | ||
| import { ModuleRef } from '@nestjs/core'; | ||
| import { | ||
| OWNERSHIP_CHECK_KEY, | ||
| OwnershipConfig, | ||
| ServiceRegistry, | ||
| } from './ownership.decorator'; | ||
|
|
||
| @Injectable() | ||
| export class OwnershipGuard implements CanActivate { | ||
| constructor(private reflector: Reflector, private moduleRef: ModuleRef) {} | ||
|
|
||
| async canActivate(context: ExecutionContext): Promise<boolean> { | ||
| const config = this.reflector.get<OwnershipConfig>( | ||
| OWNERSHIP_CHECK_KEY, | ||
| context.getHandler(), | ||
| ); | ||
|
|
||
| if (!config) { | ||
| return true; | ||
| } | ||
|
|
||
| // Process all request information and the logged in user | ||
| const req = context.switchToHttp().getRequest(); | ||
| const user = req.user; | ||
|
|
||
| // Admins bypass ownership checks | ||
| if (user.role === 'ADMIN') { | ||
| return true; | ||
| } | ||
|
|
||
| if (!user) { | ||
| throw new ForbiddenException('Not authenticated'); | ||
| } | ||
|
|
||
| // Get the id from the parameters | ||
| const entityId = Number(req.params[config.idParam]); | ||
|
|
||
| if (isNaN(entityId)) { | ||
| throw new ForbiddenException(`Invalid ${config.idParam}`); | ||
| } | ||
|
|
||
| // Create a service registry that easily resolves services | ||
| const services = this.createServiceRegistry(); | ||
|
|
||
| try { | ||
| // Execute the lambda function to get the owner user ID | ||
| const ownerId = await config.resolver({ | ||
| entityId, | ||
| services, | ||
| }); | ||
|
|
||
| if (ownerId === null || ownerId === undefined) { | ||
| throw new ForbiddenException('Unable to determine resource ownership'); | ||
| } | ||
|
|
||
| if (ownerId !== user.id) { | ||
| throw new ForbiddenException('Access denied'); | ||
| } | ||
|
|
||
| return true; | ||
| } catch (error) { | ||
| console.error('Error in ownership resolver:', error); | ||
| throw new ForbiddenException('Error verifying resource ownership'); | ||
| } | ||
| } | ||
|
|
||
| // Use a service registry for easy service resolution and caching | ||
| private createServiceRegistry(): ServiceRegistry { | ||
| const cache = new Map<Type<unknown>, unknown>(); | ||
| const moduleRef = this.moduleRef; | ||
|
|
||
| return { | ||
| get<T>(serviceClass: Type<T>): T { | ||
| // Return cached service if already resolved before | ||
| if (cache.has(serviceClass)) { | ||
| return cache.get(serviceClass) as T; | ||
| } | ||
|
|
||
| // Resolve and cache the service | ||
| try { | ||
| const service = moduleRef.get(serviceClass, { strict: false }); | ||
| cache.set(serviceClass, service); | ||
| return service; | ||
| } catch (error) { | ||
| throw new Error(`Could not resolve service: ${serviceClass.name}`); | ||
| } | ||
| }, | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { SetMetadata } from '@nestjs/common'; | ||
| import { Role } from '../users/types'; | ||
|
|
||
| export const ROLES_KEY = 'roles'; | ||
| export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
love documentation 🤩