@gerege-systems/eid-mongolia-sdk 0.14.1 → 0.15.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/README.md CHANGED
@@ -7,7 +7,8 @@ RP-API v3, Smart-ID нийцтэй. Бүрэн баримт: <https://developer.
7
7
  > ⚠️ SDK **backend**-д ажиллана: бүх дуудлага API secret (`rp_sk_…`)-ээр баталгаажна, secret-ийг браузер/гар утсанд
8
8
  > хэзээ ч задлахгүй. Браузерт зөвхөн таны backend-ийн өгсөн холбоос/QR/товч харагдана — «eID-ээр нэвтрэх» товч
9
9
  > (`/button`) ч таны backend-ийг л дуудна (§1a). `@gerege-systems/eid-mongolia-sdk/browser`-ийн legacy device-link
10
- > туслахууд 2026-10-07-оос ажиллахгүй (§4).
10
+ > туслахууд 2026-10-07-оос ажиллахгүй (§4). Үл хамаарах: `@gerege-systems/eid-mongolia-sdk/pades` (клиент талын PDF
11
+ > гарын үсэг, secret-гүй) хөтөч болон Node-д ажиллана (§3d).
11
12
 
12
13
  ## Суулгах
13
14
 
@@ -15,7 +16,8 @@ RP-API v3, Smart-ID нийцтэй. Бүрэн баримт: <https://developer.
15
16
  npm install @gerege-systems/eid-mongolia-sdk
16
17
  ```
17
18
 
18
- Node.js ≥ 22 (global `fetch`, `node:crypto`).
19
+ Node.js ≥ 18 (global `fetch`, `node:crypto`) — бүх entry (`.`, `/browser`, `/button`, `/pades`), CI-д Node 18 / 20 / 22-оор шалгагдана.
20
+ `/pades` мөн орчин үеийн хөтөчид (Web Crypto) ажиллана.
19
21
 
20
22
  ## 1. RP бүртгүүлэх
21
23
 
@@ -235,7 +237,7 @@ UTF8String/PrintableString/BMPString. `parseSubject` нь зөвхөн задл
235
237
  > жагсаалтаар ирдэг; Smart-ID ба eID-д Notification төрлийн device link **байхгүй** (CA `docs/DEVICE_LINK_V3.md` §3).
236
238
  > Хуучин `/dl?sessionId&vc` холбоос (`deviceLink`) push session-д ч юу ч өгөхгүй (сервер `DEVICE_LINK_REQUIRED`).
237
239
  > Иргэн RP-ийг eID апптай ижил утсан дээр нээсэн бол push-ийн оронд **device-link Web2App** урсгал хэрэглэ
238
- > (`auth.deviceLink*` / `pdf.prepare({ flow: "device-link" })` + `buildDeviceLink(…, "Web2App")`, §4) — товч аппыг шууд
240
+ > (`auth.deviceLink*` / `signPdfDigest({ flow: "device-link" })` + `buildDeviceLink(…, "Web2App")`, §4) — товч аппыг шууд
239
241
  > нээж, callback-аар буцна. Push-д «eID аппаа нээнэ үү» гэсэн текст л хангалттай.
240
242
 
241
243
  ## 3. Гарын үсэг (qualified signature)
@@ -272,10 +274,16 @@ RAW_DIGEST_SIGNATURE-д `flowType` гарын үсэгт ордоггүй (зө
272
274
  холбоосыг backend-д `buildDeviceLink(deviceLinkInput(s, "QR", "mon"))` (auth/cert session-д ч мөн). `s.sign`
273
275
  (`SignContext`: digest, hash, алгоритм, interactions, callback)-ийг session-тэй хамт серверт хадгал.
274
276
 
275
- **`expiresAt`** (ms epoch) — sign / certificate-choice session-ий *тооцоолсон* дуусах мөч: CA session start хариунд
276
- хугацаа буцаадаггүй (зөвхөн `sessionID`), CA-ийн анхдагч 600с (`DEFAULT_SIGN_SESSION_TTL_MS`; CA админ 60–1800с болгож
277
- болно → `signSessionTtlMs`). Хүсэлтээс өмнөх цагаас тооцдог тул CA-ийнхаас хойно хэзээ ч биш; эцсийн үнэн нь
278
- `GET /session` (`TIMEOUT`).
277
+ **`expiresAt`** (ms epoch) — session дуусах мөч: CA-ийн session start хариуны `expiresAt` (RFC 3339; auth / sign /
278
+ certificate-choice бүгдэд, 2026-10-14-өөс). Хуучин CA (талбаргүй) үед sign / certificate-choice-д SDK тооцоолно — CA-ийн
279
+ анхдагч 600с (`DEFAULT_SIGN_SESSION_TTL_MS`; CA админ 60–1800с болгож болно → `signSessionTtlMs`), auth-д undefined.
280
+ Эцсийн үнэн нь `GET /session` (`TIMEOUT`).
281
+
282
+ **Session эхлэх алдаа** (`ApiError`-ийн дэд анги, `.code`-той): `NoSigningCertificateError` — 409
283
+ `NO_SIGNING_CERTIFICATE` (sign / certificate-choice start, `/pdf/sign/prepare`: иргэнд идэвхтэй төхөөрөмж / хүчинтэй
284
+ гарын үсгийн гэрчилгээ алга, session үүсээгүй); `CertChoiceInvalidError` — 409 `CERT_CHOICE_INVALID` (linked sign-ийн
285
+ certificate-choice хүчингүй / хуучирсан / ашиглагдсан → cert-choice-ийг дахин эхлүүл); `RequestInvalidError` — 400
286
+ `REQUEST_INVALID` (ж: linked-д `linkedSessionID` дутуу).
279
287
 
280
288
  ### 3.1. Гэрчилгээг урьдчилж мэдэх (client-side PAdES / CMS)
281
289
 
@@ -318,42 +326,22 @@ const { representations } = await eid.organization.getRepresentations("PNOMN-123
318
326
  // → [{ orgEtsi: "NTRMN-6235972", orgName: "Гэрэгэ Системс ХХК", role: "ceo", roleLabel: "Гүйцэтгэх захирал",
319
327
  // rightType: "ADMIN", rightTypeLabel: "Админ", source: "REGISTRY", sourceLabel: "Улсын бүртгэл", … }]
320
328
 
321
- // 2. PDF (PAdES, CA угсарна) — санал болгох урсгал.
329
+ // 2. PDF: §3d (клиент талын PAdES) — signPdfDigest({ …, onBehalfOf }) + prepare({ claimedRole: rep.claimedRole }).
330
+ // 3. Raw digest гарын үсэг: eid.sign.digestByEtsi(id, digest, interactions, { onBehalfOf: "NTRMN-6235972" })
331
+ // — CMS-ийг та угсардаг тул мэдүүлсэн үүргийг (signer-attributes-v2) өөрөө нэмнэ.
322
332
  try {
323
- const s = await eid.pdf.prepare({
324
- pdf: pdfBytes, fileName: "Гэрээ №12.pdf",
325
- signer: { etsi: "PNOMN-12345678" }, flow: "notification",
326
- onBehalfOf: "NTRMN-6235972",
327
- });
328
- showToUser(s.vc);
329
- const r = await eid.session.waitForResult(s.sessionId);
330
- // MUST: endResult OK, pdf.documentStatus READY, pdf.signer.onBehalfOf = хүссэн байгууллага,
331
- // татсан файлын SHA-256 = pdf.outSha256, validation.indication "valid", ltv true.
332
- if (r.pdf?.documentStatus !== "READY" || r.pdf.signer?.onBehalfOf !== "NTRMN-6235972") throw new Error("…");
333
- const signed = await eid.pdf.documentVerified(s.sessionId, r.pdf); // SHA-256 ≠ pdf.outSha256 → ValidationError
334
- console.log(r.pdf.signer.claimedRole); // "Гүйцэтгэх захирал, Гэрэгэ Системс ХХК (NTRMN-6235972)"
333
+ /* … */
335
334
  } catch (e) {
336
335
  if ((e instanceof ForbiddenError || e instanceof ApiError) && e.code) {
337
336
  // REPRESENTATION_DENIED | REPRESENTATION_PENDING | REPRESENTATION_EXPIRED | ORG_NOT_ACTIVE |
338
- // SIGNER_UNIDENTIFIED (403) · ORG_NOT_FOUND (404) · REPRESENTATION_REVOKED (403, document татах үед)
337
+ // SIGNER_UNIDENTIFIED (403) · ORG_NOT_FOUND (404) · REPRESENTATION_REVOKED (403, бүртгэлийн үед)
339
338
  }
340
339
  throw e;
341
340
  }
342
-
343
- // Raw digest гарын үсэгт ч мөн: eid.sign.digestByEtsi(id, digest, interactions, { onBehalfOf: "NTRMN-6235972" })
344
- // — CMS/PDF-ийг та угсардаг тул мэдүүлсэн үүргийг (signer-attributes-v2) өөрөө нэмнэ.
345
341
  ```
