Revert "fix: sync and script keycloak"
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m11s

This reverts commit d667ad9173.
This commit is contained in:
Warunee Tamkoo 2026-02-26 23:16:43 +07:00
parent d667ad9173
commit c5c19b6d5e
12 changed files with 33 additions and 2444 deletions

View file

@ -4,8 +4,8 @@ const KC_URL = process.env.KC_URL;
const KC_REALMS = process.env.KC_REALMS;
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
const KC_SECRET = process.env.KC_SERVICE_ACCOUNT_SECRET;
// const AUTH_ACCOUNT_SECRET = process.env.AUTH_ACCOUNT_SECRET;
// const API_KEY = process.env.API_KEY;
const AUTH_ACCOUNT_SECRET = process.env.AUTH_ACCOUNT_SECRET;
const API_KEY = process.env.API_KEY;
let token: string | null = null;
let decoded: DecodedJwt | null = null;
@ -165,119 +165,16 @@ export async function getUserList(first = "", max = "", search = "") {
if (!res) return false;
if (!res.ok) {
const errorText = await res.text();
return Boolean(console.error("Keycloak Error Response: ", errorText));
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
}
// Get raw text first to handle potential JSON parsing errors
const rawText = await res.text();
// Log response size for debugging
console.log(`[getUserList] Response size: ${rawText.length} bytes`);
try {
const data = JSON.parse(rawText) as any[];
return data.map((v: Record<string, string>) => ({
id: v.id,
username: v.username,
firstName: v.firstName,
lastName: v.lastName,
email: v.email,
enabled: v.enabled,
}));
} catch (error) {
console.error(`[getUserList] Failed to parse JSON response:`);
console.error(`[getUserList] Response preview (first 500 chars):`, rawText.substring(0, 500));
console.error(`[getUserList] Response preview (last 200 chars):`, rawText.slice(-200));
throw new Error(
`Failed to parse Keycloak response as JSON. Response may be truncated or malformed.`,
);
}
}
/**
* Get all keycloak users with pagination to avoid response size limits
*
* Client must have permission to manage realm's user
*
* @returns user list if success, false otherwise.
*/
export async function getAllUsersPaginated(
search: string = "",
batchSize: number = 100,
): Promise<
| Array<{
id: string;
username: string;
firstName?: string;
lastName?: string;
email?: string;
enabled: boolean;
}>
| false
> {
const allUsers: any[] = [];
let first = 0;
let hasMore = true;
while (hasMore) {
const res = await fetch(
`${KC_URL}/admin/realms/${KC_REALMS}/users?first=${first}&max=${batchSize}${search ? `&search=${search}` : ""}`,
{
headers: {
authorization: `Bearer ${await getToken()}`,
"content-type": `application/json`,
},
},
).catch((e) => console.log("Keycloak Error: ", e));
if (!res) return false;
if (!res.ok) {
const errorText = await res.text();
console.error("Keycloak Error Response: ", errorText);
return false;
}
const rawText = await res.text();
try {
const batch = JSON.parse(rawText) as any[];
if (batch.length === 0) {
hasMore = false;
} else {
allUsers.push(...batch);
first += batch.length;
hasMore = batch.length === batchSize;
// Log progress for large datasets
if (allUsers.length % 500 === 0) {
console.log(`[getAllUsersPaginated] Fetched ${allUsers.length} users so far...`);
}
}
} catch (error) {
console.error(`[getAllUsersPaginated] Failed to parse JSON response at offset ${first}:`);
console.error(
`[getAllUsersPaginated] Response preview (first 500 chars):`,
rawText.substring(0, 500),
);
console.error(
`[getAllUsersPaginated] Response preview (last 200 chars):`,
rawText.slice(-200),
);
throw new Error(`Failed to parse Keycloak response as JSON at batch starting at ${first}.`);
}
}
console.log(`[getAllUsersPaginated] Total users fetched: ${allUsers.length}`);
return allUsers.map((v: any) => ({
return ((await res.json()) as any[]).map((v: Record<string, string>) => ({
id: v.id,
username: v.username,
firstName: v.firstName,
lastName: v.lastName,
email: v.email,
enabled: v.enabled === true || v.enabled === "true",
enabled: v.enabled,
}));
}
@ -323,34 +220,17 @@ export async function getUserListOrg(first = "", max = "", search = "", userIds:
if (!res) return false;
if (!res.ok) {
const errorText = await res.text();
return Boolean(console.error("Keycloak Error Response: ", errorText));
return Boolean(console.error("Keycloak Error Response: ", await res.json()));
}
// Get raw text first to handle potential JSON parsing errors
const rawText = await res.text();
try {
const data = JSON.parse(rawText) as any[];
return data.map((v: Record<string, string>) => ({
id: v.id,
username: v.username,
firstName: v.firstName,
lastName: v.lastName,
email: v.email,
enabled: v.enabled,
}));
} catch (error) {
console.error(`[getUserListOrg] Failed to parse JSON response:`);
console.error(
`[getUserListOrg] Response preview (first 500 chars):`,
rawText.substring(0, 500),
);
console.error(`[getUserListOrg] Response preview (last 200 chars):`, rawText.slice(-200));
throw new Error(
`Failed to parse Keycloak response as JSON. Response may be truncated or malformed.`,
);
}
return ((await res.json()) as any[]).map((v: Record<string, string>) => ({
id: v.id,
username: v.username,
firstName: v.firstName,
lastName: v.lastName,
email: v.email,
enabled: v.enabled,
}));
}
export async function getUserCountOrg(first = "", max = "", search = "", userIds: string[] = []) {
@ -564,12 +444,10 @@ export async function getRoles(name?: string, token?: string) {
}));
}
// Return single role object
return {
id: data.id,
name: data.name,
description: data.description,
};
// return {
// id: data.id,
// name: data.name,
// };
}
/**
@ -894,73 +772,6 @@ export async function changeUserPassword(userId: string, newPassword: string) {
}
}
/**
* Update user attributes in Keycloak
*
* @param userId - Keycloak user ID
* @param attributes - Object containing attribute names and their values (as arrays)
* @returns true if success, false otherwise
*/
export async function updateUserAttributes(
userId: string,
attributes: Record<string, string[]>,
): Promise<boolean> {
try {
// Get existing user data to preserve other attributes
const existingUser = await getUser(userId);
if (!existingUser) {
console.error(`User ${userId} not found in Keycloak`);
return false;
}
// Merge existing attributes with new attributes
// IMPORTANT: Spread all existing user fields to preserve firstName, lastName, email, etc.
// The Keycloak PUT endpoint performs a full update, so we must include all fields
const updatedAttributes = {
...existingUser,
attributes: {
...(existingUser.attributes || {}),
...attributes,
},
};
console.log(
`[updateUserAttributes] Sending to Keycloak:`,
JSON.stringify(updatedAttributes, null, 2),
);
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
headers: {
authorization: `Bearer ${await getToken()}`,
"content-type": "application/json",
},
method: "PUT",
body: JSON.stringify(updatedAttributes),
}).catch((e) => {
console.error(`[updateUserAttributes] Network error:`, e);
return null;
});
if (!res) {
console.error(`[updateUserAttributes] No response from Keycloak`);
return false;
}
if (!res.ok) {
const errorText = await res.text();
console.error(`[updateUserAttributes] Keycloak Error (${res.status}):`, errorText);
return false;
}
console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`);
return true;
} catch (error) {
console.error(`[updateUserAttributes] Error updating attributes for user ${userId}:`, error);
return false;
}
}
// Function to reset password
export async function resetPassword(username: string) {
try {