#### __1. Role-Based Security Architecture__
All checks were successful
Build And Push Image / docker (push) Successful in 2m58s
All checks were successful
Build And Push Image / docker (push) Successful in 2m58s
- 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
This commit is contained in:
177
server/api/members/[id]/create-portal-account.post.ts
Normal file
177
server/api/members/[id]/create-portal-account.post.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
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);
|
||||
|
||||
// 6. Update member record with keycloak_id
|
||||
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 });
|
||||
|
||||
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';
|
||||
}
|
||||
Reference in New Issue
Block a user