Add Keycloak integration support
Build And Push Image / docker (push) Successful in 2m59s
Details
Build And Push Image / docker (push) Successful in 2m59s
Details
- Update domain configuration to portal subdomain with HTTPS - Add keycloak_id field to member creation and update operations - Add API endpoint for linking Keycloak accounts to existing members
This commit is contained in:
parent
794b6a09f0
commit
358e9c0ad1
|
|
@ -35,5 +35,5 @@ NUXT_SESSION_SECRET=your-48-character-session-secret-key-here
|
||||||
NUXT_ENCRYPTION_KEY=your-32-character-encryption-key-here
|
NUXT_ENCRYPTION_KEY=your-32-character-encryption-key-here
|
||||||
|
|
||||||
# Public Configuration
|
# Public Configuration
|
||||||
NUXT_PUBLIC_DOMAIN=monacousa.org
|
NUXT_PUBLIC_DOMAIN=https://portal.monacousa.org
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
console.log('[api/admin/link-keycloak-account.post] Manual Keycloak account linking');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate session and require admin privileges
|
||||||
|
const sessionManager = createSessionManager();
|
||||||
|
const cookieHeader = getCookie(event, 'monacousa-session') ? getHeader(event, 'cookie') : undefined;
|
||||||
|
const session = sessionManager.getSession(cookieHeader);
|
||||||
|
|
||||||
|
if (!session?.user || session.user.tier !== 'admin') {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Admin privileges required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { memberId, keycloakId, keycloakEmail } = body;
|
||||||
|
|
||||||
|
if (!memberId || !keycloakId) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: 'Member ID and Keycloak ID are required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get member data
|
||||||
|
const { getMemberById, updateMember } = await import('~/server/utils/nocodb');
|
||||||
|
const member = await getMemberById(memberId);
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
statusMessage: 'Member not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the Keycloak user exists
|
||||||
|
const { createKeycloakAdminClient } = await import('~/server/utils/keycloak-admin');
|
||||||
|
const keycloakAdmin = createKeycloakAdminClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const keycloakUser = await keycloakAdmin.getUserById(keycloakId);
|
||||||
|
console.log('[link-keycloak-account] Found Keycloak user:', keycloakUser.email);
|
||||||
|
} catch (error) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
statusMessage: 'Keycloak user not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update member record with keycloak_id
|
||||||
|
console.log('[link-keycloak-account] Linking member', memberId, 'to Keycloak user', keycloakId);
|
||||||
|
await updateMember(memberId, { keycloak_id: keycloakId });
|
||||||
|
|
||||||
|
console.log('[link-keycloak-account] ✅ Successfully linked accounts');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Keycloak account successfully linked to member',
|
||||||
|
data: {
|
||||||
|
member_id: memberId,
|
||||||
|
keycloak_id: keycloakId,
|
||||||
|
member_email: member.email,
|
||||||
|
keycloak_email: keycloakEmail,
|
||||||
|
name: `${member.first_name} ${member.last_name}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[link-keycloak-account] ❌ Linking failed:', error);
|
||||||
|
|
||||||
|
if (error.statusCode) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: error.message || 'Failed to link Keycloak account'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -310,7 +310,8 @@ export const createMember = async (data: Partial<Member>): Promise<Member> => {
|
||||||
"payment_due_date",
|
"payment_due_date",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
"address",
|
"address",
|
||||||
"member_since"
|
"member_since",
|
||||||
|
"keycloak_id"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filter the data to only include allowed fields
|
// Filter the data to only include allowed fields
|
||||||
|
|
@ -392,7 +393,8 @@ export const updateMember = async (id: string, data: Partial<Member>, retryCount
|
||||||
"payment_due_date",
|
"payment_due_date",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
"address",
|
"address",
|
||||||
"member_since"
|
"member_since",
|
||||||
|
"keycloak_id"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filter the data to only include allowed fields
|
// Filter the data to only include allowed fields
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue