แก้ปัญหา password inconsistency ระหว่างการสร้าง user ใหม่กับ reset-password endpoint โดยใช้ birthDate จาก profile ที่ save แล้วแทน input data
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m0s

This commit is contained in:
harid 2026-07-14 11:41:04 +07:00
parent af88dffbbe
commit cd057bf81d
2 changed files with 246 additions and 136 deletions

View file

@ -0,0 +1,204 @@
/**
* 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.`,
);
}