All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m59s
269 lines
7.4 KiB
TypeScript
269 lines
7.4 KiB
TypeScript
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<UserWithKeycloak[]> {
|
|
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<AssignResult> {
|
|
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);
|