@gerege-systems/eid-mongolia-sdk 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -12
- package/dist/{browser-Db82DlYN.d.cts → browser-DqKDj2nB.d.cts} +4 -3
- package/dist/{browser-Db82DlYN.d.ts → browser-DqKDj2nB.d.ts} +4 -3
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/index.cjs +289 -0
- package/dist/index.d.cts +141 -5
- package/dist/index.d.ts +141 -5
- package/dist/index.js +283 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
(authentication) ба хууль ёсны цахим гарын үсэг (qualified signature) хийлгэх Node.js/TypeScript сан.
|
|
5
5
|
RP-API v3, Smart-ID нийцтэй. Бүрэн баримт: <https://developer.eidmongolia.mn/>.
|
|
6
6
|
|
|
7
|
-
> ⚠️
|
|
8
|
-
>
|
|
7
|
+
> ⚠️ SDK **backend**-д ажиллана: бүх дуудлага API secret (`rp_sk_…`)-ээр баталгаажна, secret-ийг браузер/гар утсанд
|
|
8
|
+
> хэзээ ч задлахгүй. Браузерт зөвхөн таны backend-ийн өгсөн холбоос/QR/товч харагдана — «eID-ээр нэвтрэх» товч ч
|
|
9
|
+
> энгийн холбоос, `code` → `id_token` солих нь backend-д (§1a). `@gerege-systems/eid-mongolia-sdk/browser`-ийн
|
|
10
|
+
> legacy device-link туслахууд 2026-10-07-оос ажиллахгүй (§4).
|
|
9
11
|
|
|
10
12
|
## Суулгах
|
|
11
13
|
|
|
@@ -17,9 +19,111 @@ Node.js ≥ 22 (global `fetch`, `node:crypto`).
|
|
|
17
19
|
|
|
18
20
|
## 1. RP бүртгүүлэх
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
RP-г eID Mongolia-гийн оператор бүртгэнэ — хүсэлтээ <eIDsupport@gerege.com> эсвэл 7777-3773-аар гаргана
|
|
23
|
+
(нийтийн `POST /v3/rp-applications` 2026-10-14-нөөс засвартай: `503 rp_applications_maintenance`). Оператор
|
|
24
|
+
баталсны дараа **relyingPartyUUID** ба **API secret** (`rp_sk_…`) олгогдоно; secret **зөвхөн нэг удаа** харагдана.
|
|
25
|
+
Шаардлагатай мэдээлэл (байгууллага, зорилго `AUTH`/`SIGN`, `callbackHosts`, OIDC redirect URI…):
|
|
26
|
+
<https://developer.eidmongolia.mn/#registration>.
|
|
27
|
+
|
|
28
|
+
## 1a. «eID-ээр нэвтрэх» (OpenID Connect)
|
|
29
|
+
|
|
30
|
+
Хамгийн хялбар нэвтрэлт: таны вэбэд нэг товч, нэвтрэх хуудсыг (QR · утсан дээрх аппаар нээх · регистрээр
|
|
31
|
+
мэдэгдэл) eID сервер өөрөө харуулж, гарын үсэг ба гэрчилгээний гинжийг шалгаад танд стандарт OpenID Connect
|
|
32
|
+
(Authorization Code + PKCE `S256`) `id_token` өгнө — Google/Apple нэвтрэлттэй ижил. Утасны хөтчөөс дарахад
|
|
33
|
+
хуудасгүйгээр шууд eID апп нээгдэнэ. Гэрээ: <https://developer.eidmongolia.mn/#oidc>.
|
|
34
|
+
|
|
35
|
+
**Хаана юу ажиллах.** Товч нь браузерт (энгийн `<a href>` таны backend-ийн `/auth/eid/login` руу); `EidOidcClient`
|
|
36
|
+
нь backend-д — authorize URL угсрах, callback-д `code`-ийг `client_secret`-ээр `id_token` болгон солих, гарын үсгийг
|
|
37
|
+
шалгах. eID-ийн OIDC client нь одоогоор зөвхөн **confidential client** (`client_secret_basic`): backend-гүй SPA эсвэл
|
|
38
|
+
native апп дангаараа token endpoint-ийг дуудаж чадахгүй — native апп ч `code`-ийг өөрийн backend-ээр солино (доор).
|
|
39
|
+
|
|
40
|
+
**Бүртгэл.** `client_id` = таны RP UUID (`relyingPartyUUID`), `client_secret` = RP API secret (`rp_sk_…`).
|
|
41
|
+
RP-д `AUTH` эрх, OIDC асаалттай, `redirect_uri` (https, ≤ 10) бүртгэлтэй байна — authorize/token-ий `redirect_uri`
|
|
42
|
+
бүртгэлтэйтэй **тэмдэгт бүрээрээ** таарах ёстой. Иргэний ETSI дугаар (`pnomn` claim) хэрэгтэй бол `PNOMN` эрх
|
|
43
|
+
хүсэж, `scope: "openid pnomn"`. Token endpoint-д RP-ийн IP allowlist мөрдөгдөнө; `requireMtls` RP OIDC ашиглахгүй.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { EidOidcClient, EidOidcError } from "@gerege-systems/eid-mongolia-sdk";
|
|
47
|
+
|
|
48
|
+
const oidc = new EidOidcClient({
|
|
49
|
+
clientId: process.env.EID_RP_UUID!,
|
|
50
|
+
clientSecret: process.env.EID_SECRET!, // rp_sk_… — зөвхөн backend
|
|
51
|
+
redirectUri: "https://bank.example.mn/auth/eid/callback",
|
|
52
|
+
// issuer анхдагч https://login.eidmongolia.mn, scope анхдагч "openid"
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const TX = "eid_oidc_tx";
|
|
56
|
+
const cookieOpts = { httpOnly: true, secure: true, sameSite: "lax", path: "/auth/eid", maxAge: 600_000 } as const;
|
|
57
|
+
|
|
58
|
+
// GET /auth/eid/login — товчны href
|
|
59
|
+
app.get("/auth/eid/login", async (_req, res) => {
|
|
60
|
+
const a = await oidc.createAuthorization(); // { url, state, nonce, codeVerifier }
|
|
61
|
+
res.cookie(TX, JSON.stringify({ state: a.state, nonce: a.nonce, codeVerifier: a.codeVerifier }), cookieOpts);
|
|
62
|
+
res.set("Cache-Control", "no-store").redirect(302, a.url);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// GET /auth/eid/callback?code&state (эсвэл ?error) — бүртгэлтэй redirect_uri
|
|
66
|
+
app.get("/auth/eid/callback", async (req, res) => {
|
|
67
|
+
const tx = JSON.parse(req.cookies[TX] ?? "null");
|
|
68
|
+
res.clearCookie(TX, { path: "/auth/eid" });
|
|
69
|
+
if (!tx) return res.redirect(303, "/login?error=state");
|
|
70
|
+
try {
|
|
71
|
+
const { claims } = await oidc.handleCallback(req.originalUrl, tx); // state → token → id_token (ES256)
|
|
72
|
+
const user = await users.findOrCreateBySub(claims.sub); // pairwise sub-аар таньна (нэрээр биш)
|
|
73
|
+
await startSession(res, user); // таны session-ийг шинээр үүсгэ (session fixation)
|
|
74
|
+
res.redirect(303, "/");
|
|
75
|
+
} catch (e) {
|
|
76
|
+
const code = e instanceof EidOidcError ? e.code : "server_error"; // access_denied → «дахин оролдоно уу»
|
|
77
|
+
res.redirect(303, `/login?error=${code}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Next.js App Router-д ижил: `app/auth/eid/login/route.ts` → `NextResponse.redirect(a.url, 302)` + `res.cookies.set(…)`;
|
|
83
|
+
callback → `oidc.handleCallback(request.nextUrl.searchParams, tx)` (зөвхөн query уншина — прокси ард host хамаагүй).
|
|
84
|
+
|
|
85
|
+
**Товч** — энгийн холбоос (`<a href>` эсвэл form GET). `fetch`/XHR/JS redirect биш: iOS Universal Link зөвхөн
|
|
86
|
+
хэрэглэгчийн tap-аас эхэлсэн redirect гинжид eID аппыг нээнэ.
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<a class="eid-login" href="/auth/eid/login">
|
|
90
|
+
<img src="/img/eid-mongolia.png" alt="" width="20" height="20">
|
|
91
|
+
eID Mongolia-аар нэвтрэх
|
|
92
|
+
</a>
|
|
93
|
+
<style>
|
|
94
|
+
.eid-login { display: inline-flex; align-items: center; justify-content: center; gap: .5rem; min-height: 44px;
|
|
95
|
+
padding: 0 1.25rem; border: 1px solid #d0d5dd; border-radius: 6px; background: #fff; color: #111827;
|
|
96
|
+
font: 500 1rem/1 system-ui, sans-serif; text-decoration: none; }
|
|
97
|
+
.eid-login:hover { background: #f5f7fa; }
|
|
98
|
+
.eid-login:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
|
|
99
|
+
</style>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Бичвэр «eID Mongolia-аар нэвтрэх», зүүн талд eID Mongolia лого (<https://developer.eidmongolia.mn/logo-96.png>-ийг
|
|
103
|
+
татаж өөрийн хостоос үйлчил). Google/Apple-ийн товч шиг саармаг (цагаан, хүрээтэй) — таны сайтын бусад нэвтрэх
|
|
104
|
+
товчтой зэрэгцэж болно. Жишээ: <https://demo.eidmongolia.mn>.
|
|
105
|
+
|
|
106
|
+
`createAuthorization({ display: "page" })` — утсан дээр ч нэвтрэх хуудас (QR / push) харуулна (ж: таблет киоск).
|
|
107
|
+
|
|
108
|
+
**`id_token` claims** (`EidIdTokenClaims`): `sub` (pairwise — RP тус бүрд өөр, иргэнд тогтмол), `acr`
|
|
109
|
+
(`urn:eidmongolia:loa:qualified` | `…:advanced`), `auth_time`, `given_name` / `family_name` (латин, гэрчилгээнээс),
|
|
110
|
+
`given_name_mn` / `family_name_mn` (кирилл, байвал), `pnomn` (`pnomn` scope + эрхтэй үед). `id_token` 5 минут
|
|
111
|
+
хүчинтэй; access_token / refresh_token / userinfo байхгүй — нэвтрэлтийн дараа өөрийн session-оо хэрэглэнэ.
|
|
112
|
+
|
|
113
|
+
**Аюулгүй байдал.**
|
|
114
|
+
|
|
115
|
+
- `state`, `nonce`, `codeVerifier`-ийг **хөтчийн session-тэй** (httpOnly, Secure, SameSite=Lax cookie эсвэл server
|
|
116
|
+
session) холбож хадгална; `handleCallback` state-ийг timing-safe тулгаж, таарахгүй бол code-ийг солихгүй
|
|
117
|
+
(`state_mismatch`). Үүнгүйгээр «өөрийн бүртгэлд хохирогчийн eID-ийг холбуулах» (login CSRF) нээлттэй.
|
|
118
|
+
- `handleCallback` / `verifyIdToken` нь ES256 гарын үсэг (JWKS `kid`, cache 1 цаг, танихгүй `kid` → нэг удаа дахин
|
|
119
|
+
татна), `iss`, `aud` (= `clientId`; олон aud бол `azp`), `exp` / `iat` (±60 сек), `nonce`-ийг шалгана.
|
|
120
|
+
`EidOidcError` (`invalid_id_token`) гарвал **итгэхгүй**.
|
|
121
|
+
- Code нэг удаагийн, 60 секунд — callback-ийг дахин ачаалбал `invalid_grant`; хэрэглэгчийг дахин нэвтрүүлнэ.
|
|
122
|
+
- `error_description` (`EidOidcError.description`)-ийг логт бичиж болно, хэрэглэгчид шууд харуулахгүй.
|
|
123
|
+
- **Native апп (RFC 8252):** authorize-ийг системийн хөтчөөр нээнэ — iOS `ASWebAuthenticationSession`
|
|
124
|
+
(`prefersEphemeralWebBrowserSession = false`), Android Custom Tabs. **WebView хориотой.** `redirect_uri` = таны
|
|
125
|
+
https Universal Link / App Link (custom scheme дэмжихгүй). `state` / `nonce` / `codeVerifier` апп-ын санах ойд;
|
|
126
|
+
code-ийг таны backend `client_secret`-ээр солино (`client_secret`-ийг аппад хадгалахгүй).
|
|
23
127
|
|
|
24
128
|
## 2. Нэвтрэлт (push)
|
|
25
129
|
|
|
@@ -36,7 +140,7 @@ const eid = new EidClient({
|
|
|
36
140
|
trust: { trustAnchorsPem: [process.env.EID_NATIONAL_ROOT_CA_PEM!] }, // ← Mongolian National Root CA, production-д ЗААВАЛ
|
|
37
141
|
});
|
|
38
142
|
|
|
39
|
-
// ETSI (PNOMN-<иргэний дугаар>) / регистр / иргэний дугаар — аль нь ч болно (сервер
|
|
143
|
+
// ETSI (PNOMN-<иргэний дугаар>) / регистр / иргэний дугаар — аль нь ч болно (сервер танина)
|
|
40
144
|
const s = await eid.auth.notificationByEtsi("PNOMN-12345678", [
|
|
41
145
|
{ type: "displayTextAndPIN", displayText60: "Хаан Банк-д нэвтрэх" },
|
|
42
146
|
]);
|
|
@@ -139,7 +243,8 @@ import { buildDeviceLink, type DeviceLinkSession } from "@gerege-systems/eid-mon
|
|
|
139
243
|
// backend — session эхлүүлээд бүх хариуг СЕРВЕРТ хадгална (sessionToken/sessionSecret/receivedAt/acsp)
|
|
140
244
|
const s = await eid.auth.deviceLinkAnonymous(
|
|
141
245
|
[{ type: "displayTextAndPIN", displayText60: "Хаан Банк-д нэвтрэх" }],
|
|
142
|
-
{ callbackUrl: "https://bank.example.mn/eid/callback?state=…" }, // товч харуулах
|
|
246
|
+
{ callbackUrl: "https://bank.example.mn/auth/eid/callback?state=…" }, // товч харуулах бол; host нь callbackHosts-д,
|
|
247
|
+
// зам нь үргэлж /auth/eid/callback болж нормчлогдоно (зөвхөн query хадгалагдана), https заавал
|
|
143
248
|
);
|
|
144
249
|
store.put(s.sessionId, s);
|
|
145
250
|
res.json({ sessionId: s.sessionId, vc: s.vc }); // браузерт ЗӨВХӨН эдгээр
|
|
@@ -188,10 +293,9 @@ eid.validator.validateAuth(result, s.acsp, {
|
|
|
188
293
|
QR ба товч хоёуланг харуулсан хуудсанд: callback-аар ирсэн үр дүнг `Web2App`/`App2App`-аар, poll-оор ирснийг
|
|
189
294
|
`QR`-аар шалга (товчоор нээгдсэн session зөвхөн callback-аар дуусна).
|
|
190
295
|
|
|
191
|
-
> ⚠️
|
|
192
|
-
>
|
|
193
|
-
>
|
|
194
|
-
> `openOrShowQR` **deprecated** — тэр мөчөөс сервер статик холбоосыг хүлээн авахгүй.
|
|
296
|
+
> ⚠️ **Legacy холбоос хасагдсан (2026-10-07).** Статик `deviceLink(session)` (`{deviceLinkBase}?sessionId=…&vc=…`)
|
|
297
|
+
> болон `/browser` entry-ийн `openOrShowQR`-ээр нээгдсэн session-д сервер утасны үйлдлийг `DEVICE_LINK_REQUIRED`-аар
|
|
298
|
+
> татгалзана — зөвхөн v3 (`buildDeviceLink`) ажиллана. Аппын доод хувилбар: iOS 2.2.3 (59), Android 70.
|
|
195
299
|
|
|
196
300
|
## Аюулгүй байдал — яагаад `validator` заавал
|
|
197
301
|
|
|
@@ -206,7 +310,7 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
206
310
|
- **auth** → ACSP_V2 payload (`smart-id|ACSP_V2|serverRandom|rpChallenge|userChallenge|B64(rpName)|B64(brokeredRpName)|B64(SHA-256(interactions))|interactionTypeUsed|initialCallbackUrl|flowType`, hash = хүсэлтийн `hashAlgorithm`) дээр — `flowType`, `userChallenge`, `serverRandom` ба алгоритмын downgrade-ийг мөн шалгана;
|
|
207
311
|
`expectedFlowType` өгвөл `flowType` эхлүүлсэн урсгалтай ЯГ таарах, Web2App/App2App-д `userChallengeVerifier` заавал
|
|
208
312
|
`initialCallbackUrl`-ийг хариуны `signature.initialCallbackUrl`-аас авна (CA урсгалаар шийднэ: QR/Notification →
|
|
209
|
-
`""`, Web2App/App2App → таны callback
|
|
313
|
+
`""`, Web2App/App2App → таны callback) — зөвхөн `""` эсвэл
|
|
210
314
|
таны илгээсэн callback байхад, Web2App/App2App-д заавал таны callback; өөр утга → татгалзана
|
|
211
315
|
- **sign** → RP-ийн өгсөн digest дээр
|
|
212
316
|
|
|
@@ -222,6 +326,7 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
222
326
|
| `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
|
|
223
327
|
| `ApiError` | бусад HTTP алдаа (`.status`, `.body`, серверийн `.code` — ж: 404 `ORG_NOT_FOUND`) |
|
|
224
328
|
| `NetworkError` | timeout/сүлжээ |
|
|
329
|
+
| `EidOidcError` | «eID-ээр нэвтрэх» (OIDC) — `.code`: `access_denied`, `login_required`, `state_mismatch`, `invalid_grant`, `invalid_id_token`, `server_error` … |
|
|
225
330
|
| `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
|
|
226
331
|
|
|
227
332
|
## mTLS (eIDAS qualified орчин)
|
|
@@ -216,9 +216,8 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
|
|
|
216
216
|
* QR-д эсвэл «eID аппаар нээх» товчинд тавина.
|
|
217
217
|
*
|
|
218
218
|
* @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
|
|
219
|
+
* Сервер 2026-10-07-нд хассан: ийм холбоосоор нээгдсэн session-д утасны үйлдлийг `DEVICE_LINK_REQUIRED`-аар
|
|
220
|
+
* татгалзана. Backend-д {@link buildDeviceLink} (v3) ашиглана.
|
|
222
221
|
*
|
|
223
222
|
* @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
|
|
224
223
|
*/
|
|
@@ -251,6 +250,8 @@ interface OpenOrShowQROptions {
|
|
|
251
250
|
* Link-ийг алгасаж /dl хуудсыг нээдэг; тэр хуудас «Аппаар нээх» товч ба store холбоосыг санал болгоно.
|
|
252
251
|
*
|
|
253
252
|
* @throws {DeviceLinkError} холбоос угсрах боломжгүй бол ({@link deviceLink}).
|
|
253
|
+
* @deprecated Legacy {@link deviceLink} холбоос дээр тулгуурладаг — сервер 2026-10-07-нд хассан тул ажиллахгүй.
|
|
254
|
+
* QR-ийг backend-ийн `buildDeviceLink` (v3)-ээр, утсан дээр Web2App холбоосыг энгийн `<a href>`-ээр харуулна.
|
|
254
255
|
*/
|
|
255
256
|
declare function openOrShowQR(session: DeviceLinkInput, opts: OpenOrShowQROptions): "opened" | "qr";
|
|
256
257
|
|
|
@@ -216,9 +216,8 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
|
|
|
216
216
|
* QR-д эсвэл «eID аппаар нээх» товчинд тавина.
|
|
217
217
|
*
|
|
218
218
|
* @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
|
|
219
|
+
* Сервер 2026-10-07-нд хассан: ийм холбоосоор нээгдсэн session-д утасны үйлдлийг `DEVICE_LINK_REQUIRED`-аар
|
|
220
|
+
* татгалзана. Backend-д {@link buildDeviceLink} (v3) ашиглана.
|
|
222
221
|
*
|
|
223
222
|
* @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
|
|
224
223
|
*/
|
|
@@ -251,6 +250,8 @@ interface OpenOrShowQROptions {
|
|
|
251
250
|
* Link-ийг алгасаж /dl хуудсыг нээдэг; тэр хуудас «Аппаар нээх» товч ба store холбоосыг санал болгоно.
|
|
252
251
|
*
|
|
253
252
|
* @throws {DeviceLinkError} холбоос угсрах боломжгүй бол ({@link deviceLink}).
|
|
253
|
+
* @deprecated Legacy {@link deviceLink} холбоос дээр тулгуурладаг — сервер 2026-10-07-нд хассан тул ажиллахгүй.
|
|
254
|
+
* QR-ийг backend-ийн `buildDeviceLink` (v3)-ээр, утсан дээр Web2App холбоосыг энгийн `<a href>`-ээр харуулна.
|
|
254
255
|
*/
|
|
255
256
|
declare function openOrShowQR(session: DeviceLinkInput, opts: OpenOrShowQROptions): "opened" | "qr";
|
|
256
257
|
|
package/dist/browser.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-
|
|
1
|
+
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-DqKDj2nB.cjs';
|
package/dist/browser.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-
|
|
1
|
+
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-DqKDj2nB.js';
|
package/dist/index.cjs
CHANGED
|
@@ -25,8 +25,13 @@ __export(index_exports, {
|
|
|
25
25
|
AuthenticationError: () => AuthenticationError,
|
|
26
26
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
27
27
|
DeviceLinkError: () => DeviceLinkError,
|
|
28
|
+
EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
|
|
29
|
+
EID_ACR_QUALIFIED: () => EID_ACR_QUALIFIED,
|
|
30
|
+
EID_OIDC_ISSUER: () => EID_OIDC_ISSUER,
|
|
28
31
|
EidClient: () => EidClient,
|
|
29
32
|
EidError: () => EidError,
|
|
33
|
+
EidOidcClient: () => EidOidcClient,
|
|
34
|
+
EidOidcError: () => EidOidcError,
|
|
30
35
|
ForbiddenError: () => ForbiddenError,
|
|
31
36
|
Http: () => Http,
|
|
32
37
|
NetworkError: () => NetworkError,
|
|
@@ -46,6 +51,7 @@ __export(index_exports, {
|
|
|
46
51
|
isMobileUserAgent: () => isMobileUserAgent,
|
|
47
52
|
openOrShowQR: () => openOrShowQR,
|
|
48
53
|
parseSessionResult: () => parseSessionResult,
|
|
54
|
+
pkceChallenge: () => pkceChallenge,
|
|
49
55
|
qrDeviceLinkTicker: () => qrDeviceLinkTicker,
|
|
50
56
|
randomChallenge: () => randomChallenge,
|
|
51
57
|
resolveBaseUrl: () => resolveBaseUrl,
|
|
@@ -1032,6 +1038,283 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
1032
1038
|
t.unref?.();
|
|
1033
1039
|
return () => clearInterval(t);
|
|
1034
1040
|
}
|
|
1041
|
+
|
|
1042
|
+
// src/oidc.ts
|
|
1043
|
+
var import_node_crypto4 = require("crypto");
|
|
1044
|
+
var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
|
|
1045
|
+
var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
|
|
1046
|
+
var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
|
|
1047
|
+
var CLOCK_SKEW_S = 60;
|
|
1048
|
+
var CACHE_MS = 60 * 60 * 1e3;
|
|
1049
|
+
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
1050
|
+
var KNOWN_CODES = /* @__PURE__ */ new Set([
|
|
1051
|
+
"access_denied",
|
|
1052
|
+
"login_required",
|
|
1053
|
+
"invalid_request",
|
|
1054
|
+
"invalid_scope",
|
|
1055
|
+
"unauthorized_client",
|
|
1056
|
+
"unsupported_response_type",
|
|
1057
|
+
"request_not_supported",
|
|
1058
|
+
"temporarily_unavailable",
|
|
1059
|
+
"server_error",
|
|
1060
|
+
"invalid_client",
|
|
1061
|
+
"invalid_grant",
|
|
1062
|
+
"unsupported_grant_type"
|
|
1063
|
+
]);
|
|
1064
|
+
var EidOidcError = class extends EidError {
|
|
1065
|
+
constructor(code, message, description) {
|
|
1066
|
+
super(message);
|
|
1067
|
+
this.code = code;
|
|
1068
|
+
this.description = description;
|
|
1069
|
+
}
|
|
1070
|
+
code;
|
|
1071
|
+
description;
|
|
1072
|
+
};
|
|
1073
|
+
var EidOidcClient = class {
|
|
1074
|
+
constructor(cfg) {
|
|
1075
|
+
this.cfg = cfg;
|
|
1076
|
+
if (!cfg.clientId) throw new EidError("EidOidcClient: clientId \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
1077
|
+
if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
1078
|
+
if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
|
|
1079
|
+
this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
|
|
1080
|
+
this.scope = cfg.scope ?? "openid";
|
|
1081
|
+
}
|
|
1082
|
+
cfg;
|
|
1083
|
+
issuer;
|
|
1084
|
+
scope;
|
|
1085
|
+
discovery;
|
|
1086
|
+
jwks;
|
|
1087
|
+
/** Discovery document (1 цаг cache). `issuer` нь тохиргоотой яг таарах ёстой (OIDC Discovery §4.3). */
|
|
1088
|
+
discover() {
|
|
1089
|
+
const c = this.discovery;
|
|
1090
|
+
if (c && Date.now() - c.at < CACHE_MS) return c.d;
|
|
1091
|
+
const d = this.fetchDiscovery();
|
|
1092
|
+
this.discovery = { at: Date.now(), d };
|
|
1093
|
+
d.catch(() => {
|
|
1094
|
+
if (this.discovery?.d === d) this.discovery = void 0;
|
|
1095
|
+
});
|
|
1096
|
+
return d;
|
|
1097
|
+
}
|
|
1098
|
+
async fetchDiscovery() {
|
|
1099
|
+
const d = await this.getJson(this.issuer + "/.well-known/openid-configuration");
|
|
1100
|
+
if (!d || d.issuer !== this.issuer) {
|
|
1101
|
+
throw new EidOidcError("discovery_failed", `OIDC discovery-\u0438\u0439\u043D issuer \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439 (\u0445\u04AF\u043B\u044D\u044D\u0441\u044D\u043D ${this.issuer})`);
|
|
1102
|
+
}
|
|
1103
|
+
for (const k of ["authorization_endpoint", "token_endpoint", "jwks_uri"]) {
|
|
1104
|
+
if (typeof d[k] !== "string" || !d[k].startsWith("https://")) {
|
|
1105
|
+
throw new EidOidcError("discovery_failed", `OIDC discovery: ${k} \u0431\u0443\u0440\u0443\u0443`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return d;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Authorize URL + санамсаргүй state / nonce / PKCE codeVerifier. RP буцаасан `state`, `nonce`, `codeVerifier`-ийг
|
|
1112
|
+
* хөтчийн session-тэй холбож (httpOnly cookie эсвэл server session) хадгалаад хөтчийг `url` руу 302 хийнэ.
|
|
1113
|
+
*/
|
|
1114
|
+
async createAuthorization(opts = {}) {
|
|
1115
|
+
const d = await this.discover();
|
|
1116
|
+
const state = randomB64url(32);
|
|
1117
|
+
const nonce = randomB64url(32);
|
|
1118
|
+
const codeVerifier = randomB64url(32);
|
|
1119
|
+
const u = new URL(d.authorization_endpoint);
|
|
1120
|
+
const p = new URLSearchParams({
|
|
1121
|
+
response_type: "code",
|
|
1122
|
+
client_id: this.cfg.clientId,
|
|
1123
|
+
redirect_uri: this.cfg.redirectUri,
|
|
1124
|
+
scope: opts.scope ?? this.scope,
|
|
1125
|
+
state,
|
|
1126
|
+
nonce,
|
|
1127
|
+
code_challenge: await pkceChallenge(codeVerifier),
|
|
1128
|
+
code_challenge_method: "S256"
|
|
1129
|
+
});
|
|
1130
|
+
if (opts.display) p.set("display", opts.display);
|
|
1131
|
+
u.search = p.toString();
|
|
1132
|
+
return { url: u.toString(), state, nonce, codeVerifier };
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* redirect_uri руу ирсэн callback-ийг шийднэ: state тулгах → `error` бол {@link EidOidcError} → code солих →
|
|
1136
|
+
* id_token шалгах. `input` — бүтэн callback URL, харьцангуй зам (`/cb?code=…`) эсвэл query (`URLSearchParams` / `code=…&state=…`); зөвхөн query-г уншина.
|
|
1137
|
+
* Амжилтгүй бүх тохиолдолд {@link EidOidcError} (эсвэл {@link NetworkError}) шиднэ.
|
|
1138
|
+
*/
|
|
1139
|
+
async handleCallback(input, checks) {
|
|
1140
|
+
const q = callbackParams(input);
|
|
1141
|
+
const state = q.get("state") ?? "";
|
|
1142
|
+
if (!checks.state || !timingSafeEqualStr(state, checks.state)) {
|
|
1143
|
+
throw new EidOidcError("state_mismatch", "OIDC state \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439 \u2014 \u043D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u0433 \u0434\u0430\u0445\u0438\u043D \u044D\u0445\u043B\u04AF\u04AF\u043B\u043D\u044D \u04AF\u04AF");
|
|
1144
|
+
}
|
|
1145
|
+
const iss = q.get("iss");
|
|
1146
|
+
if (iss !== null && iss !== this.issuer) {
|
|
1147
|
+
throw new EidOidcError("invalid_callback", "OIDC callback-\u0438\u0439\u043D iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1148
|
+
}
|
|
1149
|
+
const err = q.get("error");
|
|
1150
|
+
if (err) {
|
|
1151
|
+
const code2 = KNOWN_CODES.has(err) ? err : "server_error";
|
|
1152
|
+
throw new EidOidcError(code2, `eID \u043D\u044D\u0432\u0442\u0440\u044D\u043B\u0442 \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${code2}`, q.get("error_description") ?? void 0);
|
|
1153
|
+
}
|
|
1154
|
+
const code = q.get("code") ?? "";
|
|
1155
|
+
if (code === "" || code.length > 512) throw new EidOidcError("invalid_callback", "OIDC callback-\u0434 code \u0430\u043B\u0433\u0430");
|
|
1156
|
+
const idToken = await this.exchangeCode(code, checks.codeVerifier);
|
|
1157
|
+
const claims = await this.verifyIdToken(idToken, { nonce: checks.nonce });
|
|
1158
|
+
return { claims, idToken };
|
|
1159
|
+
}
|
|
1160
|
+
/** `POST token_endpoint` (client_secret_basic, RFC 6749 §2.3.1) → id_token. */
|
|
1161
|
+
async exchangeCode(code, codeVerifier) {
|
|
1162
|
+
const d = await this.discover();
|
|
1163
|
+
const basic = Buffer.from(formEncode(this.cfg.clientId) + ":" + formEncode(this.cfg.clientSecret)).toString("base64");
|
|
1164
|
+
const res = await this.fetch(d.token_endpoint, {
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
headers: {
|
|
1167
|
+
Authorization: "Basic " + basic,
|
|
1168
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1169
|
+
Accept: "application/json"
|
|
1170
|
+
},
|
|
1171
|
+
body: new URLSearchParams({
|
|
1172
|
+
grant_type: "authorization_code",
|
|
1173
|
+
code,
|
|
1174
|
+
redirect_uri: this.cfg.redirectUri,
|
|
1175
|
+
code_verifier: codeVerifier
|
|
1176
|
+
}).toString()
|
|
1177
|
+
});
|
|
1178
|
+
let j = {};
|
|
1179
|
+
try {
|
|
1180
|
+
j = await res.json();
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1183
|
+
if (!res.ok) {
|
|
1184
|
+
const e = typeof j.error === "string" && KNOWN_CODES.has(j.error) ? j.error : "server_error";
|
|
1185
|
+
throw new EidOidcError(
|
|
1186
|
+
e,
|
|
1187
|
+
`OIDC token ${res.status}: ${e}`,
|
|
1188
|
+
typeof j.error_description === "string" ? j.error_description : void 0
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
if (typeof j.id_token !== "string" || j.id_token === "") {
|
|
1192
|
+
throw new EidOidcError("server_error", "OIDC token \u0445\u0430\u0440\u0438\u0443\u043D\u0434 id_token \u0430\u043B\u0433\u0430");
|
|
1193
|
+
}
|
|
1194
|
+
return j.id_token;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* id_token шалгана: JWKS (`kid`, cache; танихгүй kid → нэг удаа дахин татна) ES256 гарын үсэг, `iss`, `aud`
|
|
1198
|
+
* (= clientId; олон aud бол `azp`), `exp`/`iat` (±60 сек), `nonce`. Амжилтгүй бол `invalid_id_token`.
|
|
1199
|
+
*/
|
|
1200
|
+
async verifyIdToken(idToken, opts) {
|
|
1201
|
+
const bad = (why) => new EidOidcError("invalid_id_token", `id_token: ${why}`);
|
|
1202
|
+
const parts = idToken.split(".");
|
|
1203
|
+
if (parts.length !== 3) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1204
|
+
const header = decodeJsonPart(parts[0]);
|
|
1205
|
+
const payload = decodeJsonPart(parts[1]);
|
|
1206
|
+
if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1207
|
+
if (header.alg !== "ES256") throw bad("alg ES256 \u0431\u0438\u0448");
|
|
1208
|
+
const kid = typeof header.kid === "string" ? header.kid : "";
|
|
1209
|
+
if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
|
|
1210
|
+
const pick = (ks) => ks.find(
|
|
1211
|
+
(k) => k.kid === kid && k.kty === "EC" && k.crv === "P-256" && (k.use === void 0 || k.use === "sig") && (k.alg === void 0 || k.alg === "ES256")
|
|
1212
|
+
);
|
|
1213
|
+
const jwk = pick(await this.keys(false)) ?? pick(await this.keys(true));
|
|
1214
|
+
if (!jwk || !jwk.x || !jwk.y) throw bad(`\u0442\u0430\u043D\u0438\u0445\u0433\u04AF\u0439 kid ${kid}`);
|
|
1215
|
+
const sig = Buffer.from(parts[2], "base64url");
|
|
1216
|
+
if (sig.length !== 64) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
1217
|
+
let ok = false;
|
|
1218
|
+
try {
|
|
1219
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
1220
|
+
"jwk",
|
|
1221
|
+
{ kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y },
|
|
1222
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
1223
|
+
false,
|
|
1224
|
+
["verify"]
|
|
1225
|
+
);
|
|
1226
|
+
ok = await globalThis.crypto.subtle.verify(
|
|
1227
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
1228
|
+
key,
|
|
1229
|
+
sig,
|
|
1230
|
+
new TextEncoder().encode(parts[0] + "." + parts[1])
|
|
1231
|
+
);
|
|
1232
|
+
} catch {
|
|
1233
|
+
ok = false;
|
|
1234
|
+
}
|
|
1235
|
+
if (!ok) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
1236
|
+
const c = payload;
|
|
1237
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1238
|
+
if (c.iss !== this.issuer) throw bad("iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1239
|
+
const aud = Array.isArray(c.aud) ? c.aud : [c.aud];
|
|
1240
|
+
if (!aud.includes(this.cfg.clientId)) throw bad("aud \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1241
|
+
if (aud.length > 1 && c.azp !== this.cfg.clientId) throw bad("azp \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1242
|
+
if (typeof c.exp !== "number" || now > c.exp + CLOCK_SKEW_S) throw bad("\u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0434\u0443\u0443\u0441\u0441\u0430\u043D");
|
|
1243
|
+
if (typeof c.iat !== "number" || c.iat > now + CLOCK_SKEW_S) throw bad("iat \u0438\u0440\u044D\u044D\u0434\u04AF\u0439\u0434");
|
|
1244
|
+
if (typeof c.nonce !== "string" || !opts.nonce || !timingSafeEqualStr(c.nonce, opts.nonce)) throw bad("nonce \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1245
|
+
if (typeof c.sub !== "string" || c.sub === "") throw bad("sub \u0430\u043B\u0433\u0430");
|
|
1246
|
+
return c;
|
|
1247
|
+
}
|
|
1248
|
+
keys(force) {
|
|
1249
|
+
const c = this.jwks;
|
|
1250
|
+
if (!force && c && Date.now() - c.at < CACHE_MS) return c.keys;
|
|
1251
|
+
const keys = this.discover().then(async (d) => {
|
|
1252
|
+
const j = await this.getJson(d.jwks_uri);
|
|
1253
|
+
return Array.isArray(j?.keys) ? j.keys : [];
|
|
1254
|
+
});
|
|
1255
|
+
this.jwks = { at: Date.now(), keys };
|
|
1256
|
+
keys.catch(() => {
|
|
1257
|
+
if (this.jwks?.keys === keys) this.jwks = void 0;
|
|
1258
|
+
});
|
|
1259
|
+
return keys;
|
|
1260
|
+
}
|
|
1261
|
+
async getJson(url) {
|
|
1262
|
+
const res = await this.fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1263
|
+
if (!res.ok) throw new EidOidcError("discovery_failed", `OIDC ${res.status}: ${url}`);
|
|
1264
|
+
try {
|
|
1265
|
+
return await res.json();
|
|
1266
|
+
} catch {
|
|
1267
|
+
throw new EidOidcError("discovery_failed", `OIDC JSON \u0431\u0438\u0448: ${url}`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
async fetch(url, init) {
|
|
1271
|
+
const f = this.cfg.fetchImpl ?? fetch;
|
|
1272
|
+
try {
|
|
1273
|
+
return await f(url, {
|
|
1274
|
+
...init,
|
|
1275
|
+
redirect: "error",
|
|
1276
|
+
signal: AbortSignal.timeout(this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
1277
|
+
});
|
|
1278
|
+
} catch (e) {
|
|
1279
|
+
const err = e;
|
|
1280
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
1281
|
+
throw new NetworkError(`OIDC \u0445\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${url}`);
|
|
1282
|
+
}
|
|
1283
|
+
throw new NetworkError(`OIDC \u0441\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${url}): ${err.message}`);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
async function pkceChallenge(codeVerifier) {
|
|
1288
|
+
const h = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
|
|
1289
|
+
return Buffer.from(h).toString("base64url");
|
|
1290
|
+
}
|
|
1291
|
+
function randomB64url(bytes) {
|
|
1292
|
+
return Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(bytes))).toString("base64url");
|
|
1293
|
+
}
|
|
1294
|
+
function formEncode(s) {
|
|
1295
|
+
return new URLSearchParams({ v: s }).toString().slice(2);
|
|
1296
|
+
}
|
|
1297
|
+
function timingSafeEqualStr(a, b) {
|
|
1298
|
+
const x = Buffer.from(a, "utf8");
|
|
1299
|
+
const y = Buffer.from(b, "utf8");
|
|
1300
|
+
return x.length === y.length && (0, import_node_crypto4.timingSafeEqual)(x, y);
|
|
1301
|
+
}
|
|
1302
|
+
function decodeJsonPart(s) {
|
|
1303
|
+
try {
|
|
1304
|
+
const v = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
|
|
1305
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1306
|
+
} catch {
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function callbackParams(input) {
|
|
1311
|
+
if (input instanceof URLSearchParams) return input;
|
|
1312
|
+
if (input instanceof URL) return input.searchParams;
|
|
1313
|
+
const s = input.trim();
|
|
1314
|
+
if (/^https?:\/\//i.test(s)) return new URL(s).searchParams;
|
|
1315
|
+
const i = s.indexOf("?");
|
|
1316
|
+
return new URLSearchParams(i >= 0 ? s.slice(i + 1) : s);
|
|
1317
|
+
}
|
|
1035
1318
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1036
1319
|
0 && (module.exports = {
|
|
1037
1320
|
ApiError,
|
|
@@ -1039,8 +1322,13 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
1039
1322
|
AuthenticationError,
|
|
1040
1323
|
DEFAULT_BASE_URL,
|
|
1041
1324
|
DeviceLinkError,
|
|
1325
|
+
EID_ACR_ADVANCED,
|
|
1326
|
+
EID_ACR_QUALIFIED,
|
|
1327
|
+
EID_OIDC_ISSUER,
|
|
1042
1328
|
EidClient,
|
|
1043
1329
|
EidError,
|
|
1330
|
+
EidOidcClient,
|
|
1331
|
+
EidOidcError,
|
|
1044
1332
|
ForbiddenError,
|
|
1045
1333
|
Http,
|
|
1046
1334
|
NetworkError,
|
|
@@ -1060,6 +1348,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
1060
1348
|
isMobileUserAgent,
|
|
1061
1349
|
openOrShowQR,
|
|
1062
1350
|
parseSessionResult,
|
|
1351
|
+
pkceChallenge,
|
|
1063
1352
|
qrDeviceLinkTicker,
|
|
1064
1353
|
randomChallenge,
|
|
1065
1354
|
resolveBaseUrl,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType } from './browser-
|
|
2
|
-
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput,
|
|
1
|
+
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-DqKDj2nB.cjs';
|
|
2
|
+
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, P as PdfDocumentStatus, n as PdfSessionBlock, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-DqKDj2nB.cjs';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -218,8 +218,7 @@ interface ValidateAuthOptions {
|
|
|
218
218
|
* (verifier-тэй) ирсэн үр дүнг "Web2App"/"App2App"-аар, poll-оор ирснийг "QR"-аар шалга — товчоор
|
|
219
219
|
* нээгдсэн session poll замаар OK болохгүй, callback-аар л дуусна.
|
|
220
220
|
*
|
|
221
|
-
*
|
|
222
|
-
* энэ шалгалт relay-ээс бүрэн хамгаалахгүй.
|
|
221
|
+
* Legacy `/dl?sessionId&vc` холбоос 2026-10-07-нд серверээс хасагдсан — төрөл үргэлж authCode-оор хамгаалагдана.
|
|
223
222
|
*/
|
|
224
223
|
expectedFlowType?: FlowType;
|
|
225
224
|
/**
|
|
@@ -457,4 +456,141 @@ interface QrDeviceLinkTickerOptions {
|
|
|
457
456
|
*/
|
|
458
457
|
declare function qrDeviceLinkTicker(input: Omit<BuildDeviceLinkInput, "deviceLinkType" | "now">, onLink: (link: string) => void, opts?: QrDeviceLinkTickerOptions): () => void;
|
|
459
458
|
|
|
460
|
-
|
|
459
|
+
/** eID-ийн OIDC issuer (төгсгөлд `/` байхгүй). */
|
|
460
|
+
declare const EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
|
|
461
|
+
/** `acr` утгууд — иргэний гэрчилгээний бодит түвшин. */
|
|
462
|
+
declare const EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
|
|
463
|
+
declare const EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
|
|
464
|
+
/**
|
|
465
|
+
* OIDC алдааны код. Authorize redirect (RFC 6749 §4.1.2.1) ба token endpoint (§5.2)-ийн стандарт кодууд, мөн
|
|
466
|
+
* SDK-ийн өөрийн шалгалтын кодууд:
|
|
467
|
+
* - `state_mismatch` — callback-ийн `state` хадгалсантай таарсангүй (CSRF / өөр tab / cookie дууссан) → дахин эхлүүл;
|
|
468
|
+
* - `invalid_callback` — `code` алга / хэт урт, эсвэл `iss` параметр өөр;
|
|
469
|
+
* - `invalid_id_token` — id_token-ий гарын үсэг / iss / aud / exp / iat / nonce шалгалт унасан (ИТГЭХГҮЙ);
|
|
470
|
+
* - `discovery_failed` — discovery/JWKS буруу эсвэл issuer таарахгүй.
|
|
471
|
+
*
|
|
472
|
+
* `access_denied` = иргэн татгалзсан, нэвтрэлтийн хугацаа дууссан, эсвэл гэрчилгээний шалгалт унасан → «дахин оролд».
|
|
473
|
+
*/
|
|
474
|
+
type EidOidcErrorCode = "access_denied" | "login_required" | "invalid_request" | "invalid_scope" | "unauthorized_client" | "unsupported_response_type" | "request_not_supported" | "temporarily_unavailable" | "server_error" | "invalid_client" | "invalid_grant" | "unsupported_grant_type" | "state_mismatch" | "invalid_callback" | "invalid_id_token" | "discovery_failed";
|
|
475
|
+
/**
|
|
476
|
+
* OIDC урсгалын алдаа. `code`-оор салгана. `description` нь серверийн `error_description` — логт л,
|
|
477
|
+
* хэрэглэгчид шууд харуулахгүй.
|
|
478
|
+
*/
|
|
479
|
+
declare class EidOidcError extends EidError {
|
|
480
|
+
readonly code: EidOidcErrorCode;
|
|
481
|
+
readonly description?: string | undefined;
|
|
482
|
+
constructor(code: EidOidcErrorCode, message: string, description?: string | undefined);
|
|
483
|
+
}
|
|
484
|
+
interface EidOidcClientConfig {
|
|
485
|
+
/** RP UUID (`relyingPartyUUID`). */
|
|
486
|
+
clientId: string;
|
|
487
|
+
/** RP API secret (`rp_sk_…`) — зөвхөн backend-д. */
|
|
488
|
+
clientSecret: string;
|
|
489
|
+
/** Бүртгэлтэй redirect URI — тэмдэгт бүрээрээ таарах ёстой. */
|
|
490
|
+
redirectUri: string;
|
|
491
|
+
/** Анхдагч {@link EID_OIDC_ISSUER}. */
|
|
492
|
+
issuer?: string;
|
|
493
|
+
/** Анхдагч `"openid"`. Иргэний ETSI дугаар хэрэгтэй бол `"openid pnomn"` (RP-д `PNOMN` эрх). */
|
|
494
|
+
scope?: string;
|
|
495
|
+
/** HTTP timeout (мс), анхдагч 10000. */
|
|
496
|
+
timeoutMs?: number;
|
|
497
|
+
/** fetch орлуулагч (тест). */
|
|
498
|
+
fetchImpl?: typeof fetch;
|
|
499
|
+
}
|
|
500
|
+
interface EidOidcDiscovery {
|
|
501
|
+
issuer: string;
|
|
502
|
+
authorization_endpoint: string;
|
|
503
|
+
token_endpoint: string;
|
|
504
|
+
jwks_uri: string;
|
|
505
|
+
[k: string]: unknown;
|
|
506
|
+
}
|
|
507
|
+
interface CreateAuthorizationOptions {
|
|
508
|
+
/** Энэ хүсэлтэд scope-ыг дарна. */
|
|
509
|
+
scope?: string;
|
|
510
|
+
/**
|
|
511
|
+
* `touch` (анхдагч) — утасны хөтчөөс шууд eID апп; `page` — үргэлж нэвтрэх хуудас (QR / push), ж: таблет киоск.
|
|
512
|
+
*/
|
|
513
|
+
display?: "page" | "touch";
|
|
514
|
+
}
|
|
515
|
+
/** RP-ийн хадгалах ёстой гүйлгээ — httpOnly cookie / server session-д (браузерийн JS-д биш). */
|
|
516
|
+
interface EidAuthorization {
|
|
517
|
+
/** Хөтчийг энэ рүү 302. */
|
|
518
|
+
url: string;
|
|
519
|
+
state: string;
|
|
520
|
+
nonce: string;
|
|
521
|
+
codeVerifier: string;
|
|
522
|
+
}
|
|
523
|
+
/** createAuthorization-аас хадгалсан утгууд. */
|
|
524
|
+
interface EidCallbackChecks {
|
|
525
|
+
state: string;
|
|
526
|
+
nonce: string;
|
|
527
|
+
codeVerifier: string;
|
|
528
|
+
}
|
|
529
|
+
/** id_token-ий claim-ууд (`docs/OIDC.md` §4). */
|
|
530
|
+
interface EidIdTokenClaims {
|
|
531
|
+
iss: string;
|
|
532
|
+
aud: string | string[];
|
|
533
|
+
azp?: string;
|
|
534
|
+
/** Pairwise — RP тус бүрд өөр, иргэнд тогтмол. Хэрэглэгчийг ҮҮГЭЭР таньна. */
|
|
535
|
+
sub: string;
|
|
536
|
+
exp: number;
|
|
537
|
+
iat: number;
|
|
538
|
+
nonce: string;
|
|
539
|
+
/** Иргэн баталгаажуулсан мөч (Unix сек). */
|
|
540
|
+
auth_time: number;
|
|
541
|
+
/** {@link EID_ACR_QUALIFIED} эсвэл {@link EID_ACR_ADVANCED}. */
|
|
542
|
+
acr: string;
|
|
543
|
+
/** Гэрчилгээний GIVENNAME — латин (ICAO, том үсэг). */
|
|
544
|
+
given_name: string;
|
|
545
|
+
/** Гэрчилгээний SURNAME (эцэг/эхийн нэр) — латин. */
|
|
546
|
+
family_name: string;
|
|
547
|
+
/** Нэр — кирилл (бүртгэлд байхгүй бол алга). */
|
|
548
|
+
given_name_mn?: string;
|
|
549
|
+
/** Эцэг/эхийн нэр — кирилл (бүртгэлд байхгүй бол алга). */
|
|
550
|
+
family_name_mn?: string;
|
|
551
|
+
/** `PNOMN-<иргэний дугаар>` — зөвхөн `pnomn` scope + `PNOMN` эрхтэй үед. */
|
|
552
|
+
pnomn?: string;
|
|
553
|
+
[k: string]: unknown;
|
|
554
|
+
}
|
|
555
|
+
interface EidCallbackResult {
|
|
556
|
+
claims: EidIdTokenClaims;
|
|
557
|
+
idToken: string;
|
|
558
|
+
}
|
|
559
|
+
declare class EidOidcClient {
|
|
560
|
+
private readonly cfg;
|
|
561
|
+
readonly issuer: string;
|
|
562
|
+
private readonly scope;
|
|
563
|
+
private discovery?;
|
|
564
|
+
private jwks?;
|
|
565
|
+
constructor(cfg: EidOidcClientConfig);
|
|
566
|
+
/** Discovery document (1 цаг cache). `issuer` нь тохиргоотой яг таарах ёстой (OIDC Discovery §4.3). */
|
|
567
|
+
discover(): Promise<EidOidcDiscovery>;
|
|
568
|
+
private fetchDiscovery;
|
|
569
|
+
/**
|
|
570
|
+
* Authorize URL + санамсаргүй state / nonce / PKCE codeVerifier. RP буцаасан `state`, `nonce`, `codeVerifier`-ийг
|
|
571
|
+
* хөтчийн session-тэй холбож (httpOnly cookie эсвэл server session) хадгалаад хөтчийг `url` руу 302 хийнэ.
|
|
572
|
+
*/
|
|
573
|
+
createAuthorization(opts?: CreateAuthorizationOptions): Promise<EidAuthorization>;
|
|
574
|
+
/**
|
|
575
|
+
* redirect_uri руу ирсэн callback-ийг шийднэ: state тулгах → `error` бол {@link EidOidcError} → code солих →
|
|
576
|
+
* id_token шалгах. `input` — бүтэн callback URL, харьцангуй зам (`/cb?code=…`) эсвэл query (`URLSearchParams` / `code=…&state=…`); зөвхөн query-г уншина.
|
|
577
|
+
* Амжилтгүй бүх тохиолдолд {@link EidOidcError} (эсвэл {@link NetworkError}) шиднэ.
|
|
578
|
+
*/
|
|
579
|
+
handleCallback(input: string | URL | URLSearchParams, checks: EidCallbackChecks): Promise<EidCallbackResult>;
|
|
580
|
+
/** `POST token_endpoint` (client_secret_basic, RFC 6749 §2.3.1) → id_token. */
|
|
581
|
+
private exchangeCode;
|
|
582
|
+
/**
|
|
583
|
+
* id_token шалгана: JWKS (`kid`, cache; танихгүй kid → нэг удаа дахин татна) ES256 гарын үсэг, `iss`, `aud`
|
|
584
|
+
* (= clientId; олон aud бол `azp`), `exp`/`iat` (±60 сек), `nonce`. Амжилтгүй бол `invalid_id_token`.
|
|
585
|
+
*/
|
|
586
|
+
verifyIdToken(idToken: string, opts: {
|
|
587
|
+
nonce: string;
|
|
588
|
+
}): Promise<EidIdTokenClaims>;
|
|
589
|
+
private keys;
|
|
590
|
+
private getJson;
|
|
591
|
+
private fetch;
|
|
592
|
+
}
|
|
593
|
+
/** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
|
|
594
|
+
declare function pkceChallenge(codeVerifier: string): Promise<string>;
|
|
595
|
+
|
|
596
|
+
export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType } from './browser-
|
|
2
|
-
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput,
|
|
1
|
+
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-DqKDj2nB.js';
|
|
2
|
+
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, P as PdfDocumentStatus, n as PdfSessionBlock, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-DqKDj2nB.js';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -218,8 +218,7 @@ interface ValidateAuthOptions {
|
|
|
218
218
|
* (verifier-тэй) ирсэн үр дүнг "Web2App"/"App2App"-аар, poll-оор ирснийг "QR"-аар шалга — товчоор
|
|
219
219
|
* нээгдсэн session poll замаар OK болохгүй, callback-аар л дуусна.
|
|
220
220
|
*
|
|
221
|
-
*
|
|
222
|
-
* энэ шалгалт relay-ээс бүрэн хамгаалахгүй.
|
|
221
|
+
* Legacy `/dl?sessionId&vc` холбоос 2026-10-07-нд серверээс хасагдсан — төрөл үргэлж authCode-оор хамгаалагдана.
|
|
223
222
|
*/
|
|
224
223
|
expectedFlowType?: FlowType;
|
|
225
224
|
/**
|
|
@@ -457,4 +456,141 @@ interface QrDeviceLinkTickerOptions {
|
|
|
457
456
|
*/
|
|
458
457
|
declare function qrDeviceLinkTicker(input: Omit<BuildDeviceLinkInput, "deviceLinkType" | "now">, onLink: (link: string) => void, opts?: QrDeviceLinkTickerOptions): () => void;
|
|
459
458
|
|
|
460
|
-
|
|
459
|
+
/** eID-ийн OIDC issuer (төгсгөлд `/` байхгүй). */
|
|
460
|
+
declare const EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
|
|
461
|
+
/** `acr` утгууд — иргэний гэрчилгээний бодит түвшин. */
|
|
462
|
+
declare const EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
|
|
463
|
+
declare const EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
|
|
464
|
+
/**
|
|
465
|
+
* OIDC алдааны код. Authorize redirect (RFC 6749 §4.1.2.1) ба token endpoint (§5.2)-ийн стандарт кодууд, мөн
|
|
466
|
+
* SDK-ийн өөрийн шалгалтын кодууд:
|
|
467
|
+
* - `state_mismatch` — callback-ийн `state` хадгалсантай таарсангүй (CSRF / өөр tab / cookie дууссан) → дахин эхлүүл;
|
|
468
|
+
* - `invalid_callback` — `code` алга / хэт урт, эсвэл `iss` параметр өөр;
|
|
469
|
+
* - `invalid_id_token` — id_token-ий гарын үсэг / iss / aud / exp / iat / nonce шалгалт унасан (ИТГЭХГҮЙ);
|
|
470
|
+
* - `discovery_failed` — discovery/JWKS буруу эсвэл issuer таарахгүй.
|
|
471
|
+
*
|
|
472
|
+
* `access_denied` = иргэн татгалзсан, нэвтрэлтийн хугацаа дууссан, эсвэл гэрчилгээний шалгалт унасан → «дахин оролд».
|
|
473
|
+
*/
|
|
474
|
+
type EidOidcErrorCode = "access_denied" | "login_required" | "invalid_request" | "invalid_scope" | "unauthorized_client" | "unsupported_response_type" | "request_not_supported" | "temporarily_unavailable" | "server_error" | "invalid_client" | "invalid_grant" | "unsupported_grant_type" | "state_mismatch" | "invalid_callback" | "invalid_id_token" | "discovery_failed";
|
|
475
|
+
/**
|
|
476
|
+
* OIDC урсгалын алдаа. `code`-оор салгана. `description` нь серверийн `error_description` — логт л,
|
|
477
|
+
* хэрэглэгчид шууд харуулахгүй.
|
|
478
|
+
*/
|
|
479
|
+
declare class EidOidcError extends EidError {
|
|
480
|
+
readonly code: EidOidcErrorCode;
|
|
481
|
+
readonly description?: string | undefined;
|
|
482
|
+
constructor(code: EidOidcErrorCode, message: string, description?: string | undefined);
|
|
483
|
+
}
|
|
484
|
+
interface EidOidcClientConfig {
|
|
485
|
+
/** RP UUID (`relyingPartyUUID`). */
|
|
486
|
+
clientId: string;
|
|
487
|
+
/** RP API secret (`rp_sk_…`) — зөвхөн backend-д. */
|
|
488
|
+
clientSecret: string;
|
|
489
|
+
/** Бүртгэлтэй redirect URI — тэмдэгт бүрээрээ таарах ёстой. */
|
|
490
|
+
redirectUri: string;
|
|
491
|
+
/** Анхдагч {@link EID_OIDC_ISSUER}. */
|
|
492
|
+
issuer?: string;
|
|
493
|
+
/** Анхдагч `"openid"`. Иргэний ETSI дугаар хэрэгтэй бол `"openid pnomn"` (RP-д `PNOMN` эрх). */
|
|
494
|
+
scope?: string;
|
|
495
|
+
/** HTTP timeout (мс), анхдагч 10000. */
|
|
496
|
+
timeoutMs?: number;
|
|
497
|
+
/** fetch орлуулагч (тест). */
|
|
498
|
+
fetchImpl?: typeof fetch;
|
|
499
|
+
}
|
|
500
|
+
interface EidOidcDiscovery {
|
|
501
|
+
issuer: string;
|
|
502
|
+
authorization_endpoint: string;
|
|
503
|
+
token_endpoint: string;
|
|
504
|
+
jwks_uri: string;
|
|
505
|
+
[k: string]: unknown;
|
|
506
|
+
}
|
|
507
|
+
interface CreateAuthorizationOptions {
|
|
508
|
+
/** Энэ хүсэлтэд scope-ыг дарна. */
|
|
509
|
+
scope?: string;
|
|
510
|
+
/**
|
|
511
|
+
* `touch` (анхдагч) — утасны хөтчөөс шууд eID апп; `page` — үргэлж нэвтрэх хуудас (QR / push), ж: таблет киоск.
|
|
512
|
+
*/
|
|
513
|
+
display?: "page" | "touch";
|
|
514
|
+
}
|
|
515
|
+
/** RP-ийн хадгалах ёстой гүйлгээ — httpOnly cookie / server session-д (браузерийн JS-д биш). */
|
|
516
|
+
interface EidAuthorization {
|
|
517
|
+
/** Хөтчийг энэ рүү 302. */
|
|
518
|
+
url: string;
|
|
519
|
+
state: string;
|
|
520
|
+
nonce: string;
|
|
521
|
+
codeVerifier: string;
|
|
522
|
+
}
|
|
523
|
+
/** createAuthorization-аас хадгалсан утгууд. */
|
|
524
|
+
interface EidCallbackChecks {
|
|
525
|
+
state: string;
|
|
526
|
+
nonce: string;
|
|
527
|
+
codeVerifier: string;
|
|
528
|
+
}
|
|
529
|
+
/** id_token-ий claim-ууд (`docs/OIDC.md` §4). */
|
|
530
|
+
interface EidIdTokenClaims {
|
|
531
|
+
iss: string;
|
|
532
|
+
aud: string | string[];
|
|
533
|
+
azp?: string;
|
|
534
|
+
/** Pairwise — RP тус бүрд өөр, иргэнд тогтмол. Хэрэглэгчийг ҮҮГЭЭР таньна. */
|
|
535
|
+
sub: string;
|
|
536
|
+
exp: number;
|
|
537
|
+
iat: number;
|
|
538
|
+
nonce: string;
|
|
539
|
+
/** Иргэн баталгаажуулсан мөч (Unix сек). */
|
|
540
|
+
auth_time: number;
|
|
541
|
+
/** {@link EID_ACR_QUALIFIED} эсвэл {@link EID_ACR_ADVANCED}. */
|
|
542
|
+
acr: string;
|
|
543
|
+
/** Гэрчилгээний GIVENNAME — латин (ICAO, том үсэг). */
|
|
544
|
+
given_name: string;
|
|
545
|
+
/** Гэрчилгээний SURNAME (эцэг/эхийн нэр) — латин. */
|
|
546
|
+
family_name: string;
|
|
547
|
+
/** Нэр — кирилл (бүртгэлд байхгүй бол алга). */
|
|
548
|
+
given_name_mn?: string;
|
|
549
|
+
/** Эцэг/эхийн нэр — кирилл (бүртгэлд байхгүй бол алга). */
|
|
550
|
+
family_name_mn?: string;
|
|
551
|
+
/** `PNOMN-<иргэний дугаар>` — зөвхөн `pnomn` scope + `PNOMN` эрхтэй үед. */
|
|
552
|
+
pnomn?: string;
|
|
553
|
+
[k: string]: unknown;
|
|
554
|
+
}
|
|
555
|
+
interface EidCallbackResult {
|
|
556
|
+
claims: EidIdTokenClaims;
|
|
557
|
+
idToken: string;
|
|
558
|
+
}
|
|
559
|
+
declare class EidOidcClient {
|
|
560
|
+
private readonly cfg;
|
|
561
|
+
readonly issuer: string;
|
|
562
|
+
private readonly scope;
|
|
563
|
+
private discovery?;
|
|
564
|
+
private jwks?;
|
|
565
|
+
constructor(cfg: EidOidcClientConfig);
|
|
566
|
+
/** Discovery document (1 цаг cache). `issuer` нь тохиргоотой яг таарах ёстой (OIDC Discovery §4.3). */
|
|
567
|
+
discover(): Promise<EidOidcDiscovery>;
|
|
568
|
+
private fetchDiscovery;
|
|
569
|
+
/**
|
|
570
|
+
* Authorize URL + санамсаргүй state / nonce / PKCE codeVerifier. RP буцаасан `state`, `nonce`, `codeVerifier`-ийг
|
|
571
|
+
* хөтчийн session-тэй холбож (httpOnly cookie эсвэл server session) хадгалаад хөтчийг `url` руу 302 хийнэ.
|
|
572
|
+
*/
|
|
573
|
+
createAuthorization(opts?: CreateAuthorizationOptions): Promise<EidAuthorization>;
|
|
574
|
+
/**
|
|
575
|
+
* redirect_uri руу ирсэн callback-ийг шийднэ: state тулгах → `error` бол {@link EidOidcError} → code солих →
|
|
576
|
+
* id_token шалгах. `input` — бүтэн callback URL, харьцангуй зам (`/cb?code=…`) эсвэл query (`URLSearchParams` / `code=…&state=…`); зөвхөн query-г уншина.
|
|
577
|
+
* Амжилтгүй бүх тохиолдолд {@link EidOidcError} (эсвэл {@link NetworkError}) шиднэ.
|
|
578
|
+
*/
|
|
579
|
+
handleCallback(input: string | URL | URLSearchParams, checks: EidCallbackChecks): Promise<EidCallbackResult>;
|
|
580
|
+
/** `POST token_endpoint` (client_secret_basic, RFC 6749 §2.3.1) → id_token. */
|
|
581
|
+
private exchangeCode;
|
|
582
|
+
/**
|
|
583
|
+
* id_token шалгана: JWKS (`kid`, cache; танихгүй kid → нэг удаа дахин татна) ES256 гарын үсэг, `iss`, `aud`
|
|
584
|
+
* (= clientId; олон aud бол `azp`), `exp`/`iat` (±60 сек), `nonce`. Амжилтгүй бол `invalid_id_token`.
|
|
585
|
+
*/
|
|
586
|
+
verifyIdToken(idToken: string, opts: {
|
|
587
|
+
nonce: string;
|
|
588
|
+
}): Promise<EidIdTokenClaims>;
|
|
589
|
+
private keys;
|
|
590
|
+
private getJson;
|
|
591
|
+
private fetch;
|
|
592
|
+
}
|
|
593
|
+
/** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
|
|
594
|
+
declare function pkceChallenge(codeVerifier: string): Promise<string>;
|
|
595
|
+
|
|
596
|
+
export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
|
package/dist/index.js
CHANGED
|
@@ -897,14 +897,296 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
897
897
|
t.unref?.();
|
|
898
898
|
return () => clearInterval(t);
|
|
899
899
|
}
|
|
900
|
+
|
|
901
|
+
// src/oidc.ts
|
|
902
|
+
import { timingSafeEqual } from "crypto";
|
|
903
|
+
var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
|
|
904
|
+
var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
|
|
905
|
+
var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
|
|
906
|
+
var CLOCK_SKEW_S = 60;
|
|
907
|
+
var CACHE_MS = 60 * 60 * 1e3;
|
|
908
|
+
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
909
|
+
var KNOWN_CODES = /* @__PURE__ */ new Set([
|
|
910
|
+
"access_denied",
|
|
911
|
+
"login_required",
|
|
912
|
+
"invalid_request",
|
|
913
|
+
"invalid_scope",
|
|
914
|
+
"unauthorized_client",
|
|
915
|
+
"unsupported_response_type",
|
|
916
|
+
"request_not_supported",
|
|
917
|
+
"temporarily_unavailable",
|
|
918
|
+
"server_error",
|
|
919
|
+
"invalid_client",
|
|
920
|
+
"invalid_grant",
|
|
921
|
+
"unsupported_grant_type"
|
|
922
|
+
]);
|
|
923
|
+
var EidOidcError = class extends EidError {
|
|
924
|
+
constructor(code, message, description) {
|
|
925
|
+
super(message);
|
|
926
|
+
this.code = code;
|
|
927
|
+
this.description = description;
|
|
928
|
+
}
|
|
929
|
+
code;
|
|
930
|
+
description;
|
|
931
|
+
};
|
|
932
|
+
var EidOidcClient = class {
|
|
933
|
+
constructor(cfg) {
|
|
934
|
+
this.cfg = cfg;
|
|
935
|
+
if (!cfg.clientId) throw new EidError("EidOidcClient: clientId \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
936
|
+
if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
937
|
+
if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
|
|
938
|
+
this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
|
|
939
|
+
this.scope = cfg.scope ?? "openid";
|
|
940
|
+
}
|
|
941
|
+
cfg;
|
|
942
|
+
issuer;
|
|
943
|
+
scope;
|
|
944
|
+
discovery;
|
|
945
|
+
jwks;
|
|
946
|
+
/** Discovery document (1 цаг cache). `issuer` нь тохиргоотой яг таарах ёстой (OIDC Discovery §4.3). */
|
|
947
|
+
discover() {
|
|
948
|
+
const c = this.discovery;
|
|
949
|
+
if (c && Date.now() - c.at < CACHE_MS) return c.d;
|
|
950
|
+
const d = this.fetchDiscovery();
|
|
951
|
+
this.discovery = { at: Date.now(), d };
|
|
952
|
+
d.catch(() => {
|
|
953
|
+
if (this.discovery?.d === d) this.discovery = void 0;
|
|
954
|
+
});
|
|
955
|
+
return d;
|
|
956
|
+
}
|
|
957
|
+
async fetchDiscovery() {
|
|
958
|
+
const d = await this.getJson(this.issuer + "/.well-known/openid-configuration");
|
|
959
|
+
if (!d || d.issuer !== this.issuer) {
|
|
960
|
+
throw new EidOidcError("discovery_failed", `OIDC discovery-\u0438\u0439\u043D issuer \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439 (\u0445\u04AF\u043B\u044D\u044D\u0441\u044D\u043D ${this.issuer})`);
|
|
961
|
+
}
|
|
962
|
+
for (const k of ["authorization_endpoint", "token_endpoint", "jwks_uri"]) {
|
|
963
|
+
if (typeof d[k] !== "string" || !d[k].startsWith("https://")) {
|
|
964
|
+
throw new EidOidcError("discovery_failed", `OIDC discovery: ${k} \u0431\u0443\u0440\u0443\u0443`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return d;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Authorize URL + санамсаргүй state / nonce / PKCE codeVerifier. RP буцаасан `state`, `nonce`, `codeVerifier`-ийг
|
|
971
|
+
* хөтчийн session-тэй холбож (httpOnly cookie эсвэл server session) хадгалаад хөтчийг `url` руу 302 хийнэ.
|
|
972
|
+
*/
|
|
973
|
+
async createAuthorization(opts = {}) {
|
|
974
|
+
const d = await this.discover();
|
|
975
|
+
const state = randomB64url(32);
|
|
976
|
+
const nonce = randomB64url(32);
|
|
977
|
+
const codeVerifier = randomB64url(32);
|
|
978
|
+
const u = new URL(d.authorization_endpoint);
|
|
979
|
+
const p = new URLSearchParams({
|
|
980
|
+
response_type: "code",
|
|
981
|
+
client_id: this.cfg.clientId,
|
|
982
|
+
redirect_uri: this.cfg.redirectUri,
|
|
983
|
+
scope: opts.scope ?? this.scope,
|
|
984
|
+
state,
|
|
985
|
+
nonce,
|
|
986
|
+
code_challenge: await pkceChallenge(codeVerifier),
|
|
987
|
+
code_challenge_method: "S256"
|
|
988
|
+
});
|
|
989
|
+
if (opts.display) p.set("display", opts.display);
|
|
990
|
+
u.search = p.toString();
|
|
991
|
+
return { url: u.toString(), state, nonce, codeVerifier };
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* redirect_uri руу ирсэн callback-ийг шийднэ: state тулгах → `error` бол {@link EidOidcError} → code солих →
|
|
995
|
+
* id_token шалгах. `input` — бүтэн callback URL, харьцангуй зам (`/cb?code=…`) эсвэл query (`URLSearchParams` / `code=…&state=…`); зөвхөн query-г уншина.
|
|
996
|
+
* Амжилтгүй бүх тохиолдолд {@link EidOidcError} (эсвэл {@link NetworkError}) шиднэ.
|
|
997
|
+
*/
|
|
998
|
+
async handleCallback(input, checks) {
|
|
999
|
+
const q = callbackParams(input);
|
|
1000
|
+
const state = q.get("state") ?? "";
|
|
1001
|
+
if (!checks.state || !timingSafeEqualStr(state, checks.state)) {
|
|
1002
|
+
throw new EidOidcError("state_mismatch", "OIDC state \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439 \u2014 \u043D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u0433 \u0434\u0430\u0445\u0438\u043D \u044D\u0445\u043B\u04AF\u04AF\u043B\u043D\u044D \u04AF\u04AF");
|
|
1003
|
+
}
|
|
1004
|
+
const iss = q.get("iss");
|
|
1005
|
+
if (iss !== null && iss !== this.issuer) {
|
|
1006
|
+
throw new EidOidcError("invalid_callback", "OIDC callback-\u0438\u0439\u043D iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1007
|
+
}
|
|
1008
|
+
const err = q.get("error");
|
|
1009
|
+
if (err) {
|
|
1010
|
+
const code2 = KNOWN_CODES.has(err) ? err : "server_error";
|
|
1011
|
+
throw new EidOidcError(code2, `eID \u043D\u044D\u0432\u0442\u0440\u044D\u043B\u0442 \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${code2}`, q.get("error_description") ?? void 0);
|
|
1012
|
+
}
|
|
1013
|
+
const code = q.get("code") ?? "";
|
|
1014
|
+
if (code === "" || code.length > 512) throw new EidOidcError("invalid_callback", "OIDC callback-\u0434 code \u0430\u043B\u0433\u0430");
|
|
1015
|
+
const idToken = await this.exchangeCode(code, checks.codeVerifier);
|
|
1016
|
+
const claims = await this.verifyIdToken(idToken, { nonce: checks.nonce });
|
|
1017
|
+
return { claims, idToken };
|
|
1018
|
+
}
|
|
1019
|
+
/** `POST token_endpoint` (client_secret_basic, RFC 6749 §2.3.1) → id_token. */
|
|
1020
|
+
async exchangeCode(code, codeVerifier) {
|
|
1021
|
+
const d = await this.discover();
|
|
1022
|
+
const basic = Buffer.from(formEncode(this.cfg.clientId) + ":" + formEncode(this.cfg.clientSecret)).toString("base64");
|
|
1023
|
+
const res = await this.fetch(d.token_endpoint, {
|
|
1024
|
+
method: "POST",
|
|
1025
|
+
headers: {
|
|
1026
|
+
Authorization: "Basic " + basic,
|
|
1027
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1028
|
+
Accept: "application/json"
|
|
1029
|
+
},
|
|
1030
|
+
body: new URLSearchParams({
|
|
1031
|
+
grant_type: "authorization_code",
|
|
1032
|
+
code,
|
|
1033
|
+
redirect_uri: this.cfg.redirectUri,
|
|
1034
|
+
code_verifier: codeVerifier
|
|
1035
|
+
}).toString()
|
|
1036
|
+
});
|
|
1037
|
+
let j = {};
|
|
1038
|
+
try {
|
|
1039
|
+
j = await res.json();
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
if (!res.ok) {
|
|
1043
|
+
const e = typeof j.error === "string" && KNOWN_CODES.has(j.error) ? j.error : "server_error";
|
|
1044
|
+
throw new EidOidcError(
|
|
1045
|
+
e,
|
|
1046
|
+
`OIDC token ${res.status}: ${e}`,
|
|
1047
|
+
typeof j.error_description === "string" ? j.error_description : void 0
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
if (typeof j.id_token !== "string" || j.id_token === "") {
|
|
1051
|
+
throw new EidOidcError("server_error", "OIDC token \u0445\u0430\u0440\u0438\u0443\u043D\u0434 id_token \u0430\u043B\u0433\u0430");
|
|
1052
|
+
}
|
|
1053
|
+
return j.id_token;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* id_token шалгана: JWKS (`kid`, cache; танихгүй kid → нэг удаа дахин татна) ES256 гарын үсэг, `iss`, `aud`
|
|
1057
|
+
* (= clientId; олон aud бол `azp`), `exp`/`iat` (±60 сек), `nonce`. Амжилтгүй бол `invalid_id_token`.
|
|
1058
|
+
*/
|
|
1059
|
+
async verifyIdToken(idToken, opts) {
|
|
1060
|
+
const bad = (why) => new EidOidcError("invalid_id_token", `id_token: ${why}`);
|
|
1061
|
+
const parts = idToken.split(".");
|
|
1062
|
+
if (parts.length !== 3) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1063
|
+
const header = decodeJsonPart(parts[0]);
|
|
1064
|
+
const payload = decodeJsonPart(parts[1]);
|
|
1065
|
+
if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1066
|
+
if (header.alg !== "ES256") throw bad("alg ES256 \u0431\u0438\u0448");
|
|
1067
|
+
const kid = typeof header.kid === "string" ? header.kid : "";
|
|
1068
|
+
if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
|
|
1069
|
+
const pick = (ks) => ks.find(
|
|
1070
|
+
(k) => k.kid === kid && k.kty === "EC" && k.crv === "P-256" && (k.use === void 0 || k.use === "sig") && (k.alg === void 0 || k.alg === "ES256")
|
|
1071
|
+
);
|
|
1072
|
+
const jwk = pick(await this.keys(false)) ?? pick(await this.keys(true));
|
|
1073
|
+
if (!jwk || !jwk.x || !jwk.y) throw bad(`\u0442\u0430\u043D\u0438\u0445\u0433\u04AF\u0439 kid ${kid}`);
|
|
1074
|
+
const sig = Buffer.from(parts[2], "base64url");
|
|
1075
|
+
if (sig.length !== 64) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
1076
|
+
let ok = false;
|
|
1077
|
+
try {
|
|
1078
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
1079
|
+
"jwk",
|
|
1080
|
+
{ kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y },
|
|
1081
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
1082
|
+
false,
|
|
1083
|
+
["verify"]
|
|
1084
|
+
);
|
|
1085
|
+
ok = await globalThis.crypto.subtle.verify(
|
|
1086
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
1087
|
+
key,
|
|
1088
|
+
sig,
|
|
1089
|
+
new TextEncoder().encode(parts[0] + "." + parts[1])
|
|
1090
|
+
);
|
|
1091
|
+
} catch {
|
|
1092
|
+
ok = false;
|
|
1093
|
+
}
|
|
1094
|
+
if (!ok) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
1095
|
+
const c = payload;
|
|
1096
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1097
|
+
if (c.iss !== this.issuer) throw bad("iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1098
|
+
const aud = Array.isArray(c.aud) ? c.aud : [c.aud];
|
|
1099
|
+
if (!aud.includes(this.cfg.clientId)) throw bad("aud \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1100
|
+
if (aud.length > 1 && c.azp !== this.cfg.clientId) throw bad("azp \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1101
|
+
if (typeof c.exp !== "number" || now > c.exp + CLOCK_SKEW_S) throw bad("\u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0434\u0443\u0443\u0441\u0441\u0430\u043D");
|
|
1102
|
+
if (typeof c.iat !== "number" || c.iat > now + CLOCK_SKEW_S) throw bad("iat \u0438\u0440\u044D\u044D\u0434\u04AF\u0439\u0434");
|
|
1103
|
+
if (typeof c.nonce !== "string" || !opts.nonce || !timingSafeEqualStr(c.nonce, opts.nonce)) throw bad("nonce \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1104
|
+
if (typeof c.sub !== "string" || c.sub === "") throw bad("sub \u0430\u043B\u0433\u0430");
|
|
1105
|
+
return c;
|
|
1106
|
+
}
|
|
1107
|
+
keys(force) {
|
|
1108
|
+
const c = this.jwks;
|
|
1109
|
+
if (!force && c && Date.now() - c.at < CACHE_MS) return c.keys;
|
|
1110
|
+
const keys = this.discover().then(async (d) => {
|
|
1111
|
+
const j = await this.getJson(d.jwks_uri);
|
|
1112
|
+
return Array.isArray(j?.keys) ? j.keys : [];
|
|
1113
|
+
});
|
|
1114
|
+
this.jwks = { at: Date.now(), keys };
|
|
1115
|
+
keys.catch(() => {
|
|
1116
|
+
if (this.jwks?.keys === keys) this.jwks = void 0;
|
|
1117
|
+
});
|
|
1118
|
+
return keys;
|
|
1119
|
+
}
|
|
1120
|
+
async getJson(url) {
|
|
1121
|
+
const res = await this.fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1122
|
+
if (!res.ok) throw new EidOidcError("discovery_failed", `OIDC ${res.status}: ${url}`);
|
|
1123
|
+
try {
|
|
1124
|
+
return await res.json();
|
|
1125
|
+
} catch {
|
|
1126
|
+
throw new EidOidcError("discovery_failed", `OIDC JSON \u0431\u0438\u0448: ${url}`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async fetch(url, init) {
|
|
1130
|
+
const f = this.cfg.fetchImpl ?? fetch;
|
|
1131
|
+
try {
|
|
1132
|
+
return await f(url, {
|
|
1133
|
+
...init,
|
|
1134
|
+
redirect: "error",
|
|
1135
|
+
signal: AbortSignal.timeout(this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
1136
|
+
});
|
|
1137
|
+
} catch (e) {
|
|
1138
|
+
const err = e;
|
|
1139
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
1140
|
+
throw new NetworkError(`OIDC \u0445\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${url}`);
|
|
1141
|
+
}
|
|
1142
|
+
throw new NetworkError(`OIDC \u0441\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${url}): ${err.message}`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
async function pkceChallenge(codeVerifier) {
|
|
1147
|
+
const h = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
|
|
1148
|
+
return Buffer.from(h).toString("base64url");
|
|
1149
|
+
}
|
|
1150
|
+
function randomB64url(bytes) {
|
|
1151
|
+
return Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(bytes))).toString("base64url");
|
|
1152
|
+
}
|
|
1153
|
+
function formEncode(s) {
|
|
1154
|
+
return new URLSearchParams({ v: s }).toString().slice(2);
|
|
1155
|
+
}
|
|
1156
|
+
function timingSafeEqualStr(a, b) {
|
|
1157
|
+
const x = Buffer.from(a, "utf8");
|
|
1158
|
+
const y = Buffer.from(b, "utf8");
|
|
1159
|
+
return x.length === y.length && timingSafeEqual(x, y);
|
|
1160
|
+
}
|
|
1161
|
+
function decodeJsonPart(s) {
|
|
1162
|
+
try {
|
|
1163
|
+
const v = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
|
|
1164
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1165
|
+
} catch {
|
|
1166
|
+
return null;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function callbackParams(input) {
|
|
1170
|
+
if (input instanceof URLSearchParams) return input;
|
|
1171
|
+
if (input instanceof URL) return input.searchParams;
|
|
1172
|
+
const s = input.trim();
|
|
1173
|
+
if (/^https?:\/\//i.test(s)) return new URL(s).searchParams;
|
|
1174
|
+
const i = s.indexOf("?");
|
|
1175
|
+
return new URLSearchParams(i >= 0 ? s.slice(i + 1) : s);
|
|
1176
|
+
}
|
|
900
1177
|
export {
|
|
901
1178
|
ApiError,
|
|
902
1179
|
AuthApi,
|
|
903
1180
|
AuthenticationError,
|
|
904
1181
|
DEFAULT_BASE_URL,
|
|
905
1182
|
DeviceLinkError,
|
|
1183
|
+
EID_ACR_ADVANCED,
|
|
1184
|
+
EID_ACR_QUALIFIED,
|
|
1185
|
+
EID_OIDC_ISSUER,
|
|
906
1186
|
EidClient,
|
|
907
1187
|
EidError,
|
|
1188
|
+
EidOidcClient,
|
|
1189
|
+
EidOidcError,
|
|
908
1190
|
ForbiddenError,
|
|
909
1191
|
Http,
|
|
910
1192
|
NetworkError,
|
|
@@ -924,6 +1206,7 @@ export {
|
|
|
924
1206
|
isMobileUserAgent,
|
|
925
1207
|
openOrShowQR,
|
|
926
1208
|
parseSessionResult,
|
|
1209
|
+
pkceChallenge,
|
|
927
1210
|
qrDeviceLinkTicker,
|
|
928
1211
|
randomChallenge,
|
|
929
1212
|
resolveBaseUrl,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gerege-systems/eid-mongolia-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsup src/index.ts src/browser.ts --format esm,cjs --dts --clean",
|
|
32
|
-
"test": "npm run build && node --test --experimental-strip-types test/sdk.test.ts",
|
|
32
|
+
"test": "npm run build && node --test --experimental-strip-types test/sdk.test.ts test/oidc.test.ts",
|
|
33
33
|
"lint": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|