Skip to content

4 · Auth model + external integrations

บทนี้รวม auth ที่พิสูจน์จากซอร์ส (no role / no @Public) และแผนที่ outbound ทั้งหมดของ ai-recruitment-api บน branch prod

Pipeline ที่เรียก integration: 02 Core pipeline
ตาราง DB: 03 Persistence


Auth model — global JWT only

ไม่มี NestJS / ไม่มี @Public / ไม่มี RolesGuard

Repo นี้เป็น Express — ไม่มี decorator @Public(), ไม่มี Passport strategy, ไม่มี RolesGuard
เทียบกับ theme "no-role-auth" ใน brief ของ volume นี้: ยืนยันแล้วว่ามีแค่การ verify signature + expiry

Mount จุดเดียว

javascript
app.use(verifyJWT);
// ...
app.use('/ai-recruitment-api/api', router);

แหล่ง: server.js:37, server.js:60

ไม่มี per-route middleware ที่ข้าม JWT ใน routes/index.js

verifyJWT implementation

javascript
const SECRET_KEY = process.env.JWT_SECRET || '';

const verifyJWT = async (req, res, next) => {
    const token = req.header('Authorization')?.split(' ')[1];
    if (!token) {
        return res.status(401).json({ message: 'Access Denied. No token provided.' });
    }
    try {
        const decoded = jwt.verify(token, SECRET_KEY);
        req.user = decoded;
        const { payload } = await getPayLoad(token)
        // ... GMT+7 console.log with payload?.uid
        next();
    } catch (err) {
        if (err.name === 'TokenExpiredError') {
            return res.status(401).send('TOKEN_EXPIRED')
        } else {
            return res.status(401).send('TOKEN_INVALID')
        }
    }
};

แหล่ง: middleware/verifyJWT.js:4-39

ตรวจอะไรผล
มี Bearer tokenจำเป็น — ไม่มี → 401 JSON
jwt.verify(token, JWT_SECRET)signature + exp
iss / aud / client / role / scopeไม่ตรวจ
Quota ต่อ userไม่มี

getPayLoad ใช้ decode จาก jsonwebtoken โดยส่ง SECRET_KEY เป็น argument ที่สอง (:45) — ใน API ของ jsonwebtoken, decode ไม่ได้ verify (ต่างจาก verify); ใช้เพื่อ log uid เท่านั้น หลัง verify ผ่านแล้ว

req.user ถูกอ่านใน logger ของ server (server.js:49req.user?.uid) และใน uploadController error log (uploadController.js:22)

Shared JWT secret ข้าม 3 services

ServiceEnv key ที่อ่านHash ของค่า (SHA-256 16 hex แรก)
ai-recruitment-apiJWT_SECRET (.env:11)68e3ccd444f33362
benefit-apiSECRET_KEY68e3ccd444f33362 (ตรงกัน)
recruitment-apiSECRET_KEY68e3ccd444f33362 (ตรงกัน)

ความยาวค่า 372 ตัวอักษรทั้งสาม — ยืนยันว่าเป็น secret ชุดเดียวกันบนเครื่องเอกสารนี้

ผลเชิงสถาปัตยกรรม:

  • Token ที่ออกด้วย secret นี้จาก service ใดในกลุ่ม สามารถผ่าน verifyJWT ได้ ถ้ายังไม่หมดอายุ
  • ไม่มีชั้นแยกว่า token มาจาก back-office หรือ candidate portal
  • ค่าใช้จ่าย OpenAI/remove.bg ผูกกับ API key เดียวของ process — ไม่มี per-user metering ในโค้ด

CORS vs Auth

CORS จำกัด origin เฉพาะ back-office + localhost (server.js:24-28) — ลดการเรียกจาก browser โดเมนอื่น
แต่ ไม่ได้แทนที่ auth: client ที่ไม่ใช่ browser (curl, server-side) ที่ถือ JWT ที่ถูกต้องยังเรียกได้โดยไม่สนใจ CORS

Route upload ตั้ง Access-Control-Allow-Origin: req.headers.origin || '*' (routes/index.js:91-106) กว้างกว่า global CORS — เป็นจุดไม่สอดคล้องที่ควรรู้ตอน audit

Headers ที่ CORS อนุญาต

Content-Type, authorization, apiKey (server.js:30)
โค้ดฝั่งนี้ ไม่พบการอ่าน header apiKey เพื่อ authorize — ต่างจาก legacy vtrc-api ที่ใช้ device apiKey
ชื่อ header น่าจะเหลือเพื่อให้ browser preflight ผ่านถ้า client ส่งมาด้วย


External integrations map

                    ai-recruitment-api :9430

        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
  OpenAI API            remove.bg API         api.qrserver.com
  chat/completions      /v1.0/removebg        create-qr-code
  images.generate
        │                     │                     │
        └──────────┬──────────┴──────────┐          │
                   ▼                     ▼          ▼
              MSSQL recruitment_ai    filesystem   Puppeteer
              (read-only)             storage*     local Chromium

ไม่พบ axios/fetch ไปยัง vtrc-api, centralize-api, cu-central-api, LINE, หรือ vtrc-common ในโค้ด .js ของ repo นี้


Integration 1 — OpenAI Chat Completions

รายการค่า
EnvOPENAI_API_KEY (.env:5)
URLhttps://api.openai.com/v1/chat/completions
Auth ไปปลายทางAuthorization: Bearer ${OPENAI_API_KEY}

Wrapper กลาง

config/openai.js:7-28 — model gpt-4o, messages แบบ user เดี่ยว, fail → ''

ผู้เรียก wrapper:

  • generateJobDescriptionController.js ×4
  • module2_2_controller.js ×1 (career)