346
342
 
347
- `prepare`-ийн хариу `digest` (DTBS, base64) — иргэний зурах 32 байт, push ба device-link хоёуланд (хуучин CA push-д
348
- `undefined`). Иргэний гарын үсгийг өөрөө шалгах бол: `eid.validator.validateSign(r, s.digest!, { expectedFlowType: "QR" })`
349
- (харуулсан урсгалаа). Гарын үсэгтэй PDF-ийг
350
- `pdf.document()`-ийн оронд `pdf.documentVerified(sessionId, r.pdf)`-аар тат — `READY` + `outSha256` тулгалтыг хийнэ.
351
-
352
- Түр алдаа — `ApiError.retryable` үнэн, `retryAfterMs`-ийн (CA-ийн `Retry-After`, секунд эсвэл огноо) дараа давтана:
353
- prepare 429 `PDF_PREPARE_BUSY`, 503 `LTV_UNAVAILABLE` / `TSA_UNAVAILABLE`; document 503 `DOCUMENT_PENDING`,
354
- `TSA_UNAVAILABLE`, `LTV_UNAVAILABLE` (`RETRYABLE_ERROR_CODES`). `headers` — хариуны header. PDF дуудлагын хугацаа
355
- `pdfTimeoutMs` (анхдагч 80000). `pdf.document` санах ойд `pdfMaxBytes` (анхдагч `DEFAULT_PDF_MAX_BYTES` = 64 MiB)
356
- хүртэл уншина; хэтэрвэл `ResponseTooLargeError`. JSON хариу 4 MiB-ээс хэтэрвэл мөн `ResponseTooLargeError` (таслахгүй).
343
+ Түр алдаа — `ApiError.retryable` үнэн, `retryAfterMs`-ийн (CA-ийн `Retry-After`, секунд эсвэл огноо) дараа давтана
344
+ (`RETRYABLE_ERROR_CODES`). `headers` — хариуны header. JSON хариу 4 MiB-ээс хэтэрвэл `ResponseTooLargeError` (таслахгүй).
357
345
  `getRepresentations`-ийн `raw` — серверийн түүхий JSON.
358
346
 
