Skip to content
Draft
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
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