import "dotenv/config"; import { AppDataSource } from "../src/database/data-source"; import { Profile } from "../src/entities/Profile"; import { ProfileEmployee } from "../src/entities/ProfileEmployee"; import * as keycloak from "../src/keycloak/index"; const USER_ROLE_NAME = "USER"; interface AssignOptions { dryRun: boolean; targetUsernames?: string[]; } interface UserWithKeycloak { keycloakId: string; citizenId: string; source: "Profile" | "ProfileEmployee"; } interface AssignResult { total: number; assigned: number; skipped: number; failed: number; errors: Array<{ userId: string; username: string; error: string; }>; } /** * Get all users from database who have Keycloak IDs set */ async function getUsersWithKeycloak(): Promise { const users: UserWithKeycloak[] = []; const profileRepo = AppDataSource.getRepository(Profile); const profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); // Get from Profile table const profiles = await profileRepo .createQueryBuilder("profile") .where("profile.keycloak IS NOT NULL") .andWhere("profile.keycloak != ''") .getMany(); for (const profile of profiles) { users.push({ keycloakId: profile.keycloak, citizenId: profile.citizenId || profile.id, source: "Profile", }); } // Get from ProfileEmployee table const employees = await profileEmployeeRepo .createQueryBuilder("profileEmployee") .where("profileEmployee.keycloak IS NOT NULL") .andWhere("profileEmployee.keycloak != ''") .getMany(); for (const employee of employees) { // Avoid duplicates - check if keycloak ID already exists if (!users.some((u) => u.keycloakId === employee.keycloak)) { users.push({ keycloakId: employee.keycloak, citizenId: employee.citizenId || employee.id, source: "ProfileEmployee", }); } } return users; } /** * Assign USER role to users who don't have it */ async function assignUserRoleToUsers( users: UserWithKeycloak[], userRoleId: string, options: AssignOptions, ): Promise { const result: AssignResult = { total: users.length, assigned: 0, skipped: 0, failed: 0, errors: [], }; console.log(`Processing ${result.total} users...`); for (let i = 0; i < users.length; i++) { const user = users[i]; const index = i + 1; try { // Get user's current roles const userRoles = await keycloak.getUserRoles(user.keycloakId); if (!userRoles || typeof userRoles === "boolean") { console.log( `[${index}/${result.total}] Skipped: ${user.citizenId} (source: ${user.source}) - Failed to get roles`, ); result.failed++; result.errors.push({ userId: user.keycloakId, username: user.citizenId, error: "Failed to get user roles", }); continue; } // Handle both array and single object return types // getUserRoles can return an array or a single object const rolesArray = Array.isArray(userRoles) ? userRoles : [userRoles]; // Check if user already has USER role const hasUserRole = rolesArray.some((role: { id: string; name: string }) => role.name === USER_ROLE_NAME, ); if (hasUserRole) { console.log( `[${index}/${result.total}] Skipped: ${user.citizenId} (source: ${user.source}) - Already has USER role`, ); result.skipped++; continue; } // Assign USER role if (options.dryRun) { console.log( `[${index}/${result.total}] [DRY-RUN] Would assign USER role: ${user.citizenId} (source: ${user.source})`, ); result.assigned++; } else { const assignResult = await keycloak.addUserRoles(user.keycloakId, [ { id: userRoleId, name: USER_ROLE_NAME }, ]); if (assignResult) { console.log( `[${index}/${result.total}] Assigned USER role: ${user.citizenId} (source: ${user.source})`, ); result.assigned++; } else { console.log( `[${index}/${result.total}] Failed: ${user.citizenId} (source: ${user.source}) - Could not assign role`, ); result.failed++; result.errors.push({ userId: user.keycloakId, username: user.citizenId, error: "Failed to assign USER role", }); } } // Small delay to avoid rate limiting if (index % 50 === 0) { await new Promise((resolve) => setTimeout(resolve, 100)); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.log( `[${index}/${result.total}] Error: ${user.citizenId} (source: ${user.source}) - ${errorMessage}`, ); result.failed++; result.errors.push({ userId: user.keycloakId, username: user.citizenId, error: errorMessage, }); } } return result; } /** * Main function */ async function main() { const args = process.argv.slice(2); const dryRun = args.includes("--dry-run"); console.log("=".repeat(60)); console.log("Keycloak USER Role Assignment Script"); console.log("=".repeat(60)); console.log(`Mode: ${dryRun ? "DRY-RUN (no changes)" : "LIVE"}`); console.log(""); // Initialize database try { await AppDataSource.initialize(); console.log("Database connected"); } catch (error) { console.error("Failed to connect to database:", error); process.exit(1); } // Validate Keycloak connection try { await keycloak.getToken(); console.log("Keycloak connected"); } catch (error) { console.error("Failed to connect to Keycloak:", error); await AppDataSource.destroy(); process.exit(1); } // Get USER role from Keycloak console.log(""); const userRole = await keycloak.getRoles(USER_ROLE_NAME); // Check if USER role exists and is valid (has id property) if (!userRole || typeof userRole === "boolean" || userRole === null || !("id" in userRole)) { console.error(`ERROR: ${USER_ROLE_NAME} role not found in Keycloak`); await AppDataSource.destroy(); process.exit(1); } // Type assertion via unknown to bypass union type issues const role = userRole as unknown as { id: string; name: string; description?: string }; const roleId = role.id; console.log(`${USER_ROLE_NAME} role found: ${roleId}`); console.log(""); // Get users from database console.log("Fetching users from database..."); let users = await getUsersWithKeycloak(); if (users.length === 0) { console.log("No users with Keycloak IDs found in database"); await AppDataSource.destroy(); process.exit(0); } console.log(`Found ${users.length} users with Keycloak IDs`); console.log(""); // Assign USER role const result = await assignUserRoleToUsers(users, roleId, { dryRun }); // Summary console.log(""); console.log("=".repeat(60)); console.log("Summary:"); console.log(` Total users: ${result.total}`); console.log(` Assigned: ${result.assigned}`); console.log(` Skipped: ${result.skipped}`); console.log(` Failed: ${result.failed}`); console.log("=".repeat(60)); if (result.errors.length > 0) { console.log(""); console.log("Errors:"); result.errors.forEach((e) => { console.log(` ${e.username}: ${e.error}`); }); } // Cleanup await AppDataSource.destroy(); } main().catch(console.error);