Skip to content

4.2.3 · State/styling/locale + Security/Performance + Scorecard


State — 3 ระบบผสม (verify ตรงกับ v1)

  1. Local class state (this.state/this.setState) — module เก่า
  2. Local hook state (useState) — module ใหม่ที่ไม่ share state
  3. Global state (Recoil)useRecoilState/useRecoilValue/useSetRecoilState — module WorkForce/PMS (4.2.2)
  4. Persisted state (localStorage/sessionStorage)token, refreshToken, isRemember (auth.js — verify ตรง), currentEmployeeId/viewOnly/searchList (WorkForce, ผ่าน work-force.service.js), มี helper storage.js ที่รองรับ TTL (setWithExpiry/getWithExpiry)

ความเสี่ยงของ localStorage เหมือน vtrc-web — อ่านได้จาก XSS ใดๆ, ไม่ sync ข้าม tab, token ค้างถ้าโค้ดลืมเคลียร์


Styling — CSS 7 ชั้น (verify ขนาดไฟล์ตรงกับ v1 100%)

ชั้นขนาด (verify)ความเสี่ยง conflict
AntD v4 default (antd/dist/antd.css)~600 KBต่ำ
config/custom-antd.css (compiled จาก sass ด้วย Prepros)21,189 บรรทัด (ตรงกับ v1)ปานกลาง — ห้ามแก้ไฟล์นี้ตรง แก้ที่ sass source แทน
assets/css/main.css4,644 บรรทัด (ตรงกับ v1)สูง — utility class คล้าย Tailwind แต่ไม่ใช่
assets/css/theme/*.css~1,000 บรรทัดสูง — มี typo 3 ไฟล์ใกล้เคียงกันจริง: template.css (ถูก), themeplate.css (typo เพิ่ม h), themplate.css (typo หาย e) + time_attendace.css (typo หาย d) — ยืนยันมีอยู่จริงทั้ง 4 ไฟล์
assets/css/work-force.css794 บรรทัด (ตรงกับ v1)ต่ำ — isolated เฉพาะ WorkForce module
Component-scoped CSS (.css คู่กับ .js)~5,000 บรรทัดรวมปานกลาง — ไม่ใช่ CSS Modules จริง class ชนกันได้
Inline style(ไม่นับ)ปานกลาง — responsive/media query ทำยาก

รวม ~35,000 บรรทัด CSS โหลดตอนเปิดแอป — มากกว่า vtrc-web (~10,000 บรรทัดใน custom override)


Locale — พ.ศ. (Buddhist Era) ด้วย moment.js + manual +543

Pattern เดียวกับ vtrc-webformatDateBE() บวก 543 ปีตอนแสดงผล, parseBE() ลบ 543 ตอนรับ input จาก user, เก็บ ค.ศ. เสมอใน state/server ยืนยันมีการ import "moment/locale/th" ในหลายไฟล์ (เช่น pages/NotificationMain/) ข้อควรระวังเหมือนกัน — moment.locale("th") ปรับปีเป็นพ.ศ.อัตโนมัติถ้าใช้ format YYYY ทำให้สับสนกับ manual +543 ที่ยังใช้อยู่บางจุด moment ล้าสมัยแล้ว ควรย้ายไป dayjs/date-fns


Security — Critical 3 + High 4 + Moderate 5 (verify กับโค้ดจริง)

Critical

C1. .env committed ลง git มี secret จริง — ยืนยันแล้วว่า .env (519 ไบต์, 6 บรรทัด) ไม่ได้อยู่ใน .gitignore (ตรวจ .gitignore แล้ว — มีแค่ .env.local/.env.development.local/ฯลฯ ไม่มี .env เปล่าๆ) ตัวแปรที่มี — REACT_APP_PRIVATE_KEY (AES key), REACT_APP_STIMUL_CONN_STRING (MSSQL connection string), REACT_APP_AI_RECRUITMENT_API, REACT_APP_PMS_API_END_POINT, REACT_APP_END_POINT_EXPORT, REACT_APP_END_POINT_RECRUITMENT_API_ATTACHFILE (ไม่แสดงค่าจริงในเอกสารนี้ — ตรวจสอบแยกกับทีม infra ให้ rotate ทุกตัว)

C2. REACT_APP_STIMUL_CONN_STRING ฝังใน bundle ส่งให้ browser — CRA ฝัง process.env.REACT_APP_* ทุกตัวลง bundle ที่ browser โหลดได้ตรง (design choice ของ Stimulsoft report ที่ query MSSQL ฝั่ง client) — ผลคือ connection string (รวม username/password) เปิดให้ทุกคนที่เปิด DevTools → Sources อ่านได้

C3. SSO bypass /pathLogin/:token/:refreshToken รับ token ใน URL — ยืนยัน route จริงใน containers/App/index.js:24 — token+refreshToken อยู่ใน URL รั่วได้ผ่าน browser history, referer header, server/proxy log, browser extension แนะนำเปลี่ยนเป็น POST body/fragment หรือ authorization-code flow

High

H1. dangerouslySetInnerHTML — 30 ไฟล์ (verify ตรงกับ v1 100% จากการ grep โค้ดปัจจุบัน) — ควร sanitize ด้วย DOMPurify ก่อน render เนื้อหาจาก backend (news content ฯลฯ)

H2. utils/cryptoJs.js ใช้ AES key จาก REACT_APP_PRIVATE_KEY ที่ expose ใน bundle — AES ฝั่ง client ไม่มีความหมายถ้า key อยู่ใน bundle ทุกอย่างที่ encrypt ด้วย helper นี้ถอดได้โดยทุกคน

H3. Token เก็บใน localStorage/sessionStorage ทั้งหมด — XSS ใดๆ (รวมจาก H1) อ่าน token ได้ตรง แนะนำ HttpOnly cookie ถ้าเป็นไปได้

H4. apiKeyNews() ฝัง UUID ตรงๆ ตาม origin — ยืนยันจากโค้ดจริง utils/apiKey.js มี 2 ค่า UUID คงที่ (prod 1 ค่า, UAT/dev/localhost อีก 1 ค่าเดียวกัน) เขียนอยู่ในคอมเมนต์ด้วยว่าเป็น "APIKEY PRODUCTION"/"APIKEY UAT" ชัดเจน — apiKey ทำหน้าที่เหมือน client ID ไม่ใช่ secret จริง แต่ยืนยันตัวตนจริงต้องพึ่ง JWT (Authorization header) เท่านั้น

Moderate

  • M1 checkLinkURL() ใช้ .includes() แทนการ parse origin จริง — เสี่ยง match ผิดถ้า query string มีคำตรงกันบังเอิญ
  • M2 errorLink (Apollo) ไม่มี queue/singleton สำหรับ token refresh — เสี่ยง race condition ถ้าหลาย request ได้ TOKEN_EXPIRED พร้อมกัน (axios ฝั่ง AxiosBO มี guard นี้แล้วแต่ Apollo client ไม่มี — ดู 4.2.1)
  • M3 ไม่มี Content-Security-Policy meta/header
  • M4 ไม่มี CSRF token — แต่ใช้ Authorization header ไม่ใช่ cookie จึงลดความเสี่ยงลงมาก
  • M5 ไม่มี Subresource Integrity (SRI) สำหรับสคริปต์ CDN ภายนอก (เช่น Stimulsoft)

Performance — 6 ปัญหา (verify ตรงกับ v1)

  1. Bundle ~11-13 MB — AntD v4 full import (~1.2MB), Stimulsoft (~5MB ใหญ่ที่สุด), PDF library 3 ตัว (~2MB), Excel library 2 ตัว (~800KB), MUI v5 ที่ใช้น้อย (~300KB)
  2. ไม่มี code splitting ระดับ route — ไม่มี React.lazy/Suspense ทุกหน้าถูก import ตรงเข้า bundle เดียว
  3. fetchPolicy: 'no-cache' เป็น default ทั้ง 2 Apollo Client (verify ตรงจาก apolloClient.defaultOptions) — ทุก navigation re-fetch เสมอ
  4. console.log = console.warn = console.error = () => {} (verify ตรงจาก App/index.js:17) — debug production ไม่ได้ต้องใช้ debugger
  5. N+1 query pattern ในบาง list component (4.2.2)
  6. build/ ~130 MB commit ลง git จริง (verify du -sh build = 130M และ .gitignore ไม่มี build/) — clone ช้า, Docker image ใหญ่ถ้า COPY . ., git history โตผิดปกติ

Scorecard

ด้านคะแนนหมายเหตุ
โครงสร้างโค้ด6/10module เก่า+ใหม่ผสม, typos, legacy 75 ไฟล์
การ build5/10CRA 3.4.3 ล้าสมัย, 130MB build/ commit, ไม่มี code splitting
การจัดการ state7/10Recoil ใช้เป็นระบบใน WorkForce, class state ก็ทำงานได้
การจัดการ auth4/10token ใน localStorage, SSO bypass ผ่าน URL
Security3/10secret รั่วจริง (.env commit), ไม่มี CSP
Performance4/10bundle ใหญ่, ไม่มี cache, N+1
Maintainability5/10ไฟล์ใหญ่หลายไฟล์ (2,075/1,824/1,168 บรรทัด), snapshot เยอะ
Documentation3/10ไม่มี inline doc
Testing1/10ไม่มี test จริง
รวม4.2/10ต้องการ modernization เร่งด่วน — ต่ำกว่า vtrc-web ในด้าน security เพราะมี secret รั่วจริงและ SSO bypass

จุดแข็ง

Recoil ใช้เป็นระบบใน WorkForce module (atom แยกตาม entity+หน้าที่ debug/maintain ง่าย), errorLink ครอบคลุม error code หลากหลาย, เมนู dynamic ลดความจำเป็นในการ deploy บ่อย, utils/ มี helper ที่ reuse ได้ (date, auth, storage, crypto)

จุดอ่อนสำคัญที่สุด (เรียงตามความเสี่ยง)

  1. .env commit ลง git — มี AES key + MSSQL connection string
  2. SSO bypass ส่ง token ทาง URL
  3. 30 ไฟล์ dangerouslySetInnerHTML ไม่ sanitize
  4. ไม่มี CSP
  5. Bundle ใหญ่ไม่มี code splitting
  6. build/ 130MB commit ลง git
  7. Legacy/snapshot 75 ไฟล์ + typo หลายจุด
  8. ไม่มี test จริง

แผน remediation ระยะสั้น (เร่งด่วนที่สุด)

Rotate secret ทั้งหมด (AES key, MSSQL password, API keys ของ Stimulsoft/PMS/AI recruitment) → ลบ .env จาก git history → เพิ่ม .env และ build/ ใน .gitignore → เพิ่ม DOMPurify สำหรับ dangerouslySetInnerHTML → เพิ่ม CSP header → เปลี่ยน SSO bypass ให้ไม่ส่ง token ผ่าน URL (ระยะกลาง-ยาว ดูรายละเอียดเดียวกันกับ v1 บท 7.10 — ไม่เปลี่ยนแปลง priority)


เปรียบเทียบสรุปกับ vtrc-web

ด้านvtrc-rc-backofficevtrc-web
.env committed จริงใช่ (มี MSSQL conn string)ไม่มี
dangerouslySetInnerHTML30 ไฟล์~15 ไฟล์
SSO bypass ผ่าน URLมีไม่มี
Token storagelocalStorage/sessionStoragelocalStorage
Recoilมี (57 โฟลเดอร์, ~212 atom)ไม่มี
Bundle~11-13 MB~12 MB
Scorecard รวม4.2/10(ดู Volume 4.1 บท 3)

สรุป — ทั้งสองแอปมี pattern คล้ายกันมาก (Apollo v2, no-cache default, class+HOC legacy, token ใน web storage) เพราะมาจากรากเดียวกัน (vtrc-api) แต่ vtrc-rc-backoffice มี security debt รุนแรงกว่าอย่างมีนัยสำคัญ เพราะมีการ commit ความลับจริงลง git และมี SSO bypass ที่ vtrc-web ไม่มี