@gerege-systems/eid-mongolia-sdk 0.9.0 → 0.10.0

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/dist/index.js CHANGED
@@ -6,12 +6,15 @@ import {
6
6
  ForbiddenError,
7
7
  NetworkError,
8
8
  ON_BEHALF_ERROR_CODES,
9
+ RETRYABLE_ERROR_CODES,
9
10
  SessionFailedError,
10
11
  ValidationError,
11
12
  deviceLink,
12
13
  isMobileUserAgent,
13
- openOrShowQR
14
- } from "./chunk-NXDKQZIW.js";
14
+ maskPath,
15
+ openOrShowQR,
16
+ parseRetryAfter
17
+ } from "./chunk-YDVPZMAL.js";
15
18
 
16
19
  // src/http.ts
17
20
  var DEFAULT_TIMEOUT_MS = 15e3;
@@ -54,26 +57,32 @@ var Http = class {
54
57
  };
55
58
  if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
56
59
  let res;
60
+ const op = `${method} ${maskPath(path)}`;
57
61
  try {
58
62
  res = await f(url, init);
59
63
  } catch (e) {
60
64
  const err = e;
61
65
  if (err.name === "TimeoutError" || err.name === "AbortError") {
62
- throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${method} ${path}`);
66
+ throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${op}`, { op, timeout: true });
63
67
  }