359
347
  **Хүнд харуулахдаа `roleLabel` / `rightTypeLabel` / `sourceLabel` ашигла** (representations болон session-ий
@@ -362,9 +350,7 @@ prepare 429 `PDF_PREPARE_BUSY`, 503 `LTV_UNAVAILABLE` / `TSA_UNAVAILABLE`; docum
362
350
  машин утга, өөрөө орчуулахгүй. Хэл: `new EidClient({ …, language: "en" })` → дуудлага бүрд `Accept-Language`; анхдагч
363
351
  илгээхгүй (сервер mn). Хуучин CA-д эдгээр талбар байхгүй.
364
352
 
365
- Олон талт гэрээ: А байгууллагын гаралтыг өөрчлөлтгүй Б-ийн `prepare`-д (`docID`, өөр `onBehalfOf`) илгээнэ.
366
- Session-ий `onBehalfOf` блок (`role`/`roleLabel`/`rightType`/`rightTypeLabel`) нь хариу угсрах үеийн бүртгэл; `pdf.signer.onBehalfOf`/`claimedRole`
367
- нь баримтад бичигдсэн утга.
353
+ Session-ий `onBehalfOf` блок (`role`/`roleLabel`/`rightType`/`rightTypeLabel`) нь хариу угсрах үеийн бүртгэл.
368
354
 
369
355
  ## 3b. Иргэний PKI самбар (`PKI_READ` эрх)
370
356
 
@@ -388,14 +374,111 @@ const ch = await eid.token.authChallenge();
388
374
  const digest = tokenAuthDigest(ch.rpUUID, ch.challenge); // токен CKM_ECDSA → түүхий r||s
389
375
  const who = await eid.token.authVerify(ch.challengeId, authCertDer, rs);
390
376
 
391
- // токеноор PDF (PAdES, SIGN эрх) — session/утасгүй; байгууллагын токенд onBehalfOf = тухайн байгууллага
392
- const p = await eid.pdf.tokenPrepare({ pdf, fileName: "Гэрээ.pdf", signerEtsi, certificate: signCertDer });
393
- const rs = /* токен (PKCS#11): CKM_ECDSA(p.hashToSign) → түүхий r||s, 64 байт — p.expiresAt хүртэл */ new Uint8Array(64);
394
- const blk = await eid.pdf.tokenFinalize(p.signingId, rs); // READY; 503 DOCUMENT_PENDING → ижил rs-ийг дахин
395
- const signed = await eid.pdf.tokenDocument(p.signingId); // SHA-256 = blk.outSha256 тулга
377
+ // токеноор PDF: §3d — prepare() → токен CKM_ECDSA(dtbs) → r||s → complete() → registerSignature() (sessionID-гүй, medium "token")
378
+ ```
379
+
380
+ `/token/auth/*` нь CA-д байгаа ч `/.well-known/eid`-д хараахан жагсаагдаагүй (гэрээний тест WARN).
381
+
382
+ ## 3d. PDF гарын үсэг (PAdES, клиент тал) — `@gerege-systems/eid-mongolia-sdk/pades`
383
+
384
+ **PDF eID-ийн сервер рүү ОЧИХГҮЙ** (Smart-ID / DigiDoc загвар). Баримтыг таны тал (браузер эсвэл Node) НАТИВ
385
+ TypeScript-ээр (WASM-гүй, Web Crypto) угсарна; CA зөвхөн 32 байт DTBS-д гарын үсгийн session (ердийн
386
+ `RAW_DIGEST_SIGNATURE`) ба CMS-ийн бүртгэлийг хийнэ. Гаралт: PAdES-BASELINE-LT (цагийн тэмдэг tsa.timeserver.mn, OCSP/CRL
387
+ DSS-д), Adobe / EU DSS-д «LTV enabled», баталгаажуулах (QR) хуудастай (mn анхдагч / en). Go reference
388
+ (`eid-mongolia-sdk-go/pades`, профайл `pades-client/1`)-тэй **байт-ижил** — conformance golden-оор CI-д шалгагдана.
389
+
390
+ | Функц | Хаана | Тайлбар |
391
+ |---|---|---|
392
+ | `prepare(pdf, { signerCert, claimedRole?, verifyPage?, fileName?, lang?, docId?, … })` | браузер / Node | `{ dtbs, digest, docId, state }` — сүлжээгүй |
393
+ | `eid.signPdfDigest({ dtbs, fileName, signer, flow?, onBehalfOf? })` | RP backend | DTBS-д гарын үсгийн session (push эсвэл device-link) |
394
+ | `complete(state, signature)` | браузер / Node | TSA + OCSP/CRL → `{ pdf, cms, docId, outSha256, inSha256, hasPage, pageSha256, … }` |
395
+ | `eid.registerSignature(registrationFromCompleted(done, { fileName, sessionID }))` | RP backend | `POST /pdf/signatures` — `/verify/{docID}` бүртгэл |
396
+
397
+ `/pades` хөтчийн bundle ~125 KB gzip (Noto Sans фонт asset-тэй, CI-д ≤ 300 KB); үндсэн entry үүнийг агуулахгүй. `/pades` (ба бүх багц) **Node ≥ 18** (CI: 18 / 20 / 22),
398
+ орчин үеийн хөтөч. Браузерийн CSP: `connect-src https://tsa.timeserver.mn https://ocsp.eidmongolia.mn
399
+ https://crl.eidmongolia.mn`. Хөтөчид гэрчилгээний `http://` AIA/CDP-г зөвхөн эдгээр хостод `https://` болгоно; үндэсний
400
+ CA-ийн CRL (`esign.gov.mn`) нь `crl.eidmongolia.mn` толин хуулбараас (бүх платформд). Шалгагч (`verify`) энд
401
+ **байхгүй** — бүрэн криптограф шалгалт Go backend SDK-д; `prepare` нь өмнөх гарын үсгийг «хөнгөн» шалгуураар (бүтэц,
402
+ CMS гарын үсэг, eID олгогч, timestamp, DSS) л шалгана.
403
+
404
+ **Зурагчийн гэрчилгээ.** `prepare` иргэний SIGN гэрчилгээг (`signingCertificateV2`) шаарддаг тул session-оос ӨМНӨ
405
+ хэрэгтэй — одоогоор `eid.person.certificates(etsi)` (`PKI_READ`; `type: "SIGN"`, `status: "VALID"`, `keyMedium: "MOBILE"`
406
+ мөрийн `certValue` + `documentNumber`). Session-ийг тэр `documentNumber`-ээр эхлүүлнэ — өөр төхөөрөмж зурвал `complete`
407
+ `SIGNATURE_INVALID`.
408
+
409
+ ### Вэб RP (браузер prepare/complete, backend session)
410
+
411
+ ```ts
412
+ // ── браузер ─────────────────────────────────────────────────────────────────────────────
413
+ import { prepare, complete } from "@gerege-systems/eid-mongolia-sdk/pades";
414
+
415
+ const { signerCert, documentNumber } = await api("/sign/pdf/signer"); // backend: person.certificates
416
+ const p = await prepare(pdfBytes, {
417
+ signerCert, verifyPage: true, fileName: "Гэрээ №12.pdf",
418
+ claimedRole, // байгууллагаар бол getRepresentations()-ийн claimedRole ЯГ ХЭВЭЭР
419
+ });
420
+ await idb.put("pades-state", p.state); // хуудас дахин ачаалагдаж болно
421
+ const s = await api("/sign/pdf/start", { dtbs: b64(p.dtbs), documentNumber, fileName: "Гэрээ №12.pdf" });
422
+ showToUser(s.vc); // эсвэл backend-ийн QR (§4)
423
+ const { signature } = await api(`/sign/pdf/${s.sessionId}/result`); // backend poll + validateSign
424
+ const done = await complete(p.state, signature); // TSA + OCSP/CRL + DSS
425
+ const { pdf, signerCert: _c, ...meta } = done; // PDF backend рүү очихгүй
426
+ await api(`/sign/pdf/${s.sessionId}/register`, { ...meta, cms: b64(done.cms) });
427
+ download(done.pdf, "Гэрээ №12.pdf");
428
+
429
+ // ── RP backend (Node) ─────────────────────────────────────────────────────────────────────
430
+ // /sign/pdf/start — sessionSecret (device-link) backend-ээс гарахгүй; s.digest-ийг session-д хадгал.
431
+ const s = await eid.signPdfDigest({
432
+ dtbs: req.body.dtbs, fileName: req.body.fileName,
433
+ signer: { documentNumber: req.body.documentNumber }, // эсвэл { etsi }
434
+ onBehalfOf: "NTRMN-6235972", // заавал биш
435
+ });
436
+ store(s.sessionId, { digest: s.digest, fileName: req.body.fileName });
437
+ // /sign/pdf/:id/result
438
+ const r = await eid.session.waitForResult(sessionId);
439
+ const sig = eid.validator.validateSign(r, store.get(sessionId).digest, { expectedFlowType: "Notification" });
440
+ reply({ signature: sig.signatureValueB64 });
441
+ // /sign/pdf/:id/register — CA CMS-ийг өөрөө шалгана (session digest = SHA-256(signedAttrs), cert, claimedRole)
442
+ // registrationFromCompleted: CA dto-той яг ижил бие (hasPage илгээхгүй, pageSha256 зөвхөн хуудастай үед); cms base64 ч болно.
443
+ const reg = await eid.registerSignature(registrationFromCompleted(req.body, {
444
+ fileName: store.get(sessionId).fileName, sessionID: sessionId, onBehalfOf: "NTRMN-6235972" }));
445
+ reply({ verifyUrl: reg.verifyUrl });
396
446
  ```
397
447
 
398
- `/token/auth/*`, `/pdf/token/*` нь CA-д байгаа ч `/.well-known/eid`-д хараахан жагсаагдаагүй (гэрээний тест WARN).
448
+ ### Зөвхөн Node (backend бүгдийг хийнэ)
449
+
450
+ ```ts
451
+ import { registrationFromCompleted } from "@gerege-systems/eid-mongolia-sdk";
452
+ import { prepare, complete, PadesError } from "@gerege-systems/eid-mongolia-sdk/pades";
453
+
454
+ const p = await prepare(pdfBytes, { signerCert, verifyPage: true, fileName: "Гэрээ №12.pdf" });
455
+ const s = await eid.signPdfDigest({ dtbs: p.dtbs, fileName: "Гэрээ №12.pdf", signer: { documentNumber } });
456
+ showToUser(s.vc);
457
+ const r = await eid.session.waitForResult(s.sessionId);
458
+ const sig = eid.validator.validateSign(r, s.digest);
459
+ let done;
460
+ for (let i = 0; ; i++) {
461
+ try {
462
+ done = await complete(p.state, sig.signatureValueB64);
463
+ break;
464
+ } catch (e) {
465
+ if (e instanceof PadesError && e.retryable && i < 3) continue; // TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил state+гарын үсэг
466
+ throw e;
467
+ }
468
+ }
469
+ await eid.registerSignature(registrationFromCompleted(done, { fileName: "Гэрээ №12.pdf", sessionID: s.sessionId }));
470
+ writeFileSync("signed.pdf", done.pdf);
471
+ ```
472
+
473
+ `PadesError.code` (тогтвортой, бүх платформд ижил): `PDF_INVALID`, `PDF_ENCRYPTED`, `PDF_LOCKED` (DocMDP P=1),
474
+ `PDF_TOO_LARGE`, `PDF_TOO_MANY_SIGNATURES`, `PDF_PRIOR_SIGNATURE_INVALID`, `PDF_FOREIGN_SIGNATURE`, `SIGNER_INVALID`,
475
+ `REQUEST_INVALID`, `SIGNATURE_INVALID` (state хэвээр — дахин зуруулж болно), `TSA_UNAVAILABLE`, `LTV_UNAVAILABLE`
476
+ (`retryable`), `SELF_VALIDATION_FAILED`, `PLACEHOLDER_OVERFLOW`, `STATE_INVALID`, `INTERNAL`.
477
+ `state` нь бэлтгэсэн PDF-ийг агуулна — PDF-тэй ижил нууцлалтай хадгал. Формат нь Go SDK-тай ижил (хөтөчид `prepare`,
478
+ Go backend-д `complete` хийж болно).
479
+
480
+ 0.14.0-д `eid.pdf` (`prepare`, `document`, `documentVerified`, `tokenPrepare`, `tokenFinalize`, `tokenDocument`) ба
481
+ `pdfTimeoutMs` / `pdfMaxBytes` устсан — PDF upload endpoint-ууд CA-д байхгүй болно.
399
482
 
400
483
  ## 4. QR ба ижил төхөөрөмж — device link v3
401
484
 
@@ -522,9 +605,13 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
522
605
  | `ForbiddenError` | 403 — IP allowlist / mTLS / RP-ийн эрх; onBehalfOf татгалзвал `.code` (`OnBehalfErrorCode`) |
523
606
  | `SessionFailedError` | endResult ≠ OK (TIMEOUT, USER_REFUSED…) — `.endResult`-оор салга |
524
607
  | `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
608
+ | `NoSigningCertificateError` | 409 `NO_SIGNING_CERTIFICATE` — гарын үсгийн гэрчилгээ / төхөөрөмж алга (sign, cert-choice start, PDF prepare); `ApiError`-ийн дэд анги |
609
+ | `CertChoiceInvalidError` | 409 `CERT_CHOICE_INVALID` — linked sign-ийн certificate-choice хүчингүй; `ApiError`-ийн дэд анги |
610
+ | `RequestInvalidError` | 400 `REQUEST_INVALID` — хүсэлт буруу (ж: `linkedSessionID` дутуу); `ApiError`-ийн дэд анги |
525
611
  | `ApiError` | бусад HTTP алдаа (`.status`, `.body`, серверийн `.code`, `.retryAfterMs`, `.headers`, `.retryable` — ж: 404 `ORG_NOT_FOUND`) |
526
- | `ResponseTooLargeError` | хариу SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — `.op`, `.limit` |
612
+ | `ResponseTooLargeError` | хариу SDK-ийн хязгаараас их (JSON 4 MiB) — `.op`, `.limit` |
527
613
  | `NetworkError` | timeout/сүлжээ (`.op` — таних тэмдэггүй зам, `.timeout`) |
614
+ | `PadesError` (`/pades`) | клиент талын PAdES — `.code` (§3d), `.retryable` |
528
615
  | `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
529
616
 
530
617
  ## mTLS (eIDAS qualified орчин)
@@ -541,10 +628,17 @@ const eid = new EidClient({
541
628
 
542
629
  ```bash
543
630
  npm install
544
- npm run lint # tsc --noEmit
545
- npm test # build + node --test
631
+ npm run lint # tsc --noEmit
632
+ npm test # build + node --test (pades golden — Go reference-тэй байт-ижил)
633
+ npm run golden:check # test/fixtures/pades/golden + src/pades/assets ↔ бэхэлсэн Go commit
634
+ npm run size:pades # /pades хөтчийн bundle gzip ≤ 300 KB
546
635
  ```
547
636
 
637
+ `/pades`-ийн conformance fixture (golden: оролт, тестийн PKI-ийн гэрчилгээ, TSA/OCSP/CRL-ийн тогтмол хариу, хүлээгдэх
638
+ sha256) ба фонт/гинжийн asset-ийг Go reference-ээс `node scripts/sync-pades-golden.mjs` хуулна (`GO_SDK_COMMIT` —
639
+ tag `pades-golden-vN`); CI `--check`-ээр бэхэлсэн commit-тэй тулгана. Профайл: Go репогийн `pades/PROFILE.md`,
640
+ `PAGE_SPEC.md`, `PRECHECKS.md`, `WIRE.md`.
641
+
548
642
  ### Гэрээний шалгалт
549
643
 
550
644
  SDK-ийн дууддаг RP-API зам бүр (`src/contract.ts`) live [`/.well-known/eid`](https://rp.eidmongolia.mn/.well-known/eid)-ийн
@@ -1,3 +1,5 @@
1
+ import './errors-Z7SgLVTr.cjs';
2
+
1
3
  /** Сертификатын баталгаажуулалтын түвшин (eIDAS LoA). QUALIFIED — хууль ёсны гарын үсэгт. */
2
4
  type CertificateLevel = "ADVANCED" | "QUALIFIED" | "QSCD";
3
5
  /** Гарын үсгийн протокол: auth → ACSP_V2, sign → RAW_DIGEST_SIGNATURE (Smart-ID v3). */
@@ -94,8 +96,9 @@ interface SignContext {
94
96
  }
95
97
  /**
96
98
  * CA-ийн SIGNATURE ба CERTIFICATE_CHOICE session-ий анхдагч хүчинтэй хугацаа (мс; `EID_SIGN_SESSION_TTL_SECONDS` = 600).
97
- * CA-ийн админ «Системийн тохиргоо»-нд 60–1800 секундэд өөрчилж болно; session start хариунд CA хугацааг буцаадаггүй
98
- * (зөвхөн `sessionID`) тул SDK үүнийг `ClientConfig.signSessionTtlMs`-ээр тооцоолно.
99
+ * CA-ийн админ «Системийн тохиргоо»-нд 60–1800 секундэд өөрчилж болно. CA 2026-10-14-өөс session start хариунд
100
+ * `expiresAt` (RFC 3339) буцаадаг — SDK түүнийг ашиглана; хуучин CA (талбаргүй) үед л `ClientConfig.signSessionTtlMs`-ээр
101
+ * тооцоолно ({@link startExpiry}).
99
102
  */
100
103
  declare const DEFAULT_SIGN_SESSION_TTL_MS = 600000;
101
104
  /**
@@ -104,6 +107,10 @@ declare const DEFAULT_SIGN_SESSION_TTL_MS = 600000;
104
107
  * эхлүүлэх» шийдвэрт л — эцсийн үнэн нь `GET /session` (`TIMEOUT`).
105
108
  */
106
109
  declare function sessionExpiry(sentAtMs: number, ttlMs?: number): number;
110
+ /** CA-ийн `expiresAt` (RFC 3339) → ms epoch; байхгүй / задрахгүй бол undefined. */
111
+ declare function parseExpiresAt(v: unknown): number | undefined;
112
+ /** Session start-ын дуусах мөч: CA-ийн `expiresAt` байвал түүнийг, үгүй бол {@link sessionExpiry} (тооцоолол). */
113
+ declare function startExpiry(raw: unknown, sentAtMs: number, ttlMs?: number): number;
107
114
  /**
108
115
  * Device-link (QR/Web2App/App2App) session. Иргэн QR уншиж эсвэл товч дарж нэгдэнэ.
109
116
  * `sessionToken`, `sessionSecret`-ийг ЗӨВХӨН backend-д хадгална — браузер руу бүү гарга.
@@ -136,7 +143,10 @@ interface DeviceLinkSession {
136
143
  sign?: SignContext;
137
144
  /** Certificate-choice session-ий хүсэлтэд илгээсэн нэр (cert холбоосын authCode-д). */
138
145
  relyingPartyName?: string;
139
- /** Sign / certificate-choice session-ий ТООЦООЛСОН дуусах мөч (ms epoch, {@link sessionExpiry}); auth-д undefined. */
146
+ /**
147
+ * Session дуусах мөч (ms epoch): CA-ийн start хариуны `expiresAt`; хуучин CA-д sign / certificate-choice-д тооцоолсон
148
+ * ({@link startExpiry}), auth-д undefined.
149
+ */
140
150
  expiresAt?: number;
141
151
  }
