161 lines
5.3 KiB
TypeScript
161 lines
5.3 KiB
TypeScript
|
|
/**
|
||
|
|
* Password Setup API Endpoint
|
||
|
|
* Handles setting passwords for newly registered users
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { createKeycloakAdminClient } from '~/server/utils/keycloak-admin';
|
||
|
|
import { validatePassword } from '~/server/utils/security';
|
||
|
|
|
||
|
|
interface SetupPasswordRequest {
|
||
|
|
email: string;
|
||
|
|
password: string;
|
||
|
|
token?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default defineEventHandler(async (event) => {
|
||
|
|
console.log('[api/auth/setup-password] =========================');
|
||
|
|
console.log('[api/auth/setup-password] POST /api/auth/setup-password - Password setup');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const body = await readBody(event) as SetupPasswordRequest;
|
||
|
|
console.log('[api/auth/setup-password] Setup password attempt for:', body.email);
|
||
|
|
|
||
|
|
// 1. Validate request data
|
||
|
|
if (!body.email?.trim()) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 400,
|
||
|
|
statusMessage: 'Email address is required'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!body.password?.trim()) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 400,
|
||
|
|
statusMessage: 'Password is required'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Validate password strength
|
||
|
|
const passwordValidation = validatePassword(body.password);
|
||
|
|
if (!passwordValidation.isValid) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 422,
|
||
|
|
statusMessage: `Password validation failed: ${passwordValidation.errors.join(', ')}`
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. Find user in Keycloak
|
||
|
|
const keycloakAdmin = createKeycloakAdminClient();
|
||
|
|
const existingUsers = await keycloakAdmin.findUserByEmail(body.email);
|
||
|
|
|
||
|
|
if (existingUsers.length === 0) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 404,
|
||
|
|
statusMessage: 'User not found. Please register first or contact support.'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const user = existingUsers[0];
|
||
|
|
|
||
|
|
// 4. Check if user already has a password set by checking if they have any required actions
|
||
|
|
console.log('[api/auth/setup-password] User found:', user.id, 'Required actions:', user.requiredActions);
|
||
|
|
|
||
|
|
if (user.requiredActions && !user.requiredActions.includes('UPDATE_PASSWORD')) {
|
||
|
|
console.log('[api/auth/setup-password] User already has password set, allowing password update');
|
||
|
|
// Allow password updates - this could be a password reset scenario
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5. Set the user's password in Keycloak using direct REST API
|
||
|
|
console.log('[api/auth/setup-password] Setting password for user:', user.id);
|
||
|
|
|
||
|
|
const adminToken = await keycloakAdmin.getAdminToken();
|
||
|
|
const config = useRuntimeConfig();
|
||
|
|
const adminBaseUrl = config.keycloak.issuer.replace('/realms/', '/admin/realms/');
|
||
|
|
|
||
|
|
// Set password using Keycloak Admin REST API
|
||
|
|
const setPasswordResponse = await fetch(`${adminBaseUrl}/users/${user.id}/reset-password`, {
|
||
|
|
method: 'PUT',
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${adminToken}`,
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'User-Agent': 'MonacoUSA-Portal/1.0'
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
type: 'password',
|
||
|
|
value: body.password,
|
||
|
|
temporary: false
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!setPasswordResponse.ok) {
|
||
|
|
const errorText = await setPasswordResponse.text().catch(() => 'Unknown error');
|
||
|
|
throw createError({
|
||
|
|
statusCode: setPasswordResponse.status,
|
||
|
|
statusMessage: `Failed to set password: ${errorText}`
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 6. Update user to ensure they're enabled, email is verified, and remove required actions
|
||
|
|
const updateUserResponse = await fetch(`${adminBaseUrl}/users/${user.id}`, {
|
||
|
|
method: 'PUT',
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${adminToken}`,
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'User-Agent': 'MonacoUSA-Portal/1.0'
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
...user,
|
||
|
|
enabled: true,
|
||
|
|
emailVerified: true,
|
||
|
|
requiredActions: [], // Remove all required actions including UPDATE_PASSWORD
|
||
|
|
attributes: {
|
||
|
|
...user.attributes,
|
||
|
|
needsPasswordSetup: ['false'],
|
||
|
|
passwordSetAt: [new Date().toISOString()]
|
||
|
|
}
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!updateUserResponse.ok) {
|
||
|
|
const errorText = await updateUserResponse.text().catch(() => 'Unknown error');
|
||
|
|
console.warn('[api/auth/setup-password] Failed to update user profile:', errorText);
|
||
|
|
// Don't fail the entire operation if this update fails
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[api/auth/setup-password] ✅ Password setup successful for user: ${body.email}`);
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
message: 'Password set successfully! You can now log in to your account.',
|
||
|
|
data: {
|
||
|
|
email: body.email,
|
||
|
|
passwordSet: true,
|
||
|
|
canLogin: true
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error('[api/auth/setup-password] ❌ Password setup failed:', error);
|
||
|
|
|
||
|
|
// Handle Keycloak specific errors
|
||
|
|
if (error.response?.status === 404) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 404,
|
||
|
|
statusMessage: 'User not found. Please register first or contact support.'
|
||
|
|
});
|
||
|
|
} else if (error.response?.status === 409) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 409,
|
||
|
|
statusMessage: 'Password has already been set. You can log in with your existing password.'
|
||
|
|
});
|
||
|
|
} else if (error.response?.status === 400) {
|
||
|
|
throw createError({
|
||
|
|
statusCode: 422,
|
||
|
|
statusMessage: 'Password does not meet Keycloak security requirements. Please choose a stronger password.'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
});
|