64
- throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
68
+ throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${op}): ${err.message.split(url).join(op)}`, { op, timeout: false });
65
69
  }
66
70
  if (res.status < 300) return res;
67
71
  const text = await res.text();
68
72
  const { error, code } = errorBody(text);
69
- if (res.status === 401) throw new AuthenticationError("API secret \u0431\u0443\u0440\u0443\u0443 \u044D\u0441\u0432\u044D\u043B \u0431\u0430\u0439\u0445\u0433\u04AF\u0439 (401)");
73
+ const headers = res.headers;
74
+ const retryAfterMs = parseRetryAfter(headers.get("Retry-After"));
75
+ if (res.status === 401) {
76
+ throw new AuthenticationError(`API secret \u0431\u0443\u0440\u0443\u0443 \u044D\u0441\u0432\u044D\u043B \u0431\u0430\u0439\u0445\u0433\u04AF\u0439 (401${code ? ` ${code}` : ""})`, { code, serverMessage: error, body: text, headers });
77
+ }
70
78
  if (res.status === 403) {
71
79
  throw new ForbiddenError(
72
80
  error ? `RP-API 403${code ? ` ${code}` : ""}: ${error}` : "IP allowlist \u044D\u0441\u0432\u044D\u043B mTLS \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u04E9\u0433\u04AF\u0439 (403)",
73
- code
81
+ code,
82
+ { headers, retryAfterMs }
74
83
  );
75
84
  }
76
- throw new ApiError(res.status, text, code);
85
+ throw new ApiError(res.status, text, code, { headers, retryAfterMs });
77
86
  }
78
87
  };
79
88
  function errorBody(text) {
@@ -90,6 +99,48 @@ function errorBody(text) {
90
99
  }
91
100
  }
92
101
 
102
+ // src/contract.ts
103
+ var RP_ROUTES = {
104
+ authNotificationEtsi: { method: "POST", path: "/authentication/notification/etsi/{semanticsIdentifier}" },
105
+ authNotificationDocument: { method: "POST", path: "/authentication/notification/document/{documentNumber}" },
106
+ authDeviceLinkAnonymous: { method: "POST", path: "/authentication/device-link/anonymous" },
107
+ authDeviceLinkEtsi: { method: "POST", path: "/authentication/device-link/etsi/{semanticsIdentifier}" },
108
+ signNotificationEtsi: { method: "POST", path: "/signature/notification/etsi/{semanticsIdentifier}" },
109
+ signNotificationDocument: { method: "POST", path: "/signature/notification/document/{documentNumber}" },
110
+ pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
111
+ pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
112
+ orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
113
+ session: { method: "GET", path: "/session/{sessionId}" },
114
+ personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
115
+ personDevices: { method: "GET", path: "/devices/etsi/{personEtsi}" },
116
+ personActivity: { method: "GET", path: "/rp/activity/etsi/{personEtsi}" },
117
+ personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
118
+ personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
119
+ tokenCertificate: { method: "POST", path: "/token/certificate" },
120
+ tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
121
+ };
122
+ var RP_ROUTES_UNDISCOVERED = {
123
+ tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
124
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
125
+ };
126
+ function routePath(route, ...args) {
127
+ let i = 0;
128
+ const out = route.path.replace(/\{[^}]*\}/g, () => {
129
+ const a = args[i++];
130
+ if (a === void 0) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0434\u0443\u0442\u0443\u0443`);
131
+ return encodeURIComponent(a);
132
+ });
133
+ if (i !== args.length) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0438\u043B\u04AF\u04AF`);
134
+ return out;
135
+ }
136
+ var OIDC_EXPECT = {
137
+ defaultScope: "openid",
138
+ pkceMethod: "S256",
139
+ idTokenAlg: "ES256",
140
+ /** `Authorization: Basic` (RFC 6749 §2.3.1). */
141
+ tokenEndpointAuthMethod: "client_secret_basic"
142
+ };
143
+
93
144
  // src/crypto.ts
94
145
  import { createHash, randomBytes } from "crypto";
95
146
  function sha256Base64(data) {
@@ -192,25 +243,25 @@ var AuthApi = class {
192
243
  /** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
193
244
  async notificationByEtsi(id, interactions, opts) {
194
245
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
195
- const raw = await this.http.post(`/authentication/notification/etsi/${encodeURIComponent(id)}`, body);
246
+ const raw = await this.http.post(routePath(RP_ROUTES.authNotificationEtsi, id), body);
196
247
  return this.parseNotification(raw, acsp);
197
248
  }
198
249
  /** Push — баримтын дугаараар (тодорхой төхөөрөмж рүү). */
199
250
  async notificationByDocument(documentNumber, interactions, opts) {
200
251
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
201
- const raw = await this.http.post(`/authentication/notification/document/${encodeURIComponent(documentNumber)}`, body);
252
+ const raw = await this.http.post(routePath(RP_ROUTES.authNotificationDocument, documentNumber), body);
202
253
  return this.parseNotification(raw, acsp);
203
254
  }
204
255
  /** QR/App2App — нэр томьёололгүй (иргэн хэн болохыг урьдчилж мэдэхгүй). QR код үүсгэхэд. */
205
256
  async deviceLinkAnonymous(interactions, opts) {
206
257
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
207
- const raw = await this.http.post(`/authentication/device-link/anonymous`, body);
258
+ const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkAnonymous), body);
208
259
  return this.parseDeviceLink(raw, acsp);
209
260
  }
210
261
  /** QR/App2App — тодорхой иргэн (РД/civil_id/ETSI). */
211
262
  async deviceLinkByEtsi(id, interactions, opts) {
212
263
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
213
- const raw = await this.http.post(`/authentication/device-link/etsi/${encodeURIComponent(id)}`, body);
264
+ const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkEtsi, id), body);
214
265
  return this.parseDeviceLink(raw, acsp);
215
266
  }
216
267
  };
@@ -243,6 +294,7 @@ var SignApi = class {
243
294
  };
244
295
  if (opts?.callbackUrl?.trim()) body.initialCallbackUrl = opts.callbackUrl.trim();
245
296
  if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
297
+ if (opts?.fileName?.trim()) body.fileName = opts.fileName.trim();
246
298
  return body;
247
299
  }
248
300
  parse(raw) {
@@ -255,12 +307,12 @@ var SignApi = class {
255
307
  * digest-ийг гаднаас бэлдэж өгнө — текст бус бинар баримтад тохиромжтой.
256
308
  */
257
309
  async digestByEtsi(id, digestB64, interactions, opts) {
258
- const raw = await this.http.post(`/signature/notification/etsi/${encodeURIComponent(id)}`, this.body(digestB64, interactions, opts));
310
+ const raw = await this.http.post(routePath(RP_ROUTES.signNotificationEtsi, id), this.body(digestB64, interactions, opts));
259
311
  return this.parse(raw);
260
312
  }
261
313
  /** Бэлэн digest-ээр push sign — баримтын дугаараар (auth-аас гарсан documentNumber дээр). */
262
314
  async digestByDocument(documentNumber, digestB64, interactions, opts) {
263
- const raw = await this.http.post(`/signature/notification/document/${encodeURIComponent(documentNumber)}`, this.body(digestB64, interactions, opts));
315
+ const raw = await this.http.post(routePath(RP_ROUTES.signNotificationDocument, documentNumber), this.body(digestB64, interactions, opts));
264
316
  return this.parse(raw);
265
317
  }
266
318
  /** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
@@ -301,9 +353,44 @@ function parseSessionResult(raw) {
301
353
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
302
354
  interactionTypeUsed: str(r.interactionTypeUsed),
303
355
  onBehalfOf: parseOrg(r.onBehalfOf),
304
- pdf: parsePdf(r.pdf)
356
+ pdf: parsePdf(r.pdf),
357
+ person: parsePerson(r.person),
358
+ tokenCertificate: parseTokenCert(r.tokenCertificate),
359
+ raw
305
360
  };
306
361
  }
362
+ var PERSON_STRINGS = [
363
+ "firstName",
364
+ "lastName",
365
+ "familyName",
366
+ "firstNameEn",
367
+ "lastNameEn",
368
+ "civilId",
369
+ "regNo",
370
+ "birthDate",
371
+ "gender"
372
+ ];
373
+ function parsePerson(raw) {
374
+ const p = asRecord(raw);
375
+ const out = {};
376
+ for (const k of PERSON_STRINGS) if (typeof p[k] === "string" && p[k]) out[k] = p[k];
377
+ if (typeof p.geID === "number" && p.geID !== 0) out.geID = p.geID;
378
+ return Object.keys(out).length ? out : null;
379
+ }
380
+ function parseTokenCert(raw) {
381
+ const t = asRecord(raw);
382
+ const status = str(t.status);
383
+ if (!status) return null;
384
+ const out = { status };
385
+ if (t.errorCode) out.errorCode = String(t.errorCode);
386
+ if (t.authSerialHex) out.authSerialHex = String(t.authSerialHex);
387
+ if (t.signSerialHex) out.signSerialHex = String(t.signSerialHex);
388
+ return out;
389
+ }
390
+ function personMatchesCertificate(person, subject) {
391
+ const serial = typeof subject === "string" ? subject : subject?.serialNumber;
392
+ return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
393
+ }
307
394
  function parsePdf(raw) {
308
395
  const p = asRecord(raw);
309
396
  const docID = str(p.docID);
@@ -357,7 +444,7 @@ var SessionApi = class {
357
444
  /** Нэг удаагийн long-poll. serverPollMs хүртэл хүлээнэ. */
358
445
  async poll(sessionId, serverPollMs = SERVER_POLL_MS) {
359
446
  const raw = await this.http.get(
360
- `/session/${encodeURIComponent(sessionId)}?timeoutMs=${serverPollMs}`,
447
+ `${routePath(RP_ROUTES.session, sessionId)}?timeoutMs=${serverPollMs}`,
361
448
  serverPollMs + 1e4
362
449
  // HTTP timeout нь серверийн poll-оос урт
363
450
  );
@@ -382,6 +469,7 @@ var SessionApi = class {
382
469
 
383
470
  // src/pdf.ts
384
471
  import { createHash as createHash2 } from "crypto";
472
+ var DEFAULT_PDF_TIMEOUT_MS = 8e4;
385
473
  function rec3(v) {
386
474
  return v && typeof v === "object" ? v : {};
387
475
  }
@@ -394,7 +482,10 @@ var PdfApi = class {
394
482
  http;
395
483
  creds;
396
484
  defaults;
397
- /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
485
+ /**
486
+ * POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
487
+ * PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
488
+ */
398
489
  async prepare(input) {
399
490
  const request = {
400
491
  relyingPartyUUID: this.creds.rpUUID,
@@ -410,7 +501,7 @@ var PdfApi = class {
410
501
  const form = new FormData();
411
502
  form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
412
503
  form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
413
- const r = rec3(await this.http.postMultipart("/pdf/sign/prepare", form, 6e4));
504
+ const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS));
414
505
  const receivedAt = Date.now();
415
506
  const vc = rec3(r.vc).value;
416
507
  const dl = rec3(r.deviceLink);
@@ -432,10 +523,12 @@ var PdfApi = class {
432
523
  }
433
524
  /**
434
525
  * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
435
- * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
526
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
527
+ * бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
528
+ * дараа дахин татна (`retryable`).
436
529
  */
437
530
  document(sessionId) {
438
- return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
531
+ return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS);
439
532
  }
440
533
  /**
441
534
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
@@ -467,14 +560,54 @@ var OrganizationApi = class {
467
560
  http;
468
561
  /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
469
562
  async getRepresentations(personEtsi) {
470
- const raw = await this.http.get(`/organization/representations/etsi/${encodeURIComponent(personEtsi)}`);
563
+ const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
471
564
  const list = Array.isArray(raw.representations) ? raw.representations : [];
472
565
  return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
473
566
  }
474
567
  };
475
568
 
476
- // src/validator.ts
477
- import { X509Certificate as X509Certificate2, constants, createHash as createHash3, publicDecrypt, verify } from "crypto";
569
+ // src/person.ts
570
+ function rec4(v) {
571
+ return v && typeof v === "object" ? v : {};
572
+ }
573
+ var PersonApi = class {
574
+ constructor(http) {
575
+ this.http = http;
576
+ }
577
+ http;
578
+ async get(path) {
579
+ const raw = await this.http.get(path);
580
+ return { ...rec4(raw), raw };
581
+ }
582
+ /** Иргэний бүх гэрчилгээ + статусын тоолол. */
583
+ certificates(personEtsi) {
584
+ return this.get(routePath(RP_ROUTES.personCertificates, personEtsi));
585
+ }
586
+ /** Иргэний холбоотой төхөөрөмжүүд (идэвхтэй + идэвхгүй). */
587
+ devices(personEtsi) {
588
+ return this.get(routePath(RP_ROUTES.personDevices, personEtsi));
589
+ }
590
+ /** Иргэний үйл ажиллагааны түүх. */
591
+ activity(personEtsi, query) {
592
+ const q = new URLSearchParams();
593
+ if (query?.flow) q.set("flow", query.flow);
594
+ if (query?.limit && query.limit > 0) q.set("limit", String(query.limit));
595
+ if (query?.offset && query.offset > 0) q.set("offset", String(query.offset));
596
+ const qs = q.toString();
597
+ return this.get(routePath(RP_ROUTES.personActivity, personEtsi) + (qs ? `?${qs}` : ""));
598
+ }
599
+ /** Самбарын нэгдсэн тоо. */
600
+ summary(personEtsi) {
601
+ return this.get(routePath(RP_ROUTES.personSummary, personEtsi));
602
+ }
603
+ /** Асран хамгаалж буй хүүхдүүд (зөвхөн унших). */
604
+ children(personEtsi) {
605
+ return this.get(routePath(RP_ROUTES.personChildren, personEtsi));
606
+ }
607
+ };
608
+
609
+ // src/token.ts
610
+ import { X509Certificate as X509Certificate2, createHash as createHash3, createPublicKey, timingSafeEqual } from "crypto";
478
611
 
479
612
  // src/subject.ts
480
613
  import { X509Certificate } from "crypto";
@@ -580,8 +713,207 @@ function parseSubject(cert) {
580
713
  out.name = full || out.commonName || null;
581
714
  return out;
582
715
  }
716
+ function certExtension(der, oid) {
717
+ const top = tlv(der, 0, der.length);
718
+ const tbs = tlv(der, top.start, top.end);
719
+ let f = tlv(der, tbs.start, tbs.end);
720
+ if (f.tag === 160) f = tlv(der, f.end, tbs.end);
721
+ for (let k = 0; k < 5; k++) f = tlv(der, f.end, tbs.end);
722
+ for (let pos = f.end; pos < tbs.end; ) {
723
+ const t = tlv(der, pos, tbs.end);
724
+ if (t.tag === 163) {
725
+ const seq = tlv(der, t.start, t.end);
726
+ for (let ep = seq.start; ep < seq.end; ) {
727
+ const ext = tlv(der, ep, seq.end);
728
+ const id = tlv(der, ext.start, ext.end);
729
+ let v = tlv(der, id.end, ext.end);
730
+ if (v.tag === 1) v = tlv(der, v.end, ext.end);
731
+ if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
732
+ if (oidOf(der, id) === oid) return der.subarray(v.start, v.end);
733
+ ep = ext.end;
734
+ }
735
+ return void 0;
736
+ }
737
+ pos = t.end;
738
+ }
739
+ return void 0;
740
+ }
741
+
742
+ // src/token.ts
743
+ function rec5(v) {
744
+ return v && typeof v === "object" ? v : {};
745
+ }
746
+ function hexSha256Equal(hex, data) {
747
+ if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
748
+ return timingSafeEqual(createHash3("sha256").update(data).digest(), Buffer.from(hex, "hex"));
749
+ }
750
+ var TOKEN_RESULT_TIMEOUT_MS = 3e4;
751
+ var TokenApi = class {
752
+ constructor(http, creds) {
753
+ this.http = http;
754
+ this.creds = creds;
755
+ }
756
+ http;
757
+ creds;
758
+ /**
759
+ * POST /token/certificate — токены AUTH + SIGN гэрчилгээ олголт эхлүүлнэ (иргэн утсаараа батална). CA-ийн буцаасан
760
+ * CSR hash-ууд илгээсэнтэй таарахгүй бол ValidationError.
761
+ */
762
+ async issueCertificate(input) {
763
+ if (!input.signerEtsi?.trim() || !input.csrAuth?.length || !input.csrSign?.length || !input.tokenSerial?.trim()) {
764
+ throw new Error("token: signerEtsi, csrAuth, csrSign, tokenSerial \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
765
+ }
766
+ const body = {
767
+ relyingPartyUUID: this.creds.rpUUID,
768
+ relyingPartyName: this.creds.rpName,
769
+ signer: { etsi: input.signerEtsi.trim() },
770
+ csrAuth: Buffer.from(input.csrAuth).toString("base64"),
771
+ csrSign: Buffer.from(input.csrSign).toString("base64"),
772
+ tokenSerial: input.tokenSerial.trim(),
773
+ onBehalfOf: input.onBehalfOf?.trim() ?? "",
774
+ flow: "notification"
775
+ };
776
+ if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
777
+ const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
778
+ if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
779
+ throw new ValidationError("token: CA-\u0438\u0439\u043D csrAuthSha256/csrSignSha256 \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D CSR-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
780
+ }
781
+ const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
782
+ return {
783
+ sessionId: String(r.sessionID ?? ""),
784
+ vc: vc != null ? String(vc) : null,
785
+ csrAuthSha256: String(r.csrAuthSha256).toLowerCase(),
786
+ csrSignSha256: String(r.csrSignSha256).toLowerCase(),
787
+ ...typeof r.expiresAt === "string" ? { expiresAt: r.expiresAt } : {}
788
+ };
789
+ }
790
+ /**
791
+ * GET /token/certificate/{sessionID} — олгосон AUTH + SIGN гэрчилгээ + гинж. Токенд бичихээс өмнө
792
+ * {@link verifyTokenCertificates}-ээр CSR-ийн түлхүүртэй тулга.
793
+ */
794
+ async certificateResult(sessionId) {
795
+ const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
796
+ const r = rec5(raw);
797
+ const item = (v) => v && typeof v === "object" ? v : null;
798
+ return {
799
+ status: String(r.status ?? ""),
800
+ auth: item(r.auth),
801
+ sign: item(r.sign),
802
+ chain: Array.isArray(r.chain) ? r.chain.map(String) : [],
803
+ chainPem: String(r.chainPem ?? ""),
804
+ organization: r.organization && typeof r.organization === "object" ? r.organization : null,
805
+ keyMedium: String(r.keyMedium ?? ""),
806
+ certificateLevel: String(r.certificateLevel ?? ""),
807
+ raw
808
+ };
809
+ }
810
+ /** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
811
+ async authChallenge() {
812
+ const r = rec5(
813
+ await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
814
+ relyingPartyUUID: this.creds.rpUUID,
815
+ relyingPartyName: this.creds.rpName
816
+ })
817
+ );
818
+ return {
819
+ challengeId: String(r.challengeId ?? ""),
820
+ challenge: String(r.challenge ?? ""),
821
+ rpUUID: String(r.rpUUID ?? ""),
822
+ expiresAt: String(r.expiresAt ?? "")
823
+ };
824
+ }
825
+ /**
826
+ * POST /token/auth/verify — токены AUTH гэрчилгээ (DER) + challenge дээрх түүхий r||s. CA гинж, төлөв, цуцлалт, гарын
827
+ * үсгийг шалгана; SDK нэмж endResult === OK ба identity.etsi === гэрчилгээний SERIALNUMBER-ийг тулгана (эс бөгөөс
828
+ * ValidationError). Challenge нэг удаагийн — алдааны дараа шинээр авна.
829
+ */
830
+ async authVerify(challengeId, certDer, signature) {
831
+ let serial;
832
+ try {
833
+ serial = parseSubject(new X509Certificate2(certDer)).serialNumber;
834
+ } catch {
835
+ throw new Error("token auth: \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
836
+ }
837
+ if (!challengeId || !signature?.length) throw new Error("token auth: challengeId \u0431\u0430 signature \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
838
+ const raw = await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthVerify), {
839
+ challengeId,
840
+ certificate: Buffer.from(certDer).toString("base64"),
841
+ signature: Buffer.from(signature).toString("base64")
842
+ });
843
+ const r = rec5(raw);
844
+ if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
845
+ const id = rec5(r.identity);
846
+ if (!id.etsi || id.etsi !== serial) throw new ValidationError("token auth: identity.etsi \u043D\u044C \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 SERIALNUMBER \u0431\u0438\u0448");
847
+ const org = rec5(r.organization);
848
+ const cert = rec5(r.cert);
849
+ return {
850
+ identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
851
+ serialHex: String(cert.serialHex ?? ""),
852
+ notAfter: String(cert.notAfter ?? ""),
853
+ organization: org.etsi ? { etsi: String(org.etsi), name: String(org.name ?? "") } : null,
854
+ raw
855
+ };
856
+ }
857
+ };
858
+ function csrSpki(csr) {
859
+ const top = tlv(csr, 0, csr.length);
860
+ const info = tlv(csr, top.start, top.end);
861
+ const version = tlv(csr, info.start, info.end);
862
+ const subject = tlv(csr, version.end, info.end);
863
+ const spki = tlv(csr, subject.end, info.end);
864
+ return createPublicKey({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
865
+ format: "der",
866
+ type: "spki"
867
+ });
868
+ }
869
+ function verifyTokenCertificates(result, csrAuth, csrSign) {
870
+ if (!result?.auth || !result.sign) throw new ValidationError("token: auth/sign \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
871
+ for (const [name, item, csr] of [
872
+ ["auth", result.auth, csrAuth],
873
+ ["sign", result.sign, csrSign]
874
+ ]) {
875
+ let want;
876
+ try {
877
+ want = csrSpki(csr);
878
+ } catch {
879
+ throw new Error(`token: ${name} CSR \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
880
+ }
881
+ let got;
882
+ try {
883
+ got = new X509Certificate2(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
884
+ } catch {
885
+ throw new ValidationError(`token: ${name} \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
886
+ }
887
+ if (!got.equals(want)) throw new ValidationError(`token: ${name} \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440 CSR-\u0438\u0439\u043D\u0445\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
888
+ }
889
+ }
890
+ function tokenAuthDigest(rpUUID, challengeB64) {
891
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(rpUUID)) {
892
+ throw new Error("token auth: rpUUID \u0436\u0438\u0436\u0438\u0433 \u04AF\u0441\u0433\u0438\u0439\u043D 36 \u0442\u044D\u043C\u0434\u044D\u0433\u0442 UUID \u0431\u0438\u0448");
893
+ }
894
+ const ch = Buffer.from(challengeB64, "base64");
895
+ if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
896
+ return createHash3("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
897
+ }
898
+ function tokenLimitOf(err) {
899
+ if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
900
+ let b;
901
+ try {
902
+ b = rec5(JSON.parse(err.body));
903
+ } catch {
904
+ return void 0;
905
+ }
906
+ if (typeof b.limit !== "number" || !Number.isInteger(b.limit) || b.limit < 0) return void 0;
907
+ if (b.kind === "PERSONAL") return { kind: "PERSONAL", limit: b.limit };
908
+ if (b.kind === "ORGANIZATION" && typeof b.organization === "string" && b.organization.startsWith("NTRMN-")) {
909
+ return { kind: "ORGANIZATION", limit: b.limit, organization: b.organization };
910
+ }
911
+ return void 0;
912
+ }
583
913
 
584
914
  // src/validator.ts
915
+ import { X509Certificate as X509Certificate3, constants, createHash as createHash4, publicDecrypt, verify } from "crypto";
916
+ var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
585
917
  var ResponseValidator = class {
586
918
  anchors;
587
919
  intermediates;
@@ -590,14 +922,28 @@ var ResponseValidator = class {
590
922
  allowUntrusted;
591
923
  revocation;
592
924
  revocationMode;
925
+ allowedIssuers;
926
+ checkKeyUsage;
927
+ schemeName;
593
928
  constructor(trust = {}) {
594
- this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new X509Certificate2(p));
595
- this.intermediates = (trust.intermediatesPem ?? []).map((p) => new X509Certificate2(p));
929
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new X509Certificate3(p));
930
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new X509Certificate3(p));
596
931
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
597
932
  this.skew = trust.clockSkewMs ?? 0;
598
933
  this.allowUntrusted = trust.allowUntrusted ?? false;
599
934
  this.revocation = trust.revocation;
600
935
  this.revocationMode = trust.revocationMode ?? "hard-fail";
936
+ if (trust.allowedIssuerSkis?.length) {
937
+ this.allowedIssuers = new Set(
938
+ trust.allowedIssuerSkis.map((s) => {
939
+ const ski = s.trim().replace(/:/g, "").toLowerCase();
940
+ if (!/^(?:[0-9a-f]{2})+$/.test(ski)) throw new Error(`ResponseValidator: allowedIssuerSkis: "${s}" hex key identifier \u0431\u0438\u0448`);
941
+ return ski;
942
+ })
943
+ );
944
+ }
945
+ this.checkKeyUsage = trust.checkKeyUsage ?? false;
946
+ this.schemeName = trust.schemeName;
601
947
  }
