Skip to content

new-vtrc-mobile — Auth, RSA & AsyncStorage Pipeline

บทนี้ (branch: master) trace flow login จริง, การสร้าง csID, RSA encrypt, และการ persist token ผ่าน AsyncStorage


Boot → navigator gate

index.js → App.tsx
  │  initializeI18n() แล้วค่อย render
  │  `App.tsx:11-30`

AppProviders
  │  Redux Provider + PersistGate(AsyncStorage) + NavigationContainer
  │  `src/providers/AppProviders.tsx`

RootNavigator
  │  isLoggedIn = state.auth.isLoggedIn
  │  true  → BottomTabNavigator (Home / Menu / Setting)
  │  false → AuthNavigator (Login เท่านั้น)
  │  `RootNavigator.tsx:8-19`

CheckEmpCode มีไฟล์ครบแต่ถูก comment ออกจาก AuthNavigator (AuthNavigator.tsx:5,12) — ไม่เข้า runtime tree


Persist: AsyncStorage (ไม่ใช่ Keychain)

persistConfig = {
  key: 'root',
  storage: AsyncStorage,      ← plain AsyncStorage
  whitelist: ['auth'],
}
`src/redux/store.ts:20-25`

สิ่งที่ถูก persist ทั้งก้อนใน auth slice (authSlice.ts:11-22):

Fieldเก็บหลัง login?ความเสี่ยง
accessTokenใช่ (setLoginData)plaintext บน device storage
refreshTokenใช่plaintext
passwordใช่ (ค่าที่ encrypt แล้วถูกใส่ใน payload)ยังอยู่ใน persist bag
isLoggedInใช่
ssoId / lsidใช่
profile / employeeProfile / empDetailตั้งหลัง fetchอาจมี PII ใน persist

เทียบ vtrc-mobile ที่ใช้ sensitive storage — ที่นี่ถอยไปใช้ AsyncStorage ตรง (SEC-NEW-02)

Auth.ts ยังอ่าน/เขียน key แยก accessToken / refreshToken บน AsyncStorage อีกชั้น (Auth.ts:4-16) นอกเหนือจาก redux-persist — สองช่องทาง


RSA — สองเส้นทาง

1) makeCsID() — device proof สำหรับ captcha / verify

DeviceInfo.getUniqueId()
  + moment().valueOf()
  + API_KEY
  → sha256 hex
  → JSON { clientId, createdAt, hashSum }
  → RSA publicEncrypt(RSA_KEY.public)
  → base64 string = csID
`src/utils/CSID.ts:7-27`

ใช้ RSA_KEY.public จาก constants/RSAKey.ts:1-10

2) encryptToBase64 / decryptToString — รหัสผ่าน

encryptToBase64(text)
  → publicEncrypt(RSA_KEY.public, JSON.stringify(text))
  → base64
`EncryptCrypto.ts:4-13`

decryptToString(encryptedBase64)
  → privateDecrypt(RSA_KEY.private, ...)
  → JSON.parse
`EncryptCrypto.ts:15-24`

Private key อยู่ใน client bundle (RSAKey.ts:11-37) — ใคร unpack APK/IPA ก็ decrypt ได้ → SEC-NEW-01


Login service pipeline (authService.ts)

ลำดับที่พบบน service layer (เรียกจาก LoginScreen):

1. generateCaptcha()
     GraphQL GENERATE_CAPTCHA → HOSTGRAHQL
     usePinning: true, useAuth: false
     `authService.ts:39-55`

2. ssoCheckEmpLogin(username)
     POST `${HOST}/SSOService/checkEmpLogin`
     zod validate → dispatch setSsoData
     `authService.ts:57-96`

3. verifyEmpCodeLogin({ empCode, csID })
     GraphQL VERIFY_EMP_CODE_LOGIN
     variables: empCode, csId, organization, workflow:'SIGNIN'
     `authService.ts:98-117`

