Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions apps/backend/lambdas/donors/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { CognitoJwtVerifier } from 'aws-jwt-verify';
import db from './db';

const COGNITO_USER_POOL_ID = process.env.COGNITO_USER_POOL_ID!;
const COGNITO_CLIENT_ID = process.env.COGNITO_APP_CLIENT_ID!;

// Create verifier instance lazily (only when needed)
let verifier: any = null;

function getVerifier() {
if (!verifier) {
if (!COGNITO_USER_POOL_ID) {
throw new Error('COGNITO_USER_POOL_ID environment variable is not set');
}
verifier = CognitoJwtVerifier.create({
userPoolId: COGNITO_USER_POOL_ID,
tokenUse: 'access',
clientId: COGNITO_CLIENT_ID,
});
}
return verifier;
}

export interface AuthenticatedUser {
cognitoSub: string;
userId?: number;
email: string;
isAdmin?: boolean;
cognitoGroups?: string[];
}

export interface AuthContext {
user?: AuthenticatedUser;
isAuthenticated: boolean;
}

/**
* Encode a JWT token
*/
function extractToken(event: any): string | null {
const authHeader = event.headers?.authorization || event.headers?.Authorization;

if (!authHeader) {
return null;
}

const parts = authHeader.split(' ');
if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') {
return parts[1];
}

return authHeader;
}

export async function authenticateRequest(event: any): Promise<AuthContext> {
const token = extractToken(event);

if (!token) {
return { isAuthenticated: false };
}

try {
const payload = await getVerifier().verify(token);

const dbUser = await db
.selectFrom('branch.users')
.where('cognito_sub', '=', payload.sub)
.selectAll()
.executeTakeFirst();

if (!dbUser) {
return { isAuthenticated: false };
}

const user: AuthenticatedUser = {
cognitoSub: payload.sub,
email: payload.email,
userId: dbUser.user_id,
isAdmin: dbUser.is_admin || false,
cognitoGroups: payload['cognito:groups'] || [],
};

if (user.cognitoGroups?.includes('Admins')) {
user.isAdmin = true;
}

return { user, isAuthenticated: true };
} catch (error) {
console.error('Authentication error:', error);
return { isAuthenticated: false };
}
}
1 change: 1 addition & 0 deletions apps/backend/lambdas/donors/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface BranchProjects {
}

export interface BranchUsers {
cognito_sub: string | null;
created_at: Generated<Timestamp | null>;
email: string;
is_admin: Generated<boolean | null>;
Expand Down
9 changes: 8 additions & 1 deletion apps/backend/lambdas/donors/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { APIGatewayProxyResult } from 'aws-lambda';
import db from './db';
import { authenticateRequest } from './auth';

export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
try {
Expand All @@ -15,11 +16,17 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
return json(200, { ok: true, timestamp: new Date().toISOString() });
}

const authContext = await authenticateRequest(event);
if (!authContext.isAuthenticated) {
return json(401, { message: 'Unauthorized' });
}

// >>> ROUTES-START (do not remove this marker)
// CLI-generated routes will be inserted here

// GET /donors
if (rawPath === '/' && method === 'GET') {

const donors = await db.selectFrom("branch.donors").selectAll().execute()
return json(200, donors ?? []);
}
Expand Down
42 changes: 26 additions & 16 deletions apps/backend/lambdas/donors/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/backend/lambdas/donors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"typescript": "^5.4.5"
},
"dependencies": {
"aws-jwt-verify": "^5.1.1",
"jest": "^30.2.0",
"kysely": "^0.28.8",
"pg": "^8.17.2"
Expand Down
Loading