Skip to content
Draft
16 changes: 8 additions & 8 deletions apps/backend/lambdas/auth/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {

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

// POST /register
if (normalizedPath === '/register' && method === 'POST') {
return await handleRegister(event);
}


// POST /login
if (normalizedPath === '/login' && method === 'POST') {
return await handleLogin(event);
Expand Down Expand Up @@ -200,7 +200,7 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
return json(500, { message: 'Failed to resend verification code' });
}
}

// POST /logout
if (normalizedPath === '/logout' && method === 'POST') {
const authHeader = event.headers?.authorization || event.headers?.Authorization;
Expand All @@ -209,8 +209,8 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
}

// Extract token (remove "Bearer " prefix if present)
const accessToken = authHeader.startsWith('Bearer ')
? authHeader.slice(7)
const accessToken = authHeader.startsWith('Bearer ')
? authHeader.slice(7)
: authHeader;

if (!accessToken) {
Expand All @@ -234,7 +234,7 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
return json(500, { message: 'Failed to logout' });
}
}

// POST /forgot-password
if (normalizedPath === '/forgot-password' && method === 'POST') {
const body = event.body ? JSON.parse(event.body) as Record<string, unknown> : {};
Expand Down Expand Up @@ -270,7 +270,7 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
return json(500, { message: 'Failed to initiate password reset' });
}
}

// POST /reset-password
if (normalizedPath === '/reset-password' && method === 'POST') {
const body = event.body ? JSON.parse(event.body) as Record<string, unknown> : {};
Expand Down Expand Up @@ -309,7 +309,7 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
return json(500, { message: 'Failed to reset password' });
}
}
// <<< ROUTES-END
// <<< ROUTES-END

return json(404, { message: 'Not Found', path: normalizedPath, method });
} catch (err) {
Expand Down
54 changes: 49 additions & 5 deletions apps/backend/lambdas/users/handler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import {
CognitoIdentityProviderClient,
AdminCreateUserCommand,
AdminDeleteUserCommand,
} from '@aws-sdk/client-cognito-identity-provider';
import db from './db'
import { authenticateRequest, checkAuthorization, AuthContext } from './auth';
import { UserValidationUtils } from './validation-utils';

const cognitoClient = new CognitoIdentityProviderClient({
region: process.env.AWS_REGION || 'us-east-2',
});
const USER_POOL_ID = process.env.COGNITO_USER_POOL_ID || '';

function requireAuth(authContext: AuthContext, level: Parameters<typeof checkAuthorization>[1], resourceUserId?: number | string): APIGatewayProxyResult | undefined {
const authCheck = checkAuthorization(authContext, level, resourceUserId);
if (!authCheck.allowed) {
Expand Down Expand Up @@ -226,7 +236,7 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
const isAdmin = isAdminResult.value as boolean;
const profile_image = profileImageResult.value ?? undefined;

// Check if user with this email already exists
// Check if user with this email already exists in DB
const existingUser = await db
.selectFrom('branch.users')
.where('email', '=', email)
Expand All @@ -236,15 +246,49 @@ export const handler = async (event: any): Promise<APIGatewayProxyResult> => {
if (existingUser) {
return json(409, { message: 'User with this email already exists' });
}

// insert new user (user_id auto-increments)

// Create user in Cognito via AdminCreateUser — sends invite email with temp password
let cognitoSub: string;
try {
const cognitoResponse = await cognitoClient.send(new AdminCreateUserCommand({
UserPoolId: USER_POOL_ID,
Username: email,
DesiredDeliveryMediums: ['EMAIL'],
UserAttributes: [
{ Name: 'email', Value: email },
{ Name: 'email_verified', Value: 'true' },
{ Name: 'name', Value: name },
],
}));
const sub = cognitoResponse.User?.Attributes?.find(a => a.Name === 'sub')?.Value;
if (!sub) throw new Error('No sub returned from AdminCreateUser');
cognitoSub = sub;
} catch (err: any) {
console.error('Cognito AdminCreateUser error:', err);
if (err.name === 'UsernameExistsException') {
return json(409, { message: 'User with this email already exists' });
}
return json(500, { message: 'Failed to create user in authentication service' });
}

// Insert into database with cognito_sub
try {
await db
.insertInto('branch.users')
.values({ email, name, is_admin: isAdmin, profile_image })
.values({ cognito_sub: cognitoSub, email, name, is_admin: isAdmin, profile_image })
.execute();
} catch (err) {
} catch (err: any) {
console.error('Database insert error:', err);
// Rollback: delete Cognito user to keep systems in sync
try {
await cognitoClient.send(new AdminDeleteUserCommand({
UserPoolId: USER_POOL_ID,
Username: email,
}));
console.log('Rolled back Cognito user after database failure');
} catch (rollbackErr) {
console.error('Failed to rollback Cognito user:', rollbackErr);
}
return json(500, { message: 'Failed to create user' });
}

Expand Down
Loading
Loading