Inline axios (ไม่ผ่าน wrapper)

ไฟล์Model
summarizeJobDescriptionController.js:44-60gpt-3.5-turbo
summarizeJobQualificationController.jsgpt-3.5-turbo
generateInterviewQuestionsController.js:106-124 (callChatGPT)gpt-3.5-turbo

ไม่มี timeout ที่ตั้งใน axios ของ wrappers เหล่านี้ (ต่างจาก remove.bg ที่มี 30–45s)


Integration 2 — OpenAI Images API

รายการค่า
Clientofficial openai npm package
Methodopenai.images.generate
EnvOPENAI_API_KEY ชุดเดียวกับ chat
Default modelgpt-image-1
Callermodule2_2_controller.js:177 ผ่าน callDalle

แหล่ง wrapper: config/dalle.js:41-52

Env DALLE_ENDPOINT ใน .env:8 ไม่ถูกอ่าน ใน dalle.js — endpoint ถูกกำหนดโดย SDK


Integration 3 — remove.bg

รายการค่า
EnvREMOVE_BG_API_KEY (.env:7)
URLhttps://api.remove.bg/v1.0/removebg
Auth headerX-API-Key / X-Api-Key

Call site A — service (character pipeline)

services/removeBgService.js:46-85 — รองรับ buffer / b64 / URL
timeout 30s (:75)
เรียกจาก module2_2_controller.js:194

Call site B — upload controller

controllers/removeBgUploadController.js:31-86 — multipart หรือ urlencoded
timeout 45s (:45, :83)
เรียกจาก route routes/index.js:117

ถ้าขาด API key: throw / 500 (removeBgService.js:47,131, removeBgUploadController.js:90-95)


Integration 4 — QR Server

รายการค่า
URLhttps://api.qrserver.com/v1/create-qr-code/?size=300x300&data=...
Envไม่มี — hardcode
Authไม่มี
CallercombineJobPostingController.js:65-75 (และ orphan module_2_3_combine_image.js:65)

ส่ง job_link จาก client ตรงเข้า query string หลัง encodeURIComponent — dependency ภายนอกสำหรับทุกภาพประกาศที่ combine


Integration 5 — Puppeteer / Chromium

ไม่ใช่ HTTP ภายนอก แต่เป็น local subprocess:

รายการค่าแหล่ง
Packagepuppeteer ^22.6.5package.json:23
Executable/usr/bin/chromium-browsercombineJobPostingController.js:378, Dockerfile:21
Skip downloadPUPPETEER_SKIP_CHROMIUM_DOWNLOAD=trueDockerfile:20

ใช้เฉพาะ combine pipeline (mounted + orphan twin)


Integration 6 — MSSQL recruitment_ai

ดู 03 Persistence — read-only จากมุม service นี้


Integration 7 — HCTI (ประกาศใน env แต่ไม่พบ caller)

Key.env
HCTI_USER_IDบรรทัด 9
HCTI_API_KEYบรรทัด 10

ค้นทั้ง repo ไม่พบ string HCTI / hcti ในไฟล์ .jsUNVERIFIED ว่าเคยใช้ในอดีต; ปัจจุบันเป็น dead credential ใน env
Comment ใน combineJobPostingController บอกว่าเคยย้ายจาก online renderer มาเป็น Puppeteer local (combineJobPostingController.js:1-9) — สอดคล้องกับ HCTI ที่เหลือใน env


Secrets บน disk / ใน git

ประเภทตำแหน่งTracked?
MSSQL password.env:2ใช่ (git ls-files .env คืนไฟล์) แม้ .gitignore:51 มี .env
OpenAI API key.env:5ใช่
remove.bg API key.env:7ใช่
HCTI credentials.env:9-10ใช่
JWT secret (shared).env:11ใช่

เอกสารนี้ ไม่คัดลอกค่า secret — อ้างเฉพาะหมายเลขบรรทัดและประเภท

Dockerfile COPY . . (Dockerfile:31) จะ bake .env เข้า image ถ้าไฟล์อยู่ใน build context — ความเสี่ยง operational เพิ่มเติมเมื่อ build จาก working tree ที่มี .env


สิ่งที่ service นี้ไม่เชื่อม

ยืนยันจากการค้นโค้ด:

  • ไม่เรียก GraphQL ของ vtrc-api
  • ไม่เรียก NestJS centralize-api / cu-central-api
  • ไม่ใช้ shared library vtrc-common
  • ไม่มี LINE / SSO / SFTP client

จุดเชื่อมแพลตฟอร์มที่เหลือมีแค่: (1) shared JWT secret (2) shared DB recruitment_ai (3) CORS ไป back-office


Checklist ตอน debug auth / integration

  1. 401 No token provided → ขาด header Authorization: Bearer ...
  2. TOKEN_INVALID → secret ไม่ตรง หรือ token เสียรูป (ตรวจว่าฝั่งออก token ใช้ SECRET_KEY ชุดเดียวกับ JWT_SECRET)
  3. TOKEN_EXPIRED → ต่ออายุที่ issuer เดิม
  4. CORS error ใน browser → origin ไม่ใช่ 3 ค่าใน whitelist (หรือ path upload ที่ตั้ง * แต่ยังต้องผ่าน JWT)
  5. OpenAI ว่างเงียบ → ดูว่าเป็น fail-soft ของ callOpenAI หรือไม่; ตรวจ billing/key
  6. remove.bg 401/402 → key หรือโควต้า
  7. combine 500 เรื่อง Chromium → path /usr/bin/chromium-browser บน host

ไป บท 5 Cron jobs