Skip to content

Танилт ба эрх

Хэн нэвтэрч байна, ямар замаар, хэр удаан, юу хийж чадах вэ. Энэ баримт нь internal/workspace/auth, internal/workspace/identity, internal/workspace/ssoprovider ба internal/operator/operator-ийн кодоос уншиж бичигдсэн.

Баримтын төв · Архитектур


Нэвтрэх зам

Зам Маршрут Юу нотлох вэ
Нууц үг POST /api/v1/auth/login (+ /auth/login/second-factor TOTP) Мэддэг зүйл
Passkey /auth/passkey/begin → /finish Төхөөрөмж дээрх түлхүүр
eID (QR / нэг төхөөрөмж) /auth/eid/start → /auth/eid/poll Иргэний үнэмлэхийн гэрчилгээ
eID (регистрээр) /auth/eid/start-id Мөн адил, эхлэл нь өөр
Google /auth/google/start → /callback Гуравдагч талын бүртгэл
Федерацлагдсан SSO /auth/sso/start → /callback Байгууллагын өөрийн IdP
Peer (өөр Nexus г.м.) /auth/<key>/start → /callback Тэр provider дээрх бүртгэл
Урилга / нууц үг сэргээх POST /api/v1/auth/credential/redeem-code И-мэйлээр ирсэн 6 оронтой код
Impersonation POST /api/v1/auth/impersonation/redeem Операторын гаргасан нэг удаагийн холбоос

Дөрвөн гадаад рэйл — eID, ДАН, Google, федерацлагдсан IdP — бүгд ижил хоёр асуултаар төгсдөг: энэ нь аль локал бүртгэл вэ, ба бүртгэл байхгүй бол шинээр үүсгэж болох уу. Эхнийх нь binding, хоёр дахь нь access mode.

Клиент бүр nil байж болно. Тохируулаагүй рэйл нь алдаа биш — тохируулаагүй гэдгээ хэлдэг.

Peer — нэмэлт OIDC provider (SSO_PEERS)

SSO_CLIENT_ISSUER нэг л provider-ийг цорын ганц нэвтрэлт болгодог. Олон платформоос хүн ирдэг бүтээгдэхүүнд (contract.mn: e-property.mn-ээр нэвтэрдэг, мөн nexus.gerege.net дээр суусан external апп) хоёр дахь provider хэрэгтэй — тэр нь peer. Peer бол Apple/GitHub-ийн хажуудах товч, дүрэм нь тэдгээрийнх: анхны нэвтрэлт eID-г хүлээнэ (binding), identity-г (issuer, subject)-ээр түлхүүрлэнэ, өөр хүний бүртгэлээс хэзээ ч зөөхгүй.

SSO_PEERS=nexus
SSO_PEER_NEXUS_ISSUER=https://nexus.gerege.net
SSO_PEER_NEXUS_CLIENT_ID=contract-mn
SSO_PEER_NEXUS_CLIENT_SECRET=        # заавал биш — public клиент PKCE-ээр
SSO_PEER_NEXUS_NAME=Gerege Nexus     # товч ба binding дэлгэц дээрх нэр

Provider талд бүртгэх callback: <энэ origin>/api/v1/auth/<key>/callback. External аппын manifest-д launch_url нь <энэ origin>/api/v1/auth/<key>/start, sso_client_id нь тэр клиент, redirect_uris нь [<энэ origin>/api/v1/auth/<key>/callback] — provider тал каталогоо sync хиймэгц client-ийг өөрөө бүртгэнэ (MINI_APPS.md), install gate нь аппыг суулгаагүй байгууллагад нэвтрэлт олгохгүй. Буруу тохиргоотой peer нь startup-д log бичээд товчоо гаргахгүй.

Нэг төхөөрөмж дээр (Web2App / App2App)

