205 lines
6.6 KiB
TypeScript
205 lines
6.6 KiB
TypeScript
|
|
/**
|
||
|
|
* Helper functions for Keycloak user operations
|
||
|
|
*
|
||
|
|
* สร้างเพื่อแยก Keycloak operations ออกจาก DB transaction
|
||
|
|
* และใช้ profile data ที่ save แล้วแทน input data
|
||
|
|
*/
|
||
|
|
|
||
|
|
import {
|
||
|
|
createUser,
|
||
|
|
addUserRoles,
|
||
|
|
removeUserRoles,
|
||
|
|
updateUserAttributes,
|
||
|
|
getUserByUsername,
|
||
|
|
getRoleMappings,
|
||
|
|
} from "./index";
|
||
|
|
import { Profile } from "../entities/Profile";
|
||
|
|
import HttpError from "../interfaces/http-error";
|
||
|
|
import HttpStatusCode from "../interfaces/http-status";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* สร้าง password จาก profile.birthDate เหมือน reset-password endpoint
|
||
|
|
*
|
||
|
|
* @param profile - Profile object ที่มี birthDate
|
||
|
|
* @returns password string ในรูปแบบ ddmmyyyy (เช่น "31122563")
|
||
|
|
*
|
||
|
|
* Reference: UserController.ts reset-password (บรรทัด 867-876)
|
||
|
|
*/
|
||
|
|
export function generatePasswordFromProfile(profile: Profile): string {
|
||
|
|
if (!profile.birthDate) {
|
||
|
|
throw new Error("Profile birthDate is required for password generation");
|
||
|
|
}
|
||
|
|
|
||
|
|
const _date = new Date(profile.birthDate.toDateString())
|
||
|
|
.getDate()
|
||
|
|
.toString()
|
||
|
|
.padStart(2, "0");
|
||
|
|
const _month = (
|
||
|
|
new Date(profile.birthDate.toDateString()).getMonth() + 1
|
||
|
|
)
|
||
|
|
.toString()
|
||
|
|
.padStart(2, "0");
|
||
|
|
const _year = new Date(profile.birthDate.toDateString()).getFullYear() + 543;
|
||
|
|
|
||
|
|
return `${_date}${_month}${_year}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sync Keycloak user สำหรับ profile ที่ save แล้ว
|
||
|
|
*
|
||
|
|
* รวมทุก Keycloak operations:
|
||
|
|
* - ตรวจสอบว่ามี user อยู่แล้วหรือไม่
|
||
|
|
* - สร้าง user ใหม่ (ถ้ายังไม่มี) ด้วย password จาก profile.birthDate
|
||
|
|
* - กำหนด role USER
|
||
|
|
* - อัปเดต attributes (profileId, prefix)
|
||
|
|
*
|
||
|
|
* @param profile - Profile object ที่ถูก save แล้ว (ต้องมี id และ birthDate)
|
||
|
|
* @param roleList - List of roles จาก Keycloak (ผลลัพธ์จาก getRoles())
|
||
|
|
* @returns userKeycloakId string หรือ throws HttpError
|
||
|
|
*
|
||
|
|
* @throws HttpError ถ้า createUser หรือ role operations ล้มเหลว
|
||
|
|
*/
|
||
|
|
export async function syncKeycloakForProfile(
|
||
|
|
profile: Profile,
|
||
|
|
roleList: any[],
|
||
|
|
): Promise<string> {
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Starting for citizenId: ${profile.citizenId}, profileId: ${profile.id}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!profile.id) {
|
||
|
|
throw new Error("Profile ID is required for Keycloak sync");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ตรวจสอบว่ามี user อยู่แล้วหรือไม่
|
||
|
|
const checkUser = await getUserByUsername(profile.citizenId);
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Keycloak user exists: ${checkUser.length > 0}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
let userKeycloakId: string;
|
||
|
|
|
||
|
|
if (checkUser.length === 0) {
|
||
|
|
// สร้าง user ใหม่
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Creating new Keycloak user for citizenId: ${profile.citizenId}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
// สร้าง password จาก profile.birthDate (NOT input birthDate)
|
||
|
|
const password = generatePasswordFromProfile(profile);
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Generated password from profile.birthDate: ${profile.birthDate}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
// กรอง "." ออกจาก firstName ก่อนส่งไป keycloak
|
||
|
|
const sanitizedFirstName = profile.firstName?.replace(/\./g, "") ?? "";
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Creating user - firstName: ${sanitizedFirstName}, lastName: ${profile.lastName}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
const createUserResult: any = await createUser(profile.citizenId, password, {
|
||
|
|
firstName: sanitizedFirstName,
|
||
|
|
lastName: profile.lastName ?? "",
|
||
|
|
});
|
||
|
|
|
||
|
|
// ตรวจสอบ createUser error
|
||
|
|
if (
|
||
|
|
createUserResult &&
|
||
|
|
typeof createUserResult === "object" &&
|
||
|
|
createUserResult.errorMessage
|
||
|
|
) {
|
||
|
|
console.error(
|
||
|
|
`[syncKeycloakForProfile] createUser FAILED - field: ${createUserResult.field}, errorMessage: ${createUserResult.errorMessage}`,
|
||
|
|
);
|
||
|
|
throw new HttpError(
|
||
|
|
HttpStatusCode.BAD_REQUEST,
|
||
|
|
`Keycloak validation failed: ${createUserResult.field} - ${createUserResult.errorMessage}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
userKeycloakId = createUserResult;
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] User created successfully, userKeycloakId: ${userKeycloakId}`,
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
// ใช้ user ที่มีอยู่แล้ว
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Using existing Keycloak user, userKeycloakId: ${checkUser[0].id}`,
|
||
|
|
);
|
||
|
|
userKeycloakId = checkUser[0].id;
|
||
|
|
|
||
|
|
// ลบ roles เดิมแล้วกำหนด role USER ใหม่
|
||
|
|
const rolesData = await getRoleMappings(userKeycloakId);
|
||
|
|
if (rolesData) {
|
||
|
|
const _delRole = rolesData.map((x: any) => ({
|
||
|
|
id: x.id,
|
||
|
|
name: x.name,
|
||
|
|
}));
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Removing old roles: ${_delRole.length}`,
|
||
|
|
);
|
||
|
|
await removeUserRoles(userKeycloakId, _delRole);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// กำหนด role USER
|
||
|
|
console.log(`[syncKeycloakForProfile] Assigning USER role`);
|
||
|
|
const result = await addUserRoles(
|
||
|
|
userKeycloakId,
|
||
|
|
roleList
|
||
|
|
.filter((v) => v.name === "USER")
|
||
|
|
.map((x) => ({
|
||
|
|
id: x.id,
|
||
|
|
name: x.name,
|
||
|
|
})),
|
||
|
|
);
|
||
|
|
|
||
|
|
console.log(`[syncKeycloakForProfile] USER role assigned, result: ${result}`);
|
||
|
|
|
||
|
|
// อัปเดต attributes
|
||
|
|
console.log(`[syncKeycloakForProfile] Updating user attributes`);
|
||
|
|
await updateUserAttributes(userKeycloakId, {
|
||
|
|
profileId: [profile.id],
|
||
|
|
prefix: [profile.prefix || ""],
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
`[syncKeycloakForProfile] Completed successfully for citizenId: ${profile.citizenId}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
return userKeycloakId;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* จัดการ error จาก Keycloak operations
|
||
|
|
*
|
||
|
|
* @param error - Error object จาก Keycloak operations
|
||
|
|
* @param context - Context object { citizenId, profileId, operation }
|
||
|
|
*
|
||
|
|
* @returns HttpError ที่ formatted สำหรับ throw กลับไป
|
||
|
|
*/
|
||
|
|
export function handleKeycloakError(
|
||
|
|
error: any,
|
||
|
|
context: {
|
||
|
|
citizenId?: string;
|
||
|
|
profileId?: string;
|
||
|
|
operation: string;
|
||
|
|
},
|
||
|
|
): HttpError {
|
||
|
|
console.error(
|
||
|
|
`[KeycloakError] ${context.operation} failed for citizenId: ${context.citizenId}, profileId: ${context.profileId}`,
|
||
|
|
error,
|
||
|
|
);
|
||
|
|
|
||
|
|
const errorMessage =
|
||
|
|
error instanceof Error ? error.message : "Unknown Keycloak error";
|
||
|
|
|
||
|
|
return new HttpError(
|
||
|
|
HttpStatusCode.INTERNAL_SERVER_ERROR,
|
||
|
|
`Keycloak ${context.operation} failed: ${errorMessage}. Profile created but Keycloak sync failed - manual fix required.`,
|
||
|
|
);
|
||
|
|
}
|