Last active
January 22, 2025 22:43
-
-
Save mgregier/baeb0c3de2246701d48036972abf0140 to your computer and use it in GitHub Desktop.
Allowed GCP service account NestJS Guard
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
import { SetMetadata } from '@nestjs/common'; | |
import { SERVICE_ACCOUNT_KEY } from './google-auth.guard'; | |
export const AllowedServiceAccounts = (...accounts: string[]) => | |
SetMetadata(SERVICE_ACCOUNT_KEY, accounts); |
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
import { Controller, Get, UseGuards } from '@nestjs/common'; | |
import { GoogleAuthGuard } from './guards/google-auth.guard'; | |
import { AllowedServiceAccounts } from './decorators/allowed-service-accounts.decorator'; | |
@Controller('cdil-sample') | |
export class SecureController { | |
@Get('protected-endpoint') | |
@UseGuards(GoogleAuthGuard) | |
@AllowedServiceAccounts('[email protected]') | |
getProtectedData() { | |
return { message: 'This endpoint is protected by GoogleAuthGuard' }; | |
} | |
// Endpoint with required SA authorization | |
@Get('partners') | |
@UseGuards(GoogleAuthGuard) | |
@AllowedServiceAccounts( | |
'[email protected]', | |
'[email protected]', | |
) | |
getAnotherProtectedData() { | |
return { message: 'This endpoint is also protected by GoogleAuthGuard' }; | |
} | |
// Public endpoint | |
@Get('public-endpoint') | |
getPublicData() { | |
return { message: 'This endpoint is public and does not require a guard' }; | |
} | |
} |
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
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; | |
import { Reflector } from '@nestjs/core'; | |
import { google } from 'google-auth-library'; | |
// Custom decorator key | |
export const SERVICE_ACCOUNT_KEY = 'allowedServiceAccounts'; | |
@Injectable() | |
export class GoogleAuthGuard implements CanActivate { | |
constructor(private reflector: Reflector) {} | |
async canActivate(context: ExecutionContext): Promise<boolean> { | |
const request = context.switchToHttp().getRequest(); | |
const authHeader = request.headers['authorization']; | |
// Check if the route has specific allowed service accounts | |
const allowedServiceAccounts = this.reflector.get<string[]>( | |
SERVICE_ACCOUNT_KEY, | |
context.getHandler(), | |
); | |
if (!allowedServiceAccounts || allowedServiceAccounts.length === 0) { | |
return true; // No specific service accounts are required for this endpoint | |
} | |
if (!authHeader || !authHeader.startsWith('Bearer ')) { | |
throw new UnauthorizedException('Missing or invalid authorization header'); | |
} | |
const token = authHeader.split(' ')[1]; | |
try { | |
// Verify the token using Google's library | |
const client = new google.auth.GoogleAuth(); | |
const ticket = await client.verifyIdToken({ | |
idToken: token, | |
audience: 'cdil-partners', // Replace with expected audience | |
}); | |
const payload = ticket.getPayload(); | |
if (!payload || !payload.email) { | |
throw new UnauthorizedException('Invalid token payload'); | |
} | |
// Check if the service account email is in the allowed list | |
if (!allowedServiceAccounts.includes(payload.email)) { | |
throw new UnauthorizedException('Access denied: Unauthorized service account'); | |
} | |
request.user = payload; // Attach user info to the request object if needed | |
return true; | |
} catch (error) { | |
console.error('Token verification failed', error); | |
throw new UnauthorizedException('Invalid token'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment