2025-08-06 14:31:16 +02:00
|
|
|
export default defineEventHandler(async (event) => {
|
2025-08-07 03:17:25 +02:00
|
|
|
console.log('🔄 Callback endpoint called at:', new Date().toISOString());
|
|
|
|
|
|
2025-08-06 14:31:16 +02:00
|
|
|
const query = getQuery(event);
|
|
|
|
|
const { code, state } = query;
|
2025-08-07 03:17:25 +02:00
|
|
|
|
|
|
|
|
console.log('📝 Callback query params:', {
|
|
|
|
|
hasCode: !!code,
|
|
|
|
|
hasState: !!state,
|
|
|
|
|
state: state ? 'present' : 'missing'
|
|
|
|
|
});
|
2025-08-06 14:31:16 +02:00
|
|
|
|
|
|
|
|
if (!code || !state) {
|
|
|
|
|
throw createError({
|
|
|
|
|
statusCode: 400,
|
|
|
|
|
statusMessage: 'Missing authorization code or state',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify state
|
|
|
|
|
const storedState = getCookie(event, 'oauth-state');
|
|
|
|
|
if (state !== storedState) {
|
|
|
|
|
throw createError({
|
|
|
|
|
statusCode: 400,
|
|
|
|
|
statusMessage: 'Invalid state parameter',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const keycloak = createKeycloakClient();
|
|
|
|
|
const sessionManager = createSessionManager();
|
|
|
|
|
|
|
|
|
|
// Exchange code for tokens
|
|
|
|
|
const tokens = await keycloak.exchangeCodeForTokens(code as string);
|
|
|
|
|
|
|
|
|
|
// Get user info
|
|
|
|
|
const userInfo = await keycloak.getUserInfo(tokens.access_token);
|
|
|
|
|
|
2025-08-31 18:28:38 +02:00
|
|
|
// Use member-tiers service for proper tier determination
|
|
|
|
|
const { determineMemberTierByEmail, determineMemberTierFromKeycloak } = await import('~/server/utils/member-tiers');
|
|
|
|
|
|
|
|
|
|
// First try to determine tier by email (checks NocoDB and Keycloak)
|
|
|
|
|
const tierResult = await determineMemberTierByEmail(userInfo.email);
|
|
|
|
|
|
|
|
|
|
// Log tier determination for monitoring
|
|
|
|
|
console.log(`[auth-callback] Tier determination for ${userInfo.email}:`, {
|
|
|
|
|
tier: tierResult.tier,
|
|
|
|
|
source: tierResult.source,
|
|
|
|
|
confidence: tierResult.confidence,
|
|
|
|
|
reason: tierResult.reason
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Alert admin if tier confidence is low
|
|
|
|
|
if (tierResult.confidence === 'low') {
|
|
|
|
|
console.warn(`[auth-callback] Low confidence tier assignment for ${userInfo.email}. Manual review recommended.`);
|
|
|
|
|
}
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-06 14:31:16 +02:00
|
|
|
// Create session
|
|
|
|
|
const sessionData = {
|
|
|
|
|
user: {
|
|
|
|
|
id: userInfo.sub,
|
|
|
|
|
email: userInfo.email,
|
2025-08-07 12:28:41 +02:00
|
|
|
name: userInfo.name || `${userInfo.given_name || ''} ${userInfo.family_name || ''}`.trim(),
|
|
|
|
|
firstName: userInfo.given_name,
|
|
|
|
|
lastName: userInfo.family_name,
|
|
|
|
|
username: userInfo.preferred_username,
|
2025-08-31 18:28:38 +02:00
|
|
|
tier: tierResult.tier,
|
|
|
|
|
tierSource: tierResult.source, // Track where tier came from
|
2025-08-07 12:28:41 +02:00
|
|
|
groups: userInfo.groups || ['user'],
|
2025-08-06 14:31:16 +02:00
|
|
|
},
|
|
|
|
|
tokens: {
|
|
|
|
|
accessToken: tokens.access_token,
|
|
|
|
|
refreshToken: tokens.refresh_token,
|
|
|
|
|
expiresAt: Date.now() + (tokens.expires_in * 1000),
|
|
|
|
|
},
|
|
|
|
|
createdAt: Date.now(),
|
|
|
|
|
lastActivity: Date.now(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sessionCookie = sessionManager.createSession(sessionData);
|
|
|
|
|
|
|
|
|
|
// Set session cookie
|
|
|
|
|
setHeader(event, 'Set-Cookie', sessionCookie);
|
|
|
|
|
|
|
|
|
|
// Clear state cookie
|
|
|
|
|
deleteCookie(event, 'oauth-state');
|
|
|
|
|
|
|
|
|
|
return sendRedirect(event, '/dashboard');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Auth callback error:', error);
|
|
|
|
|
throw createError({
|
|
|
|
|
statusCode: 500,
|
|
|
|
|
statusMessage: 'Authentication failed',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|