monacousa-portal/server/api/members/[id]/create-portal-account.post.ts

202 lines
7.6 KiB
TypeScript
Raw Normal View History

#### __1. Role-Based Security Architecture__ - Replaces group-based tiers with proper Keycloak realm roles - `monaco-user`, `monaco-board`, `monaco-admin` roles - Backward compatibility with existing group system #### __2. Advanced User Management__ - Comprehensive user profile synchronization - Membership data stored in Keycloak user attributes - Bidirectional sync between NocoDB and Keycloak #### __3. Session Security & Monitoring__ - Real-time session tracking and management - Administrative session control capabilities - Enhanced security analytics foundation #### __4. Email Workflow System__ - Multiple email types: DUES_REMINDER, MEMBERSHIP_RENEWAL, WELCOME, VERIFICATION - Customizable email parameters and lifespans - Advanced email template support #### __5. Seamless Migration Path__ - All existing functionality continues to work - New users automatically get realm roles - Gradual migration from groups to roles - Zero breaking changes ### 🔧 __What You Can Do Now__ #### __For New Users:__ - Public registrations automatically assign `monaco-user` role - Portal account creation syncs member data to Keycloak attributes - Enhanced email verification and welcome workflows #### __For Administrators:__ - Session management and monitoring capabilities - Advanced user profile management with member data sync - Comprehensive role assignment and management - Enhanced email communication workflows #### __For Developers:__ - Use `hasRole('monaco-admin')` for role-based checks - Access `getAllRoles()` for debugging and analytics - Enhanced `useAuth()` composable with backward compatibility - Comprehensive TypeScript support throughout ### 🛡️ __Security & Reliability__ - __Backward Compatibility__: Existing users continue to work seamlessly - __Enhanced Security__: Proper realm role-based authorization - __Error Handling__: Comprehensive error handling and fallbacks - __Type Safety__: Full TypeScript support throughout the system
2025-08-08 19:40:13 +02:00
export default defineEventHandler(async (event) => {
console.log('[api/members/[id]/create-portal-account.post] =========================');
console.log('[api/members/[id]/create-portal-account.post] POST /api/members/:id/create-portal-account - Create portal account for member');
try {
// Validate session and require board/admin privileges
const sessionManager = createSessionManager();
const cookieHeader = getCookie(event, 'monacousa-session') ? getHeader(event, 'cookie') : undefined;
const session = sessionManager.getSession(cookieHeader);
if (!session?.user) {
throw createError({
statusCode: 401,
statusMessage: 'Authentication required'
});
}
// Require board or admin privileges
if (session.user.tier !== 'admin' && session.user.tier !== 'board') {
throw createError({
statusCode: 403,
statusMessage: 'Board or Admin privileges required'
});
}
console.log('[api/members/[id]/create-portal-account.post] Authorized user:', session.user.email, 'tier:', session.user.tier);
// Get member ID from route parameter
const memberId = getRouterParam(event, 'id');
if (!memberId) {
throw createError({
statusCode: 400,
statusMessage: 'Member ID is required'
});
}
console.log('[api/members/[id]/create-portal-account.post] Processing member ID:', memberId);
// 1. Get member data
const { getMemberById } = await import('~/server/utils/nocodb');
const member = await getMemberById(memberId);
if (!member) {
throw createError({
statusCode: 404,
statusMessage: 'Member not found'
});
}
console.log('[api/members/[id]/create-portal-account.post] Found member:', member.email);
// 2. Check if member already has portal account
if (member.keycloak_id) {
console.log('[api/members/[id]/create-portal-account.post] Member already has portal account:', member.keycloak_id);
throw createError({
statusCode: 409,
statusMessage: 'Member already has a portal account'
});
}
// 3. Validate member data
if (!member.email || !member.first_name || !member.last_name) {
throw createError({
statusCode: 400,
statusMessage: 'Member must have email, first name, and last name to create portal account'
});
}
// 4. Check if user already exists in Keycloak (by email)
const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin');
const keycloakAdmin = createKeycloakAdminClient();
console.log('[api/members/[id]/create-portal-account.post] Checking for existing Keycloak user...');
const existingUsers = await keycloakAdmin.findUserByEmail(member.email);
if (existingUsers.length > 0) {
console.log('[api/members/[id]/create-portal-account.post] User already exists in Keycloak');
throw createError({
statusCode: 409,
statusMessage: 'A user with this email already exists in the system'
});
}
// 5. Determine membership tier based on member data
const membershipTier = determineMembershipTier(member);
console.log('[api/members/[id]/create-portal-account.post] Determined membership tier:', membershipTier);
// 6. Prepare membership data for Keycloak sync
const membershipData = {
membershipStatus: member.membership_status || 'Active',
duesStatus: member.current_year_dues_paid === 'true' ? 'paid' as const : 'unpaid' as const,
memberSince: member.member_since || new Date().getFullYear().toString(),
nationality: member.nationality || '',
phone: member.phone || '',
address: member.address || '',
registrationDate: member.registration_date || new Date().toISOString(),
paymentDueDate: member.payment_due_date || '',
membershipTier,
nocodbMemberId: memberId
};
// 7. Create Keycloak user with role-based registration
console.log('[api/members/[id]/create-portal-account.post] Creating Keycloak user with role-based system...');
const keycloakId = await keycloakAdmin.createUserWithRoleRegistration({
email: member.email,
firstName: member.first_name,
lastName: member.last_name,
membershipTier,
membershipData
});
console.log('[api/members/[id]/create-portal-account.post] Created Keycloak user with ID:', keycloakId);
// 8. Update member record with keycloak_id
#### __1. Role-Based Security Architecture__ - Replaces group-based tiers with proper Keycloak realm roles - `monaco-user`, `monaco-board`, `monaco-admin` roles - Backward compatibility with existing group system #### __2. Advanced User Management__ - Comprehensive user profile synchronization - Membership data stored in Keycloak user attributes - Bidirectional sync between NocoDB and Keycloak #### __3. Session Security & Monitoring__ - Real-time session tracking and management - Administrative session control capabilities - Enhanced security analytics foundation #### __4. Email Workflow System__ - Multiple email types: DUES_REMINDER, MEMBERSHIP_RENEWAL, WELCOME, VERIFICATION - Customizable email parameters and lifespans - Advanced email template support #### __5. Seamless Migration Path__ - All existing functionality continues to work - New users automatically get realm roles - Gradual migration from groups to roles - Zero breaking changes ### 🔧 __What You Can Do Now__ #### __For New Users:__ - Public registrations automatically assign `monaco-user` role - Portal account creation syncs member data to Keycloak attributes - Enhanced email verification and welcome workflows #### __For Administrators:__ - Session management and monitoring capabilities - Advanced user profile management with member data sync - Comprehensive role assignment and management - Enhanced email communication workflows #### __For Developers:__ - Use `hasRole('monaco-admin')` for role-based checks - Access `getAllRoles()` for debugging and analytics - Enhanced `useAuth()` composable with backward compatibility - Comprehensive TypeScript support throughout ### 🛡️ __Security & Reliability__ - __Backward Compatibility__: Existing users continue to work seamlessly - __Enhanced Security__: Proper realm role-based authorization - __Error Handling__: Comprehensive error handling and fallbacks - __Type Safety__: Full TypeScript support throughout the system
2025-08-08 19:40:13 +02:00
console.log('[api/members/[id]/create-portal-account.post] Updating member record with keycloak_id...');
const { updateMember } = await import('~/server/utils/nocodb');
await updateMember(memberId, { keycloak_id: keycloakId });
// 9. Send welcome/verification email using our custom email system
console.log('[api/members/[id]/create-portal-account.post] Sending welcome/verification email...');
try {
const { getEmailService } = await import('~/server/utils/email');
const { generateEmailVerificationToken } = await import('~/server/utils/email-tokens');
const emailService = getEmailService();
const verificationToken = await generateEmailVerificationToken(keycloakId, member.email);
const config = useRuntimeConfig();
const verificationLink = `${config.public.domain}/api/auth/verify-email?token=${verificationToken}`;
await emailService.sendWelcomeEmail(member.email, {
firstName: member.first_name,
lastName: member.last_name,
verificationLink,
memberId: memberId
});
console.log('[api/members/[id]/create-portal-account.post] Welcome email sent successfully');
} catch (emailError: any) {
console.error('[api/members/[id]/create-portal-account.post] Failed to send welcome email:', emailError.message);
// Don't fail the account creation if email fails - user can resend verification email later
}
#### __1. Role-Based Security Architecture__ - Replaces group-based tiers with proper Keycloak realm roles - `monaco-user`, `monaco-board`, `monaco-admin` roles - Backward compatibility with existing group system #### __2. Advanced User Management__ - Comprehensive user profile synchronization - Membership data stored in Keycloak user attributes - Bidirectional sync between NocoDB and Keycloak #### __3. Session Security & Monitoring__ - Real-time session tracking and management - Administrative session control capabilities - Enhanced security analytics foundation #### __4. Email Workflow System__ - Multiple email types: DUES_REMINDER, MEMBERSHIP_RENEWAL, WELCOME, VERIFICATION - Customizable email parameters and lifespans - Advanced email template support #### __5. Seamless Migration Path__ - All existing functionality continues to work - New users automatically get realm roles - Gradual migration from groups to roles - Zero breaking changes ### 🔧 __What You Can Do Now__ #### __For New Users:__ - Public registrations automatically assign `monaco-user` role - Portal account creation syncs member data to Keycloak attributes - Enhanced email verification and welcome workflows #### __For Administrators:__ - Session management and monitoring capabilities - Advanced user profile management with member data sync - Comprehensive role assignment and management - Enhanced email communication workflows #### __For Developers:__ - Use `hasRole('monaco-admin')` for role-based checks - Access `getAllRoles()` for debugging and analytics - Enhanced `useAuth()` composable with backward compatibility - Comprehensive TypeScript support throughout ### 🛡️ __Security & Reliability__ - __Backward Compatibility__: Existing users continue to work seamlessly - __Enhanced Security__: Proper realm role-based authorization - __Error Handling__: Comprehensive error handling and fallbacks - __Type Safety__: Full TypeScript support throughout the system
2025-08-08 19:40:13 +02:00
console.log('[api/members/[id]/create-portal-account.post] ✅ Portal account creation successful');
return {
success: true,
message: 'Portal account created successfully. The member will receive an email to verify their account and set their password.',
data: {
keycloak_id: keycloakId,
member_id: memberId,
email: member.email,
name: `${member.first_name} ${member.last_name}`
}
};
} catch (error: any) {
console.error('[api/members/[id]/create-portal-account.post] ❌ Portal account creation failed:', error);
// If it's already an HTTP error, re-throw it
if (error.statusCode) {
throw error;
}
// Otherwise, wrap it in a generic error
throw createError({
statusCode: 500,
statusMessage: error.message || 'Failed to create portal account'
});
}
});
/**
* Determine membership tier based on member data
* This function analyzes member information to assign appropriate portal roles
*/
function determineMembershipTier(member: any): 'user' | 'board' | 'admin' {
// Check for explicit tier indicators in member data
// This could be based on membership type, special flags, or other criteria
// For now, default all members to 'user' tier
// In the future, you might want to check specific fields like:
// - member.membership_type
// - member.is_board_member
// - member.is_admin
// - specific email domains for admins
// - etc.
// Example logic (uncomment and modify as needed):
/*
if (member.email && member.email.includes('@admin.monacousa.org')) {
return 'admin';
}
if (member.membership_type === 'Board' || member.is_board_member === 'true') {
return 'board';
}
*/
// Default to user tier for all members
return 'user';
}