diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index fce9bb98..8be7aa0f 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -40,7 +40,7 @@ import { calculateRetireLaw } from "../interfaces/utils"; import { RequestWithUser } from "../middlewares/user"; @Route("api/v1/org/dotnet") @Tags("Dotnet") -@Security("bearerAuth") +// @Security("bearerAuth") @Response( HttpStatus.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", @@ -73,6 +73,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("check-citizen") + @Security("internalAuth") public async CheckCitizen( @Body() body: { @@ -90,6 +91,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("search") + @Security("internalAuth") public async SearchProfile( @Body() body: { @@ -304,6 +306,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("search-employee") + @Security("internalAuth") public async SearchProfileEmployee( @Body() body: { @@ -488,6 +491,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} id Id หน่วยงาน */ @Get("org/{id}") + @Security("internalAuth") async GetOrganizationById(@Path() id: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: id }, @@ -497,6 +501,7 @@ export class OrganizationDotnetController extends Controller { } @Get("agency/{id}") + @Security("internalAuth") async GetOrgAgencyById(@Path() id: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: id }, @@ -506,6 +511,7 @@ export class OrganizationDotnetController extends Controller { } @Get("go-agency/{id}") + @Security("internalAuth") async GetOrgGoAgencyById(@Path() id: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: id }, @@ -521,6 +527,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("get-profileId") + @Security("bearerAuth") async getProfileInbox(@Request() request: { user: Record }) { let profile: any; //OFF @@ -556,6 +563,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("keycloak-old/{keycloakId}") + @Security("internalAuth") async GetProfileByKeycloakIdAsyncOld(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ relations: [ @@ -1355,6 +1363,7 @@ export class OrganizationDotnetController extends Controller { } @Get("keycloak/{keycloakId}") + @Security("internalAuth") async GetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -1783,6 +1792,7 @@ export class OrganizationDotnetController extends Controller { } @Get("by-keycloak/{keycloakId}") + @Security("internalAuth") async NewGetProfileByKeycloakIdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2017,6 +2027,7 @@ export class OrganizationDotnetController extends Controller { // เพิ่มที่อยู่ปัจจุบัน + ตำแหน่งหัวหน้า @Get("by-keycloak2/{keycloakId}") + @Security("internalAuth") async NewGetProfileByKeycloak2IdAsync(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2358,8 +2369,11 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId keycloakId profile */ @Get("check-keycloak/{keycloakId}") + @Security("internalAuth") async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) { try { + console.log(`[check-keycloak] START - keycloakId=${keycloakId}`); + /* ========================= * 1. Load profile (Officer) * ========================= */ @@ -2379,6 +2393,8 @@ export class OrganizationDotnetController extends Controller { // Employee if (!profile) { + console.log(`[check-keycloak] OFFICER_NOT_FOUND - keycloakId=${keycloakId}, checking EMPLOYEE`); + const empProfile = await this.profileEmpRepo.findOne({ where: { keycloak: keycloakId }, relations: { @@ -2392,7 +2408,12 @@ export class OrganizationDotnetController extends Controller { }, }, }); - if (!empProfile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + if (!empProfile) { + console.log(`[check-keycloak] EMPLOYEE_NOT_FOUND - keycloakId=${keycloakId}`); + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + } + const currentHolder = empProfile.current_holders?.find( (x) => x.orgRevision?.orgRevisionIsDraft === false && @@ -2426,9 +2447,15 @@ export class OrganizationDotnetController extends Controller { child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, }; + console.log( + `[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`, + ); + return new HttpSuccess(mapProfile); } + console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`); + /* ========================================= * 2. current holder (Officer) * ========================================= */ @@ -2467,6 +2494,10 @@ export class OrganizationDotnetController extends Controller { child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, }; + console.log( + `[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`, + ); + return new HttpSuccess(mapProfile); } catch (error: any) { // Log เฉพาะ unexpected errors (ไม่ใช่ HttpError) @@ -2485,6 +2516,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId keycloakId profile */ @Get("user-logs/{keycloakId}") + @Security("internalAuth") async UserLogs(@Path() keycloakId: string) { /* ========================= * 1. Load profile @@ -2564,6 +2596,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} profileId Id profile */ @Get("profile/{profileId}") + @Security("internalAuth") async GetProfileByProfileIdAsync(@Path() profileId: string) { const profile = await this.profileRepo.findOne({ relations: [ @@ -3233,6 +3266,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} citizenId citizen Id */ @Get("citizenId/{citizenId}") + @Security("internalAuth") async GetProfileByCitizenIdAsync(@Path() citizenId: string) { const profile = await this.profileRepo.findOne({ relations: [ @@ -3887,6 +3921,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("root/officer/{rootId}") + @Security("internalAuth") async GetProfileByRootIdAsync(@Path() rootId: string) { const profiles = await this.profileRepo.find({ relations: [ @@ -4199,6 +4234,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("root/employee/{rootId}") + @Security("internalAuth") async GetProfileByRootIdEmpAsync(@Path() rootId: string) { const profiles = await this.profileEmpRepo.find({ relations: [ @@ -4403,6 +4439,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Post("find/employee/position") + @Security("internalAuth") async GetProfileByPositionEmpAsync( @Body() body: { @@ -4732,6 +4769,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("user-fullname/{keycloakId}") + @Security("internalAuth") async GetUserFullName(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, @@ -4750,6 +4788,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("user-oc/{keycloakId}") + @Security("internalAuth") async getProfileByKeycloak(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, @@ -4801,6 +4840,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("user-oc-all/{keycloakId}") + @Security("internalAuth") async getAllProfileByKeycloak(@Path() keycloakId: string) { const profile = await this.profileRepo.findOne({ where: { keycloak: keycloakId }, @@ -4968,6 +5008,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} ocId Id หน่วยงาน */ @Get("root-oc/{ocId}") + @Security("internalAuth") async GetRootOcId(@Path() ocId: string) { const orgRoot = await this.orgRootRepo.findOne({ where: { id: ocId }, @@ -4984,6 +5025,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("keycloak") + @Security("internalAuth") async GetProfileWithKeycloak() { const profile = await this.profileRepo.find({ where: { keycloak: Not(IsNull()) }, @@ -5193,6 +5235,7 @@ export class OrganizationDotnetController extends Controller { * */ @Get("keycloak-employee") + @Security("internalAuth") async GetProfileWithKeycloakEmployee() { const profile = await this.profileEmpRepo.find({ where: { keycloak: Not(IsNull()) || Not("") }, @@ -5321,6 +5364,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-officer") + @Security("internalAuth") async PostProfileWithKeycloakAllOfficer( @Body() body: { @@ -5491,6 +5535,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-officer/date") + @Security("internalAuth") async PostProfileWithKeycloakAllOfficerDate( @Body() body: { @@ -5669,6 +5714,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("none-validate-keycloak-all-officer") + @Security("internalAuth") async PostProfileWithNoneValidateKeycloakAllOfficer( @Body() body: { @@ -5838,6 +5884,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("find-node-name") + @Security("internalAuth") async findNodeName( @Body() body: { @@ -5946,6 +5993,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-role") + @Security("internalAuth") async GetOfficersByAdminRole( @Body() body: { @@ -6283,6 +6331,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("keycloak-all-employee") + @Security("internalAuth") async PostProfileWithKeycloakAllEmployee( @Body() body: { @@ -6436,6 +6485,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("none-validate-keycloak-all-employee") + @Security("internalAuth") async PostProfileWithNoneValidateKeycloakAllEmployee( @Body() body: { @@ -6589,6 +6639,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("employee-by-admin-role") + @Security("internalAuth") async GetEmployeesByAdminRole( @Body() body: { @@ -6929,8 +6980,9 @@ export class OrganizationDotnetController extends Controller { * @summary รายชื่อลูกจ้างประจำ ตามสิทธิ์ admin */ @Post("employee-by-admin-rolev2") + @Security("internalAuth") async GetEmployeesByAdminRoleV2( - @Request() req: RequestWithUser, + // @Request() req: RequestWithUser, // ไม่ได้ใช้ @Body() body: { node: number; @@ -7199,6 +7251,7 @@ export class OrganizationDotnetController extends Controller { * */ @Put("update-dutytime") + @Security("bearerAuth") async UpdateDutyTimeAsync( @Request() req: RequestWithUser, @Body() @@ -7241,6 +7294,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("insignia/Dumb") + @Security("bearerAuth") public async newInsignia(@Request() req: RequestWithUser, @Body() body: CreateProfileInsignia) { if (!body.profileId) { throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId"); @@ -7317,6 +7371,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} keycloakId Id keycloak */ @Get("profile-leave/keycloak/{keycloakId}") + @Security("internalAuth") async GetProfileLeaveByKeycloakIdAsync(@Path() keycloakId: string) { const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); let _currentDate = CURRENT_DATE[0].today; @@ -7604,6 +7659,7 @@ export class OrganizationDotnetController extends Controller { } @Post("profile-leave/keycloak") + @Security("internalAuth") async GetProfileLeaveReportByKeycloakIdAsync( @Body() body: { keycloakId: string; report?: string }, ) { @@ -7908,6 +7964,7 @@ export class OrganizationDotnetController extends Controller { * @param {string} type ประเภท (ข้าราชการ หรือ ลูกจ้าง) */ @Post("find/insignia-requests-profile/{type}") + @Security("internalAuth") async GetInsigniaRequestsProfileAsync( @Path() type: string, @Body() @@ -8037,6 +8094,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev2") + @Security("internalAuth") async GetOfficersByAdminRoleV2( @Body() body: { @@ -8260,6 +8318,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev3") + @Security("internalAuth") async GetOfficersByAdminRoleV3( @Body() body: { @@ -8606,6 +8665,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("officer-by-admin-rolev4") + @Security("internalAuth") async GetOfficersByAdminRoleV4( @Body() body: { @@ -8882,6 +8942,7 @@ export class OrganizationDotnetController extends Controller { * */ @Post("find-staff") + @Security("internalAuth") async findHigher( @Body() requestBody: { diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index 9a571572..fc006b33 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -4,6 +4,7 @@ import { createDecoder, createVerifier } from "fast-jwt"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { handleWebServiceAuth } from "./authWebService"; +import { handleInternalAuth } from "./authInternal"; if (!process.env.AUTH_PUBLIC_KEY && !process.env.AUTH_REALM_URL) { throw new Error("Require keycloak AUTH_PUBLIC_KEY or AUTH_REALM_URL."); @@ -39,6 +40,11 @@ export async function expressAuthentication( return { preferred_username: "bypassed" }; } + // เพิ่มการจัดการสำหรับ Internal Authentication (.NET service) + if (securityName === "internalAuth") { + return await handleInternalAuth(request); + } + // เพิ่มการจัดการสำหรับ Web Service Authentication if (securityName === "webServiceAuth") { return await handleWebServiceAuth(request); diff --git a/src/middlewares/authInternal.ts b/src/middlewares/authInternal.ts new file mode 100644 index 00000000..8d29ceb6 --- /dev/null +++ b/src/middlewares/authInternal.ts @@ -0,0 +1,30 @@ +import * as express from "express"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; + +// Internal Authentication (สำหรับ Internal Service เช่น .NET) +// ตรวจสอบ API Key จาก Environment Variable (API_KEY) +export async function handleInternalAuth(request: express.Request) { + // รองรับ header หลายรูปแบบ + const apiKey = + request.headers["api-key"] || request.headers["apikey"]; + + if (!apiKey || typeof apiKey !== "string") { + throw new HttpError(HttpStatus.UNAUTHORIZED, "API Key is required"); + } + + // ตรวจสอบ API Key จาก Environment Variable (API_KEY) + if (apiKey !== process.env.API_KEY) { + console.log(`[InternalAuth] Invalid API key attempt: ${apiKey.substring(0, 5)}...`); + throw new HttpError(HttpStatus.UNAUTHORIZED, "Invalid API Key"); + } + + console.log(`[InternalAuth] Authentication successful`); + + return { + sub: "internal_service", + preferred_username: "internal_service", + name: "Internal Service", + internalKey: true, + }; +} diff --git a/tsoa.json b/tsoa.json index 492907b8..e346e3b1 100644 --- a/tsoa.json +++ b/tsoa.json @@ -29,6 +29,12 @@ "name": "X-API-Key", "description": "API KEY สำหรับ Web Service", "in": "header" + }, + "internalAuth": { + "type": "apiKey", + "name": "api-key", + "description": "API KEY สำหรับ Internal Service (.NET, HRMS)", + "in": "header" } }, "tags": [