// server/api/users/[id].patch.ts // Update user data import Database from 'better-sqlite3'; import { join } from 'path'; import { existsSync } from 'fs'; const getDbPath = () => { const dbDir = join(process.cwd(), 'data'); return join(dbDir, 'users.db'); }; export default defineEventHandler(async (event) => { const userId = getRouterParam(event, 'id'); const body = await readBody(event); console.log(`🔄 Update user endpoint called for ID: ${userId}`); if (!userId) { throw createError({ statusCode: 400, statusMessage: "User ID is required", }); } try { const dbPath = getDbPath(); if (!existsSync(dbPath)) { throw createError({ statusCode: 404, statusMessage: "Database not found", }); } const db = new Database(dbPath); // Ensure schema is up to date try { const tableInfo = db.prepare("PRAGMA table_info(users)").all() as any[]; const columnNames = tableInfo.map(col => col.name); if (!columnNames.includes('lastLogin')) { db.exec(`ALTER TABLE users ADD COLUMN lastLogin INTEGER`); console.log('✅ Added column: lastLogin'); } if (!columnNames.includes('realmRoles')) { db.exec(`ALTER TABLE users ADD COLUMN realmRoles TEXT DEFAULT '[]'`); console.log('✅ Added column: realmRoles'); } if (!columnNames.includes('accountRoles')) { db.exec(`ALTER TABLE users ADD COLUMN accountRoles TEXT DEFAULT '[]'`); console.log('✅ Added column: accountRoles'); } if (!columnNames.includes('resourceRoles')) { db.exec(`ALTER TABLE users ADD COLUMN resourceRoles TEXT DEFAULT '[]'`); console.log('✅ Added column: resourceRoles'); } } catch (e: any) { console.warn('Migration note:', e.message); } // Check if user exists const existingUser = db.prepare('SELECT * FROM users WHERE id = ?').get(userId) as any; if (!existingUser) { db.close(); throw createError({ statusCode: 404, statusMessage: "User not found", }); } // Handle password update to Keycloak if password is provided if (body.password && body.password.trim() !== '') { try { const config = useRuntimeConfig(); // Get access token from current user session let accessToken: string | null = null; try { const { getSessionFromCookie } = await import('~/server/utils/sessionStore'); const session = await getSessionFromCookie(event); if (session && session.accessToken) { accessToken = session.accessToken; } } catch (e) { console.warn("⚠️ No valid session found for password update"); } if (!accessToken) { db.close(); throw createError({ statusCode: 401, statusMessage: "Authentication required to update password", }); } // Extract realm from issuer const issuerUrl = new URL(config.keycloakIssuer); const realm = issuerUrl.pathname.split('/').filter(Boolean).pop() || 'master'; const keycloakBaseUrl = config.keycloakIssuer.replace('/realms/' + realm, ''); const passwordUpdateUrl = `${keycloakBaseUrl}/admin/realms/${realm}/users/${userId}/reset-password`; console.log(`🔐 Updating password for user ${userId} in Keycloak`); console.log(`🔗 Password update URL: ${passwordUpdateUrl}`); // Create abort controller for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout // Update password in Keycloak let passwordResponse; try { passwordResponse = await fetch(passwordUpdateUrl, { method: 'PUT', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'password', value: body.password, temporary: false, // Set to true if you want to force password change on next login }), signal: controller.signal, }); clearTimeout(timeoutId); } catch (fetchError: any) { clearTimeout(timeoutId); console.error('❌ Fetch error during password update:'); console.error(' - Error type:', fetchError.name); console.error(' - Error message:', fetchError.message); console.error(' - URL attempted:', passwordUpdateUrl); db.close(); let errorMsg = 'Failed to connect to Keycloak server to update password.'; if (fetchError.name === 'AbortError' || fetchError.message.includes('timeout')) { errorMsg = 'Keycloak server timeout. Please try again.'; } else if (fetchError.message.includes('ENOTFOUND') || fetchError.message.includes('getaddrinfo')) { errorMsg = 'Cannot reach Keycloak server. Please check network connection.'; } else if (fetchError.message.includes('ECONNREFUSED')) { errorMsg = 'Keycloak server refused connection. Server may be down.'; } else if (fetchError.message.includes('certificate') || fetchError.message.includes('SSL')) { errorMsg = 'SSL certificate error. Please contact administrator.'; } throw createError({ statusCode: 500, statusMessage: errorMsg, }); } if (!passwordResponse.ok) { const errorText = await passwordResponse.text(); console.error('❌ Keycloak password update failed:', errorText); db.close(); throw createError({ statusCode: passwordResponse.status, statusMessage: `Failed to update password in Keycloak: ${errorText}`, }); } console.log(`✅ Password updated successfully in Keycloak for user ${userId}`); } catch (passwordError: any) { console.error('❌ Error updating password in Keycloak:', passwordError); db.close(); // Re-throw if it's already a createError if (passwordError.statusCode) { throw passwordError; } throw createError({ statusCode: 500, statusMessage: `Failed to update password: ${passwordError.message}`, }); } } // Prepare update fields const updateFields: string[] = []; const updateValues: any[] = []; if (body.namaLengkap !== undefined) { updateFields.push('namaLengkap = ?'); updateValues.push(body.namaLengkap); } if (body.tipeUser !== undefined) { updateFields.push('tipeUser = ?'); updateValues.push(body.tipeUser); } if (body.lastLogin !== undefined) { updateFields.push('lastLogin = ?'); updateValues.push(body.lastLogin); } if (body.roles !== undefined) { updateFields.push('roles = ?'); updateValues.push(JSON.stringify(Array.isArray(body.roles) ? body.roles : [])); } if (body.realmRoles !== undefined) { updateFields.push('realmRoles = ?'); updateValues.push(JSON.stringify(Array.isArray(body.realmRoles) ? body.realmRoles : [])); } if (body.accountRoles !== undefined) { updateFields.push('accountRoles = ?'); updateValues.push(JSON.stringify(Array.isArray(body.accountRoles) ? body.accountRoles : [])); } if (body.resourceRoles !== undefined) { updateFields.push('resourceRoles = ?'); updateValues.push(JSON.stringify(Array.isArray(body.resourceRoles) ? body.resourceRoles : [])); } if (body.groups !== undefined) { updateFields.push('groups = ?'); updateValues.push(JSON.stringify(Array.isArray(body.groups) ? body.groups : [])); } if (updateFields.length === 0) { db.close(); return { success: true, message: 'No fields to update' }; } // Add updatedAt updateFields.push('updatedAt = strftime(\'%s\', \'now\')'); updateValues.push(userId); // Execute update const sql = `UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`; db.prepare(sql).run(...updateValues); db.close(); console.log(`✅ User updated: ${userId}`); return { success: true, message: 'User updated successfully' }; } catch (error: any) { console.error("❌ Error updating user:", error); throw createError({ statusCode: error.statusCode || 500, statusMessage: error.message || "Failed to update user", }); } });