@gerege-systems/eid-mongolia-sdk 0.6.0 → 0.7.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
@@ -67,6 +67,51 @@ const sig = eid.validator.validateSign(result, digest); // digest-ийн эср
67
67
  console.log(sig.signatureValueB64, sig.subject);
68
68
  ```
69
69
 
70
+ ## 3a. Байгууллагын нэрийн өмнөөс гарын үсэг (onBehalfOf)
71
+
72
+ Иргэн **өөрийн PIN2 гэрчилгээгээр** зурна; тухайн мөчид байгууллагыг төлөөлөх эрхтэйг eID **өөрийн бүртгэлээр**
73
+ шалгана (Эстонийн загвар). Энэ нь **хувь хүний квалификацтай гарын үсэг** — байгууллагын гэрчилгээ, тамга (e-Seal) биш.
74
+ e-Seal нь зөвхөн хүнгүй, автомат системийн баримтад. Урьдчилсан нөхцөл: байгууллага eID-д `ACTIVE` (захирал
75
+ аппаараа ХУР-аар холбоно), зурагч нь `ADMIN` эсвэл PIN2-оор баталгаажуулсан `MANAGER`, RP-д `SIGN` эрх.
76
+ Дэлгэрэнгүй: [developer.eidmongolia.mn](https://developer.eidmongolia.mn/).
77
+
78
+ ```ts
79
+ import { ForbiddenError, ApiError, ON_BEHALF_ERROR_CODES } from "@gerege-systems/eid-mongolia-sdk";
80
+
81
+ // 1. Иргэн сүүлийн 24 цагт танай RP-ээр нэвтэрсэн байх ёстой.
82
+ const { representations } = await eid.organization.getRepresentations("PNOMN-12345678");
83
+ // → [{ orgEtsi: "NTRMN-6235972", orgName: "Гэрэгэ Системс ХХК", role: "ceo", rightType: "ADMIN", … }]
84
+
85
+ // 2. PDF (PAdES, CA угсарна) — санал болгох урсгал.
86
+ try {
87
+ const s = await eid.pdf.prepare({
88
+ pdf: pdfBytes, fileName: "Гэрээ №12.pdf",
89
+ signer: { etsi: "PNOMN-12345678" }, flow: "notification",
90
+ onBehalfOf: "NTRMN-6235972",
91
+ });
92
+ showToUser(s.vc);
93
+ const r = await eid.session.waitForResult(s.sessionId);
94
+ // MUST: endResult OK, pdf.documentStatus READY, pdf.signer.onBehalfOf = хүссэн байгууллага,
95
+ // татсан файлын SHA-256 = pdf.outSha256, validation.indication "valid", ltv true.
96
+ if (r.pdf?.documentStatus !== "READY" || r.pdf.signer?.onBehalfOf !== "NTRMN-6235972") throw new Error("…");
97
+ const signed = await eid.pdf.document(s.sessionId);
98
+ console.log(r.pdf.signer.claimedRole); // "Гүйцэтгэх захирал, Гэрэгэ Системс ХХК (NTRMN-6235972)"
99
+ } catch (e) {
100
+ if ((e instanceof ForbiddenError || e instanceof ApiError) && e.code) {
101
+ // REPRESENTATION_DENIED | REPRESENTATION_PENDING | REPRESENTATION_EXPIRED | ORG_NOT_ACTIVE |
102
+ // SIGNER_UNIDENTIFIED (403) · ORG_NOT_FOUND (404) · REPRESENTATION_REVOKED (403, document татах үед)
103
+ }
104
+ throw e;
105
+ }
106
+
107
+ // Raw digest гарын үсэгт ч мөн: eid.sign.digestByEtsi(id, digest, interactions, { onBehalfOf: "NTRMN-6235972" })
108
+ // — CMS/PDF-ийг та угсардаг тул мэдүүлсэн үүргийг (signer-attributes-v2) өөрөө нэмнэ.
109
+ ```
110
+
111
+ Олон талт гэрээ: А байгууллагын гаралтыг өөрчлөлтгүй Б-ийн `prepare`-д (`docID`, өөр `onBehalfOf`) илгээнэ.
112
+ Session-ий `onBehalfOf` блок (`role`/`rightType`) нь хариу угсрах үеийн бүртгэл; `pdf.signer.onBehalfOf`/`claimedRole`
113
+ нь баримтад бичигдсэн утга.
114
+
70
115
  ## 4. QR ба ижил төхөөрөмж — device link v3
71
116
 
72
117
  Иргэн өөр төхөөрөмжийн дэлгэц дээрх **QR**-ийг утсаараа уншина, эсвэл RP-ийн сайт/аппыг eID апптай **ижил
@@ -160,6 +205,9 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
160
205
  4. Гарын үсгийг иргэний public key-ээр шалгана:
161
206
  - **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-ийг мөн шалгана;
162
207
  `expectedFlowType` өгвөл `flowType` эхлүүлсэн урсгалтай ЯГ таарах, Web2App/App2App-д `userChallengeVerifier` заавал
208
+ `initialCallbackUrl`-ийг хариуны `signature.initialCallbackUrl`-аас авна (CA урсгалаар шийднэ: QR/Notification →
209
+ `""`, Web2App/App2App → таны callback; шилжилтийн үед legacy `/dl` ба push-д таны callback) — зөвхөн `""` эсвэл
210
+ таны илгээсэн callback байхад, Web2App/App2App-д заавал таны callback; өөр утга → татгалзана
163
211
  - **sign** → RP-ийн өгсөн digest дээр
164
212
 
165
213
  Аль нэг алхам бүтэлгүйтвэл `ValidationError` шиднэ — хариунд **итгэхгүй**.
@@ -169,10 +217,10 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
169
217
  | Алдаа | Утга |
170
218
  |---|---|
171
219
  | `AuthenticationError` | 401 — API secret буруу/байхгүй |
172
- | `ForbiddenError` | 403 — IP allowlist / mTLS зөвшөөрөөгүй |
220
+ | `ForbiddenError` | 403 — IP allowlist / mTLS / RP-ийн эрх; onBehalfOf татгалзвал `.code` (`OnBehalfErrorCode`) |
173
221
  | `SessionFailedError` | endResult ≠ OK (TIMEOUT, USER_REFUSED…) — `.endResult`-оор салга |
174
222
  | `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
175
- | `ApiError` | бусад HTTP алдаа (`.status`, `.body`) |
223
+ | `ApiError` | бусад HTTP алдаа (`.status`, `.body`, серверийн `.code` — ж: 404 `ORG_NOT_FOUND`) |
176
224
  | `NetworkError` | timeout/сүлжээ |
