Add password setup flow with server-side validation
All checks were successful
Build And Push Image / docker (push) Successful in 3m2s
All checks were successful
Build And Push Image / docker (push) Successful in 3m2s
- Replace external password setup link with internal navigation - Add comprehensive password validation utility with strength requirements - Create dedicated password setup page and API endpoint - Streamline user flow from email verification to password creation
This commit is contained in:
160
server/api/auth/setup-password.post.ts
Normal file
160
server/api/auth/setup-password.post.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user