142
152
  /** Session poll-ийн нэгтгэсэн үр дүн (validator-т орох түүхий материал). */
@@ -169,11 +179,6 @@ interface SessionResult {
169
179
  * RP энэ талбарыг л итгэж уншина (клиентийн сонголтод биш). docs/ORG_LOGIN.md.
170
180
  */
171
181
  onBehalfOf: OrgResult | null;
172
- /**
173
- * PAdES (`POST /pdf/sign/prepare`) session-ий `pdf` блок — PDF урсгал биш бол null (талбар алга = хуучин SDK-аар
174
- * угсарсан объект). {@link PdfSessionBlock}.
175
- */
176
- pdf?: PdfSessionBlock | null;
177
182
  /**
178
183
  * COMPLETE+OK auth session-ий `person` блок (CA бүртгэлээс: кирилл нэр, төрсөн огноо, хүйс — гэрчилгээнд байдаггүй).
179
184
  * Зөвхөн session эзэмшигч RP-д; CA иргэнийг олоогүй бол null. ГАРЫН ҮСЭГТ ОРДОГГҮЙ — validateAuth-ийн дараа
@@ -215,35 +220,6 @@ interface TokenCertSessionBlock {
215
220
  authSerialHex?: string;
216
221
  signSerialHex?: string;
217
222
  }
218
- /** PDF баримтын төлөв: PENDING → READY (эсвэл FAILED / EXPIRED). */
219
- type PdfDocumentStatus = "PENDING" | "READY" | "FAILED" | "EXPIRED";
220
- /** PAdES гаралтын гарын үсэг зурагч. */
221
- interface PdfSigner {
222
- etsi: string;
223
- certSerial: string;
224
- /** Байгууллагын нэрийн өмнөөс бол байгууллагын ETSI (`NTRMN-…`) — баримтад бичигдсэн утга. */
225
- onBehalfOf?: string;
226
- /** CMS signer-attributes-v2-д бичигдсэн мэдүүлсэн үүрэг: «<албан тушаал>, <нэр> (NTRMN-…)». */
227
- claimedRole?: string;
228
- }
229
- /** GET /session/{id}-ийн `pdf` объект (PAdES, CA угсарна). */
230
- interface PdfSessionBlock {
231
- docID: string;
232
- documentStatus: PdfDocumentStatus | string;
233
- /** FAILED үед — ж: `REPRESENTATION_REVOKED` (prepare-ээс хойш эрх хасагдсан, баримт үүсээгүй). */
234
- errorCode?: string;
235
- /** Гарын үсэгтэй PDF-ийн SHA-256 (hex) — татсан файлтай тулгана. */
236
- outSha256?: string;
237
- size?: number;
238
- expiresAt?: string;
239
- signatureLevel?: string;
240
- signer?: PdfSigner;
241
- validation?: {
242
- indication: string;
243
- ltv: boolean;
244
- signatures: number;
245
- };
246
- }
247
223
  /** Иргэний төлөөлж чадах нэг байгууллага (GET /organization/representations/etsi/{personEtsi}). */
248
224
  interface Representation {
249
225
  /** `NTRMN-<бүртгэл>` — `onBehalfOf`-д өгөх утга. */
@@ -267,6 +243,11 @@ interface Representation {
267
243
  source: "REGISTRY" | "MANUAL" | string;
268
244
  /** Эх үүсвэрийн хүнд харуулах нэр (REGISTRY → «Улсын бүртгэл», MANUAL → «Захирал нэмсэн»; хэл нь `roleLabel`-тэй ижил). Хуучин CA-д байхгүй. */
269
245
  sourceLabel?: string;
246
+ /**
247
+ * CMS signer-attributes-v2-д бичих мэдүүлсэн үүрэг («<албан тушаал>, <нэр> (NTRMN-…)») — клиент талын PAdES-д
248
+ * `pades.prepare({claimedRole})`-д ЯГ ХЭВЭЭР өгнө (CA бүртгэлд дахин тооцож тулгана). Хуучин CA-д байхгүй.
249
+ */
250
+ claimedRole?: string;
270
251
  /** ISO 8601. */
271
252
  validFrom: string;
272
253
  /** ISO 8601; байхгүй = хугацаагүй. */
@@ -352,135 +333,4 @@ interface OpenOrShowQROptions {
352
333
  */
353
334
  declare function openOrShowQR(session: DeviceLinkInput, opts: OpenOrShowQROptions): "opened" | "qr";
354
335
 
355
- /** Бүх SDK алдааны суурь. */
356
- declare class EidError extends Error {
357
- constructor(message: string);
358
- }
359
- /** HTTP хариуны нэмэлт мэдээлэл (алдааны төрлүүдэд). */
360
- interface HttpErrorInit {
361
- /** Хариуны header-ууд. */
362
- headers?: Headers;
363
- /** Retry-After (секунд эсвэл HTTP-огноо задалсан), мс; байхгүй/буруу бол undefined. */
364
- retryAfterMs?: number;
365
- }
366
- /**
367
- * HTTP 401 — API secret буруу/байхгүй. Тохиргооны (RP secret) алдаа. CA-ийн өөрийн 401 нь JSON `{"error"[,"code"]}` —
368
- * `serverMessage` (ба байвал `code`) бөглөгдөнө; JSON биш (proxy / nginx) бол хоёул undefined — үүгээр CA-ийн 401-ийг
369
- * proxy-ийнхоос ялгана.
370
- */
371
- declare class AuthenticationError extends EidError {
372
- /** Серверийн `{"code"}`; байхгүй / JSON биш (proxy) бол undefined. */
373
- readonly code?: string;
374
- /** Серверийн `{"error"}` текст (логт); JSON биш (proxy) бол undefined. */
375
- readonly serverMessage?: string;
376
- /** Хариуны бие (таслагдаагүй текст). */
377
- readonly body?: string;
378
- readonly headers?: Headers;
379
- constructor(message: string, init?: {
380
- code?: string;
381
- serverMessage?: string;
382
- body?: string;
383
- headers?: Headers;
384
- });
385
- }
386
- /**
387
- * HTTP 403 — IP allowlist/mTLS зөвшөөрөөгүй, RP-д эрх алга, эсвэл onBehalfOf-ийн төлөөлөл татгалзсан.
388
- * Сервер `{"error","code"}` буцаасан бол `code` (ж: `REPRESENTATION_DENIED`) — салааллыг үүгээр хийнэ.
389
- */
390
- declare class ForbiddenError extends EidError {
391
- /** Серверийн тогтвортой алдааны код (`{"code"}`), байхгүй бол undefined. */
392
- readonly code?: string | undefined;
393
- readonly headers?: Headers;
394
- /** Retry-After (мс), байхгүй бол undefined. */
395
- readonly retryAfterMs?: number;
396
- constructor(message: string,
397
- /** Серверийн тогтвортой алдааны код (`{"code"}`), байхгүй бол undefined. */
398
- code?: string | undefined, init?: HttpErrorInit);
399
- }
400
- /** RP-API-аас ирсэн бусад HTTP алдаа (4xx/5xx). status ба хариуны биеийг агуулна. */
401
- declare class ApiError extends EidError {
402
- readonly status: number;
403
- readonly body: string;
404
- /** Серверийн тогтвортой алдааны код (`{"error","code"}`-ийн `code`), байхгүй бол undefined. */
405
- readonly code?: string | undefined;
406
- readonly headers?: Headers;
407
- /**
408
- * Retry-After (мс, секунд эсвэл HTTP-огноо задалсан); байхгүй/буруу бол undefined. CA түр алдаанд (429
409
- * PDF_PREPARE_BUSY / RATE_LIMITED, 503 DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE г.м.) тавина.
410
- */
411
- readonly retryAfterMs?: number;
412
- constructor(status: number, body: string,
413
- /** Серверийн тогтвортой алдааны код (`{"error","code"}`-ийн `code`), байхгүй бол undefined. */
414
- code?: string | undefined, init?: HttpErrorInit);
415
- /**
416
- * CA-ийн түр алдаа ({@link RETRYABLE_ERROR_CODES}-ийн нэг, эсвэл 429/503 + Retry-After): `retryAfterMs`-ийн дараа
417
- * ижил хүсэлтийг давтаж болно.
418
- */
419
- get retryable(): boolean;
420
- }
421
- /**
422
- * CA-ийн түр (давтаж болох) алдааны кодууд — `ApiError.code`. PDF_PREPARE_BUSY (429 prepare, Retry-After 5),
423
- * DOCUMENT_PENDING (503 document, 5), TSA_UNAVAILABLE (503, 30), LTV_UNAVAILABLE (503, 60), RATE_LIMITED (429 токен),
424
- * STATE_UNAVAILABLE (503 токен), ISSUANCE_PENDING (503 токены үр дүн, 5).
425
- */
426
- type RetryableErrorCode = "PDF_PREPARE_BUSY" | "DOCUMENT_PENDING" | "TSA_UNAVAILABLE" | "LTV_UNAVAILABLE" | "RATE_LIMITED" | "STATE_UNAVAILABLE" | "ISSUANCE_PENDING";
427
- /** {@link RetryableErrorCode}-ийн бүх утга. */
428
- declare const RETRYABLE_ERROR_CODES: readonly RetryableErrorCode[];
429
- /** RFC 9110 §10.2.3: delay-seconds эсвэл HTTP-date → мс. Буруу / өнгөрсөн / 24 цагаас их → undefined. */
430
- declare function parseRetryAfter(value: string | null | undefined, now?: number): number | undefined;
431
- /**
432
- * Байгууллагын нэрийн өмнөөс (`onBehalfOf`) урсгалын татгалзлын кодууд — `ForbiddenError.code` /
433
- * `ApiError.code`-д ирнэ. 403: REPRESENTATION_DENIED (идэвхтэй төлөөлөгч биш), REPRESENTATION_PENDING (PIN2-оор
434
- * баталгаажуулаагүй), REPRESENTATION_EXPIRED (хугацааны гадна), ORG_NOT_ACTIVE, SIGNER_UNIDENTIFIED (anonymous
435
- * session); 404: ORG_NOT_FOUND; PDF баримт татахад 403 REPRESENTATION_REVOKED (prepare-ээс хойш эрх хасагдсан —
436
- * баримт үүсээгүй).
437
- */
438
- type OnBehalfErrorCode = "REPRESENTATION_DENIED" | "REPRESENTATION_PENDING" | "REPRESENTATION_EXPIRED" | "ORG_NOT_ACTIVE" | "SIGNER_UNIDENTIFIED" | "ORG_NOT_FOUND" | "REPRESENTATION_REVOKED";
439
- /** {@link OnBehalfErrorCode}-ийн бүх утга (runtime шалгалтад). */
440
- declare const ON_BEHALF_ERROR_CODES: readonly OnBehalfErrorCode[];
441
- /**
442
- * Сүлжээ/timeout — upstream хариу өгсөнгүй. Мессеж ба `op`-д хувь хүний таних тэмдэг ОРОХГҮЙ (замын `/etsi/{…}`,
443
- * `/document/{…}` → `…`) — шууд лог руу бичиж болно.
444
- */
445
- declare class NetworkError extends EidError {
446
- /** "GET /session/…" (таних тэмдэг хасагдсан зам). */
447
- readonly op?: string;
448
- /** Хүсэлт хугацаа хэтэрсэн. */
449
- readonly timeout?: boolean;
450
- constructor(message: string, init?: {
451
- op?: string;
452
- timeout?: boolean;
453
- });
454
- }
455
- /**
456
- * Хариуны бие SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — бие таслагдаж задлагдахгүй. `op` — method +
457
- * таних тэмдэггүй зам.
458
- */
459
- declare class ResponseTooLargeError extends EidError {
460
- readonly op: string;
461
- readonly limit: number;
462
- constructor(op: string, limit: number);
463
- }
464
- /**
465
- * Session амжилтгүй дууссан (endResult ≠ OK): TIMEOUT, USER_REFUSED*, DOCUMENT_UNUSABLE гм.
466
- * endResult-оор салгаж RP UI-д тохирох мессеж харуулна.
467
- */
468
- declare class SessionFailedError extends EidError {
469
- readonly endResult: string;
470
- constructor(endResult: string);
471
- }
472
- /**
473
- * Хариуны баталгаажуулалт бүтэлгүйтсэн — cert chain итгэлцэлгүй, signature challenge-тэй
474
- * таарсангүй, эсвэл certLevel хүрэлцээгүй. ЭНЭ алдаа гарвал хариунд ИТГЭХГҮЙ
475
- * (хуурамч/proxy хийгдсэн байж болзошгүй).
476
- */
477
- declare class ValidationError extends EidError {
478
- }
479
- /**
480
- * Device-link (App2App/Web2App) холбоос угсрах боломжгүй — хариунд `deviceLinkBase` байхгүй,
481
- * эсвэл sessionId/vc/суурь URL хэлбэр буруу. Session-ийг дахин эхлүүлнэ; холбоосыг таахгүй.
482
- */
483
- declare class DeviceLinkError extends EidError {
484
- }
485
-
486
- export { type AcspContext as A, type SignatureProtocol as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type TokenCertStatus as G, type HashAlgorithm as H, type Interaction as I, deviceLink as J, isMobileUserAgent as K, openOrShowQR as L, parseRetryAfter as M, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, sessionExpiry as Q, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DEFAULT_SIGN_SESSION_TTL_MS as f, DeviceLinkError as g, type DeviceLinkInput as h, type EndResult as i, ForbiddenError as j, type HttpErrorInit as k, type NavigatorLike as l, NetworkError as m, type OnBehalfErrorCode as n, type OpenOrShowQROptions as o, type OrgResult as p, type PdfDocumentStatus as q, type PdfSigner as r, RETRYABLE_ERROR_CODES as s, type Representation as t, ResponseTooLargeError as u, type RetryableErrorCode as v, SessionFailedError as w, type SessionState as x, type SignContext as y, type SignatureAlgorithmParameters as z };
336
+ export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, type EndResult as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, type OpenOrShowQROptions as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, type SessionResult as a, type RepresentationsResponse as b, DEFAULT_SIGN_SESSION_TTL_MS as c, type DeviceLinkInput as d, type NavigatorLike as e, type OrgResult as f, type Representation as g, type SessionState as h, type SignContext as i, type SignatureAlgorithmParameters as j, type SignatureProtocol as k, type TokenCertStatus as l, deviceLink as m, isMobileUserAgent as n, openOrShowQR as o, parseExpiresAt as p, startExpiry as q, sessionExpiry as s };