/auth/eid/start (ба холбох, binding, консолын /session/eid/start) нь CA-ийн device-link session эхлүүлж, хариундаа нэг холбоос өгнө: {deviceLinkBase}?sessionId=<id>&vc=<код> (deviceLinkBase нь CA-ийн хариунаас, https биш бол https://ca.eidmongolia.mn/dl; eidrp.SameDeviceLink). Энэ нь device_link талбарт, мөн QR-ийн device_link_url-д — хоёулаа ижил. Утасны камер QR-ийг энгийн холбоос шиг нээнэ; eID аппын сканнер sessionId=, &vc=-г мөрөн дотроос хайдаг тул https хэлбэрийг ч уншина.

  • Утсан дээрх хөтөч (Web2App) болон утасны апп (App2App) хоёулаа яг энэ https холбоосыг нээнэ — eidmongolia://-г шууд биш (тэр нь /dl хуудасны дотоод нөөц; апп суугаагүй бол чимээгүй унадаг). Апп суусан бол OS холбоосыг апп руу өгнө, үгүй бол /dl хуудас аппыг оролдож, store-ийн товч санал болгоно.
  • Вэб нэвтрэлт (components/EIDLogin.tsx) утсан дээр start хүсэлтийн ганц await-ын дараа шууд шилжинэ: iOS товшилтыг нэг async алхам л барьдаг, түүнээс хойш Universal Link алгасагдана. Холбох, binding, консол callback илгээдэггүй (консол mount дээр товшилтгүй эхэлдэг) тул автоматаар шилжихгүй — «eID аппаар нээх» товч л зурна. Нэвтрэлтэд ч мөн тэр товч нөөц болж үлдэнэ; утсан дээр QR-ийн оронд товч харагдана. iPadOS-ийн desktop UA (Macintosh + maxTouchPoints > 1) утас гэж тооцогдоно.
  • Буцалт: зөвшөөрсний (OK) дараа eID апп initialCallbackUrl-ийг sessionId=<id> ба userChallengeVerifier=<…> нэмж нээнэ (бусад query хэвээр). Хөтөч рүү буцах hint — iOS retScheme, Android retPkg — зөвхөн eID аппын allowlist-д байгаа утгууд (rp-eidmongolia-demo src/lib/sameDeviceReturn.ts-тэй ижил).
  • /auth/eid/callback verifier-ийг poll-той илгээнэ; /auth/eid/poll BASE64URL(SHA-256(verifier)) нь зөвшөөрсөн хариуны signature.userChallenge-тэй тэнцэхийг (constant-time) шаардаж, таарахгүй бол flow-г зарцуулалгүй 403 өгнө. Verifier-гүй poll (эхлүүлсэн таб, desktop) өмнөх шигээ flow-оор л шүүгдэнэ.
  • Буцалт эхлүүлээгүй хөтөч/таб, эсвэл push-ийн дараа өөр утсан дээр буусан ба тэр хөтөч нэвтрээгүй бол callback хуудас алдаа биш, «eID апп дээр баталгаажлаа. Нэвтрэлтийг эхлүүлсэн цонх руугаа буцна уу.» гэж хэлнэ — эхлүүлсэн цонх poll-оороо дуусгана.
  • Callback-тай device-link session-д CA flowType "App2App"-аар гарын үсэг зурна (вэб ч, native ч); callback-гүйд "QR", push-д "Notification". Баталгаажуулалт (eidrp/verify.go) зөвхөн санал болгосон төрлийг хүлээн авна.

Native App2App callback

Утасны Nexus клиент eID аппыг eidmongolia://approve?sessionId=… хаягаар нээгээд, зөвшөөрсний дараа gerege-nexus://auth?sessionId=… хаягаар буцаж ирнэ. Энэ буцалт дөрвөн тааралтай:

  • native клиент callbackUrl-аар бүтэн gerege-nexus://auth URI-г илгээнэ;
  • Android Manifest / iOS Info.plist нь gerege-nexus scheme-ийг өөртөө холбоно;
  • Nexus backend-ийн EID_APP_CALLBACKS бүтэн URI-тэй яг таарна;
  • eID Mongolia дээрх тухайн RP-ийн callback_hosts-д gerege-nexus:// scheme бүртгэгдсэн байна.

EID_APP_CALLBACKS дутвал Nexus session эхлүүлэх хүсэлтийг 400-гаар татгалзана. RP-ийн callback_hosts-д scheme дутвал eID буцах хаягийг чимээгүй хаяна: session нь poll-оороо дуусах ч хүн eID апп дотроо үлдэнэ. Callback зөвхөн клиентийг дахин идэвхжүүлнэ; нэвтрэлтийн үр дүнг URI-гаас биш, серверийн /auth/eid/poll-оос авна.

Хаалттай эсэх — access mode

Gerege Eco System-ийн энэ платформ хаалттай: platform.access_mode=private. Зөвхөн урьдчилан бүртгэсэн эсвэл урилгаар эрх олгосон хэрэглэгч нэвтэрнэ. eID-ээр биеийн байцаалт баталгаажсан нь платформд нэвтрэх эрх автоматаар олгохгүй.

Доорх public нь кодод байгаа өөр горимын тайлбар бөгөөд энэ суулгацад ашиглах тохиргоо биш.

public   → танихгүй хүн нэвтэрч, бүртгэл нь автоматаар үүснэ
private  → зөвхөн урьдчилан бүртгэгдсэн хүн

Өмнө нь энэ асуултыг хоёр environment variable хариулдаг байсан (EID_JIT_TENANT_SLUG, SSO_CLIENT_TENANT) — хоёр файлд, хоёр багц уншдаг, «танихгүй хүн орж чадах уу» гэдгийг нэг ч газар хариулдаггүй. Одоо нэг газар хариулна.

Юуг хаадаггүй: аль хэдийн бүртгэлтэй хүн нэвтрэх; урилга (урилга нь өөрөө урьдчилсан бүртгэл); операторын консол (тэр өөр хүснэгт, өөр ертөнц).

Засвар (maintenance)

Хоёр түвшин — платформ даяар (тохиргоо) ба нэг байгууллагад (мөрийн багана) — хоёулаа хэрэглэгчийн хувьд ижил утгатай: харж болно, өөрчилж болохгүй.

Унших нь зориуд нээлттэй. Бүгдийг татгалздаг засварын горим бол сайхан мессежтэй тасалдал, харин засварын горим байх учир нь доор нь юм зөөгдөж байхад хүмүүс хэрэгтэй зүйлээ харж чаддаг байх.

Session

Ажлын урсгал Операторын урсгал
Cookie session_token cp_session
Бүтэн хугацаа 12 цаг 8 цаг
Идэвхгүй хугацаа 90 минут (SESSION_IDLE_TIMEOUT) —
Хоёр дахь хүчин зүйл — Заавал

Token нь crypto/rand-ийн 256 бит. Өгөгдлийн санд зөвхөн SHA-256 digest нь хадгалагдана, тиймээс сангийн задрал нь нэвтрэлт болж давтагдахгүй.

Идэвхгүй хугацаа нь бүтэн хугацаанаас өөр асуулт хариулдаг: «нэг нэвтрэлт хэр удаан үргэлжлэх вэ» биш, «хараа хяналтгүй компьютер хүнийг хэр удаан нэвтэрсэн байлгах вэ». Найман цагийн ажил тасрах ёсгүй; нийтийн оффисын дэлгэц үдийн хоолны дараа нэвтэрсэн хэвээр байх ёсгүй. Native клиент хоёр дахь дэлгэцэн дээр удаан хугацаагаар нээлттэй байдаг тул энэ нь минут биш, цаг зэрэгцээ хэмжээтэй.

Тохиргоо нь хүсэлт бүрд уншигдана, эхлэлд барьж авдаггүй — инцидентийн үеэр хугацааг богиносгосон оператор түүнийг хүчинтэй болгохын тулд платформыг дахин ачаалах шаардлагагүй.

Session тайлах query дахь гишүүнчлэлийн join нь шалгалт, хайлт биш: хасагдсан хүний session нь дуусах цагаа хүлээхгүй, тэр дороо ажиллахаа болино.

Native клиент: device grant

Утас бол хүний өөрийн төхөөрөмж — 12 цагийн session нь түүнд хэт богино. Шинэхэн (≤10 мин, StrongSignInMethods) нэвтэрсэн native клиент POST /api/v1/profile/device-grants-аар refresh token авч Keychain-д (ThisDeviceOnly) хадгална; session дуусахад POST /api/v1/auth/device-grants/refresh нь шинэ session ба дараагийн token-ийг өгнө (registry.device_grants, migration 00158).

Ашиглаагүй 90 хоног
Туйлын Анхны нэвтрэлтээс 365 хоног
Сэргээсэн session-ий auth_method device — step-up-д тооцогдохгүй
Хаах /auth/device-grants/revoke, «бүх газраас гарах», нууц үг солих

Token бүр нэг удаа: ашиглагдсан token дахин ирвэл хоёр эзэмшигч байна гэсэн үг тул бүхэл гэр бүл хаагдана — хулгайч ба эзэн хоёулаа дахин нэвтэрнэ. Тиймээс клиент refresh-ээ нэг урсгалд хийх ёстой.

Нууц үг таамаглах

15 минутын цонхонд 5 амжилтгүй оролдлого → түгжээ.

Түгжээ дуусмагц тоолол шинээр эхэлнэ. Тоолуур дээр нэмээд орхивол нэг удаа түгжигдсэн хүн дараа нь буруу товшилт болгоны дараа түгжигддэг болно — өөрөөр хэлбэл цонх бүрд нэг хүсэлт илгээж чаддаг хэн боловч тэр бүртгэлийг хаалттай байлгаж чадна.

Платформ өөрөө IdP

internal/workspace/ssoprovider нь OAuth2 Authorization Server ба OpenID Connect Provider. Зарлагдсан зүйл нь дэмжигдсэн байдаг:

  • authorization_code — PKCE заавал
  • refresh_token — эргэлт (rotation) ба давталтын илрүүлэлт
  • client_credentials — машин дуудагчид

Access token нь opaque, /oauth2/introspect-ээр шалгагдана; id_token нь RS256 JWT, /.well-known/jwks.json-оор баталгаажна. Access token 1 цаг, refresh token 30 хоног — эхнийх нь богино байгаа шалтгаан нь introspection-ы хариуг кэшлэсэн resource server дундаа цуцлагдсаныг мэдэхгүй.

Энэ рэйл дээр Grafana ба Nexus-ийн бусад суулгацууд нэвтэрдэг. Token нь roles ба platform_admin claim авч явна; сүүлийнх нь тухайн хүн үүсгэн байгуулагч байгууллагын админ эсэхийг хэлнэ — token аль байгууллагыг нэрлэснээс үл хамааран.

Операторын эрх

Дөрвөн үүрэг, 15 чадвар, нэг хүснэгт. «Энэ үүрэг тэрийг хийж чадах уу» гэсэн асуулт бүр тэр хүснэгтийг уншиж хариулагдана — handler дотор if role == "superadmin" гэж бичигддэггүй. Тэр нь эрхийн алдаа амьдардаг газар бөгөөд бүтэн зургийг нэг дор харах боломжгүй болгодог.

Чадвар superadmin operator support auditor
tenant.read ● ● ● ●
audit.read ● ● ● ●
operator.read ● ● ●
tenant.create ● ●
tenant.suspend ● ●
quota.write ● ●
settings.write ● ●
flags.write ● ●
announce ● ●
support.act ● ● ●
user.impersonate ● ●
tenant.delete ●
deploy.trigger ●
operator.write ●

Уншигчийн буруу таамаглах гурван зүйл:

  • Дөрвөн үүрэг нь шат биш. operator байгууллага үүсгэж чадна, support чадахгүй; support байгууллагын дотор харж чадна, operator чадахгүй. Аль нь ч нөгөөгөөсөө «дээгүүр» биш.
  • auditor бүхнийг уншиж, юу ч хийж чадахгүй. Тэр нь яг л зорилго: платформыг шалгадаг, өөрчилж чаддаггүй хүн.
  • Устгал нь нэг superadmin-ы шийдвэр, 30 хоногийн дараа. Байгууллага шууд түр зогсож, тэр хугацаанд нэг товчоор буцаана; устгах нь цаг, процесс өөрөө.

Танихгүй үүрэг ямар ч чадвар барихгүй. Жагсаалтыг мэдэхгүй зүйлийн бичсэн role багана нь нэвтэрч чаддаг ба юу ч хийж чаддаггүй бүртгэл болно — бүхнийг хийж чаддаг биш.

settings.write ба flags.write нь operator-т байгаа нь зориуд: шөнө дундын дуудлагад хариулж байгаа хүн бол оператор, superadmin биш.

Impersonation

Консолын хийдэг зүйлсээс цорын ганц нь хэрэглэгчийн өгөгдөлд хүрдэг, тиймээс тэр нь чимээгүй хийх боломжгүй байхаар барьсан. Гурван нөхцөл, аль нь ч сонголттой биш (2026-10-04-нөөс шалтгаан ба step-up код асуухаа больсон):

30 минут             — session дуусна, сунгагдахгүй
banner               — байгууллагын UI үүнийг session-ээсээ өөрөө уншина
хоёр audit мөр       — операторынх, ба байгууллагын өөрийнх

Сүүлийнх нь хамгийн чухал. Зөвхөн операторууд харж чаддаг impersonation бол цаасан бичигтэй харуулдалт болно. Мөр нь байгууллагын өөрийнх нь audit_events-д болон operator_impersonations-д бичигдэх бөгөөд тэдний админууд түүнийг унших эрхтэй — «манай өгөгдлийг хэн харсан бэ» гэдэг нь биднээс асуухгүйгээр хариулагддаг асуулт.

Дамжуулалт (handover) байдгийн шалтгаан нь cookie нь hostname давж чаддаггүйд оршино: консол нэг нэрэн дээр, session нөгөө дээр тавигдах ёстой. Консол нэг удаагийн token гаргаж, операторт холбоос өгнө; платформын тал түүнийг session болгож солино — нэг удаа, нэг минутын дотор.

Хүн, түүний өөрийн орон зай

eID-ээр нээгдсэн бүртгэлд өөрийн хаяг байдаггүй: иргэн хэн болохоо нотолсон болохоос имэйлээ хэлээгүй. Платформ түүнд Gerege дугаараас гаралтай хаяг өгнө (10000263@gemail.com). Зөвхөн ийм зохиомол хаягийг дараа нь дарж бичиж болно — хүний өөрийн бичсэн хаягт хүрэхгүй.

Нэг eID нь нэг хүн. Тэр нь unique index-ээр хэрэгжинэ, Go дотор биш.