602
948
  // ── нийтийн API ──
603
949
  /**
@@ -625,6 +971,7 @@ var ResponseValidator = class {
625
971
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
626
972
  const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
627
973
  const payload = buildAcspV2Payload({
974
+ ...this.schemeName ? { schemeName: this.schemeName } : {},
628
975
  serverRandom,
629
976
  rpChallenge: acsp.rpChallenge,
630
977
  userChallenge,
@@ -648,10 +995,14 @@ var ResponseValidator = class {
648
995
  }
649
996
  /**
650
997
  * Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
651
- * @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
998
+ * @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
652
999
  */
653
- validateSign(result, digestB64, hashAlgorithm = "SHA-256") {
1000
+ validateSign(result, digestB64, opts = {}) {
1001
+ const { hashAlgorithm = "SHA-256", expectedFlowType } = typeof opts === "string" ? { hashAlgorithm: opts } : opts;
654
1002
  const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
1003
+ if (expectedFlowType && result.flowType !== expectedFlowType) {
1004
+ throw new ValidationError(`flowType ${result.flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
1005
+ }
655
1006
  const digest = Buffer.from(digestB64, "base64");
656
1007
  const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
657
1008
  if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
@@ -724,9 +1075,10 @@ var ResponseValidator = class {
724
1075
  if (!result.certificateDerB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
725
1076
  if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
726
1077
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
727
- const leaf = new X509Certificate2(Buffer.from(result.certificateDerB64, "base64"));
1078
+ const leaf = new X509Certificate3(Buffer.from(result.certificateDerB64, "base64"));
728
1079
  this.checkValidity(leaf);
729
1080
  this.checkLevel(String(result.certificateLevel ?? ""));
1081
+ this.checkPurpose(leaf, protocol);
730
1082
  if (this.anchors.length === 0) {
731
1083
  if (!this.allowUntrusted) {
732
1084
  throw new ValidationError(
@@ -736,8 +1088,35 @@ var ResponseValidator = class {
736
1088
  } else {
737
1089
  this.verifyChain(leaf);
738
1090
  }
1091
+ this.checkIssuer(leaf);
739
1092
  return leaf;
740
1093
  }
1094
+ /**
1095
+ * checkKeyUsage үед гэрчилгээний зориулалт протоколд тохирох эсэх (CA crypto/ca_profile.go): ACSP_V2 (auth) → KU
1096
+ * digitalSignature + EKU clientAuth (эсвэл anyExtendedKeyUsage); RAW_DIGEST_SIGNATURE (sign) → KU contentCommitment.
1097
+ */
1098
+ checkPurpose(leaf, protocol) {
1099
+ if (!this.checkKeyUsage) return;
1100
+ const ku = keyUsageBits(leaf);
1101
+ if (protocol === "ACSP_V2") {
1102
+ if (!(ku & 128)) throw new ValidationError("\u041D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 KeyUsage digitalSignature \u0430\u043B\u0433\u0430");
1103
+ const eku = extKeyUsage(leaf);
1104
+ if (!eku.includes("1.3.6.1.5.5.7.3.2") && !eku.includes("2.5.29.37.0")) {
1105
+ throw new ValidationError("\u041D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 ExtKeyUsage clientAuth \u0430\u043B\u0433\u0430 (\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
1106
+ }
1107
+ return;
1108
+ }
1109
+ if (!(ku & 64)) throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 KeyUsage nonRepudiation (contentCommitment) \u0430\u043B\u0433\u0430");
1110
+ }
1111
+ /** allowedIssuerSkis үед leaf-ийн шууд issuer-ийн SKI (issuer-т алга бол leaf-ийн AKI) жагсаалтад байх ёстой. */
1112
+ checkIssuer(leaf) {
1113
+ if (!this.allowedIssuers) return;
1114
+ const issuer = this.findIssuer(leaf);
1115
+ const ski = (issuer && subjectKeyId(issuer)) ?? authorityKeyId(leaf);
1116
+ if (!ski || !this.allowedIssuers.has(ski)) {
1117
+ throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u0433 \u0433\u0430\u0440\u0433\u0430\u0441\u0430\u043D CA \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u0433\u0434\u0441\u04E9\u043D \u0438\u0440\u0433\u044D\u043D\u0438\u0439 issuer \u0431\u0438\u0448 (allowedIssuerSkis)");
1118
+ }
1119
+ }
741
1120
  checkValidity(cert) {
742
1121
  const now = Date.now();
743
1122
  const from = Date.parse(cert.validFrom);
@@ -780,14 +1159,23 @@ var ResponseValidator = class {
780
1159
  * дуудна (тэдгээр нь sync; revocation нь сүлжээ I/O тул async).
781
1160
  */
782
1161
  async checkRevocation(cert) {
783
- if (!this.revocation) return;
1162
+ await this.checkRevocationStatus(cert);
1163
+ }
1164
+ /**
1165
+ * {@link checkRevocation} + юу шийдсэнийг буцаана (лог/аудитад): "good" | "unchecked" (unknown, soft-fail) |
1166
+ * "skipped" (hook алга). "revoked", мөн hard-fail горимд "unknown" → ValidationError; hook-ийн алдаа дамжина.
1167
+ */
1168
+ async checkRevocationStatus(cert) {
1169
+ if (!this.revocation) return "skipped";
784
1170
  const status = await this.revocation(cert, this.findIssuer(cert));
1171
+ if (status === "good") return "good";
785
1172
  if (status === "revoked") {
786
1173
  throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
787
1174
  }
788
- if (status === "unknown" && this.revocationMode === "hard-fail") {
1175
+ if (this.revocationMode === "hard-fail") {
789
1176
  throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 revocation \u0442\u04E9\u043B\u04E9\u0432 \u0442\u043E\u0433\u0442\u043E\u043E\u0433\u0434\u0441\u043E\u043D\u0433\u04AF\u0439 (hard-fail)");
790
1177
  }
1178
+ return "unchecked";
791
1179
  }
792
1180
  };
793
1181
  function signedCallbackUrl(fromResponse, rpCallback, flowType) {
@@ -802,7 +1190,7 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
802
1190
  return fromResponse;
803
1191
  }
804
1192
  function userChallengeOf(verifier) {
805
- return createHash3("sha256").update(verifier, "utf8").digest("base64url");
1193
+ return createHash4("sha256").update(verifier, "utf8").digest("base64url");
806
1194
  }
807
1195
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
808
1196
  const sig = Buffer.from(sigB64, "base64");
@@ -904,7 +1292,7 @@ function mgf1(hash, seed, length) {
904
1292
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
905
1293
  const c = Buffer.alloc(4);
906
1294
  c.writeUInt32BE(i, 0);
907
- out.push(createHash3(nodeHash(hash)).update(seed).update(c).digest());
1295
+ out.push(createHash4(nodeHash(hash)).update(seed).update(c).digest());
908
1296
  }
909
1297
  return Buffer.concat(out).subarray(0, length);
910
1298
  }
@@ -928,9 +1316,42 @@ function emsaPssVerify(mHash, em, emBits, hash) {
928
1316
  const psLen = emLen - hLen - sLen - 2;
929
1317
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
930
1318
  const salt = db.subarray(psLen + 1);
931
- const h2 = createHash3(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1319
+ const h2 = createHash4(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
932
1320
  return h.equals(h2);
933
1321
  }
1322
+ function keyUsageBits(cert) {
1323
+ const v = certExtension(cert.raw, "2.5.29.15");
1324
+ if (!v) return 0;
1325
+ const bs = tlv(v, 0, v.length);
1326
+ if (bs.tag !== 3 || bs.end - bs.start < 2) return 0;
1327
+ return v[bs.start + 1];
1328
+ }
1329
+ function extKeyUsage(cert) {
1330
+ const v = certExtension(cert.raw, "2.5.29.37");
1331
+ if (!v) return [];
1332
+ const seq = tlv(v, 0, v.length);
1333
+ const out = [];
1334
+ for (let p = seq.start; p < seq.end; ) {
1335
+ const t = tlv(v, p, seq.end);
1336
+ out.push(oidOf(v, t));
1337
+ p = t.end;
1338
+ }
1339
+ return out;
1340
+ }
1341
+ function subjectKeyId(cert) {
1342
+ const v = certExtension(cert.raw, "2.5.29.14");
1343
+ if (!v) return void 0;
1344
+ const o = tlv(v, 0, v.length);
1345
+ return o.tag === 4 && o.end > o.start ? Buffer.from(v.subarray(o.start, o.end)).toString("hex") : void 0;
1346
+ }
1347
+ function authorityKeyId(cert) {
1348
+ const v = certExtension(cert.raw, "2.5.29.35");
1349
+ if (!v) return void 0;
1350
+ const seq = tlv(v, 0, v.length);
1351
+ if (seq.start >= seq.end) return void 0;
1352
+ const k = tlv(v, seq.start, seq.end);
1353
+ return k.tag === 128 && k.end > k.start ? Buffer.from(v.subarray(k.start, k.end)).toString("hex") : void 0;
1354
+ }
934
1355
  function isCa(cert) {
935
1356
  return cert.ca === true;
936
1357
  }
@@ -958,6 +1379,8 @@ var EidClient = class {
958
1379
  sign;
959
1380
  pdf;
960
1381
  organization;
1382
+ person;
1383
+ token;
961
1384
  session;
962
1385
  validator;
963
1386
  constructor(cfg) {
@@ -973,8 +1396,10 @@ var EidClient = class {
973
1396
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
974
1397
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
975
1398
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
976
- this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
1399
+ this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level, timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS });
977
1400
  this.organization = new OrganizationApi(http);
1401
+ this.person = new PersonApi(http);
1402
+ this.token = new TokenApi(http, cfg.credentials);
978
1403
  this.session = new SessionApi(http);
979
1404
  this.validator = new ResponseValidator(cfg.trust ?? {});
980
1405
  }
@@ -1051,7 +1476,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
1051
1476
  }
1052
1477
 
1053
1478
  // src/oidc.ts
1054
- import { timingSafeEqual } from "crypto";
1479
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
1055
1480
  var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
1056
1481
  var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
1057
1482
  var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
@@ -1088,7 +1513,7 @@ var EidOidcClient = class {
1088
1513
  if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
1089
1514
  if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
1090
1515
  this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
1091
- this.scope = cfg.scope ?? "openid";
1516
+ this.scope = cfg.scope ?? OIDC_EXPECT.defaultScope;
1092
1517
  }
1093
1518
  cfg;
1094
1519
  issuer;
@@ -1136,7 +1561,7 @@ var EidOidcClient = class {
1136
1561
  state,
1137
1562
  nonce,
1138
1563
  code_challenge: await pkceChallenge(codeVerifier),
1139
- code_challenge_method: "S256"
1564
+ code_challenge_method: OIDC_EXPECT.pkceMethod
1140
1565
  });
1141
1566
  if (opts.display) p.set("display", opts.display);
1142
1567
  u.search = p.toString();
@@ -1215,7 +1640,7 @@ var EidOidcClient = class {
1215
1640
  const header = decodeJsonPart(parts[0]);
1216
1641
  const payload = decodeJsonPart(parts[1]);
1217
1642
  if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
1218
- if (header.alg !== "ES256") throw bad("alg ES256 \u0431\u0438\u0448");
1643
+ if (header.alg !== OIDC_EXPECT.idTokenAlg) throw bad("alg ES256 \u0431\u0438\u0448");
1219
1644
  const kid = typeof header.kid === "string" ? header.kid : "";
1220
1645
  if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
1221
1646
  const pick = (ks) => ks.find(
@@ -1308,7 +1733,7 @@ function formEncode(s) {
1308
1733
  function timingSafeEqualStr(a, b) {
1309
1734
  const x = Buffer.from(a, "utf8");
1310
1735
  const y = Buffer.from(b, "utf8");
1311
- return x.length === y.length && timingSafeEqual(x, y);
1736
+ return x.length === y.length && timingSafeEqual2(x, y);
1312
1737
  }
1313
1738
  function decodeJsonPart(s) {
1314
1739
  try {
@@ -1330,7 +1755,9 @@ export {
1330
1755
  ApiError,
1331
1756
  AuthApi,
1332
1757
  AuthenticationError,
1758
+ CITIZEN_ISSUING_CA_SKI,
1333
1759
  DEFAULT_BASE_URL,
1760
+ DEFAULT_PDF_TIMEOUT_MS,
1334
1761
  DeviceLinkError,
1335
1762
  EID_ACR_ADVANCED,
1336
1763
  EID_ACR_QUALIFIED,
@@ -1345,10 +1772,13 @@ export {
1345
1772
  ON_BEHALF_ERROR_CODES,
1346
1773
  OrganizationApi,
1347
1774
  PdfApi,
1775
+ PersonApi,
1776
+ RETRYABLE_ERROR_CODES,
1348
1777
  ResponseValidator,
1349
1778
  SessionApi,
1350
1779
  SessionFailedError,
1351
1780
  SignApi,
1781
+ TokenApi,
1352
1782
  ValidationError,
1353
1783
  acspV2Digest,
1354
1784
  buildAcspV2Payload,
@@ -1357,14 +1787,19 @@ export {
1357
1787
  interactionsToBase64,
1358
1788
  isMobileUserAgent,
1359
1789
  openOrShowQR,
1790
+ parseRetryAfter,
1360
1791
  parseSessionResult,
1361
1792
  parseSubject,
1793
+ personMatchesCertificate,
1362
1794
  pkceChallenge,
1363
1795
  qrDeviceLinkTicker,
1364
1796
  randomChallenge,
1365
1797
  resolveBaseUrl,
1366
1798
  sha256Base64,
1799
+ tokenAuthDigest,
1800
+ tokenLimitOf,
1367
1801
  userChallengeOf,
1368
1802
  verifyPayloadSignature,
1369
- verifyPrehashedSignature
1803
+ verifyPrehashedSignature,
1804
+ verifyTokenCertificates
1370
1805
  };