update user login baru dan hakakses
This commit is contained in:
No files matched your search
@@ -35,6 +35,31 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
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;
|
||||
|
||||
@@ -46,6 +71,114 @@ export default defineEventHandler(async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 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 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) {
|
||||
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[] = [];
|
||||
|
||||
@@ -17,6 +17,7 @@ const initDb = () => {
|
||||
const dbPath = getDbPath();
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Create table if not exists
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -37,18 +38,44 @@ const initDb = () => {
|
||||
)
|
||||
`);
|
||||
|
||||
// Migration: Add new columns if they don't exist
|
||||
// Migration: Check and add missing columns one by one
|
||||
try {
|
||||
db.exec(`
|
||||
ALTER TABLE users ADD COLUMN realmRoles TEXT DEFAULT '[]';
|
||||
ALTER TABLE users ADD COLUMN accountRoles TEXT DEFAULT '[]';
|
||||
ALTER TABLE users ADD COLUMN resourceRoles TEXT DEFAULT '[]';
|
||||
ALTER TABLE users ADD COLUMN lastLogin INTEGER;
|
||||
`);
|
||||
} catch (e: any) {
|
||||
if (!e.message?.includes('duplicate column')) {
|
||||
console.warn('Migration note:', e.message);
|
||||
const tableInfo = db.prepare("PRAGMA table_info(users)").all() as any[];
|
||||
const columnNames = tableInfo.map(col => col.name);
|
||||
|
||||
// Add missing columns one by one
|
||||
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');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('lastLogin')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN lastLogin INTEGER`);
|
||||
console.log('✅ Added column: lastLogin');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('given_name')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN given_name TEXT`);
|
||||
console.log('✅ Added column: given_name');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('family_name')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN family_name TEXT`);
|
||||
console.log('✅ Added column: family_name');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('❌ Migration error:', e.message);
|
||||
// Don't throw, continue with existing schema
|
||||
}
|
||||
|
||||
return db;
|
||||
|
||||
@@ -79,6 +79,47 @@ const getLastAccessFromKeycloak = async (userId: string, accessToken: string, co
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to ensure database schema is up to date
|
||||
const ensureSchema = (db: any) => {
|
||||
try {
|
||||
const tableInfo = db.prepare("PRAGMA table_info(users)").all() as any[];
|
||||
const columnNames = tableInfo.map(col => col.name);
|
||||
|
||||
// Add missing columns one by one
|
||||
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');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('lastLogin')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN lastLogin INTEGER`);
|
||||
console.log('✅ Added column: lastLogin');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('given_name')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN given_name TEXT`);
|
||||
console.log('✅ Added column: given_name');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('family_name')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN family_name TEXT`);
|
||||
console.log('✅ Added column: family_name');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('❌ Schema migration error:', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log("📋 Users list endpoint called");
|
||||
|
||||
@@ -108,23 +149,33 @@ export default defineEventHandler(async (event) => {
|
||||
console.log("ℹ️ No valid session found, will use database values for last access");
|
||||
}
|
||||
|
||||
// Open database connection
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Ensure schema is up to date before querying
|
||||
ensureSchema(db);
|
||||
|
||||
// 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;
|
||||
// Get lastLogin from database (handle null, 0, or undefined)
|
||||
let lastLogin: number | null = null;
|
||||
if (user.lastLogin !== null && user.lastLogin !== undefined && user.lastLogin !== 0) {
|
||||
lastLogin = user.lastLogin;
|
||||
}
|
||||
|
||||
// Only fetch from Keycloak if we have a valid user ID, access token, and config
|
||||
// And only if we don't have a valid lastLogin in database
|
||||
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
|
||||
// Use Keycloak last access if available and newer than database value
|
||||
if (keycloakLastAccess) {
|
||||
lastLogin = keycloakLastAccess;
|
||||
if (!lastLogin || keycloakLastAccess > lastLogin) {
|
||||
lastLogin = keycloakLastAccess;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail and use database value
|
||||
@@ -138,7 +189,7 @@ export default defineEventHandler(async (event) => {
|
||||
namaUser: user.namaUser,
|
||||
email: user.email,
|
||||
tipeUser: user.tipeUser || '',
|
||||
lastLogin: lastLogin,
|
||||
lastLogin: lastLogin, // Will be null if never logged in, otherwise timestamp in seconds
|
||||
roles: JSON.parse(user.roles || '[]'),
|
||||
realmRoles: JSON.parse(user.realmRoles || '[]'),
|
||||
accountRoles: JSON.parse(user.accountRoles || '[]'),
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
// server/api/users/sync-all.post.ts
|
||||
// Sync all users from Keycloak Admin API to database
|
||||
// This endpoint will fetch all users from Keycloak and sync them to the database
|
||||
|
||||
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
|
||||
const getLastAccessFromKeycloak = async (userId: string, accessToken: string, config: any): Promise<number | null> => {
|
||||
try {
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const issuerUrl = new URL(config.keycloakIssuer);
|
||||
const realm = issuerUrl.pathname.split('/').filter(Boolean).pop() || 'master';
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessions = await sessionsResponse.json() as any[];
|
||||
if (!sessions || sessions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let lastAccessTimestamp = 0;
|
||||
sessions.forEach(session => {
|
||||
if (session.lastAccess && session.lastAccess > lastAccessTimestamp) {
|
||||
lastAccessTimestamp = session.lastAccess;
|
||||
}
|
||||
});
|
||||
|
||||
return lastAccessTimestamp > 0 ? Math.floor(lastAccessTimestamp / 1000) : null;
|
||||
} catch (error: any) {
|
||||
console.warn(`⚠️ Error fetching last access for user ${userId}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to get user from Keycloak Admin API
|
||||
const getUserFromKeycloak = async (userId: string, accessToken: string, config: any): Promise<any | null> => {
|
||||
try {
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const issuerUrl = new URL(config.keycloakIssuer);
|
||||
const realm = issuerUrl.pathname.split('/').filter(Boolean).pop() || 'master';
|
||||
const userUrl = `${config.keycloakIssuer.replace('/realms/' + realm, '')}/admin/realms/${realm}/users/${userId}`;
|
||||
|
||||
const userResponse = await fetch(userUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!userResponse.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await userResponse.json();
|
||||
} catch (error: any) {
|
||||
console.warn(`⚠️ Error fetching user ${userId} from Keycloak:`, error.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to get all users from Keycloak Admin API
|
||||
const getAllUsersFromKeycloak = async (accessToken: string, config: any): Promise<any[]> => {
|
||||
try {
|
||||
if (!accessToken) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const issuerUrl = new URL(config.keycloakIssuer);
|
||||
const realm = issuerUrl.pathname.split('/').filter(Boolean).pop() || 'master';
|
||||
const usersUrl = `${config.keycloakIssuer.replace('/realms/' + realm, '')}/admin/realms/${realm}/users?max=1000`;
|
||||
|
||||
const usersResponse = await fetch(usersUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!usersResponse.ok) {
|
||||
console.warn('⚠️ Failed to fetch users from Keycloak:', usersResponse.status);
|
||||
return [];
|
||||
}
|
||||
|
||||
return await usersResponse.json();
|
||||
} catch (error: any) {
|
||||
console.warn(`⚠️ Error fetching all users from Keycloak:`, error.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize database
|
||||
const initDb = () => {
|
||||
const dbPath = getDbPath();
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Create table if not exists
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
namaLengkap TEXT NOT NULL,
|
||||
namaUser TEXT UNIQUE NOT NULL,
|
||||
email TEXT,
|
||||
tipeUser TEXT DEFAULT '',
|
||||
lastLogin INTEGER,
|
||||
roles TEXT DEFAULT '[]',
|
||||
realmRoles TEXT DEFAULT '[]',
|
||||
accountRoles TEXT DEFAULT '[]',
|
||||
resourceRoles TEXT DEFAULT '[]',
|
||||
groups TEXT DEFAULT '[]',
|
||||
given_name TEXT,
|
||||
family_name TEXT,
|
||||
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updatedAt INTEGER DEFAULT (strftime('%s', 'now'))
|
||||
)
|
||||
`);
|
||||
|
||||
// Migration: Check and add missing columns one by one
|
||||
try {
|
||||
const tableInfo = db.prepare("PRAGMA table_info(users)").all() as any[];
|
||||
const columnNames = tableInfo.map(col => col.name);
|
||||
|
||||
// Add missing columns one by one
|
||||
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');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('lastLogin')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN lastLogin INTEGER`);
|
||||
console.log('✅ Added column: lastLogin');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('given_name')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN given_name TEXT`);
|
||||
console.log('✅ Added column: given_name');
|
||||
}
|
||||
|
||||
if (!columnNames.includes('family_name')) {
|
||||
db.exec(`ALTER TABLE users ADD COLUMN family_name TEXT`);
|
||||
console.log('✅ Added column: family_name');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('❌ Migration error:', e.message);
|
||||
// Don't throw, continue with existing schema
|
||||
}
|
||||
|
||||
return db;
|
||||
};
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log("🔄 Sync all users endpoint called");
|
||||
|
||||
const sessionCookie = getCookie(event, "user_session");
|
||||
|
||||
if (!sessionCookie) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "No session cookie found",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const config = useRuntimeConfig();
|
||||
const session = JSON.parse(sessionCookie);
|
||||
|
||||
const isExpired = Date.now() > session.expiresAt;
|
||||
if (isExpired) {
|
||||
deleteCookie(event, "user_session");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Session expired",
|
||||
});
|
||||
}
|
||||
|
||||
const accessToken = session.accessToken;
|
||||
if (!accessToken) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "No access token found",
|
||||
});
|
||||
}
|
||||
|
||||
// Get all users from Keycloak
|
||||
console.log("📥 Fetching all users from Keycloak...");
|
||||
const keycloakUsers = await getAllUsersFromKeycloak(accessToken, config);
|
||||
console.log(`✅ Found ${keycloakUsers.length} users in Keycloak`);
|
||||
|
||||
const db = initDb();
|
||||
let createdCount = 0;
|
||||
let updatedCount = 0;
|
||||
let unchangedCount = 0;
|
||||
|
||||
// Sync each user
|
||||
for (const kcUser of keycloakUsers) {
|
||||
try {
|
||||
const userId = kcUser.id;
|
||||
const namaLengkap = kcUser.firstName && kcUser.lastName
|
||||
? `${kcUser.firstName} ${kcUser.lastName}`.trim()
|
||||
: kcUser.firstName || kcUser.lastName || kcUser.username || '';
|
||||
const namaUser = kcUser.username || kcUser.email?.split('@')[0] || '';
|
||||
const email = kcUser.email || null;
|
||||
const given_name = kcUser.firstName || null;
|
||||
const family_name = kcUser.lastName || null;
|
||||
|
||||
if (!userId || !namaUser) {
|
||||
console.warn(`⚠️ Skipping user with missing ID or username: ${userId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get user details from Keycloak (including roles and groups)
|
||||
const userDetails = await getUserFromKeycloak(userId, accessToken, config);
|
||||
|
||||
// Extract roles and groups
|
||||
const realmRoles = userDetails?.realmRoles || [];
|
||||
const groups = userDetails?.groups || [];
|
||||
|
||||
// Determine tipeUser from groups
|
||||
let tipeUser = '';
|
||||
if (Array.isArray(groups) && groups.length > 0) {
|
||||
const lastGroup = groups[groups.length - 1];
|
||||
if (typeof lastGroup === 'string') {
|
||||
const parts = lastGroup.split('/').filter(Boolean);
|
||||
if (parts.length > 0) {
|
||||
tipeUser = parts[parts.length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get last access from Keycloak
|
||||
const lastLogin = await getLastAccessFromKeycloak(userId, accessToken, config);
|
||||
|
||||
// Check if user exists in database
|
||||
const existingUser = db.prepare('SELECT * FROM users WHERE id = ?').get(userId) as any;
|
||||
|
||||
const rolesJson = JSON.stringify(realmRoles);
|
||||
const realmRolesJson = JSON.stringify(realmRoles);
|
||||
const accountRolesJson = JSON.stringify([]);
|
||||
const resourceRolesJson = JSON.stringify([]);
|
||||
const groupsJson = JSON.stringify(groups);
|
||||
|
||||
if (existingUser) {
|
||||
// Update existing user - only update if there are changes
|
||||
const needsUpdate =
|
||||
existingUser.namaLengkap !== namaLengkap ||
|
||||
existingUser.namaUser !== namaUser ||
|
||||
existingUser.email !== email ||
|
||||
existingUser.roles !== rolesJson ||
|
||||
existingUser.realmRoles !== realmRolesJson ||
|
||||
existingUser.groups !== groupsJson ||
|
||||
existingUser.given_name !== given_name ||
|
||||
existingUser.family_name !== family_name ||
|
||||
(existingUser.tipeUser === '' && tipeUser !== '') ||
|
||||
(lastLogin && existingUser.lastLogin !== lastLogin);
|
||||
|
||||
if (needsUpdate) {
|
||||
const updateTipeUser = existingUser.tipeUser === '' ? tipeUser : existingUser.tipeUser;
|
||||
const updateLastLogin = lastLogin || existingUser.lastLogin;
|
||||
|
||||
db.prepare(`
|
||||
UPDATE users
|
||||
SET namaLengkap = ?,
|
||||
namaUser = ?,
|
||||
email = ?,
|
||||
roles = ?,
|
||||
realmRoles = ?,
|
||||
accountRoles = ?,
|
||||
resourceRoles = ?,
|
||||
groups = ?,
|
||||
given_name = ?,
|
||||
family_name = ?,
|
||||
tipeUser = ?,
|
||||
lastLogin = ?,
|
||||
updatedAt = strftime('%s', 'now')
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
namaLengkap,
|
||||
namaUser,
|
||||
email || null,
|
||||
rolesJson,
|
||||
realmRolesJson,
|
||||
accountRolesJson,
|
||||
resourceRolesJson,
|
||||
groupsJson,
|
||||
given_name,
|
||||
family_name,
|
||||
updateTipeUser,
|
||||
updateLastLogin,
|
||||
userId
|
||||
);
|
||||
updatedCount++;
|
||||
console.log(`✅ Updated user: ${namaUser}`);
|
||||
} else {
|
||||
unchangedCount++;
|
||||
}
|
||||
} else {
|
||||
// Insert new user
|
||||
db.prepare(`
|
||||
INSERT INTO users (
|
||||
id, namaLengkap, namaUser, email, roles, realmRoles, accountRoles, resourceRoles, groups,
|
||||
given_name, family_name, tipeUser, lastLogin
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
userId,
|
||||
namaLengkap,
|
||||
namaUser,
|
||||
email || null,
|
||||
rolesJson,
|
||||
realmRolesJson,
|
||||
accountRolesJson,
|
||||
resourceRolesJson,
|
||||
groupsJson,
|
||||
given_name,
|
||||
family_name,
|
||||
tipeUser,
|
||||
lastLogin
|
||||
);
|
||||
createdCount++;
|
||||
console.log(`✅ Created new user: ${namaUser}`);
|
||||
}
|
||||
} catch (userError: any) {
|
||||
console.error(`❌ Error syncing user ${kcUser.id}:`, userError.message);
|
||||
// Continue with next user
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
console.log(`✅ Sync completed: ${createdCount} created, ${updatedCount} updated, ${unchangedCount} unchanged`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'All users synced successfully',
|
||||
stats: {
|
||||
created: createdCount,
|
||||
updated: updatedCount,
|
||||
unchanged: unchangedCount,
|
||||
total: keycloakUsers.length
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("❌ Error syncing all users:", error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: error.message || "Failed to sync all users",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -30,8 +30,15 @@ export default defineEventHandler(async (event) => {
|
||||
// Use session createdAt as loginTime, or current time if not available
|
||||
const { syncUserFromTokens } = await import('~/server/utils/userSync');
|
||||
const loginTime = session.createdAt || Date.now();
|
||||
|
||||
console.log("🔄 Syncing user from session...");
|
||||
console.log(" Session createdAt:", session.createdAt);
|
||||
console.log(" Login time:", loginTime);
|
||||
|
||||
const result = syncUserFromTokens(session.idToken, session.accessToken, loginTime);
|
||||
|
||||
console.log(`✅ Sync result: ${result.action} - ${result.message}`);
|
||||
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
console.error("❌ Error syncing user:", error);
|
||||
|
||||
Reference in New Issue
Block a user