2 · API endpoint resolution + auth/login flow + SSO handoff
(branch: uat ทุก citation ในบทนี้)
checkLinkURL() — pattern เดียวกับ canonical-truths บวก origin เพิ่ม
vtrc-backoffice-new/src/config/apollo.js:14-28
const checkLinkURL = () =>{
if(window.location.href.includes("https://vtrcbackoffice.redcross.or.th/")){
return "https://vtrcapi.redcross.or.th/api/graphql"
}else if(window.location.href.includes("https://uat-vtrcbackoffice.redcross.or.th/")){
return "https://uat-vtrcapi.redcross.or.th/api-uat/graphql"
// return "https://uat-rc-vtrcapi.redcross.or.th/api-uat/graphql"
}else if(window.location.href.includes("https://uat-rc-vtrcbackoffice.redcross.or.th/")){
// return "https://uat-rc-vtrcapi.redcross.or.th/api-uat/graphql"
return "https://uat-vtrcapi.redcross.or.th/api-uat/graphql"
}else if(window.location.href.includes("http://localhost:3000/") || window.location.href.includes("http://localhost:3001/")){
// return "http://192.168.0.125:8081/api-uat/graphql"
return "https://uat-vtrcapi.redcross.or.th/api-uat/graphql"
// return "https://uat-rc-vtrcapi.redcross.or.th/api-uat/graphql"
}
}(branch: uat)
การจับคู่ origin → endpoint
| Origin ที่เปิด | GraphQL endpoint |
|---|---|
vtrcbackoffice.redcross.or.th | vtrcapi.redcross.or.th/api/graphql (prod) |
uat-vtrcbackoffice.redcross.or.th | uat-vtrcapi.redcross.or.th/api-uat/graphql (UAT) |
uat-rc-vtrcbackoffice.redcross.or.th — origin เพิ่มที่ไม่มีใน vtrc-backoffice | uat-vtrcapi.redcross.or.th/api-uat/graphql (ชี้ endpoint UAT เดียวกัน) |
localhost:3000/3001 | UAT (default fallback) |
| origin อื่น ๆ | ไม่มี branch ตรง → undefined (bug เดียวกับ vtrc-backoffice) |
ยืนยันตรงกับ pattern checkLinkURL() แบบ window.location.href-based ที่ canonical-truths ระบุไว้สำหรับ vtrc-web/vtrc-rc-backoffice — ไม่ใช้ NODE_ENV เหมือนกันทุกประการ ต่างจาก vtrc-backoffice เพียงจุดเดียวคือมี origin เพิ่มสำหรับ uat-rc-vtrcbackoffice (คำนำหน้า rc- บ่งชี้ถึงความเชื่อมโยงกับ vtrc-rc-backoffice — ดูหัวข้อ SSO handoff ด้านล่างซึ่งเป็นหลักฐานตรงของความเชื่อมโยงนี้)
checkLinkURL() ตัวที่สองใน src/utils/auth.js (คืน host เปล่า ไม่ใช่ path GraphQL) ก็มี origin เพิ่มแบบเดียวกัน:
vtrc-backoffice-new/src/utils/auth.js:50-68
export const checkLinkURL = () =>{
if(window.location.href.includes("https://vtrcbackoffice.redcross.or.th/")){
return "https://vtrcapi.redcross.or.th/"
}else if(window.location.href.includes("https://uat-vtrcbackoffice.redcross.or.th/")){
return "https://uat-vtrcapi.redcross.or.th/"
}else if(window.location.href.includes("https://uat-rc-vtrcbackoffice.redcross.or.th/")){
return "https://uat-vtrcapi.redcross.or.th/"
}else if(window.location.href.includes("http://localhost:3000/") || window.location.href.includes("http://localhost:3001/")){
return "https://uat-vtrcapi.redcross.or.th/"
}
}(branch: uat) — เช่นเดียวกับ vtrc-backoffice ไม่พบจุด import ใช้งานจริงของฟังก์ชันนี้ในรอบตรวจนี้ (UNVERIFIED)
apiKeyNews() — เพิ่ม origin เดียวกัน
vtrc-backoffice-new/src/utils/apiKey.js:1-16
export const apiKeyNews = () => {
if(window.location.href.includes("https://vtrcbackoffice.redcross.or.th/")){
return 'd1de47c0-07a8-11eb-adc1-0242ac120002'
}else if(window.location.href.includes("https://uat-vtrcbackoffice.redcross.or.th/")){
return 'd912dfb3-e2f1-4ff6-be88-29127fd32b60'
}else if(window.location.href.includes("https://uat-rc-vtrcbackoffice.redcross.or.th/")){
return 'd912dfb3-e2f1-4ff6-be88-29127fd32b60'
}else if(window.location.href.includes("http://localhost:3000/") || window.location.href.includes("http://192.168.0.105:3000/")){
return 'd912dfb3-e2f1-4ff6-be88-29127fd32b60'
}
}(branch: uat) — device UUID เดียวกันกับ vtrc-backoffice เป๊ะ (ทั้ง prod และ UAT) ยืนยันว่า vtrc-backoffice-new ใช้ device key เดียวกันกับ vtrc-backoffice เมื่อคุย vtrc-api — backend คงมองสอง frontend นี้เป็น APP_TYPE เดียวกัน (ไม่ได้แยก key ต่างหาก)
Token refresh — อัปเกรดเป็น refreshTokenBO (รองรับ "UAM")
ต่างจาก vtrc-backoffice ที่ใช้ apolloFetch + query refreshToken, vtrc-backoffice-new เปลี่ยนมาใช้ apolloClient.query + mutation/query refreshTokenBO — pattern เดียวกับที่ v1 archive บันทึกไว้สำหรับ vtrc-rc-backoffice:
vtrc-backoffice-new/src/config/apollo.js:59-97
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
if (graphQLErrors && graphQLErrors[0].extensions.code === 'TOKEN_EXPIRED') {
return new Observable(async observer => {
const oldHeaders = operation.getContext().headers
const { errors, data } = await apolloClient.query({
query: Queries.refreshTokenBO,
variables: {
refreshToken: getToken().refreshToken,
token: getToken().token
},
});
console.log('result refreshTokenBO : appllo :', { errors, data })
if (!errors) {
// change endpoint to support UAM
// setToken(data.refreshToken)
setToken(data.refreshTokenBO)
operation.setContext({
headers: {
...oldHeaders,
// change endpoint to support UAM
// Authorization: `Bearer ${data.refreshToken.accessToken}`
Authorization: `Bearer ${data.refreshTokenBO.accessToken}`
}
})(branch: uat)
comment // change endpoint to support UAM (ปรากฏ 2 ครั้งในไฟล์นี้) เป็นหลักฐานตรงว่าโค้ดนี้ถูกแก้ไขทีหลังจาก baseline ที่เหมือน vtrc-backoffice เพื่อรองรับระบบที่ทีมเรียกว่า "UAM" (ขยายคำเต็มไม่ปรากฏในโค้ดที่ตรวจ — น่าจะเป็น Unified/User Access Management แต่ไม่มีหลักฐานยืนยันชัดเจนในรอบนี้ ทำเครื่องหมาย UNVERIFIED สำหรับความหมายเต็มของคำนี้) — เป็นหลักฐานที่ชัดเจนที่สุดว่า vtrc-backoffice-new ได้รับการแก้ไข/พัฒนาต่อหลังจากจุด fork จาก vtrc-backoffice ในขณะที่ตัวหลังไม่มีการเปลี่ยนแปลงนี้เลย (vtrc-backoffice/src/config/apollo.js ยังใช้ query refreshToken เดิม)
console.log(...) ที่บรรทัด 70 ของไฟล์นี้ยังไม่ถูกลบ (แม้ App/index.js override console.log เป็น no-op ไปแล้วที่ระดับ mount — ดังนั้น log นี้จะไม่ปรากฏจริงหลัง App mount แต่ยังเป็นโค้ดค้าง)
Login flow — เหมือน vtrc-backoffice จนถึงจุดหนึ่ง แล้วแตกไปที่ checkEmployeeCU
ขั้นตอน 1-2 (กรอกรหัสบุคลากร → captcha → รหัสผ่าน → mutation loginBackoffice) เหมือนกับ vtrc-backoffice ทุกประการ ยกเว้น variables ของ mutation ที่เพิ่ม field empCode และเอา lsid ออก (comment ทิ้งไว้):
vtrc-backoffice-new/src/pages/Login/index.js:51-62
const { data, errors } = await client.mutate({
mutation: Mutations.loginBackoffice,
variables: {
empCode: username,
// lsid: sessionStorage.getItem('lsid'),
password: password.password,
appVersion: "v1.0",
apiKey: API_DEVICE_KEY,
// organization: sessionStorage.getItem('organization'),
organization: getOrganization(),
}
})(branch: uat)
หลัง login สำเร็จ (setToken(data.loginBackoffice, 1) — bug เดียวกับ vtrc-backoffice: hardcode isRemember = 1 เสมอ ไม่สนใจ checkbox "จำฉันไว้" ที่ไม่ถูก render เช่นกัน) แอปเรียก query ใหม่ checkEmployeeCU ที่ไม่มีอยู่ใน vtrc-backoffice:
vtrc-backoffice-new/src/pages/Login/index.js:77-103
if (data) {
setToken(data.loginBackoffice, 1)
sessionStorage.removeItem('lsid')
sessionStorage.removeItem('organization')
sessionStorage.removeItem('reCaptchaCheck')
let checkEmployeeCU = await client.mutate({
mutation: Queries.checkEmployeeCU,
variables: {
empCode: username,
}
})
if(checkEmployeeCU.data.checkEmployeeCU.status){
window.location.href = '/';
}else{
let pathUrl = 'https://uat-rc-vtrcbackoffice.redcross.or.th'
if(window.location.href.includes("https://vtrcbackoffice.redcross.or.th/")){
pathUrl = "https://rc-vtrcbackoffice.redcross.or.th"
}
clearAllStorage()
window.location.href = `${pathUrl}/pathLogin/${data.loginBackoffice.accessToken}/${data.loginBackoffice.refreshToken}`;
}
}(branch: uat)
สิ่งที่เกิดขึ้นจริง — SSO handoff ไปยัง vtrc-rc-backoffice
- หลัง login สำเร็จ เรียก query/mutation
checkEmployeeCU(empCode)ที่vtrc-api— คืนค่า{ status: boolean } - ถ้า
status === true→ อยู่ต่อในvtrc-backoffice-new(window.location.href = '/') - ถ้า
statusเป็น falsy → เคลียร์ storage ทั้งหมดของvtrc-backoffice-newแล้ว redirect ไปยังvtrc-rc-backofficeพร้อม access/refresh token ฝังใน URL path ตรง:https://{rc-|uat-rc-}vtrcbackoffice.redcross.or.th/pathLogin/{accessToken}/{refreshToken}
นี่คือหลักฐานตรงของ SSO bypass ที่ v1 archive บันทึกไว้ฝั่งผู้รับ (vtrc-rc-backoffice มี route /pathLogin/:token/:refreshToken ตามที่ v1 Volume 7 ระบุไว้ — ดู docs-v1-archive/volume-07-vtrc-rc-backoffice/10-scorecard.md แถว "SSO bypass") — บทนี้ยืนยันฝั่งผู้ส่งคือ vtrc-backoffice-new ทำให้เห็นภาพครบว่า สภากาชาดมีระบบ backoffice 2 ตัวที่ทำงานคู่กันโดยแบ่งผู้ใช้ตามผลลัพธ์ของ checkEmployeeCU ไม่ใช่แอปที่แยกกันเด็ดขาด
ความหมายของ "CU" ใน checkEmployeeCU — ไม่มีหลักฐานในโค้ดที่ตรวจว่าคำนี้ย่อจากอะไร (อาจเกี่ยวกับประเภทบุคลากร/องค์กร) ทำเครื่องหมาย UNVERIFIED ห้ามเดาความหมายทางธุรกิจ
ข้อสังเกตด้าน security ของ handoff นี้ — access token และ refresh token ถูกส่งเป็น path segment ใน URL แบบข้าม origin (window.location.href = ...) ซึ่งจะถูกบันทึกใน browser history, server access log ของฝั่งรับ (vtrc-rc-backoffice's nginx), และอาจรั่วผ่าน Referer header ถ้าหน้าปลายทางโหลด resource ข้าม origin เพิ่มเติม — ตรงกับ finding เดิมของ v1 ("SSO bypass token ใน URL") แต่รอบนี้มีหลักฐานฝั่งผู้ส่งเพิ่มเข้ามาสมบูรณ์กว่าเดิม ดู SEC-BONEW-01 ใน 04-scorecard
Login/index_prd.js (ไฟล์ backup พิเศษที่มีเฉพาะใน repo นี้) มีโค้ด checkEmployeeCU เหมือนกันทุกประการกับ Login/index.js ปัจจุบัน (ตรวจด้วยการอ่านเนื้อหาไฟล์ตรง) — บ่งชี้ว่าเป็น snapshot ที่ถ่ายไว้ใกล้กับเวอร์ชันปัจจุบันมาก ไม่ใช่ "เวอร์ชันก่อนมี feature นี้" ตามที่ชื่อไฟล์อาจสื่อ
Auth gate + fetchPolicy
เหมือนกับ vtrc-backoffice ทุกประการ — isLoggedIn() boolean check ใน App/index.js, fetchPolicy: 'no-cache' เป็น default ทั้ง query/watchQuery (vtrc-backoffice-new/src/config/apollo.js:165-174, branch: uat)