setup auth middleware and sync code
This commit is contained in:
parent
e76e361981
commit
a487b73c3b
5 changed files with 684 additions and 0 deletions
198
src/controllers/KeycloakSyncController.ts
Normal file
198
src/controllers/KeycloakSyncController.ts
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import { Controller, Post, Get, Route, Security, Tags, Path, Request, Response, Query } from "tsoa";
|
||||
import { KeycloakAttributeService } from "../services/KeycloakAttributeService";
|
||||
import HttpSuccess from "../interfaces/http-success";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import { RequestWithUser } from "../middlewares/user";
|
||||
import { AppDataSource } from "../database/data-source";
|
||||
import { Profile } from "../entities/Profile";
|
||||
import { ProfileEmployee } from "../entities/ProfileEmployee";
|
||||
|
||||
@Route("api/v1/org/keycloak-sync")
|
||||
@Tags("Keycloak Sync")
|
||||
@Security("bearerAuth")
|
||||
@Response(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"เกิดข้อผิดพลาด ไม่สามารถดำเนินการได้ กรุณาลองใหม่ในภายหลัง",
|
||||
)
|
||||
export class KeycloakSyncController extends Controller {
|
||||
private keycloakAttributeService = new KeycloakAttributeService();
|
||||
private profileRepo = AppDataSource.getRepository(Profile);
|
||||
private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee);
|
||||
|
||||
/**
|
||||
* Sync attributes for the current logged-in user
|
||||
*
|
||||
* @summary Sync profileId and rootDnaId to Keycloak for current user
|
||||
*/
|
||||
@Post("sync-me")
|
||||
async syncCurrentUser(@Request() request: RequestWithUser) {
|
||||
const keycloakUserId = request.user.sub;
|
||||
|
||||
if (!keycloakUserId) {
|
||||
throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
|
||||
}
|
||||
|
||||
// Get attributes from database before sync
|
||||
const dbAttrs = await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);
|
||||
|
||||
const success = await this.keycloakAttributeService.syncUserAttributes(keycloakUserId);
|
||||
|
||||
if (!success) {
|
||||
throw new HttpError(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ กรุณาติดต่อผู้ดูแลระบบ",
|
||||
);
|
||||
}
|
||||
|
||||
// Verify sync by fetching attributes from Keycloak after update
|
||||
const kcAttrsAfter =
|
||||
await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);
|
||||
|
||||
return new HttpSuccess({
|
||||
message: "Sync ข้อมูลสำเร็จ",
|
||||
syncedToKeycloak: !!kcAttrsAfter?.profileId,
|
||||
databaseAttributes: dbAttrs,
|
||||
keycloakAttributesAfter: kcAttrsAfter,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current attributes of the logged-in user
|
||||
*
|
||||
* @summary Get current profileId and rootDnaId from Keycloak
|
||||
*/
|
||||
@Get("my-attributes")
|
||||
async getMyAttributes(@Request() request: RequestWithUser) {
|
||||
const keycloakUserId = request.user.sub;
|
||||
|
||||
if (!keycloakUserId) {
|
||||
throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
|
||||
}
|
||||
|
||||
const keycloakAttributes =
|
||||
await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);
|
||||
const dbAttributes =
|
||||
await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);
|
||||
|
||||
return new HttpSuccess({
|
||||
keycloakAttributes,
|
||||
databaseAttributes: dbAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync attributes for a specific profile (Admin only)
|
||||
*
|
||||
* @summary Sync profileId and rootDnaId to Keycloak by profile ID (ADMIN)
|
||||
*
|
||||
* @param {string} profileId Profile ID
|
||||
* @param {string} profileType Profile type (PROFILE or PROFILE_EMPLOYEE)
|
||||
*/
|
||||
@Post("sync-profile/:profileId")
|
||||
async syncByProfileId(
|
||||
@Path() profileId: string,
|
||||
@Query() profileType: "PROFILE" | "PROFILE_EMPLOYEE" = "PROFILE",
|
||||
) {
|
||||
if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
|
||||
);
|
||||
}
|
||||
|
||||
const success = await this.keycloakAttributeService.syncOnOrganizationChange(
|
||||
profileId,
|
||||
profileType,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
throw new HttpError(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"ไม่สามารถ sync ข้อมูลไปยัง Keycloak ได้ หรือไม่พบข้อมูล profile",
|
||||
);
|
||||
}
|
||||
|
||||
return new HttpSuccess({ message: "Sync ข้อมูลสำเร็จ" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch sync all users (Admin only)
|
||||
*
|
||||
* @summary Batch sync all users to Keycloak (ADMIN)
|
||||
*
|
||||
* @param {number} limit Maximum number of users to sync
|
||||
*/
|
||||
@Post("sync-all")
|
||||
async syncAll(@Query() limit: number = 100) {
|
||||
if (limit > 500) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "limit ต้องไม่เกิน 500");
|
||||
}
|
||||
|
||||
const result = await this.keycloakAttributeService.batchSyncUsers(limit);
|
||||
|
||||
return new HttpSuccess({
|
||||
message: "Batch sync เสร็จสิ้น",
|
||||
total: result.total,
|
||||
success: result.success,
|
||||
failed: result.failed,
|
||||
details: result.details,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ตรวจสอบสถานะ Keycloak Mapper
|
||||
*
|
||||
* @summary ตรวจสอบว่า profileId และ rootDnaId ออกมาใน token หรือไม่
|
||||
*/
|
||||
@Get("check-mapper")
|
||||
async checkMapperStatus(@Request() request: RequestWithUser) {
|
||||
const keycloakUserId = request.user.sub;
|
||||
|
||||
if (!keycloakUserId) {
|
||||
throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบ Keycloak user ID");
|
||||
}
|
||||
|
||||
// 1. ตรวจสอบ attributes ใน Keycloak
|
||||
const kcAttrs =
|
||||
await this.keycloakAttributeService.getCurrentKeycloakAttributes(keycloakUserId);
|
||||
|
||||
// 2. ตรวจสอบ attributes ใน Database
|
||||
const dbAttrs = await this.keycloakAttributeService.getUserProfileAttributes(keycloakUserId);
|
||||
|
||||
// 3. ตรวจสอบ token payload ปัจจุบัน
|
||||
const tokenPayload = request.user;
|
||||
|
||||
return new HttpSuccess({
|
||||
keycloakAttributes: kcAttrs,
|
||||
databaseAttributes: dbAttrs,
|
||||
tokenHasProfileId: !!tokenPayload.profileId,
|
||||
tokenHasOrgRootDnaId: !!tokenPayload.orgRootDnaId,
|
||||
tokenScopes: tokenPayload.scope?.split(" ") || [],
|
||||
diagnosis: {
|
||||
kcHasProfileId: !!kcAttrs?.profileId,
|
||||
kcHasOrgRootDnaId: !!kcAttrs?.orgRootDnaId,
|
||||
kcHasOrgChild1DnaId: !!kcAttrs?.orgChild1DnaId,
|
||||
kcHasOrgChild2DnaId: !!kcAttrs?.orgChild2DnaId,
|
||||
kcHasOrgChild3DnaId: !!kcAttrs?.orgChild3DnaId,
|
||||
kcHasOrgChild4DnaId: !!kcAttrs?.orgChild4DnaId,
|
||||
kcHasEmpType: !!kcAttrs?.empType,
|
||||
dbHasProfileId: !!dbAttrs?.profileId,
|
||||
dbHasOrgRootDnaId: !!dbAttrs?.orgRootDnaId,
|
||||
dbHasOrgChild1DnaId: !!dbAttrs?.orgChild1DnaId,
|
||||
dbHasOrgChild2DnaId: !!dbAttrs?.orgChild2DnaId,
|
||||
dbHasOrgChild3DnaId: !!dbAttrs?.orgChild3DnaId,
|
||||
dbHasOrgChild4DnaId: !!dbAttrs?.orgChild4DnaId,
|
||||
dbHasEmpType: !!dbAttrs?.empType,
|
||||
issue:
|
||||
!tokenPayload.profileId && kcAttrs?.profileId
|
||||
? "Attribute มีใน Keycloak แต่ไม่ออกมาใน token - แก้ไข Mapper หรือ Client Scope"
|
||||
: !kcAttrs?.profileId && dbAttrs?.profileId
|
||||
? "Attribute มีใน Database แต่ไม่มีใน Keycloak - ต้อง sync ซ้ำ"
|
||||
: !dbAttrs?.profileId
|
||||
? "ไม่พบ profile ใน database - ตรวจสอบ keycloak field"
|
||||
: "ทุกอย่างปกติ",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue