Skip to content

Instantly share code, notes, and snippets.

@mgregier
Last active January 22, 2025 22:43
Show Gist options
  • Save mgregier/baeb0c3de2246701d48036972abf0140 to your computer and use it in GitHub Desktop.
Save mgregier/baeb0c3de2246701d48036972abf0140 to your computer and use it in GitHub Desktop.
Allowed GCP service account NestJS Guard
import { SetMetadata } from '@nestjs/common';
import { SERVICE_ACCOUNT_KEY } from './google-auth.guard';
export const AllowedServiceAccounts = (...accounts: string[]) =>
SetMetadata(SERVICE_ACCOUNT_KEY, accounts);
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' };
}
}
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