4. login(...)
     password ผ่าน encryptToBase64 ก่อนส่ง
     GraphQL LOGIN
     dispatch setLoginData({ accessToken, refreshToken, password, ... })
     แล้ว fetch profile / employeeProfile / empDetail
     (ต่อใน authService หลังบรรทัด ~120)

Host default = UAT (HostAPI.ts:1-6); prod ถูก comment (HostAPI.ts:9-14)

LINK_DOWNLOAD_APP = 'https://vtrc.redcross.or.th/adn/' (HostAPI.ts:16) — ชี้ไป download center เดียวกับ vtrc-application-front


HTTP layer (ApiRequest.ts)

Functionใช้เมื่อCitation
axiosRequestREST เช่น SSOApiRequest.ts:19-111
graphqlRequestGraphQL ไป HOSTGRAHQLApiRequest.ts:113-212

ทั้งคู่:

  • ใส่ header apiKey: API_KEY เสมอ (ApiRequest.ts:38,134)
  • ใส่ Authorization: Bearer {accessToken} เมื่อ useAuth (ApiRequest.ts:39,132)
  • optional SSL pinning cert name vtrc_redcross_th (ApiRequest.ts:51-53,145-147)
  • fallback axios ถ้า pinning module โหลดไม่ได้ (ApiRequest.ts:12-17,61-66)
  • บน 401 + message match /token expired|invalid token|unauthorized/i → เรียก refreshToken() แล้ว retry ครั้งเดียว (ApiRequest.ts:83-101,184-206)

refreshToken() ยิง https://yourdomain.com/auth/refresh (Auth.ts:11) — ยังเป็น boilerplate → QUAL-NEW-01


GraphQL surface ที่ port มาแล้ว

FileOperationsCitation
graphql/mutations/auth.tsLOGIN, LOGOUTmap ใน 01-repository-map
graphql/queries/auth.tsVERIFY_EMP_CODE_LOGIN, FETCH_PROFILE, GENERATE_CAPTCHA, FETCH_EMPLOYEE_PROFILE, FETCH_EMPLOYEE_DETAIL同上
graphql/queries/news.tsFETCH_LIST_POSTnewsService.ts มีแต่ Home ยังไม่ผูก UI จริง

ยังไม่มีโดเมน leave / hospital / slip ฯลฯ ของ vtrc-mobile


Data flow กับระบบอื่น

new-vtrc-mobile
  │  GraphQL + apiKey + Bearer

uat-vtrcapi.redcross.or.th/api-uat/graphql   (default)

  ├── SSOService/checkEmpLogin บน HOST เดียวกัน
  └── (workforce host คงที่ใน HostAPI แต่ยังไม่เห็น caller ใน screen ชุดเล็กนี้ — UNVERIFIED usage)

ไม่มี Apollo Client — ใช้ axios wrapper เองทั้งหมด


RSA helpers (file:line)

FunctionBehaviorCitation
encryptToBase64publicEncrypt(RSA_KEY.public) → base64EncryptCrypto.ts:4-13
decryptToStringprivateDecrypt(RSA_KEY.private) → JSON.parseEncryptCrypto.ts:15-24
makeCsIDSHA256(clientId:createdAt:API_KEY) แล้ว publicEncrypt objectCSID.ts:7-27

shim.js polyfill Buffer/process/crypto ก่อน boot — จำเป็นสำหรับ react-native-quick-crypto

Deps js-crypto-rsa / node-forge ใน package.json ไม่พบ import ใน src/ — dead candidates


AsyncStorage key summary

KeyWriterNotes
persist:rootredux-persistauth whitelist รวม tokens + encrypted password
languagei18nth / en
accessToken / refreshTokenAuth.ts helpersคู่ขนานกับ Redux — ApiRequest อ่านจาก store เป็นหลัก

ดู scorecard SEC-NEW-02 สำหรับความเสี่ยง plain AsyncStorage