4.2.3 · State/styling/locale + Security/Performance + Scorecard
State — 3 ระบบผสม (verify ตรงกับ v1)
- Local class state (
this.state/this.setState) — module เก่า - Local hook state (
useState) — module ใหม่ที่ไม่ share state - Global state (Recoil) —
useRecoilState/useRecoilValue/useSetRecoilState— module WorkForce/PMS (4.2.2) - Persisted state (localStorage/sessionStorage) —
token,refreshToken,isRemember(auth.js — verify ตรง),currentEmployeeId/viewOnly/searchList(WorkForce, ผ่านwork-force.service.js), มี helperstorage.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.css | 4,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.css | 794 บรรทัด (ตรงกับ 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-web — formatDateBE() บวก 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()แทนการ parseoriginจริง — เสี่ยง 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 — แต่ใช้
Authorizationheader ไม่ใช่ cookie จึงลดความเสี่ยงลงมาก - M5 ไม่มี Subresource Integrity (SRI) สำหรับสคริปต์ CDN ภายนอก (เช่น Stimulsoft)
Performance — 6 ปัญหา (verify ตรงกับ v1)
- Bundle ~11-13 MB — AntD v4 full import (~1.2MB), Stimulsoft (~5MB ใหญ่ที่สุด), PDF library 3 ตัว (~2MB), Excel library 2 ตัว (~800KB), MUI v5 ที่ใช้น้อย (~300KB)
- ไม่มี code splitting ระดับ route — ไม่มี
React.lazy/Suspenseทุกหน้าถูก import ตรงเข้า bundle เดียว fetchPolicy: 'no-cache'เป็น default ทั้ง 2 Apollo Client (verify ตรงจากapolloClient.defaultOptions) — ทุก navigation re-fetch เสมอconsole.log = console.warn = console.error = () => {}(verify ตรงจากApp/index.js:17) — debug production ไม่ได้ต้องใช้debugger- N+1 query pattern ในบาง list component (4.2.2)
build/~130 MB commit ลง git จริง (verifydu -sh build= 130M และ.gitignoreไม่มีbuild/) — clone ช้า, Docker image ใหญ่ถ้าCOPY . ., git history โตผิดปกติ
Scorecard
| ด้าน | คะแนน | หมายเหตุ |
|---|---|---|
| โครงสร้างโค้ด | 6/10 | module เก่า+ใหม่ผสม, typos, legacy 75 ไฟล์ |
| การ build | 5/10 | CRA 3.4.3 ล้าสมัย, 130MB build/ commit, ไม่มี code splitting |
| การจัดการ state | 7/10 | Recoil ใช้เป็นระบบใน WorkForce, class state ก็ทำงานได้ |
| การจัดการ auth | 4/10 | token ใน localStorage, SSO bypass ผ่าน URL |
| Security | 3/10 | secret รั่วจริง (.env commit), ไม่มี CSP |
| Performance | 4/10 | bundle ใหญ่, ไม่มี cache, N+1 |
| Maintainability | 5/10 | ไฟล์ใหญ่หลายไฟล์ (2,075/1,824/1,168 บรรทัด), snapshot เยอะ |
| Documentation | 3/10 | ไม่มี inline doc |
| Testing | 1/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)
จุดอ่อนสำคัญที่สุด (เรียงตามความเสี่ยง)
.envcommit ลง git — มี AES key + MSSQL connection string- SSO bypass ส่ง token ทาง URL
- 30 ไฟล์
dangerouslySetInnerHTMLไม่ sanitize - ไม่มี CSP
- Bundle ใหญ่ไม่มี code splitting
build/130MB commit ลง git- Legacy/snapshot 75 ไฟล์ + typo หลายจุด
- ไม่มี 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-backoffice | vtrc-web |
|---|---|---|
.env committed จริง | ใช่ (มี MSSQL conn string) | ไม่มี |
dangerouslySetInnerHTML | 30 ไฟล์ | ~15 ไฟล์ |
| SSO bypass ผ่าน URL | มี | ไม่มี |
| Token storage | localStorage/sessionStorage | localStorage |
| 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 ไม่มี