@Route("api/v1/org/dotnet") ปรับใช้ API Key

This commit is contained in:
harid 2026-05-25 17:34:24 +07:00
parent 32282b016b
commit eede5f51c4
4 changed files with 106 additions and 3 deletions

View file

@ -40,7 +40,7 @@ import { calculateRetireLaw } from "../interfaces/utils";
import { RequestWithUser } from "../middlewares/user"; import { RequestWithUser } from "../middlewares/user";
@Route("api/v1/org/dotnet") @Route("api/v1/org/dotnet")
@Tags("Dotnet") @Tags("Dotnet")
@Security("bearerAuth") // @Security("bearerAuth")
@Response( @Response(
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
@ -73,6 +73,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("check-citizen") @Post("check-citizen")
@Security("internalAuth")
public async CheckCitizen( public async CheckCitizen(
@Body() @Body()
body: { body: {
@ -90,6 +91,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("search") @Post("search")
@Security("internalAuth")
public async SearchProfile( public async SearchProfile(
@Body() @Body()
body: { body: {
@ -304,6 +306,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("search-employee") @Post("search-employee")
@Security("internalAuth")
public async SearchProfileEmployee( public async SearchProfileEmployee(
@Body() @Body()
body: { body: {
@ -488,6 +491,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} id Id * @param {string} id Id
*/ */
@Get("org/{id}") @Get("org/{id}")
@Security("internalAuth")
async GetOrganizationById(@Path() id: string) { async GetOrganizationById(@Path() id: string) {
const orgRoot = await this.orgRootRepo.findOne({ const orgRoot = await this.orgRootRepo.findOne({
where: { id: id }, where: { id: id },
@ -497,6 +501,7 @@ export class OrganizationDotnetController extends Controller {
} }
@Get("agency/{id}") @Get("agency/{id}")
@Security("internalAuth")
async GetOrgAgencyById(@Path() id: string) { async GetOrgAgencyById(@Path() id: string) {
const orgRoot = await this.orgRootRepo.findOne({ const orgRoot = await this.orgRootRepo.findOne({
where: { id: id }, where: { id: id },
@ -506,6 +511,7 @@ export class OrganizationDotnetController extends Controller {
} }
@Get("go-agency/{id}") @Get("go-agency/{id}")
@Security("internalAuth")
async GetOrgGoAgencyById(@Path() id: string) { async GetOrgGoAgencyById(@Path() id: string) {
const orgRoot = await this.orgRootRepo.findOne({ const orgRoot = await this.orgRootRepo.findOne({
where: { id: id }, where: { id: id },
@ -521,6 +527,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Get("get-profileId") @Get("get-profileId")
@Security("bearerAuth")
async getProfileInbox(@Request() request: { user: Record<string, any> }) { async getProfileInbox(@Request() request: { user: Record<string, any> }) {
let profile: any; let profile: any;
//OFF //OFF
@ -556,6 +563,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("keycloak-old/{keycloakId}") @Get("keycloak-old/{keycloakId}")
@Security("internalAuth")
async GetProfileByKeycloakIdAsyncOld(@Path() keycloakId: string) { async GetProfileByKeycloakIdAsyncOld(@Path() keycloakId: string) {
const profile = await this.profileRepo.findOne({ const profile = await this.profileRepo.findOne({
relations: [ relations: [
@ -1355,6 +1363,7 @@ export class OrganizationDotnetController extends Controller {
} }
@Get("keycloak/{keycloakId}") @Get("keycloak/{keycloakId}")
@Security("internalAuth")
async GetProfileByKeycloakIdAsync(@Path() keycloakId: string) { async GetProfileByKeycloakIdAsync(@Path() keycloakId: string) {
/* ========================= /* =========================
* 1. Load profile * 1. Load profile
@ -1783,6 +1792,7 @@ export class OrganizationDotnetController extends Controller {
} }
@Get("by-keycloak/{keycloakId}") @Get("by-keycloak/{keycloakId}")
@Security("internalAuth")
async NewGetProfileByKeycloakIdAsync(@Path() keycloakId: string) { async NewGetProfileByKeycloakIdAsync(@Path() keycloakId: string) {
/* ========================= /* =========================
* 1. Load profile * 1. Load profile
@ -2017,6 +2027,7 @@ export class OrganizationDotnetController extends Controller {
// เพิ่มที่อยู่ปัจจุบัน + ตำแหน่งหัวหน้า // เพิ่มที่อยู่ปัจจุบัน + ตำแหน่งหัวหน้า
@Get("by-keycloak2/{keycloakId}") @Get("by-keycloak2/{keycloakId}")
@Security("internalAuth")
async NewGetProfileByKeycloak2IdAsync(@Path() keycloakId: string) { async NewGetProfileByKeycloak2IdAsync(@Path() keycloakId: string) {
/* ========================= /* =========================
* 1. Load profile * 1. Load profile
@ -2358,8 +2369,11 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId keycloakId profile * @param {string} keycloakId keycloakId profile
*/ */
@Get("check-keycloak/{keycloakId}") @Get("check-keycloak/{keycloakId}")
@Security("internalAuth")
async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) { async GetProfileForProcessCheckInAsync(@Path() keycloakId: string) {
try { try {
console.log(`[check-keycloak] START - keycloakId=${keycloakId}`);
/* ========================= /* =========================
* 1. Load profile (Officer) * 1. Load profile (Officer)
* ========================= */ * ========================= */
@ -2379,6 +2393,8 @@ export class OrganizationDotnetController extends Controller {
// Employee // Employee
if (!profile) { if (!profile) {
console.log(`[check-keycloak] OFFICER_NOT_FOUND - keycloakId=${keycloakId}, checking EMPLOYEE`);
const empProfile = await this.profileEmpRepo.findOne({ const empProfile = await this.profileEmpRepo.findOne({
where: { keycloak: keycloakId }, where: { keycloak: keycloakId },
relations: { 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( const currentHolder = empProfile.current_holders?.find(
(x) => (x) =>
x.orgRevision?.orgRevisionIsDraft === false && x.orgRevision?.orgRevisionIsDraft === false &&
@ -2426,9 +2447,15 @@ export class OrganizationDotnetController extends Controller {
child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null,
}; };
console.log(
`[check-keycloak] SUCCESS_EMPLOYEE - keycloakId=${keycloakId}, profileType=EMPLOYEE`,
);
return new HttpSuccess(mapProfile); return new HttpSuccess(mapProfile);
} }
console.log(`[check-keycloak] OFFICER_FOUND - keycloakId=${keycloakId}`);
/* ========================================= /* =========================================
* 2. current holder (Officer) * 2. current holder (Officer)
* ========================================= */ * ========================================= */
@ -2467,6 +2494,10 @@ export class OrganizationDotnetController extends Controller {
child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null, child4DnaId: currentHolder?.orgChild4?.ancestorDNA ?? null,
}; };
console.log(
`[check-keycloak] SUCCESS_OFFICER - keycloakId=${keycloakId}, profileType=OFFICER`,
);
return new HttpSuccess(mapProfile); return new HttpSuccess(mapProfile);
} catch (error: any) { } catch (error: any) {
// Log เฉพาะ unexpected errors (ไม่ใช่ HttpError) // Log เฉพาะ unexpected errors (ไม่ใช่ HttpError)
@ -2485,6 +2516,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId keycloakId profile * @param {string} keycloakId keycloakId profile
*/ */
@Get("user-logs/{keycloakId}") @Get("user-logs/{keycloakId}")
@Security("internalAuth")
async UserLogs(@Path() keycloakId: string) { async UserLogs(@Path() keycloakId: string) {
/* ========================= /* =========================
* 1. Load profile * 1. Load profile
@ -2564,6 +2596,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} profileId Id profile * @param {string} profileId Id profile
*/ */
@Get("profile/{profileId}") @Get("profile/{profileId}")
@Security("internalAuth")
async GetProfileByProfileIdAsync(@Path() profileId: string) { async GetProfileByProfileIdAsync(@Path() profileId: string) {
const profile = await this.profileRepo.findOne({ const profile = await this.profileRepo.findOne({
relations: [ relations: [
@ -3233,6 +3266,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} citizenId citizen Id * @param {string} citizenId citizen Id
*/ */
@Get("citizenId/{citizenId}") @Get("citizenId/{citizenId}")
@Security("internalAuth")
async GetProfileByCitizenIdAsync(@Path() citizenId: string) { async GetProfileByCitizenIdAsync(@Path() citizenId: string) {
const profile = await this.profileRepo.findOne({ const profile = await this.profileRepo.findOne({
relations: [ relations: [
@ -3887,6 +3921,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("root/officer/{rootId}") @Get("root/officer/{rootId}")
@Security("internalAuth")
async GetProfileByRootIdAsync(@Path() rootId: string) { async GetProfileByRootIdAsync(@Path() rootId: string) {
const profiles = await this.profileRepo.find({ const profiles = await this.profileRepo.find({
relations: [ relations: [
@ -4199,6 +4234,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("root/employee/{rootId}") @Get("root/employee/{rootId}")
@Security("internalAuth")
async GetProfileByRootIdEmpAsync(@Path() rootId: string) { async GetProfileByRootIdEmpAsync(@Path() rootId: string) {
const profiles = await this.profileEmpRepo.find({ const profiles = await this.profileEmpRepo.find({
relations: [ relations: [
@ -4403,6 +4439,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Post("find/employee/position") @Post("find/employee/position")
@Security("internalAuth")
async GetProfileByPositionEmpAsync( async GetProfileByPositionEmpAsync(
@Body() @Body()
body: { body: {
@ -4732,6 +4769,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("user-fullname/{keycloakId}") @Get("user-fullname/{keycloakId}")
@Security("internalAuth")
async GetUserFullName(@Path() keycloakId: string) { async GetUserFullName(@Path() keycloakId: string) {
const profile = await this.profileRepo.findOne({ const profile = await this.profileRepo.findOne({
where: { keycloak: keycloakId }, where: { keycloak: keycloakId },
@ -4750,6 +4788,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("user-oc/{keycloakId}") @Get("user-oc/{keycloakId}")
@Security("internalAuth")
async getProfileByKeycloak(@Path() keycloakId: string) { async getProfileByKeycloak(@Path() keycloakId: string) {
const profile = await this.profileRepo.findOne({ const profile = await this.profileRepo.findOne({
where: { keycloak: keycloakId }, where: { keycloak: keycloakId },
@ -4801,6 +4840,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("user-oc-all/{keycloakId}") @Get("user-oc-all/{keycloakId}")
@Security("internalAuth")
async getAllProfileByKeycloak(@Path() keycloakId: string) { async getAllProfileByKeycloak(@Path() keycloakId: string) {
const profile = await this.profileRepo.findOne({ const profile = await this.profileRepo.findOne({
where: { keycloak: keycloakId }, where: { keycloak: keycloakId },
@ -4968,6 +5008,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} ocId Id * @param {string} ocId Id
*/ */
@Get("root-oc/{ocId}") @Get("root-oc/{ocId}")
@Security("internalAuth")
async GetRootOcId(@Path() ocId: string) { async GetRootOcId(@Path() ocId: string) {
const orgRoot = await this.orgRootRepo.findOne({ const orgRoot = await this.orgRootRepo.findOne({
where: { id: ocId }, where: { id: ocId },
@ -4984,6 +5025,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Get("keycloak") @Get("keycloak")
@Security("internalAuth")
async GetProfileWithKeycloak() { async GetProfileWithKeycloak() {
const profile = await this.profileRepo.find({ const profile = await this.profileRepo.find({
where: { keycloak: Not(IsNull()) }, where: { keycloak: Not(IsNull()) },
@ -5193,6 +5235,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Get("keycloak-employee") @Get("keycloak-employee")
@Security("internalAuth")
async GetProfileWithKeycloakEmployee() { async GetProfileWithKeycloakEmployee() {
const profile = await this.profileEmpRepo.find({ const profile = await this.profileEmpRepo.find({
where: { keycloak: Not(IsNull()) || Not("") }, where: { keycloak: Not(IsNull()) || Not("") },
@ -5321,6 +5364,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("keycloak-all-officer") @Post("keycloak-all-officer")
@Security("internalAuth")
async PostProfileWithKeycloakAllOfficer( async PostProfileWithKeycloakAllOfficer(
@Body() @Body()
body: { body: {
@ -5491,6 +5535,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("keycloak-all-officer/date") @Post("keycloak-all-officer/date")
@Security("internalAuth")
async PostProfileWithKeycloakAllOfficerDate( async PostProfileWithKeycloakAllOfficerDate(
@Body() @Body()
body: { body: {
@ -5669,6 +5714,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("none-validate-keycloak-all-officer") @Post("none-validate-keycloak-all-officer")
@Security("internalAuth")
async PostProfileWithNoneValidateKeycloakAllOfficer( async PostProfileWithNoneValidateKeycloakAllOfficer(
@Body() @Body()
body: { body: {
@ -5838,6 +5884,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("find-node-name") @Post("find-node-name")
@Security("internalAuth")
async findNodeName( async findNodeName(
@Body() @Body()
body: { body: {
@ -5946,6 +5993,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("officer-by-admin-role") @Post("officer-by-admin-role")
@Security("internalAuth")
async GetOfficersByAdminRole( async GetOfficersByAdminRole(
@Body() @Body()
body: { body: {
@ -6283,6 +6331,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("keycloak-all-employee") @Post("keycloak-all-employee")
@Security("internalAuth")
async PostProfileWithKeycloakAllEmployee( async PostProfileWithKeycloakAllEmployee(
@Body() @Body()
body: { body: {
@ -6436,6 +6485,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("none-validate-keycloak-all-employee") @Post("none-validate-keycloak-all-employee")
@Security("internalAuth")
async PostProfileWithNoneValidateKeycloakAllEmployee( async PostProfileWithNoneValidateKeycloakAllEmployee(
@Body() @Body()
body: { body: {
@ -6589,6 +6639,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("employee-by-admin-role") @Post("employee-by-admin-role")
@Security("internalAuth")
async GetEmployeesByAdminRole( async GetEmployeesByAdminRole(
@Body() @Body()
body: { body: {
@ -6929,8 +6980,9 @@ export class OrganizationDotnetController extends Controller {
* @summary admin * @summary admin
*/ */
@Post("employee-by-admin-rolev2") @Post("employee-by-admin-rolev2")
@Security("internalAuth")
async GetEmployeesByAdminRoleV2( async GetEmployeesByAdminRoleV2(
@Request() req: RequestWithUser, // @Request() req: RequestWithUser, // ไม่ได้ใช้
@Body() @Body()
body: { body: {
node: number; node: number;
@ -7199,6 +7251,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Put("update-dutytime") @Put("update-dutytime")
@Security("bearerAuth")
async UpdateDutyTimeAsync( async UpdateDutyTimeAsync(
@Request() req: RequestWithUser, @Request() req: RequestWithUser,
@Body() @Body()
@ -7241,6 +7294,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("insignia/Dumb") @Post("insignia/Dumb")
@Security("bearerAuth")
public async newInsignia(@Request() req: RequestWithUser, @Body() body: CreateProfileInsignia) { public async newInsignia(@Request() req: RequestWithUser, @Body() body: CreateProfileInsignia) {
if (!body.profileId) { if (!body.profileId) {
throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId"); throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId");
@ -7317,6 +7371,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} keycloakId Id keycloak * @param {string} keycloakId Id keycloak
*/ */
@Get("profile-leave/keycloak/{keycloakId}") @Get("profile-leave/keycloak/{keycloakId}")
@Security("internalAuth")
async GetProfileLeaveByKeycloakIdAsync(@Path() keycloakId: string) { async GetProfileLeaveByKeycloakIdAsync(@Path() keycloakId: string) {
const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today");
let _currentDate = CURRENT_DATE[0].today; let _currentDate = CURRENT_DATE[0].today;
@ -7604,6 +7659,7 @@ export class OrganizationDotnetController extends Controller {
} }
@Post("profile-leave/keycloak") @Post("profile-leave/keycloak")
@Security("internalAuth")
async GetProfileLeaveReportByKeycloakIdAsync( async GetProfileLeaveReportByKeycloakIdAsync(
@Body() body: { keycloakId: string; report?: string }, @Body() body: { keycloakId: string; report?: string },
) { ) {
@ -7908,6 +7964,7 @@ export class OrganizationDotnetController extends Controller {
* @param {string} type ( ) * @param {string} type ( )
*/ */
@Post("find/insignia-requests-profile/{type}") @Post("find/insignia-requests-profile/{type}")
@Security("internalAuth")
async GetInsigniaRequestsProfileAsync( async GetInsigniaRequestsProfileAsync(
@Path() type: string, @Path() type: string,
@Body() @Body()
@ -8037,6 +8094,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("officer-by-admin-rolev2") @Post("officer-by-admin-rolev2")
@Security("internalAuth")
async GetOfficersByAdminRoleV2( async GetOfficersByAdminRoleV2(
@Body() @Body()
body: { body: {
@ -8260,6 +8318,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("officer-by-admin-rolev3") @Post("officer-by-admin-rolev3")
@Security("internalAuth")
async GetOfficersByAdminRoleV3( async GetOfficersByAdminRoleV3(
@Body() @Body()
body: { body: {
@ -8606,6 +8665,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("officer-by-admin-rolev4") @Post("officer-by-admin-rolev4")
@Security("internalAuth")
async GetOfficersByAdminRoleV4( async GetOfficersByAdminRoleV4(
@Body() @Body()
body: { body: {
@ -8882,6 +8942,7 @@ export class OrganizationDotnetController extends Controller {
* *
*/ */
@Post("find-staff") @Post("find-staff")
@Security("internalAuth")
async findHigher( async findHigher(
@Body() @Body()
requestBody: { requestBody: {

View file

@ -4,6 +4,7 @@ import { createDecoder, createVerifier } from "fast-jwt";
import HttpError from "../interfaces/http-error"; import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import { handleWebServiceAuth } from "./authWebService"; import { handleWebServiceAuth } from "./authWebService";
import { handleInternalAuth } from "./authInternal";
if (!process.env.AUTH_PUBLIC_KEY && !process.env.AUTH_REALM_URL) { if (!process.env.AUTH_PUBLIC_KEY && !process.env.AUTH_REALM_URL) {
throw new Error("Require keycloak AUTH_PUBLIC_KEY or 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" }; return { preferred_username: "bypassed" };
} }
// เพิ่มการจัดการสำหรับ Internal Authentication (.NET service)
if (securityName === "internalAuth") {
return await handleInternalAuth(request);
}
// เพิ่มการจัดการสำหรับ Web Service Authentication // เพิ่มการจัดการสำหรับ Web Service Authentication
if (securityName === "webServiceAuth") { if (securityName === "webServiceAuth") {
return await handleWebServiceAuth(request); return await handleWebServiceAuth(request);

View file

@ -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,
};
}

View file

@ -29,6 +29,12 @@
"name": "X-API-Key", "name": "X-API-Key",
"description": "API KEY สำหรับ Web Service", "description": "API KEY สำหรับ Web Service",
"in": "header" "in": "header"
},
"internalAuth": {
"type": "apiKey",
"name": "api-key",
"description": "API KEY สำหรับ Internal Service (.NET, HRMS)",
"in": "header"
} }
}, },
"tags": [ "tags": [