177
225
  | `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
178
226
 
@@ -116,6 +116,12 @@ interface SessionResult {
116
116
  serverRandom: string | null;
117
117
  userChallenge: string | null;
118
118
  flowType: string | null;
119
+ /**
120
+ * ACSP_V2 payload-д гарын үсэг зурагдсан `initialCallbackUrl` (хариуны `signature.initialCallbackUrl`,
121
+ * eID нэмэлт). Smart-ID: QR/Notification → `""`, Web2App/App2App → RP-ийн илгээсэн URL. null = хариунд
122
+ * талбар алга (хуучин CA). Validator үүнийг зөвхөн `""` эсвэл RP-ийн өөрийн callback байхад л хүлээн авна.
123
+ */
124
+ initialCallbackUrl?: string | null;
119
125
  signatureAlgorithmParameters: SignatureAlgorithmParameters | null;
120
126
  interactionTypeUsed: string | null;
121
127
  /**
@@ -124,6 +130,61 @@ interface SessionResult {
124
130
  * RP энэ талбарыг л итгэж уншина (клиентийн сонголтод биш). docs/ORG_LOGIN.md.
125
131
  */
126
132
  onBehalfOf: OrgResult | null;
133
+ /**
134
+ * PAdES (`POST /pdf/sign/prepare`) session-ий `pdf` блок — PDF урсгал биш бол null (талбар алга = хуучин SDK-аар
135
+ * угсарсан объект). {@link PdfSessionBlock}.
136
+ */
137
+ pdf?: PdfSessionBlock | null;
138
+ }
139
+ /** PDF баримтын төлөв: PENDING → READY (эсвэл FAILED / EXPIRED). */
140
+ type PdfDocumentStatus = "PENDING" | "READY" | "FAILED" | "EXPIRED";
141
+ /** PAdES гаралтын гарын үсэг зурагч. */
142
+ interface PdfSigner {
143
+ etsi: string;
144
+ certSerial: string;
145
+ /** Байгууллагын нэрийн өмнөөс бол байгууллагын ETSI (`NTRMN-…`) — баримтад бичигдсэн утга. */
146
+ onBehalfOf?: string;
147
+ /** CMS signer-attributes-v2-д бичигдсэн мэдүүлсэн үүрэг: «<албан тушаал>, <нэр> (NTRMN-…)». */
148
+ claimedRole?: string;
149
+ }
150
+ /** GET /session/{id}-ийн `pdf` объект (PAdES, CA угсарна). */
151
+ interface PdfSessionBlock {
152
+ docID: string;
153
+ documentStatus: PdfDocumentStatus | string;
154
+ /** FAILED үед — ж: `REPRESENTATION_REVOKED` (prepare-ээс хойш эрх хасагдсан, баримт үүсээгүй). */
155
+ errorCode?: string;
156
+ /** Гарын үсэгтэй PDF-ийн SHA-256 (hex) — татсан файлтай тулгана. */
157
+ outSha256?: string;
158
+ size?: number;
159
+ expiresAt?: string;
160
+ signatureLevel?: string;
161
+ signer?: PdfSigner;
162
+ validation?: {
163
+ indication: string;
164
+ ltv: boolean;
165
+ signatures: number;
166
+ };
167
+ }
168
+ /** Иргэний төлөөлж чадах нэг байгууллага (GET /organization/representations/etsi/{personEtsi}). */
169
+ interface Representation {
170
+ /** `NTRMN-<бүртгэл>` — `onBehalfOf`-д өгөх утга. */
171
+ orgEtsi: string;
172
+ orgRegister: string;
173
+ orgName: string;
174
+ orgNameEn?: string;
175
+ /** Бүртгэлийн албан тушаал (ХУР-аас холбогдсонд `ceo` / `founder`, MANAGER-т ADMIN-ий бичсэн текст). */
176
+ role?: string;
177
+ rightType: "ADMIN" | "MANAGER" | string;
178
+ source: "REGISTRY" | "MANUAL" | string;
179
+ /** ISO 8601. */
180
+ validFrom: string;
181
+ /** ISO 8601; байхгүй = хугацаагүй. */
182
+ validTo?: string;
183
+ }
184
+ /** GET /organization/representations/etsi/{personEtsi} хариу. */
185
+ interface RepresentationsResponse {
186
+ personEtsi: string;
187
+ representations: Representation[];
127
188
  }
128
189
  /** Session-ий байгууллагын блок — байгууллага + тухайн иргэний эрх (бүртгэлээс real-time). */
129
190
  interface OrgResult {
@@ -200,15 +261,37 @@ declare class EidError extends Error {
200
261
  /** HTTP 401 — API secret буруу/байхгүй. Тохиргооны (RP secret) алдаа. */
201
262
  declare class AuthenticationError extends EidError {
202
263
  }
203
- /** HTTP 403 — IP allowlist/mTLS зөвшөөрөөгүй. Танай серверийн IP бүртгэгдээгүй байж магадгүй. */
264
+ /**
265
+ * HTTP 403 — IP allowlist/mTLS зөвшөөрөөгүй, RP-д эрх алга, эсвэл onBehalfOf-ийн төлөөлөл татгалзсан.
266
+ * Сервер `{"error","code"}` буцаасан бол `code` (ж: `REPRESENTATION_DENIED`) — салааллыг үүгээр хийнэ.
267
+ */
204
268
  declare class ForbiddenError extends EidError {
269
+ /** Серверийн тогтвортой алдааны код (`{"code"}`), байхгүй бол undefined. */
270
+ readonly code?: string | undefined;
271
+ constructor(message: string,
272
+ /** Серверийн тогтвортой алдааны код (`{"code"}`), байхгүй бол undefined. */
273
+ code?: string | undefined);
205
274
  }
206
275
  /** RP-API-аас ирсэн бусад HTTP алдаа (4xx/5xx). status ба хариуны биеийг агуулна. */
207
276
  declare class ApiError extends EidError {
208
277
  readonly status: number;
209
278
  readonly body: string;
210
- constructor(status: number, body: string);
279
+ /** Серверийн тогтвортой алдааны код (`{"error","code"}`-ийн `code`), байхгүй бол undefined. */
280
+ readonly code?: string | undefined;
281
+ constructor(status: number, body: string,
282
+ /** Серверийн тогтвортой алдааны код (`{"error","code"}`-ийн `code`), байхгүй бол undefined. */
283
+ code?: string | undefined);
211
284
  }
285
+ /**
286
+ * Байгууллагын нэрийн өмнөөс (`onBehalfOf`) урсгалын татгалзлын кодууд — `ForbiddenError.code` /
287
+ * `ApiError.code`-д ирнэ. 403: REPRESENTATION_DENIED (идэвхтэй төлөөлөгч биш), REPRESENTATION_PENDING (PIN2-оор
288
+ * баталгаажуулаагүй), REPRESENTATION_EXPIRED (хугацааны гадна), ORG_NOT_ACTIVE, SIGNER_UNIDENTIFIED (anonymous
289
+ * session); 404: ORG_NOT_FOUND; PDF баримт татахад 403 REPRESENTATION_REVOKED (prepare-ээс хойш эрх хасагдсан —
290
+ * баримт үүсээгүй).
291
+ */
292
+ type OnBehalfErrorCode = "REPRESENTATION_DENIED" | "REPRESENTATION_PENDING" | "REPRESENTATION_EXPIRED" | "ORG_NOT_ACTIVE" | "SIGNER_UNIDENTIFIED" | "ORG_NOT_FOUND" | "REPRESENTATION_REVOKED";
293
+ /** {@link OnBehalfErrorCode}-ийн бүх утга (runtime шалгалтад). */
294
+ declare const ON_BEHALF_ERROR_CODES: readonly OnBehalfErrorCode[];
212
295
  /** Сүлжээ/timeout — upstream хариу өгсөнгүй. */
213
296
  declare class NetworkError extends EidError {
214
297
  }
@@ -234,4 +317,4 @@ declare class ValidationError extends EidError {
234
317
  declare class DeviceLinkError extends EidError {
235
318
  }
236
319
 
237
- export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, type OpenOrShowQROptions as O, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, ApiError as b, AuthenticationError as c, DeviceLinkError as d, type DeviceLinkInput as e, type EndResult as f, ForbiddenError as g, type NavigatorLike as h, NetworkError as i, SessionFailedError as j, type SessionState as k, type SignatureAlgorithmParameters as l, type SignatureProtocol as m, deviceLink as n, isMobileUserAgent as o, openOrShowQR as p };
320
+ export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfDocumentStatus as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfSessionBlock as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
@@ -116,6 +116,12 @@ interface SessionResult {
116
116
  serverRandom: string | null;
117
117
  userChallenge: string | null;
118
118
  flowType: string | null;
119
+ /**
120
+ * ACSP_V2 payload-д гарын үсэг зурагдсан `initialCallbackUrl` (хариуны `signature.initialCallbackUrl`,
121
+ * eID нэмэлт). Smart-ID: QR/Notification → `""`, Web2App/App2App → RP-ийн илгээсэн URL. null = хариунд
122
+ * талбар алга (хуучин CA). Validator үүнийг зөвхөн `""` эсвэл RP-ийн өөрийн callback байхад л хүлээн авна.
123
+ */
124
+ initialCallbackUrl?: string | null;
119
125
  signatureAlgorithmParameters: SignatureAlgorithmParameters | null;
120
126
  interactionTypeUsed: string | null;
121
127
  /**
@@ -124,6 +130,61 @@ interface SessionResult {
124
130
  * RP энэ талбарыг л итгэж уншина (клиентийн сонголтод биш). docs/ORG_LOGIN.md.
125
131
  */
126
132
  onBehalfOf: OrgResult | null;
133
+ /**
134
+ * PAdES (`POST /pdf/sign/prepare`) session-ий `pdf` блок — PDF урсгал биш бол null (талбар алга = хуучин SDK-аар
135
+ * угсарсан объект). {@link PdfSessionBlock}.
136
+ */
137
+ pdf?: PdfSessionBlock | null;
138
+ }
139
+ /** PDF баримтын төлөв: PENDING → READY (эсвэл FAILED / EXPIRED). */
140
+ type PdfDocumentStatus = "PENDING" | "READY" | "FAILED" | "EXPIRED";
141
+ /** PAdES гаралтын гарын үсэг зурагч. */
142
+ interface PdfSigner {
143
+ etsi: string;
144
+ certSerial: string;
145
+ /** Байгууллагын нэрийн өмнөөс бол байгууллагын ETSI (`NTRMN-…`) — баримтад бичигдсэн утга. */
146
+ onBehalfOf?: string;
147
+ /** CMS signer-attributes-v2-д бичигдсэн мэдүүлсэн үүрэг: «<албан тушаал>, <нэр> (NTRMN-…)». */
148
+ claimedRole?: string;
149
+ }
150
+ /** GET /session/{id}-ийн `pdf` объект (PAdES, CA угсарна). */
151
+ interface PdfSessionBlock {
152
+ docID: string;
153
+ documentStatus: PdfDocumentStatus | string;
154
+ /** FAILED үед — ж: `REPRESENTATION_REVOKED` (prepare-ээс хойш эрх хасагдсан, баримт үүсээгүй). */
155
+ errorCode?: string;
156
+ /** Гарын үсэгтэй PDF-ийн SHA-256 (hex) — татсан файлтай тулгана. */
157
+ outSha256?: string;
158
+ size?: number;
159
+ expiresAt?: string;
160
+ signatureLevel?: string;
161
+ signer?: PdfSigner;
162
+ validation?: {
163
+ indication: string;
164
+ ltv: boolean;
165
+ signatures: number;
166
+ };
167
+ }
168
+ /** Иргэний төлөөлж чадах нэг байгууллага (GET /organization/representations/etsi/{personEtsi}). */
169
+ interface Representation {
170
+ /** `NTRMN-<бүртгэл>` — `onBehalfOf`-д өгөх утга. */
171
+ orgEtsi: string;
172
+ orgRegister: string;
173
+ orgName: string;
174
+ orgNameEn?: string;
175
+ /** Бүртгэлийн албан тушаал (ХУР-аас холбогдсонд `ceo` / `founder`, MANAGER-т ADMIN-ий бичсэн текст). */
176
+ role?: string;
177
+ rightType: "ADMIN" | "MANAGER" | string;
178
+ source: "REGISTRY" | "MANUAL" | string;
179
+ /** ISO 8601. */
180
+ validFrom: string;
181
+ /** ISO 8601; байхгүй = хугацаагүй. */
182
+ validTo?: string;
183
+ }
184
+ /** GET /organization/representations/etsi/{personEtsi} хариу. */
185
+ interface RepresentationsResponse {
186
+ personEtsi: string;
187
+ representations: Representation[];
127
188
  }
128
189
  /** Session-ий байгууллагын блок — байгууллага + тухайн иргэний эрх (бүртгэлээс real-time). */
129
190
  interface OrgResult {
@@ -200,15 +261,37 @@ declare class EidError extends Error {
200
261
  /** HTTP 401 — API secret буруу/байхгүй. Тохиргооны (RP secret) алдаа. */
201
262
  declare class AuthenticationError extends EidError {
202
263
  }
203
- /** HTTP 403 — IP allowlist/mTLS зөвшөөрөөгүй. Танай серверийн IP бүртгэгдээгүй байж магадгүй. */
264
+ /**
265
+ * HTTP 403 — IP allowlist/mTLS зөвшөөрөөгүй, RP-д эрх алга, эсвэл onBehalfOf-ийн төлөөлөл татгалзсан.
266
+ * Сервер `{"error","code"}` буцаасан бол `code` (ж: `REPRESENTATION_DENIED`) — салааллыг үүгээр хийнэ.
267
+ */
204
268
  declare class ForbiddenError extends EidError {
269
+ /** Серверийн тогтвортой алдааны код (`{"code"}`), байхгүй бол undefined. */
270
+ readonly code?: string | undefined;
271
+ constructor(message: string,
272
+ /** Серверийн тогтвортой алдааны код (`{"code"}`), байхгүй бол undefined. */
273
+ code?: string | undefined);
205
274
  }
206
275
  /** RP-API-аас ирсэн бусад HTTP алдаа (4xx/5xx). status ба хариуны биеийг агуулна. */
207
276
  declare class ApiError extends EidError {
208
277
  readonly status: number;
209
278
  readonly body: string;
210
- constructor(status: number, body: string);
279
+ /** Серверийн тогтвортой алдааны код (`{"error","code"}`-ийн `code`), байхгүй бол undefined. */
280
+ readonly code?: string | undefined;
281
+ constructor(status: number, body: string,
282
+ /** Серверийн тогтвортой алдааны код (`{"error","code"}`-ийн `code`), байхгүй бол undefined. */
283
+ code?: string | undefined);
211
284
  }
285
+ /**
286
+ * Байгууллагын нэрийн өмнөөс (`onBehalfOf`) урсгалын татгалзлын кодууд — `ForbiddenError.code` /
287
+ * `ApiError.code`-д ирнэ. 403: REPRESENTATION_DENIED (идэвхтэй төлөөлөгч биш), REPRESENTATION_PENDING (PIN2-оор
288
+ * баталгаажуулаагүй), REPRESENTATION_EXPIRED (хугацааны гадна), ORG_NOT_ACTIVE, SIGNER_UNIDENTIFIED (anonymous
289
+ * session); 404: ORG_NOT_FOUND; PDF баримт татахад 403 REPRESENTATION_REVOKED (prepare-ээс хойш эрх хасагдсан —
290
+ * баримт үүсээгүй).
291
+ */
292
+ type OnBehalfErrorCode = "REPRESENTATION_DENIED" | "REPRESENTATION_PENDING" | "REPRESENTATION_EXPIRED" | "ORG_NOT_ACTIVE" | "SIGNER_UNIDENTIFIED" | "ORG_NOT_FOUND" | "REPRESENTATION_REVOKED";
293
+ /** {@link OnBehalfErrorCode}-ийн бүх утга (runtime шалгалтад). */
294
+ declare const ON_BEHALF_ERROR_CODES: readonly OnBehalfErrorCode[];
212
295
  /** Сүлжээ/timeout — upstream хариу өгсөнгүй. */
213
296
  declare class NetworkError extends EidError {
214
297
  }
@@ -234,4 +317,4 @@ declare class ValidationError extends EidError {
234
317
  declare class DeviceLinkError extends EidError {
235
318
  }
236
319
 
237
- export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, type OpenOrShowQROptions as O, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, ApiError as b, AuthenticationError as c, DeviceLinkError as d, type DeviceLinkInput as e, type EndResult as f, ForbiddenError as g, type NavigatorLike as h, NetworkError as i, SessionFailedError as j, type SessionState as k, type SignatureAlgorithmParameters as l, type SignatureProtocol as m, deviceLink as n, isMobileUserAgent as o, openOrShowQR as p };
320
+ export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfDocumentStatus as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfSessionBlock as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
@@ -1 +1 @@
1
- export { d as DeviceLinkError, e as DeviceLinkInput, D as DeviceLinkSession, E as EidError, h as NavigatorLike, O as OpenOrShowQROptions, n as deviceLink, o as isMobileUserAgent, p as openOrShowQR } from './browser-PhjcbuAN.cjs';
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-Db82DlYN.cjs';
package/dist/browser.d.ts CHANGED
@@ -1 +1 @@
1
- export { d as DeviceLinkError, e as DeviceLinkInput, D as DeviceLinkSession, E as EidError, h as NavigatorLike, O as OpenOrShowQROptions, n as deviceLink, o as isMobileUserAgent, p as openOrShowQR } from './browser-PhjcbuAN.js';
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-Db82DlYN.js';
package/dist/browser.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  deviceLink,
5
5
  isMobileUserAgent,
6
6
  openOrShowQR
7
- } from "./chunk-Q5TQMCJC.js";
7
+ } from "./chunk-NXDKQZIW.js";
8
8
  export {
9
9
  DeviceLinkError,
10
10
  EidError,
@@ -8,16 +8,32 @@ var EidError = class extends Error {
8
8
  var AuthenticationError = class extends EidError {
9
9
  };
10
10
  var ForbiddenError = class extends EidError {
11
+ constructor(message, code) {
12
+ super(message);
13
+ this.code = code;
14
+ }
15
+ code;
11
16
  };
12
17
  var ApiError = class extends EidError {
13
- constructor(status, body) {
18
+ constructor(status, body, code) {
14
19
  super(`RP-API ${status}: ${body}`);
15
20
  this.status = status;
16
21
  this.body = body;
22
+ this.code = code;
17
23
  }
18
24
  status;
19
25
  body;
26
+ code;
20
27
  };
28
+ var ON_BEHALF_ERROR_CODES = [
29
+ "REPRESENTATION_DENIED",
30
+ "REPRESENTATION_PENDING",
31
+ "REPRESENTATION_EXPIRED",
32
+ "ORG_NOT_ACTIVE",
33
+ "SIGNER_UNIDENTIFIED",
34
+ "ORG_NOT_FOUND",
35
+ "REPRESENTATION_REVOKED"
36
+ ];
21
37
  var NetworkError = class extends EidError {
22
38
  };
23
39
  var SessionFailedError = class extends EidError {
@@ -79,6 +95,7 @@ export {
79
95
  AuthenticationError,
80
96
  ForbiddenError,
81
97
  ApiError,
98
+ ON_BEHALF_ERROR_CODES,
82
99
  NetworkError,
83
100
  SessionFailedError,
84
101
  ValidationError,
package/dist/index.cjs CHANGED
@@ -30,6 +30,9 @@ __export(index_exports, {
30
30
  ForbiddenError: () => ForbiddenError,
31
31
  Http: () => Http,
32
32
  NetworkError: () => NetworkError,
33
+ ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
34
+ OrganizationApi: () => OrganizationApi,
35
+ PdfApi: () => PdfApi,
33
36
  ResponseValidator: () => ResponseValidator,
34
37
  SessionApi: () => SessionApi,
35
38
  SessionFailedError: () => SessionFailedError,
@@ -63,16 +66,32 @@ var EidError = class extends Error {
63
66
  var AuthenticationError = class extends EidError {
64
67
  };
65
68
  var ForbiddenError = class extends EidError {
69
+ constructor(message, code) {
70
+ super(message);
71
+ this.code = code;
72
+ }
73
+ code;
66
74
  };
67
75
  var ApiError = class extends EidError {
68
- constructor(status, body) {
76
+ constructor(status, body, code) {
69
77
  super(`RP-API ${status}: ${body}`);
70
78
  this.status = status;
71
79
  this.body = body;
80
+ this.code = code;
72
81
  }
73
82
  status;
74
83
  body;
84
+ code;
75
85
  };
86
+ var ON_BEHALF_ERROR_CODES = [
87
+ "REPRESENTATION_DENIED",
88
+ "REPRESENTATION_PENDING",
89
+ "REPRESENTATION_EXPIRED",
90
+ "ORG_NOT_ACTIVE",
91
+ "SIGNER_UNIDENTIFIED",
92
+ "ORG_NOT_FOUND",
93
+ "REPRESENTATION_REVOKED"
94
+ ];
76
95
  var NetworkError = class extends EidError {
77
96
  };
78
97
  var SessionFailedError = class extends EidError {
@@ -95,21 +114,35 @@ var Http = class {
95
114
  }
96
115
  cfg;
97
116
  post(path, body, timeoutMs) {
98
- return this.request("POST", path, body, timeoutMs);
117
+ return this.json("POST", path, JSON.stringify(body), "application/json", timeoutMs);
99
118
  }
100
119
  get(path, timeoutMs) {
101
- return this.request("GET", path, void 0, timeoutMs);
120
+ return this.json("GET", path, void 0, void 0, timeoutMs);
121
+ }
122
+ /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
123
+ postMultipart(path, form, timeoutMs) {
124
+ return this.json("POST", path, form, void 0, timeoutMs);
125
+ }
126
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
127
+ async getBytes(path, timeoutMs) {
128
+ const res = await this.send("GET", path, void 0, void 0, timeoutMs);
129
+ return new Uint8Array(await res.arrayBuffer());
130
+ }
131
+ async json(method, path, body, contentType, timeoutMs) {
132
+ const res = await this.send(method, path, body, contentType, timeoutMs);
133
+ const text = await res.text();
134
+ return text ? JSON.parse(text) : {};
102
135
  }
103
- async request(method, path, body, timeoutMs) {
136
+ async send(method, path, body, contentType, timeoutMs) {
104
137
  const f = this.cfg.fetchImpl ?? fetch;
105
138
  const url = this.cfg.baseUrl + path;
106
139
  const init = {
107
140
  method,
108
141
  headers: {
109
142
  Authorization: `Bearer ${this.cfg.apiSecret}`,
110
- ...body !== void 0 ? { "Content-Type": "application/json" } : {}
143
+ ...contentType ? { "Content-Type": contentType } : {}
111
144
  },
112
- body: body !== void 0 ? JSON.stringify(body) : void 0,
145
+ body,
113
146
  signal: AbortSignal.timeout(timeoutMs ?? this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS)
114
147
  };
115
148
  if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
@@ -123,13 +156,32 @@ var Http = class {
123
156
  }
124
157
  throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
125
158
  }
159
+ if (res.status < 300) return res;
126
160
  const text = await res.text();
161
+ const { error, code } = errorBody(text);
127
162
  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)");
128
- if (res.status === 403) throw new ForbiddenError("IP allowlist \u044D\u0441\u0432\u044D\u043B mTLS \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u04E9\u0433\u04AF\u0439 (403)");
129
- if (res.status >= 300) throw new ApiError(res.status, text);
130
- return text ? JSON.parse(text) : {};
163
+ if (res.status === 403) {
164
+ throw new ForbiddenError(
165
+ 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)",
166
+ code
167
+ );
168
+ }
169
+ throw new ApiError(res.status, text, code);
131
170
  }
132
171
  };
172
+ function errorBody(text) {
173
+ try {
174
+ const v = JSON.parse(text);
175
+ if (!v || typeof v !== "object") return {};
176
+ const o = v;
177
+ return {
178
+ error: typeof o.error === "string" && o.error ? o.error : void 0,
179
+ code: typeof o.code === "string" && o.code ? o.code : void 0
180
+ };
181
+ } catch {
182
+ return {};
183
+ }
184
+ }
133
185
 
134
186
  // src/crypto.ts
135
187
  var import_node_crypto = require("crypto");
@@ -336,11 +388,37 @@ function parseSessionResult(raw) {
336
388
  serverRandom: str(sig.serverRandom),
337
389
  userChallenge: str(sig.userChallenge),
338
390
  flowType: str(sig.flowType),
391
+ // CA-ийн eID нэмэлт (f59d14ee-оос): гарын үсэг зурагдсан ACSP_V2 initialCallbackUrl. "" нь утга —
392
+ // null/undefined-аас ялгана (талбар алга = хуучин CA).
393
+ initialCallbackUrl: typeof sig.initialCallbackUrl === "string" ? sig.initialCallbackUrl : null,
339
394
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
340
395
  interactionTypeUsed: str(r.interactionTypeUsed),
341
- onBehalfOf: parseOrg(r.onBehalfOf)
396
+ onBehalfOf: parseOrg(r.onBehalfOf),
397
+ pdf: parsePdf(r.pdf)
342
398
  };
343
399
  }
400
+ function parsePdf(raw) {
401
+ const p = asRecord(raw);
402
+ const docID = str(p.docID);
403
+ if (!docID) return null;
404
+ const out = { docID, documentStatus: String(p.documentStatus ?? "") };
405
+ if (p.errorCode != null) out.errorCode = String(p.errorCode);
406
+ if (p.outSha256 != null) out.outSha256 = String(p.outSha256);
407
+ if (p.size != null) out.size = Number(p.size);
408
+ if (p.expiresAt != null) out.expiresAt = String(p.expiresAt);
409
+ if (p.signatureLevel != null) out.signatureLevel = String(p.signatureLevel);
410
+ const sg = asRecord(p.signer);
411
+ if (sg.etsi != null) {
412
+ out.signer = { etsi: String(sg.etsi), certSerial: String(sg.certSerial ?? "") };
413
+ if (sg.onBehalfOf) out.signer.onBehalfOf = String(sg.onBehalfOf);
414
+ if (sg.claimedRole) out.signer.claimedRole = String(sg.claimedRole);
415
+ }
416
+ const v = asRecord(p.validation);
417
+ if (v.indication != null) {
418
+ out.validation = { indication: String(v.indication), ltv: v.ltv === true, signatures: Number(v.signatures ?? 0) };
419
+ }
420
+ return out;
421
+ }
344
422
  function parseSigParams(raw) {
345
423
  const p = asRecord(raw);
346
424
  const hash = str(p.hashAlgorithm);
@@ -395,6 +473,76 @@ var SessionApi = class {
395
473
  }
396
474
  };
397
475
 
476
+ // src/pdf.ts
477
+ function rec3(v) {
478
+ return v && typeof v === "object" ? v : {};
479
+ }
480
+ var PdfApi = class {
481
+ constructor(http, creds, defaults) {
482
+ this.http = http;
483
+ this.creds = creds;
484
+ this.defaults = defaults;
485
+ }
486
+ http;
487
+ creds;
488
+ defaults;
489
+ /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
490
+ async prepare(input) {
491
+ const request = {
492
+ relyingPartyUUID: this.creds.rpUUID,
493
+ relyingPartyName: this.creds.rpName,
494
+ certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
495
+ signer: input.signer,
496
+ flow: input.flow ?? "notification",
497
+ fileName: input.fileName
498
+ };
499
+ if (input.initialCallbackUrl?.trim()) request.initialCallbackUrl = input.initialCallbackUrl.trim();
500
+ if (input.docID?.trim()) request.docID = input.docID.trim();
501
+ if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
502
+ const form = new FormData();
503
+ form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
504
+ form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
505
+ const r = rec3(await this.http.postMultipart("/pdf/sign/prepare", form, 6e4));
506
+ const receivedAt = Date.now();
507
+ const vc = rec3(r.vc).value;
508
+ const dl = rec3(r.deviceLink);
509
+ return {
510
+ sessionId: String(r.sessionID),
511
+ docId: String(r.docID),
512
+ vc: vc != null ? String(vc) : null,
513
+ deviceLink: dl.sessionToken ? {
514
+ sessionToken: String(dl.sessionToken),
515
+ sessionSecret: String(dl.sessionSecret),
516
+ deviceLinkBase: String(dl.deviceLinkBase),
517
+ digest: String(dl.digest),
518
+ interactions: String(dl.interactions)
519
+ } : null,
520
+ receivedAt
521
+ };
522
+ }
523
+ /**
524
+ * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
525
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
526
+ */
527
+ document(sessionId) {
528
+ return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
529
+ }
530
+ };
531
+
532
+ // src/organization.ts
533
+ var OrganizationApi = class {
534
+ constructor(http) {
535
+ this.http = http;
536
+ }
537
+ http;
538
+ /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
539
+ async getRepresentations(personEtsi) {
540
+ const raw = await this.http.get(`/organization/representations/etsi/${encodeURIComponent(personEtsi)}`);
541
+ const list = Array.isArray(raw.representations) ? raw.representations : [];
542
+ return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
543
+ }
544
+ };
545
+
398
546
  // src/validator.ts
399
547
  var import_node_crypto2 = require("crypto");
400
548
  var ResponseValidator = class {
@@ -447,7 +595,7 @@ var ResponseValidator = class {
447
595
  brokeredRpName: acsp.brokeredRpName,
448
596
  interactions: acsp.interactions,
449
597
  interactionTypeUsed,
450
- initialCallbackUrl: acsp.initialCallbackUrl,
598
+ initialCallbackUrl: signedCallbackUrl(result.initialCallbackUrl, acsp.initialCallbackUrl, flowType),
451
599
  flowType
452
600
  });
453
601
  if (!verifyPayloadSignature(payload, result.signatureValueB64, leaf.publicKey, alg, hash)) {
@@ -584,6 +732,17 @@ var ResponseValidator = class {
584
732
  }
585
733
  }
586
734
  };
735
+ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
736
+ const rp = rpCallback ?? "";
737
+ if (fromResponse == null) return rp;
738
+ if (fromResponse !== "" && fromResponse !== rp) {
739
+ throw new ValidationError("signature.initialCallbackUrl \u043D\u044C \u0445\u043E\u043E\u0441\u043E\u043D \u0447 \u0431\u0438\u0448, \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0447 \u0431\u0438\u0448 (\u0445\u0443\u0443\u0440\u0430\u043C\u0447 \u0445\u0430\u0440\u0438\u0443 \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
740
+ }
741
+ if ((flowType === "Web2App" || flowType === "App2App") && fromResponse !== rp) {
742
+ throw new ValidationError(`${flowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 signature.initialCallbackUrl \u043D\u044C \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439`);
743
+ }
744
+ return fromResponse;
745
+ }
587
746
  function userChallengeOf(verifier) {
588
747
  return (0, import_node_crypto2.createHash)("sha256").update(verifier, "utf8").digest("base64url");
589
748
  }
@@ -738,6 +897,8 @@ function resolveBaseUrl(baseUrl) {
738
897
  var EidClient = class {
739
898
  auth;
740
899
  sign;
900
+ pdf;
901
+ organization;
741
902
  session;
742
903
  validator;
743
904
  constructor(cfg) {
@@ -753,6 +914,8 @@ var EidClient = class {
753
914
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
754
915
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
755
916
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
917
+ this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
918
+ this.organization = new OrganizationApi(http);
756
919
  this.session = new SessionApi(http);
757
920
  this.validator = new ResponseValidator(cfg.trust ?? {});
758
921
  }
@@ -881,6 +1044,9 @@ function qrDeviceLinkTicker(input, onLink, opts) {
881
1044
  ForbiddenError,
882
1045
  Http,
883
1046
  NetworkError,
1047
+ ON_BEHALF_ERROR_CODES,
1048
+ OrganizationApi,
1049
+ PdfApi,
884
1050
  ResponseValidator,
885
1051
  SessionApi,
886
1052
  SessionFailedError,
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, A as AcspContext, F as FlowType } from './browser-PhjcbuAN.cjs';
2
- export { b as ApiError, c as AuthenticationError, d as DeviceLinkError, e as DeviceLinkInput, E as EidError, f as EndResult, g as ForbiddenError, h as NavigatorLike, i as NetworkError, O as OpenOrShowQROptions, j as SessionFailedError, k as SessionState, l as SignatureAlgorithmParameters, m as SignatureProtocol, V as ValidationError, n as deviceLink, o as isMobileUserAgent, p as openOrShowQR } from './browser-PhjcbuAN.cjs';
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-Db82DlYN.cjs';
2
+ export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, E as EidError, 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-Db82DlYN.cjs';
3
3
  import { X509Certificate, KeyObject } from 'node:crypto';
4
4
 
5
5
  interface HttpConfig {
@@ -23,7 +23,12 @@ declare class Http {
23
23
  constructor(cfg: HttpConfig);
24
24
  post(path: string, body: unknown, timeoutMs?: number): Promise<unknown>;
25
25
  get(path: string, timeoutMs?: number): Promise<unknown>;
26
- private request;
26
+ /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
27
+ postMultipart(path: string, form: FormData, timeoutMs?: number): Promise<unknown>;
28
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
29
+ getBytes(path: string, timeoutMs?: number): Promise<Uint8Array>;
30
+ private json;
31
+ private send;
27
32
  }
28
33
 
29
34
  interface AuthDefaults {
@@ -131,6 +136,73 @@ declare class SessionApi {
131
136
  }): Promise<SessionResult>;
132
137
  }
133
138
 
139
+ interface PdfDefaults {
140
+ certificateLevel: CertificateLevel;
141
+ }
142
+ /** Гарын үсэг зурагч — яг нэг нь. */
143
+ type PdfSignerRef = {
144
+ etsi: string;
145
+ } | {
146
+ documentNumber: string;
147
+ } | {
148
+ certificateChoiceSessionId: string;
149
+ };
150
+ interface PdfPrepareInput {
151
+ /** Эх PDF (≤ 10 223 616 байт — `/.well-known/eid` `pdfSigning.maxInputBytes`). */
152
+ pdf: Uint8Array;
153
+ /** Баримтын нэр (≤ 120 тэмдэгт) — /verify хуудас, иргэний түүхэнд харагдана. */
154
+ fileName: string;
155
+ signer: PdfSignerRef;
156
+ /** `notification` (push) эсвэл `device-link` (QR/Web2App/App2App). Анхдагч notification. */
157
+ flow?: "notification" | "device-link";
158
+ certificateLevel?: CertificateLevel;
159
+ /** device-link Web2App/App2App буцах URL. */
160
+ initialCallbackUrl?: string;
161
+ /** Олон гарын үсэгтэй баримтад хүлээж буй docID (заавал биш — CA өөрөө олно). */
162
+ docID?: string;
163
+ /**
164
+ * Байгууллагын нэрийн өмнөөс: байгууллагын ETSI (`NTRMN-<бүртгэл>`). Сервер төлөөллийг prepare-д ба баримт
165
+ * угсрахын өмнө дахин шалгана; татгалзвал `ForbiddenError`/`ApiError` `.code` ({@link OnBehalfErrorCode}).
166
+ */
167
+ onBehalfOf?: string;
168
+ }
169
+ /** prepare-ийн хариу. device-link-д `deviceLink` (digest/interactions-ийг authCode-д ЯГ ХЭВЭЭР). */
170
+ interface PdfPrepareSession {
171
+ sessionId: string;
172
+ docId: string;
173
+ /** Иргэнд ХАРУУЛАХ баталгаажуулах код (ПИН биш). */
174
+ vc: string | null;
175
+ deviceLink: {
176
+ sessionToken: string;
177
+ sessionSecret: string;
178
+ deviceLinkBase: string;
179
+ digest: string;
180
+ interactions: string;
181
+ } | null;
182
+ /** SDK хариу хүлээн авсан мөч (ms epoch) — QR-ийн `receivedAt` ({@link buildDeviceLink}). */
183
+ receivedAt: number;
184
+ }
185
+ declare class PdfApi {
186
+ private readonly http;
187
+ private readonly creds;
188
+ private readonly defaults;
189
+ constructor(http: Http, creds: RpCredentials, defaults: PdfDefaults);
190
+ /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
191
+ prepare(input: PdfPrepareInput): Promise<PdfPrepareSession>;
192
+ /**
193
+ * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
194
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
195
+ */
196
+ document(sessionId: string): Promise<Uint8Array>;
197
+ }
198
+
199
+ declare class OrganizationApi {
200
+ private readonly http;
201
+ constructor(http: Http);
202
+ /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
203
+ getRepresentations(personEtsi: string): Promise<RepresentationsResponse>;
204
+ }
205
+
134
206
  /**
135
207
  * validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
136
208
  * (device-link relay хаалт).
@@ -283,6 +355,8 @@ declare function resolveBaseUrl(baseUrl: string | undefined): string;
283
355
  declare class EidClient {
284
356
  readonly auth: AuthApi;
285
357
  readonly sign: SignApi;
358
+ readonly pdf: PdfApi;
359
+ readonly organization: OrganizationApi;
286
360
  readonly session: SessionApi;
287
361
  readonly validator: ResponseValidator;
288
362
  constructor(cfg: ClientConfig);
@@ -383,4 +457,4 @@ interface QrDeviceLinkTickerOptions {
383
457
  */
384
458
  declare function qrDeviceLinkTicker(input: Omit<BuildDeviceLinkInput, "deviceLinkType" | "now">, onLink: (link: string) => void, opts?: QrDeviceLinkTickerOptions): () => void;
385
459
 
386
- export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, type QrDeviceLinkTickerOptions, 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, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
460
+ export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, 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, 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, A as AcspContext, F as FlowType } from './browser-PhjcbuAN.js';
2
- export { b as ApiError, c as AuthenticationError, d as DeviceLinkError, e as DeviceLinkInput, E as EidError, f as EndResult, g as ForbiddenError, h as NavigatorLike, i as NetworkError, O as OpenOrShowQROptions, j as SessionFailedError, k as SessionState, l as SignatureAlgorithmParameters, m as SignatureProtocol, V as ValidationError, n as deviceLink, o as isMobileUserAgent, p as openOrShowQR } from './browser-PhjcbuAN.js';
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-Db82DlYN.js';
2
+ export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, E as EidError, 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-Db82DlYN.js';
3
3
  import { X509Certificate, KeyObject } from 'node:crypto';
4
4
 
5
5
  interface HttpConfig {
@@ -23,7 +23,12 @@ declare class Http {
23
23
  constructor(cfg: HttpConfig);
24
24
  post(path: string, body: unknown, timeoutMs?: number): Promise<unknown>;
25
25
  get(path: string, timeoutMs?: number): Promise<unknown>;
26
- private request;
26
+ /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
27
+ postMultipart(path: string, form: FormData, timeoutMs?: number): Promise<unknown>;
28
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
29
+ getBytes(path: string, timeoutMs?: number): Promise<Uint8Array>;
30
+ private json;
31
+ private send;
27
32
  }
28
33
 
29
34
  interface AuthDefaults {
@@ -131,6 +136,73 @@ declare class SessionApi {
131
136
  }): Promise<SessionResult>;
132
137
  }
133
138
 
139
+ interface PdfDefaults {
140
+ certificateLevel: CertificateLevel;
141
+ }
142
+ /** Гарын үсэг зурагч — яг нэг нь. */
143
+ type PdfSignerRef = {
144
+ etsi: string;
145
+ } | {
146
+ documentNumber: string;
147
+ } | {
148
+ certificateChoiceSessionId: string;
149
+ };
150
+ interface PdfPrepareInput {
151
+ /** Эх PDF (≤ 10 223 616 байт — `/.well-known/eid` `pdfSigning.maxInputBytes`). */
152
+ pdf: Uint8Array;
153
+ /** Баримтын нэр (≤ 120 тэмдэгт) — /verify хуудас, иргэний түүхэнд харагдана. */
154
+ fileName: string;
155
+ signer: PdfSignerRef;
156
+ /** `notification` (push) эсвэл `device-link` (QR/Web2App/App2App). Анхдагч notification. */
157
+ flow?: "notification" | "device-link";
158
+ certificateLevel?: CertificateLevel;
159
+ /** device-link Web2App/App2App буцах URL. */
160
+ initialCallbackUrl?: string;
161
+ /** Олон гарын үсэгтэй баримтад хүлээж буй docID (заавал биш — CA өөрөө олно). */
162
+ docID?: string;
163
+ /**
164
+ * Байгууллагын нэрийн өмнөөс: байгууллагын ETSI (`NTRMN-<бүртгэл>`). Сервер төлөөллийг prepare-д ба баримт
165
+ * угсрахын өмнө дахин шалгана; татгалзвал `ForbiddenError`/`ApiError` `.code` ({@link OnBehalfErrorCode}).
166
+ */
167
+ onBehalfOf?: string;
168
+ }
169
+ /** prepare-ийн хариу. device-link-д `deviceLink` (digest/interactions-ийг authCode-д ЯГ ХЭВЭЭР). */
170
+ interface PdfPrepareSession {
171
+ sessionId: string;
172
+ docId: string;
173
+ /** Иргэнд ХАРУУЛАХ баталгаажуулах код (ПИН биш). */
174
+ vc: string | null;
175
+ deviceLink: {
176
+ sessionToken: string;
177
+ sessionSecret: string;
178
+ deviceLinkBase: string;
179
+ digest: string;
180
+ interactions: string;
181
+ } | null;
182
+ /** SDK хариу хүлээн авсан мөч (ms epoch) — QR-ийн `receivedAt` ({@link buildDeviceLink}). */
183
+ receivedAt: number;
184
+ }
185
+ declare class PdfApi {
186
+ private readonly http;
187
+ private readonly creds;
188
+ private readonly defaults;
189
+ constructor(http: Http, creds: RpCredentials, defaults: PdfDefaults);
190
+ /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
191
+ prepare(input: PdfPrepareInput): Promise<PdfPrepareSession>;
192
+ /**
193
+ * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
194
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
195
+ */
196
+ document(sessionId: string): Promise<Uint8Array>;
197
+ }
198
+
199
+ declare class OrganizationApi {
200
+ private readonly http;
201
+ constructor(http: Http);
202
+ /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
203
+ getRepresentations(personEtsi: string): Promise<RepresentationsResponse>;
204
+ }
205
+
134
206
  /**
135
207
  * validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
136
208
  * (device-link relay хаалт).
@@ -283,6 +355,8 @@ declare function resolveBaseUrl(baseUrl: string | undefined): string;
283
355
  declare class EidClient {
284
356
  readonly auth: AuthApi;
285
357
  readonly sign: SignApi;
358
+ readonly pdf: PdfApi;
359
+ readonly organization: OrganizationApi;
286
360
  readonly session: SessionApi;
287
361
  readonly validator: ResponseValidator;
288
362
  constructor(cfg: ClientConfig);
@@ -383,4 +457,4 @@ interface QrDeviceLinkTickerOptions {
383
457
  */
384
458
  declare function qrDeviceLinkTicker(input: Omit<BuildDeviceLinkInput, "deviceLinkType" | "now">, onLink: (link: string) => void, opts?: QrDeviceLinkTickerOptions): () => void;
385
459
 
386
- export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, type QrDeviceLinkTickerOptions, 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, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
460
+ export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, 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, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
package/dist/index.js CHANGED
@@ -5,12 +5,13 @@ import {
5
5
  EidError,
6
6
  ForbiddenError,
7
7
  NetworkError,
8
+ ON_BEHALF_ERROR_CODES,
8
9
  SessionFailedError,
9
10
  ValidationError,
10
11
  deviceLink,
11
12
  isMobileUserAgent,
12
13
  openOrShowQR
13
- } from "./chunk-Q5TQMCJC.js";
14
+ } from "./chunk-NXDKQZIW.js";
14
15
 
15
16
  // src/http.ts
16
17
  var DEFAULT_TIMEOUT_MS = 15e3;
@@ -20,21 +21,35 @@ var Http = class {
20
21
  }
21
22
  cfg;
22
23
  post(path, body, timeoutMs) {
23
- return this.request("POST", path, body, timeoutMs);
24
+ return this.json("POST", path, JSON.stringify(body), "application/json", timeoutMs);
24
25
  }
25
26
  get(path, timeoutMs) {
26
- return this.request("GET", path, void 0, timeoutMs);
27
+ return this.json("GET", path, void 0, void 0, timeoutMs);
27
28
  }
28
- async request(method, path, body, timeoutMs) {
29
+ /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
30
+ postMultipart(path, form, timeoutMs) {
31
+ return this.json("POST", path, form, void 0, timeoutMs);
32
+ }
33
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
34
+ async getBytes(path, timeoutMs) {
35
+ const res = await this.send("GET", path, void 0, void 0, timeoutMs);
36
+ return new Uint8Array(await res.arrayBuffer());
37
+ }
38
+ async json(method, path, body, contentType, timeoutMs) {
39
+ const res = await this.send(method, path, body, contentType, timeoutMs);
40
+ const text = await res.text();
41
+ return text ? JSON.parse(text) : {};
42
+ }
43
+ async send(method, path, body, contentType, timeoutMs) {
29
44
  const f = this.cfg.fetchImpl ?? fetch;
30
45
  const url = this.cfg.baseUrl + path;
31
46
  const init = {
32
47
  method,
33
48
  headers: {
34
49
  Authorization: `Bearer ${this.cfg.apiSecret}`,
35
- ...body !== void 0 ? { "Content-Type": "application/json" } : {}
50
+ ...contentType ? { "Content-Type": contentType } : {}
36
51
  },
37
- body: body !== void 0 ? JSON.stringify(body) : void 0,
52
+ body,
38
53
  signal: AbortSignal.timeout(timeoutMs ?? this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS)
39
54
  };
40
55
  if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
@@ -48,13 +63,32 @@ var Http = class {
48
63
  }
49
64
  throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
50
65
  }
66
+ if (res.status < 300) return res;
51
67
  const text = await res.text();
68
+ const { error, code } = errorBody(text);
52
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)");
53
- if (res.status === 403) throw new ForbiddenError("IP allowlist \u044D\u0441\u0432\u044D\u043B mTLS \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u04E9\u0433\u04AF\u0439 (403)");
54
- if (res.status >= 300) throw new ApiError(res.status, text);
55
- return text ? JSON.parse(text) : {};
70
+ if (res.status === 403) {
71
+ throw new ForbiddenError(
72
+ 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
74
+ );
75
+ }
76
+ throw new ApiError(res.status, text, code);
56
77
  }
57
78
  };
79
+ function errorBody(text) {
80
+ try {
81
+ const v = JSON.parse(text);
82
+ if (!v || typeof v !== "object") return {};
83
+ const o = v;
84
+ return {
85
+ error: typeof o.error === "string" && o.error ? o.error : void 0,
86
+ code: typeof o.code === "string" && o.code ? o.code : void 0
87
+ };
88
+ } catch {
89
+ return {};
90
+ }
91
+ }
58
92
 
59
93
  // src/crypto.ts
60
94
  import { createHash, randomBytes } from "crypto";
@@ -261,11 +295,37 @@ function parseSessionResult(raw) {
261
295
  serverRandom: str(sig.serverRandom),
262
296
  userChallenge: str(sig.userChallenge),
263
297
  flowType: str(sig.flowType),
298
+ // CA-ийн eID нэмэлт (f59d14ee-оос): гарын үсэг зурагдсан ACSP_V2 initialCallbackUrl. "" нь утга —
299
+ // null/undefined-аас ялгана (талбар алга = хуучин CA).
300
+ initialCallbackUrl: typeof sig.initialCallbackUrl === "string" ? sig.initialCallbackUrl : null,
264
301
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
265
302
  interactionTypeUsed: str(r.interactionTypeUsed),
266
- onBehalfOf: parseOrg(r.onBehalfOf)
303
+ onBehalfOf: parseOrg(r.onBehalfOf),
304
+ pdf: parsePdf(r.pdf)
267
305
  };
268
306
  }
307
+ function parsePdf(raw) {
308
+ const p = asRecord(raw);
309
+ const docID = str(p.docID);
310
+ if (!docID) return null;
311
+ const out = { docID, documentStatus: String(p.documentStatus ?? "") };
312
+ if (p.errorCode != null) out.errorCode = String(p.errorCode);
313
+ if (p.outSha256 != null) out.outSha256 = String(p.outSha256);
314
+ if (p.size != null) out.size = Number(p.size);
315
+ if (p.expiresAt != null) out.expiresAt = String(p.expiresAt);
316
+ if (p.signatureLevel != null) out.signatureLevel = String(p.signatureLevel);
317
+ const sg = asRecord(p.signer);
318
+ if (sg.etsi != null) {
319
+ out.signer = { etsi: String(sg.etsi), certSerial: String(sg.certSerial ?? "") };
320
+ if (sg.onBehalfOf) out.signer.onBehalfOf = String(sg.onBehalfOf);
321
+ if (sg.claimedRole) out.signer.claimedRole = String(sg.claimedRole);
322
+ }
323
+ const v = asRecord(p.validation);
324
+ if (v.indication != null) {
325
+ out.validation = { indication: String(v.indication), ltv: v.ltv === true, signatures: Number(v.signatures ?? 0) };
326
+ }
327
+ return out;
328
+ }
269
329
  function parseSigParams(raw) {
270
330
  const p = asRecord(raw);
271
331
  const hash = str(p.hashAlgorithm);
@@ -320,6 +380,76 @@ var SessionApi = class {
320
380
  }
321
381
  };
322
382
 
383
+ // src/pdf.ts
384
+ function rec3(v) {
385
+ return v && typeof v === "object" ? v : {};
386
+ }
387
+ var PdfApi = class {
388
+ constructor(http, creds, defaults) {
389
+ this.http = http;
390
+ this.creds = creds;
391
+ this.defaults = defaults;
392
+ }
393
+ http;
394
+ creds;
395
+ defaults;
396
+ /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
397
+ async prepare(input) {
398
+ const request = {
399
+ relyingPartyUUID: this.creds.rpUUID,
400
+ relyingPartyName: this.creds.rpName,
401
+ certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
402
+ signer: input.signer,
403
+ flow: input.flow ?? "notification",
404
+ fileName: input.fileName
405
+ };
406
+ if (input.initialCallbackUrl?.trim()) request.initialCallbackUrl = input.initialCallbackUrl.trim();
407
+ if (input.docID?.trim()) request.docID = input.docID.trim();
408
+ if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
409
+ const form = new FormData();
410
+ form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
411
+ form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
412
+ const r = rec3(await this.http.postMultipart("/pdf/sign/prepare", form, 6e4));
413
+ const receivedAt = Date.now();
414
+ const vc = rec3(r.vc).value;
415
+ const dl = rec3(r.deviceLink);
416
+ return {
417
+ sessionId: String(r.sessionID),
418
+ docId: String(r.docID),
419
+ vc: vc != null ? String(vc) : null,
420
+ deviceLink: dl.sessionToken ? {
421
+ sessionToken: String(dl.sessionToken),
422
+ sessionSecret: String(dl.sessionSecret),
423
+ deviceLinkBase: String(dl.deviceLinkBase),
424
+ digest: String(dl.digest),
425
+ interactions: String(dl.interactions)
426
+ } : null,
427
+ receivedAt
428
+ };
429
+ }
430
+ /**
431
+ * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
432
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
433
+ */
434
+ document(sessionId) {
435
+ return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
436
+ }
437
+ };
438
+
439
+ // src/organization.ts
440
+ var OrganizationApi = class {
441
+ constructor(http) {
442
+ this.http = http;
443
+ }
444
+ http;
445
+ /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
446
+ async getRepresentations(personEtsi) {
447
+ const raw = await this.http.get(`/organization/representations/etsi/${encodeURIComponent(personEtsi)}`);
448
+ const list = Array.isArray(raw.representations) ? raw.representations : [];
449
+ return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
450
+ }
451
+ };
452
+
323
453
  // src/validator.ts
324
454
  import { X509Certificate, constants, createHash as createHash2, publicDecrypt, verify } from "crypto";
325
455
  var ResponseValidator = class {
@@ -372,7 +502,7 @@ var ResponseValidator = class {
372
502
  brokeredRpName: acsp.brokeredRpName,
373
503
  interactions: acsp.interactions,
374
504
  interactionTypeUsed,
375
- initialCallbackUrl: acsp.initialCallbackUrl,
505
+ initialCallbackUrl: signedCallbackUrl(result.initialCallbackUrl, acsp.initialCallbackUrl, flowType),
376
506
  flowType
377
507
  });
378
508
  if (!verifyPayloadSignature(payload, result.signatureValueB64, leaf.publicKey, alg, hash)) {
@@ -509,6 +639,17 @@ var ResponseValidator = class {
509
639
  }
510
640
  }
511
641
  };
642
+ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
643
+ const rp = rpCallback ?? "";
644
+ if (fromResponse == null) return rp;
645
+ if (fromResponse !== "" && fromResponse !== rp) {
646
+ throw new ValidationError("signature.initialCallbackUrl \u043D\u044C \u0445\u043E\u043E\u0441\u043E\u043D \u0447 \u0431\u0438\u0448, \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0447 \u0431\u0438\u0448 (\u0445\u0443\u0443\u0440\u0430\u043C\u0447 \u0445\u0430\u0440\u0438\u0443 \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
647
+ }
648
+ if ((flowType === "Web2App" || flowType === "App2App") && fromResponse !== rp) {
649
+ throw new ValidationError(`${flowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 signature.initialCallbackUrl \u043D\u044C \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439`);
650
+ }
651
+ return fromResponse;
652
+ }
512
653
  function userChallengeOf(verifier) {
513
654
  return createHash2("sha256").update(verifier, "utf8").digest("base64url");
514
655
  }
@@ -663,6 +804,8 @@ function resolveBaseUrl(baseUrl) {
663
804
  var EidClient = class {
664
805
  auth;
665
806
  sign;
807
+ pdf;
808
+ organization;
666
809
  session;
667
810
  validator;
668
811
  constructor(cfg) {
@@ -678,6 +821,8 @@ var EidClient = class {
678
821
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
679
822
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
680
823
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
824
+ this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
825
+ this.organization = new OrganizationApi(http);
681
826
  this.session = new SessionApi(http);
682
827
  this.validator = new ResponseValidator(cfg.trust ?? {});
683
828
  }
@@ -763,6 +908,9 @@ export {
763
908
  ForbiddenError,
764
909
  Http,
765
910
  NetworkError,
911
+ ON_BEHALF_ERROR_CODES,
912
+ OrganizationApi,
913
+ PdfApi,
766
914
  ResponseValidator,
767
915
  SessionApi,
768
916
  SessionFailedError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gerege-systems/eid-mongolia-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
5
5
  "license": "MIT",
6
6
  "type": "module",