// server/api/users/list.get.ts // Get all users from database and enrich with last access from Keycloak import Database from 'better-sqlite3'; import { join } from 'path'; import { existsSync, mkdirSync } from 'fs'; // Helper to get database path const getDbPath = () => { const dbDir = join(process.cwd(), 'data'); if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }); } return join(dbDir, 'users.db'); }; // Helper to decode JWT token payload const decodeTokenPayload = (token: string | undefined): any | null => { if (!token) return null; try { const parts = token.split("."); if (parts.length < 2) return null; const payloadBase64 = parts[1]; return JSON.parse(Buffer.from(payloadBase64, "base64").toString()); } catch (e) { return null; } }; // Helper to get last access from Keycloak for a user using access token from session const getLastAccessFromKeycloak = async (userId: string, accessToken: string, config: any): Promise => { try { if (!accessToken) { return null; } // Extract realm from issuer const issuerUrl = new URL(config.keycloakIssuer); const realm = issuerUrl.pathname.split('/').filter(Boolean).pop() || 'master'; // Get user sessions from Keycloak Admin API using access token const sessionsUrl = `${config.keycloakIssuer.replace('/realms/' + realm, '')}/admin/realms/${realm}/users/${userId}/sessions`; const sessionsResponse = await fetch(sessionsUrl, { method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, }); if (!sessionsResponse.ok) { if (sessionsResponse.status === 404 || sessionsResponse.status === 403) { // User has no sessions or no permission return null; } return null; } const sessions = await sessionsResponse.json() as any[]; if (!sessions || sessions.length === 0) { return null; } // Find the most recent session (highest lastAccess timestamp) let lastAccessTimestamp = 0; sessions.forEach(session => { if (session.lastAccess && session.lastAccess > lastAccessTimestamp) { lastAccessTimestamp = session.lastAccess; } }); // Convert from milliseconds to seconds (Unix timestamp) return lastAccessTimestamp > 0 ? Math.floor(lastAccessTimestamp / 1000) : null; } catch (error: any) { console.warn(`âš ī¸ Error fetching last access for user ${userId}:`, error.message); return null; } }; export default defineEventHandler(async (event) => { console.log("📋 Users list endpoint called"); try { const config = useRuntimeConfig(); const dbPath = getDbPath(); // Check if database exists if (!existsSync(dbPath)) { console.log("â„šī¸ Database not found, returning empty array"); return []; } // Try to get access token from current user session let accessToken: string | null = null; try { const sessionCookie = getCookie(event, "user_session"); if (sessionCookie) { const session = JSON.parse(sessionCookie); const isExpired = Date.now() > session.expiresAt; if (!isExpired && session.accessToken) { accessToken = session.accessToken; } } } catch (e) { // No session available, will skip Keycloak fetch console.log("â„šī¸ No valid session found, will use database values for last access"); } const db = new Database(dbPath); // Get all users const users = db.prepare('SELECT * FROM users ORDER BY updatedAt DESC').all() as any[]; // Parse JSON fields and enrich with last access from Keycloak const formattedUsers = await Promise.all(users.map(async (user) => { // Try to get last access from Keycloak, fallback to database value let lastLogin = user.lastLogin || null; // Only fetch from Keycloak if we have a valid user ID, access token, and config if (user.id && accessToken && config.keycloakIssuer) { try { const keycloakLastAccess = await getLastAccessFromKeycloak(user.id, accessToken, config); // Use Keycloak last access if available, otherwise keep database value if (keycloakLastAccess) { lastLogin = keycloakLastAccess; } } catch (error) { // Silently fail and use database value console.warn(`âš ī¸ Could not fetch last access for user ${user.id}, using database value`); } } return { id: user.id, namaLengkap: user.namaLengkap, namaUser: user.namaUser, email: user.email, tipeUser: user.tipeUser || '', lastLogin: lastLogin, roles: JSON.parse(user.roles || '[]'), realmRoles: JSON.parse(user.realmRoles || '[]'), accountRoles: JSON.parse(user.accountRoles || '[]'), resourceRoles: JSON.parse(user.resourceRoles || '[]'), groups: JSON.parse(user.groups || '[]'), given_name: user.given_name, family_name: user.family_name, createdAt: user.createdAt, updatedAt: user.updatedAt, }; })); db.close(); console.log(`✅ Retrieved ${formattedUsers.length} users with last access data`); return formattedUsers; } catch (error: any) { console.error("❌ Error fetching users:", error); throw createError({ statusCode: 500, statusMessage: error.message || "Failed to fetch users", }); } });