@gerege-systems/eid-mongolia-sdk 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -25,11 +25,19 @@ __export(index_exports, {
25
25
  AuthenticationError: () => AuthenticationError,
26
26
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
27
27
  DeviceLinkError: () => DeviceLinkError,
28
+ EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
29
+ EID_ACR_QUALIFIED: () => EID_ACR_QUALIFIED,
30
+ EID_OIDC_ISSUER: () => EID_OIDC_ISSUER,
28
31
  EidClient: () => EidClient,
29
32
  EidError: () => EidError,
33
+ EidOidcClient: () => EidOidcClient,
34
+ EidOidcError: () => EidOidcError,
30
35
  ForbiddenError: () => ForbiddenError,
31
36
  Http: () => Http,
32
37
  NetworkError: () => NetworkError,
38
+ ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
39
+ OrganizationApi: () => OrganizationApi,
40
+ PdfApi: () => PdfApi,
33
41
  ResponseValidator: () => ResponseValidator,
34
42
  SessionApi: () => SessionApi,
35
43
  SessionFailedError: () => SessionFailedError,
@@ -43,6 +51,7 @@ __export(index_exports, {
43
51
  isMobileUserAgent: () => isMobileUserAgent,
44
52
  openOrShowQR: () => openOrShowQR,
45
53
  parseSessionResult: () => parseSessionResult,
54
+ pkceChallenge: () => pkceChallenge,
46
55
  qrDeviceLinkTicker: () => qrDeviceLinkTicker,
47
56
  randomChallenge: () => randomChallenge,
48
57
  resolveBaseUrl: () => resolveBaseUrl,
@@ -63,16 +72,32 @@ var EidError = class extends Error {
63
72
  var AuthenticationError = class extends EidError {
64
73
  };
65
74
  var ForbiddenError = class extends EidError {
75
+ constructor(message, code) {
76
+ super(message);
77
+ this.code = code;
78
+ }
79
+ code;
66
80
  };
67
81
  var ApiError = class extends EidError {
68
- constructor(status, body) {
82
+ constructor(status, body, code) {
69
83
  super(`RP-API ${status}: ${body}`);
70
84
  this.status = status;
71
85
  this.body = body;
86
+ this.code = code;
72
87
  }
73
88
  status;
74
89
  body;
90
+ code;
75
91
  };
92
+ var ON_BEHALF_ERROR_CODES = [
93
+ "REPRESENTATION_DENIED",
94
+ "REPRESENTATION_PENDING",
95
+ "REPRESENTATION_EXPIRED",
96
+ "ORG_NOT_ACTIVE",
97
+ "SIGNER_UNIDENTIFIED",
98
+ "ORG_NOT_FOUND",
99
+ "REPRESENTATION_REVOKED"
100
+ ];
76
101
  var NetworkError = class extends EidError {
77
102
  };
78
103
  var SessionFailedError = class extends EidError {
@@ -95,21 +120,35 @@ var Http = class {
95
120
  }
96
121
  cfg;
97
122
  post(path, body, timeoutMs) {
98
- return this.request("POST", path, body, timeoutMs);
123
+ return this.json("POST", path, JSON.stringify(body), "application/json", timeoutMs);
99
124
  }
100
125
  get(path, timeoutMs) {
101
- return this.request("GET", path, void 0, timeoutMs);
126
+ return this.json("GET", path, void 0, void 0, timeoutMs);
127
+ }
128
+ /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
129
+ postMultipart(path, form, timeoutMs) {
130
+ return this.json("POST", path, form, void 0, timeoutMs);
131
+ }
132
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
133
+ async getBytes(path, timeoutMs) {
134
+ const res = await this.send("GET", path, void 0, void 0, timeoutMs);
135
+ return new Uint8Array(await res.arrayBuffer());
102
136
  }
103
- async request(method, path, body, timeoutMs) {
137
+ async json(method, path, body, contentType, timeoutMs) {
138
+ const res = await this.send(method, path, body, contentType, timeoutMs);
139
+ const text = await res.text();
140
+ return text ? JSON.parse(text) : {};
141
+ }
142
+ async send(method, path, body, contentType, timeoutMs) {
104
143
  const f = this.cfg.fetchImpl ?? fetch;
105
144
  const url = this.cfg.baseUrl + path;
106
145
  const init = {
107
146
  method,
108
147
  headers: {
109
148
  Authorization: `Bearer ${this.cfg.apiSecret}`,
110
- ...body !== void 0 ? { "Content-Type": "application/json" } : {}
149
+ ...contentType ? { "Content-Type": contentType } : {}
111
150
  },
112
- body: body !== void 0 ? JSON.stringify(body) : void 0,
151
+ body,
113
152
  signal: AbortSignal.timeout(timeoutMs ?? this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS)
114
153
  };
115
154
  if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
@@ -123,13 +162,32 @@ var Http = class {
123
162
  }
124
163
  throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
125
164
  }
165
+ if (res.status < 300) return res;
126
166
  const text = await res.text();
167
+ const { error, code } = errorBody(text);
127
168
  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) : {};
169
+ if (res.status === 403) {
170
+ throw new ForbiddenError(
171
+ 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)",
172
+ code
173
+ );
174
+ }
175
+ throw new ApiError(res.status, text, code);
131
176
  }
132
177
  };
178
+ function errorBody(text) {
179
+ try {
180
+ const v = JSON.parse(text);
181
+ if (!v || typeof v !== "object") return {};
182
+ const o = v;
183
+ return {
184
+ error: typeof o.error === "string" && o.error ? o.error : void 0,
185
+ code: typeof o.code === "string" && o.code ? o.code : void 0
186
+ };
187
+ } catch {
188
+ return {};
189
+ }
190
+ }
133
191
 
134
192
  // src/crypto.ts
135
193
  var import_node_crypto = require("crypto");
@@ -341,9 +399,32 @@ function parseSessionResult(raw) {
341
399
  initialCallbackUrl: typeof sig.initialCallbackUrl === "string" ? sig.initialCallbackUrl : null,
342
400
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
343
401
  interactionTypeUsed: str(r.interactionTypeUsed),
344
- onBehalfOf: parseOrg(r.onBehalfOf)
402
+ onBehalfOf: parseOrg(r.onBehalfOf),
403
+ pdf: parsePdf(r.pdf)
345
404
  };
346
405
  }
406
+ function parsePdf(raw) {
407
+ const p = asRecord(raw);
408
+ const docID = str(p.docID);
409
+ if (!docID) return null;
410
+ const out = { docID, documentStatus: String(p.documentStatus ?? "") };
411
+ if (p.errorCode != null) out.errorCode = String(p.errorCode);
412
+ if (p.outSha256 != null) out.outSha256 = String(p.outSha256);
413
+ if (p.size != null) out.size = Number(p.size);
414
+ if (p.expiresAt != null) out.expiresAt = String(p.expiresAt);
415
+ if (p.signatureLevel != null) out.signatureLevel = String(p.signatureLevel);
416
+ const sg = asRecord(p.signer);
417
+ if (sg.etsi != null) {
418
+ out.signer = { etsi: String(sg.etsi), certSerial: String(sg.certSerial ?? "") };
419
+ if (sg.onBehalfOf) out.signer.onBehalfOf = String(sg.onBehalfOf);
420
+ if (sg.claimedRole) out.signer.claimedRole = String(sg.claimedRole);
421
+ }
422
+ const v = asRecord(p.validation);
423
+ if (v.indication != null) {
424
+ out.validation = { indication: String(v.indication), ltv: v.ltv === true, signatures: Number(v.signatures ?? 0) };
425
+ }
426
+ return out;
427
+ }
347
428
  function parseSigParams(raw) {
348
429
  const p = asRecord(raw);
349
430
  const hash = str(p.hashAlgorithm);
@@ -398,6 +479,76 @@ var SessionApi = class {
398
479
  }
399
480
  };
400
481
 
482
+ // src/pdf.ts
483
+ function rec3(v) {
484
+ return v && typeof v === "object" ? v : {};
485
+ }
486
+ var PdfApi = class {
487
+ constructor(http, creds, defaults) {
488
+ this.http = http;
489
+ this.creds = creds;
490
+ this.defaults = defaults;
491
+ }
492
+ http;
493
+ creds;
494
+ defaults;
495
+ /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
496
+ async prepare(input) {
497
+ const request = {
498
+ relyingPartyUUID: this.creds.rpUUID,
499
+ relyingPartyName: this.creds.rpName,
500
+ certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
501
+ signer: input.signer,
502
+ flow: input.flow ?? "notification",
503
+ fileName: input.fileName
504
+ };
505
+ if (input.initialCallbackUrl?.trim()) request.initialCallbackUrl = input.initialCallbackUrl.trim();
506
+ if (input.docID?.trim()) request.docID = input.docID.trim();
507
+ if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
508
+ const form = new FormData();
509
+ form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
510
+ form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
511
+ const r = rec3(await this.http.postMultipart("/pdf/sign/prepare", form, 6e4));
512
+ const receivedAt = Date.now();
513
+ const vc = rec3(r.vc).value;
514
+ const dl = rec3(r.deviceLink);
515
+ return {
516
+ sessionId: String(r.sessionID),
517
+ docId: String(r.docID),
518
+ vc: vc != null ? String(vc) : null,
519
+ deviceLink: dl.sessionToken ? {
520
+ sessionToken: String(dl.sessionToken),
521
+ sessionSecret: String(dl.sessionSecret),
522
+ deviceLinkBase: String(dl.deviceLinkBase),
523
+ digest: String(dl.digest),
524
+ interactions: String(dl.interactions)
525
+ } : null,
526
+ receivedAt
527
+ };
528
+ }
529
+ /**
530
+ * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
531
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
532
+ */
533
+ document(sessionId) {
534
+ return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
535
+ }
536
+ };
537
+
538
+ // src/organization.ts
539
+ var OrganizationApi = class {
540
+ constructor(http) {
541
+ this.http = http;
542
+ }
543
+ http;
544
+ /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
545
+ async getRepresentations(personEtsi) {
546
+ const raw = await this.http.get(`/organization/representations/etsi/${encodeURIComponent(personEtsi)}`);
547
+ const list = Array.isArray(raw.representations) ? raw.representations : [];
548
+ return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
549
+ }
550
+ };
551
+
401
552
  // src/validator.ts
402
553
  var import_node_crypto2 = require("crypto");
403
554
  var ResponseValidator = class {
@@ -752,6 +903,8 @@ function resolveBaseUrl(baseUrl) {
752
903
  var EidClient = class {
753
904
  auth;
754
905
  sign;
906
+ pdf;
907
+ organization;
755
908
  session;
756
909
  validator;
757
910
  constructor(cfg) {
@@ -767,6 +920,8 @@ var EidClient = class {
767
920
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
768
921
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
769
922
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
923
+ this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
924
+ this.organization = new OrganizationApi(http);
770
925
  this.session = new SessionApi(http);
771
926
  this.validator = new ResponseValidator(cfg.trust ?? {});
772
927
  }
@@ -883,6 +1038,283 @@ function qrDeviceLinkTicker(input, onLink, opts) {
883
1038
  t.unref?.();
884
1039
  return () => clearInterval(t);
885
1040
  }
1041
+
1042
+ // src/oidc.ts
1043
+ var import_node_crypto4 = require("crypto");
1044
+ var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
1045
+ var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
1046
+ var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
1047
+ var CLOCK_SKEW_S = 60;
1048
+ var CACHE_MS = 60 * 60 * 1e3;
1049
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
1050
+ var KNOWN_CODES = /* @__PURE__ */ new Set([
1051
+ "access_denied",
1052
+ "login_required",
1053
+ "invalid_request",
1054
+ "invalid_scope",
1055
+ "unauthorized_client",
1056
+ "unsupported_response_type",
1057
+ "request_not_supported",
1058
+ "temporarily_unavailable",
1059
+ "server_error",
1060
+ "invalid_client",
1061
+ "invalid_grant",
1062
+ "unsupported_grant_type"
1063
+ ]);
1064
+ var EidOidcError = class extends EidError {
1065
+ constructor(code, message, description) {
1066
+ super(message);
1067
+ this.code = code;
1068
+ this.description = description;
1069
+ }
1070
+ code;
1071
+ description;
1072
+ };
1073
+ var EidOidcClient = class {
1074
+ constructor(cfg) {
1075
+ this.cfg = cfg;
1076
+ if (!cfg.clientId) throw new EidError("EidOidcClient: clientId \u0445\u043E\u043E\u0441\u043E\u043D");
1077
+ if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
1078
+ if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
1079
+ this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
1080
+ this.scope = cfg.scope ?? "openid";
1081
+ }
1082
+ cfg;
1083
+ issuer;
1084
+ scope;
1085
+ discovery;
1086
+ jwks;
1087
+ /** Discovery document (1 цаг cache). `issuer` нь тохиргоотой яг таарах ёстой (OIDC Discovery §4.3). */
1088
+ discover() {
1089
+ const c = this.discovery;
1090
+ if (c && Date.now() - c.at < CACHE_MS) return c.d;
1091
+ const d = this.fetchDiscovery();
1092
+ this.discovery = { at: Date.now(), d };
1093
+ d.catch(() => {
1094
+ if (this.discovery?.d === d) this.discovery = void 0;
1095
+ });
1096
+ return d;
1097
+ }
1098
+ async fetchDiscovery() {
1099
+ const d = await this.getJson(this.issuer + "/.well-known/openid-configuration");
1100
+ if (!d || d.issuer !== this.issuer) {
1101
+ throw new EidOidcError("discovery_failed", `OIDC discovery-\u0438\u0439\u043D issuer \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439 (\u0445\u04AF\u043B\u044D\u044D\u0441\u044D\u043D ${this.issuer})`);
1102
+ }
1103
+ for (const k of ["authorization_endpoint", "token_endpoint", "jwks_uri"]) {
1104
+ if (typeof d[k] !== "string" || !d[k].startsWith("https://")) {
1105
+ throw new EidOidcError("discovery_failed", `OIDC discovery: ${k} \u0431\u0443\u0440\u0443\u0443`);
1106
+ }
1107
+ }
1108
+ return d;
1109
+ }
1110
+ /**
1111
+ * Authorize URL + санамсаргүй state / nonce / PKCE codeVerifier. RP буцаасан `state`, `nonce`, `codeVerifier`-ийг
1112
+ * хөтчийн session-тэй холбож (httpOnly cookie эсвэл server session) хадгалаад хөтчийг `url` руу 302 хийнэ.
1113
+ */
1114
+ async createAuthorization(opts = {}) {
1115
+ const d = await this.discover();
1116
+ const state = randomB64url(32);
1117
+ const nonce = randomB64url(32);
1118
+ const codeVerifier = randomB64url(32);
1119
+ const u = new URL(d.authorization_endpoint);
1120
+ const p = new URLSearchParams({
1121
+ response_type: "code",
1122
+ client_id: this.cfg.clientId,
1123
+ redirect_uri: this.cfg.redirectUri,
1124
+ scope: opts.scope ?? this.scope,
1125
+ state,
1126
+ nonce,
1127
+ code_challenge: await pkceChallenge(codeVerifier),
1128
+ code_challenge_method: "S256"
1129
+ });
1130
+ if (opts.display) p.set("display", opts.display);
1131
+ u.search = p.toString();
1132
+ return { url: u.toString(), state, nonce, codeVerifier };
1133
+ }
1134
+ /**
1135
+ * redirect_uri руу ирсэн callback-ийг шийднэ: state тулгах → `error` бол {@link EidOidcError} → code солих →
1136
+ * id_token шалгах. `input` — бүтэн callback URL, харьцангуй зам (`/cb?code=…`) эсвэл query (`URLSearchParams` / `code=…&state=…`); зөвхөн query-г уншина.
1137
+ * Амжилтгүй бүх тохиолдолд {@link EidOidcError} (эсвэл {@link NetworkError}) шиднэ.
1138
+ */
1139
+ async handleCallback(input, checks) {
1140
+ const q = callbackParams(input);
1141
+ const state = q.get("state") ?? "";
1142
+ if (!checks.state || !timingSafeEqualStr(state, checks.state)) {
1143
+ throw new EidOidcError("state_mismatch", "OIDC state \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439 \u2014 \u043D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u0433 \u0434\u0430\u0445\u0438\u043D \u044D\u0445\u043B\u04AF\u04AF\u043B\u043D\u044D \u04AF\u04AF");
1144
+ }
1145
+ const iss = q.get("iss");
1146
+ if (iss !== null && iss !== this.issuer) {
1147
+ throw new EidOidcError("invalid_callback", "OIDC callback-\u0438\u0439\u043D iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
1148
+ }
1149
+ const err = q.get("error");
1150
+ if (err) {
1151
+ const code2 = KNOWN_CODES.has(err) ? err : "server_error";
1152
+ throw new EidOidcError(code2, `eID \u043D\u044D\u0432\u0442\u0440\u044D\u043B\u0442 \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${code2}`, q.get("error_description") ?? void 0);
1153
+ }
1154
+ const code = q.get("code") ?? "";
1155
+ if (code === "" || code.length > 512) throw new EidOidcError("invalid_callback", "OIDC callback-\u0434 code \u0430\u043B\u0433\u0430");
1156
+ const idToken = await this.exchangeCode(code, checks.codeVerifier);
1157
+ const claims = await this.verifyIdToken(idToken, { nonce: checks.nonce });
1158
+ return { claims, idToken };
1159
+ }
1160
+ /** `POST token_endpoint` (client_secret_basic, RFC 6749 §2.3.1) → id_token. */
1161
+ async exchangeCode(code, codeVerifier) {
1162
+ const d = await this.discover();
1163
+ const basic = Buffer.from(formEncode(this.cfg.clientId) + ":" + formEncode(this.cfg.clientSecret)).toString("base64");
1164
+ const res = await this.fetch(d.token_endpoint, {
1165
+ method: "POST",
1166
+ headers: {
1167
+ Authorization: "Basic " + basic,
1168
+ "Content-Type": "application/x-www-form-urlencoded",
1169
+ Accept: "application/json"
1170
+ },
1171
+ body: new URLSearchParams({
1172
+ grant_type: "authorization_code",
1173
+ code,
1174
+ redirect_uri: this.cfg.redirectUri,
1175
+ code_verifier: codeVerifier
1176
+ }).toString()
1177
+ });
1178
+ let j = {};
1179
+ try {
1180
+ j = await res.json();
1181
+ } catch {
1182
+ }
1183
+ if (!res.ok) {
1184
+ const e = typeof j.error === "string" && KNOWN_CODES.has(j.error) ? j.error : "server_error";
1185
+ throw new EidOidcError(
1186
+ e,
1187
+ `OIDC token ${res.status}: ${e}`,
1188
+ typeof j.error_description === "string" ? j.error_description : void 0
1189
+ );
1190
+ }
1191
+ if (typeof j.id_token !== "string" || j.id_token === "") {
1192
+ throw new EidOidcError("server_error", "OIDC token \u0445\u0430\u0440\u0438\u0443\u043D\u0434 id_token \u0430\u043B\u0433\u0430");
1193
+ }
1194
+ return j.id_token;
1195
+ }
1196
+ /**
1197
+ * id_token шалгана: JWKS (`kid`, cache; танихгүй kid → нэг удаа дахин татна) ES256 гарын үсэг, `iss`, `aud`
1198
+ * (= clientId; олон aud бол `azp`), `exp`/`iat` (±60 сек), `nonce`. Амжилтгүй бол `invalid_id_token`.
1199
+ */
1200
+ async verifyIdToken(idToken, opts) {
1201
+ const bad = (why) => new EidOidcError("invalid_id_token", `id_token: ${why}`);
1202
+ const parts = idToken.split(".");
1203
+ if (parts.length !== 3) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
1204
+ const header = decodeJsonPart(parts[0]);
1205
+ const payload = decodeJsonPart(parts[1]);
1206
+ if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
1207
+ if (header.alg !== "ES256") throw bad("alg ES256 \u0431\u0438\u0448");
1208
+ const kid = typeof header.kid === "string" ? header.kid : "";
1209
+ if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
1210
+ const pick = (ks) => ks.find(
1211
+ (k) => k.kid === kid && k.kty === "EC" && k.crv === "P-256" && (k.use === void 0 || k.use === "sig") && (k.alg === void 0 || k.alg === "ES256")
1212
+ );
1213
+ const jwk = pick(await this.keys(false)) ?? pick(await this.keys(true));
1214
+ if (!jwk || !jwk.x || !jwk.y) throw bad(`\u0442\u0430\u043D\u0438\u0445\u0433\u04AF\u0439 kid ${kid}`);
1215
+ const sig = Buffer.from(parts[2], "base64url");
1216
+ if (sig.length !== 64) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
1217
+ let ok = false;
1218
+ try {
1219
+ const key = await globalThis.crypto.subtle.importKey(
1220
+ "jwk",
1221
+ { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y },
1222
+ { name: "ECDSA", namedCurve: "P-256" },
1223
+ false,
1224
+ ["verify"]
1225
+ );
1226
+ ok = await globalThis.crypto.subtle.verify(
1227
+ { name: "ECDSA", hash: "SHA-256" },
1228
+ key,
1229
+ sig,
1230
+ new TextEncoder().encode(parts[0] + "." + parts[1])
1231
+ );
1232
+ } catch {
1233
+ ok = false;
1234
+ }
1235
+ if (!ok) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
1236
+ const c = payload;
1237
+ const now = Math.floor(Date.now() / 1e3);
1238
+ if (c.iss !== this.issuer) throw bad("iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
1239
+ const aud = Array.isArray(c.aud) ? c.aud : [c.aud];
1240
+ if (!aud.includes(this.cfg.clientId)) throw bad("aud \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
1241
+ if (aud.length > 1 && c.azp !== this.cfg.clientId) throw bad("azp \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
1242
+ if (typeof c.exp !== "number" || now > c.exp + CLOCK_SKEW_S) throw bad("\u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0434\u0443\u0443\u0441\u0441\u0430\u043D");
1243
+ if (typeof c.iat !== "number" || c.iat > now + CLOCK_SKEW_S) throw bad("iat \u0438\u0440\u044D\u044D\u0434\u04AF\u0439\u0434");
1244
+ if (typeof c.nonce !== "string" || !opts.nonce || !timingSafeEqualStr(c.nonce, opts.nonce)) throw bad("nonce \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
1245
+ if (typeof c.sub !== "string" || c.sub === "") throw bad("sub \u0430\u043B\u0433\u0430");
1246
+ return c;
1247
+ }
1248
+ keys(force) {
1249
+ const c = this.jwks;
1250
+ if (!force && c && Date.now() - c.at < CACHE_MS) return c.keys;
1251
+ const keys = this.discover().then(async (d) => {
1252
+ const j = await this.getJson(d.jwks_uri);
1253
+ return Array.isArray(j?.keys) ? j.keys : [];
1254
+ });
1255
+ this.jwks = { at: Date.now(), keys };
1256
+ keys.catch(() => {
1257
+ if (this.jwks?.keys === keys) this.jwks = void 0;
1258
+ });
1259
+ return keys;
1260
+ }
1261
+ async getJson(url) {
1262
+ const res = await this.fetch(url, { method: "GET", headers: { Accept: "application/json" } });
1263
+ if (!res.ok) throw new EidOidcError("discovery_failed", `OIDC ${res.status}: ${url}`);
1264
+ try {
1265
+ return await res.json();
1266
+ } catch {
1267
+ throw new EidOidcError("discovery_failed", `OIDC JSON \u0431\u0438\u0448: ${url}`);
1268
+ }
1269
+ }
1270
+ async fetch(url, init) {
1271
+ const f = this.cfg.fetchImpl ?? fetch;
1272
+ try {
1273
+ return await f(url, {
1274
+ ...init,
1275
+ redirect: "error",
1276
+ signal: AbortSignal.timeout(this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
1277
+ });
1278
+ } catch (e) {
1279
+ const err = e;
1280
+ if (err.name === "TimeoutError" || err.name === "AbortError") {
1281
+ throw new NetworkError(`OIDC \u0445\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${url}`);
1282
+ }
1283
+ throw new NetworkError(`OIDC \u0441\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${url}): ${err.message}`);
1284
+ }
1285
+ }
1286
+ };
1287
+ async function pkceChallenge(codeVerifier) {
1288
+ const h = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
1289
+ return Buffer.from(h).toString("base64url");
1290
+ }
1291
+ function randomB64url(bytes) {
1292
+ return Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(bytes))).toString("base64url");
1293
+ }
1294
+ function formEncode(s) {
1295
+ return new URLSearchParams({ v: s }).toString().slice(2);
1296
+ }
1297
+ function timingSafeEqualStr(a, b) {
1298
+ const x = Buffer.from(a, "utf8");
1299
+ const y = Buffer.from(b, "utf8");
1300
+ return x.length === y.length && (0, import_node_crypto4.timingSafeEqual)(x, y);
1301
+ }
1302
+ function decodeJsonPart(s) {
1303
+ try {
1304
+ const v = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
1305
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
1306
+ } catch {
1307
+ return null;
1308
+ }
1309
+ }
1310
+ function callbackParams(input) {
1311
+ if (input instanceof URLSearchParams) return input;
1312
+ if (input instanceof URL) return input.searchParams;
1313
+ const s = input.trim();
1314
+ if (/^https?:\/\//i.test(s)) return new URL(s).searchParams;
1315
+ const i = s.indexOf("?");
1316
+ return new URLSearchParams(i >= 0 ? s.slice(i + 1) : s);
1317
+ }
886
1318
  // Annotate the CommonJS export names for ESM import in node:
887
1319
  0 && (module.exports = {
888
1320
  ApiError,
@@ -890,11 +1322,19 @@ function qrDeviceLinkTicker(input, onLink, opts) {
890
1322
  AuthenticationError,
891
1323
  DEFAULT_BASE_URL,
892
1324
  DeviceLinkError,
1325
+ EID_ACR_ADVANCED,
1326
+ EID_ACR_QUALIFIED,
1327
+ EID_OIDC_ISSUER,
893
1328
  EidClient,
894
1329
  EidError,
1330
+ EidOidcClient,
1331
+ EidOidcError,
895
1332
  ForbiddenError,
896
1333
  Http,
897
1334
  NetworkError,
1335
+ ON_BEHALF_ERROR_CODES,
1336
+ OrganizationApi,
1337
+ PdfApi,
898
1338
  ResponseValidator,
899
1339
  SessionApi,
900
1340
  SessionFailedError,
@@ -908,6 +1348,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
908
1348
  isMobileUserAgent,
909
1349
  openOrShowQR,
910
1350
  parseSessionResult,
1351
+ pkceChallenge,
911
1352
  qrDeviceLinkTicker,
912
1353
  randomChallenge,
913
1354
  resolveBaseUrl,