@gerege-systems/eid-mongolia-sdk 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -99
- package/dist/{browser-C6JHm0lR.d.cts → browser-CuMH5IVG.d.cts} +40 -1
- package/dist/{browser-C6JHm0lR.d.ts → browser-CuMH5IVG.d.ts} +40 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/button.cjs +1005 -0
- package/dist/button.d.cts +93 -0
- package/dist/button.d.ts +93 -0
- package/dist/button.js +975 -0
- package/dist/index.cjs +591 -305
- package/dist/index.d.cts +254 -146
- package/dist/index.d.ts +254 -146
- package/dist/index.js +586 -301
- package/package.json +12 -4
package/dist/index.js
CHANGED
|
@@ -138,6 +138,11 @@ var RP_ROUTES = {
|
|
|
138
138
|
authDeviceLinkEtsi: { method: "POST", path: "/authentication/device-link/etsi/{semanticsIdentifier}" },
|
|
139
139
|
signNotificationEtsi: { method: "POST", path: "/signature/notification/etsi/{semanticsIdentifier}" },
|
|
140
140
|
signNotificationDocument: { method: "POST", path: "/signature/notification/document/{documentNumber}" },
|
|
141
|
+
signNotificationLinked: { method: "POST", path: "/signature/notification/linked/{documentNumber}" },
|
|
142
|
+
signDeviceLinkEtsi: { method: "POST", path: "/signature/device-link/etsi/{semanticsIdentifier}" },
|
|
143
|
+
signDeviceLinkDocument: { method: "POST", path: "/signature/device-link/document/{documentNumber}" },
|
|
144
|
+
certChoiceDeviceLink: { method: "POST", path: "/signature/certificate-choice/device-link/anonymous" },
|
|
145
|
+
certChoiceEtsi: { method: "POST", path: "/signature/certificate-choice/notification/etsi/{semanticsIdentifier}" },
|
|
141
146
|
pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
|
|
142
147
|
pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
|
|
143
148
|
orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
|
|
@@ -167,13 +172,6 @@ function routePath(route, ...args) {
|
|
|
167
172
|
if (i !== args.length) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0438\u043B\u04AF\u04AF`);
|
|
168
173
|
return out;
|
|
169
174
|
}
|
|
170
|
-
var OIDC_EXPECT = {
|
|
171
|
-
defaultScope: "openid",
|
|
172
|
-
pkceMethod: "S256",
|
|
173
|
-
idTokenAlg: "ES256",
|
|
174
|
-
/** `Authorization: Basic` (RFC 6749 §2.3.1). */
|
|
175
|
-
tokenEndpointAuthMethod: "client_secret_basic"
|
|
176
|
-
};
|
|
177
175
|
|
|
178
176
|
// src/crypto.ts
|
|
179
177
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -271,7 +269,8 @@ var AuthApi = class {
|
|
|
271
269
|
vc: vc != null ? String(vc) : null,
|
|
272
270
|
rpChallenge: acsp.rpChallenge,
|
|
273
271
|
acsp,
|
|
274
|
-
receivedAt: Date.now()
|
|
272
|
+
receivedAt: Date.now(),
|
|
273
|
+
sessionType: "auth"
|
|
275
274
|
};
|
|
276
275
|
}
|
|
277
276
|
/** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
@@ -300,10 +299,20 @@ var AuthApi = class {
|
|
|
300
299
|
}
|
|
301
300
|
};
|
|
302
301
|
|
|
302
|
+
// src/types.ts
|
|
303
|
+
var DEFAULT_SIGN_SESSION_TTL_MS = 6e5;
|
|
304
|
+
function sessionExpiry(sentAtMs, ttlMs) {
|
|
305
|
+
return sentAtMs + (ttlMs && ttlMs > 0 ? ttlMs : DEFAULT_SIGN_SESSION_TTL_MS);
|
|
306
|
+
}
|
|
307
|
+
|
|
303
308
|
// src/sign.ts
|
|
304
309
|
function rec2(v) {
|
|
305
310
|
return v && typeof v === "object" ? v : {};
|
|
306
311
|
}
|
|
312
|
+
var vcOf = (r) => {
|
|
313
|
+
const vc = typeof r.vc === "object" && r.vc !== null ? rec2(r.vc).value : r.vc;
|
|
314
|
+
return vc != null ? String(vc) : null;
|
|
315
|
+
};
|
|
307
316
|
var SignApi = class {
|
|
308
317
|
constructor(http, creds, defaults) {
|
|
309
318
|
this.http = http;
|
|
@@ -313,46 +322,155 @@ var SignApi = class {
|
|
|
313
322
|
http;
|
|
314
323
|
creds;
|
|
315
324
|
defaults;
|
|
316
|
-
body(digestB64, interactions, opts) {
|
|
325
|
+
body(digestB64, interactions, opts, defAlg) {
|
|
326
|
+
const sign = {
|
|
327
|
+
digest: digestB64,
|
|
328
|
+
hashAlgorithm: opts?.hashAlgorithm ?? "SHA-256",
|
|
329
|
+
signatureAlgorithm: opts?.signatureAlgorithm ?? defAlg,
|
|
330
|
+
relyingPartyName: this.creds.rpName,
|
|
331
|
+
interactions: interactionsToBase64(interactions),
|
|
332
|
+
initialCallbackUrl: opts?.callbackUrl?.trim() ?? ""
|
|
333
|
+
};
|
|
317
334
|
const body = {
|
|
318
335
|
relyingPartyUUID: this.creds.rpUUID,
|
|
319
|
-
relyingPartyName:
|
|
336
|
+
relyingPartyName: sign.relyingPartyName,
|
|
320
337
|
certificateLevel: opts?.certificateLevel ?? this.defaults.certificateLevel,
|
|
321
338
|
signatureProtocol: "RAW_DIGEST_SIGNATURE",
|
|
322
339
|
signatureProtocolParameters: {
|
|
323
340
|
digest: digestB64,
|
|
324
|
-
signatureAlgorithm:
|
|
325
|
-
signatureAlgorithmParameters: { hashAlgorithm:
|
|
341
|
+
signatureAlgorithm: sign.signatureAlgorithm,
|
|
342
|
+
signatureAlgorithmParameters: { hashAlgorithm: sign.hashAlgorithm }
|
|
326
343
|
},
|
|
327
|
-
interactions:
|
|
344
|
+
interactions: sign.interactions
|
|
328
345
|
};
|
|
329
|
-
if (
|
|
346
|
+
if (sign.initialCallbackUrl) body.initialCallbackUrl = sign.initialCallbackUrl;
|
|
330
347
|
if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
|
|
331
348
|
if (opts?.fileName?.trim()) body.fileName = opts.fileName.trim();
|
|
332
|
-
return body;
|
|
349
|
+
return { body, sign };
|
|
333
350
|
}
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
return {
|
|
351
|
+
async notification(path, body, sign) {
|
|
352
|
+
const sent = Date.now();
|
|
353
|
+
const r = rec2(await this.http.post(path, body));
|
|
354
|
+
return {
|
|
355
|
+
sessionId: String(r.sessionID),
|
|
356
|
+
vc: vcOf(r),
|
|
357
|
+
rpChallenge: "",
|
|
358
|
+
...sign ? { sign } : {},
|
|
359
|
+
expiresAt: sessionExpiry(sent, this.defaults.sessionTtlMs)
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
async deviceLink(path, body, extra) {
|
|
363
|
+
const sent = Date.now();
|
|
364
|
+
const r = rec2(await this.http.post(path, body));
|
|
365
|
+
return {
|
|
366
|
+
sessionId: String(r.sessionID),
|
|
367
|
+
sessionToken: r.sessionToken != null ? String(r.sessionToken) : null,
|
|
368
|
+
sessionSecret: r.sessionSecret != null ? String(r.sessionSecret) : null,
|
|
369
|
+
deviceLinkBase: r.deviceLinkBase != null ? String(r.deviceLinkBase) : null,
|
|
370
|
+
vc: vcOf(r),
|
|
371
|
+
rpChallenge: "",
|
|
372
|
+
receivedAt: Date.now(),
|
|
373
|
+
...extra,
|
|
374
|
+
expiresAt: sessionExpiry(sent, this.defaults.sessionTtlMs)
|
|
375
|
+
};
|
|
338
376
|
}
|
|
339
377
|
/**
|
|
340
378
|
* Бэлэн digest-ээр push sign (РД/civil_id/ETSI). Баримт (PDF/файл)-ын байтын SHA-256
|
|
341
379
|
* digest-ийг гаднаас бэлдэж өгнө — текст бус бинар баримтад тохиромжтой.
|
|
380
|
+
*
|
|
381
|
+
* ⚠️ Олон утастай иргэнд CA etsi-г ЭХНИЙ идэвхтэй төхөөрөмж рүү шийднэ. Гэрчилгээг урьдчилж мэдэх шаардлагатай бол
|
|
382
|
+
* (PAdES/CMS) {@link certificateChoiceByEtsi} → {@link digestForSigner}.
|
|
342
383
|
*/
|
|
343
384
|
async digestByEtsi(id, digestB64, interactions, opts) {
|
|
344
|
-
const
|
|
345
|
-
return this.
|
|
385
|
+
const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
|
|
386
|
+
return this.notification(routePath(RP_ROUTES.signNotificationEtsi, id), body, sign);
|
|
346
387
|
}
|
|
347
|
-
/** Бэлэн digest-ээр push sign — баримтын дугаараар (auth
|
|
388
|
+
/** Бэлэн digest-ээр push sign — баримтын дугаараар (auth / certificate-choice-оос гарсан documentNumber дээр). */
|
|
348
389
|
async digestByDocument(documentNumber, digestB64, interactions, opts) {
|
|
349
|
-
const
|
|
350
|
-
return this.
|
|
390
|
+
const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
|
|
391
|
+
return this.notification(routePath(RP_ROUTES.signNotificationDocument, documentNumber), body, sign);
|
|
351
392
|
}
|
|
352
393
|
/** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
|
|
353
394
|
async textByEtsi(id, text, interactions, opts) {
|
|
354
395
|
return this.digestByEtsi(id, sha256Base64(text), interactions, opts);
|
|
355
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* QR / Web2App / App2App sign — тодорхой иргэн (РД/civil_id/ETSI). Холбоосыг backend-д
|
|
399
|
+
* `buildDeviceLink(deviceLinkInput(s, type, lang))`-ээр; Web2App/App2App-д `callbackUrl` заавал.
|
|
400
|
+
* `validateSign`-д `expectedFlowType` = харуулсан холбоосын төрөл.
|
|
401
|
+
*/
|
|
402
|
+
async deviceLinkByEtsi(id, digestB64, interactions, opts) {
|
|
403
|
+
const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
|
|
404
|
+
return this.deviceLink(routePath(RP_ROUTES.signDeviceLinkEtsi, id), body, { sessionType: "sign", sign });
|
|
405
|
+
}
|
|
406
|
+
/** QR / Web2App / App2App sign — баримтын дугаараар (тодорхой төхөөрөмж). */
|
|
407
|
+
async deviceLinkByDocument(documentNumber, digestB64, interactions, opts) {
|
|
408
|
+
const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
|
|
409
|
+
return this.deviceLink(routePath(RP_ROUTES.signDeviceLinkDocument, documentNumber), body, { sessionType: "sign", sign });
|
|
410
|
+
}
|
|
411
|
+
// ── Certificate-choice ──
|
|
412
|
+
certChoiceBody(opts) {
|
|
413
|
+
return {
|
|
414
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
415
|
+
relyingPartyName: this.creds.rpName,
|
|
416
|
+
certificateLevel: opts?.certificateLevel ?? this.defaults.certificateLevel
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Push: иргэний (РД/civil_id/ETSI) утас руу гарын үсгийн гэрчилгээ сонгуулах хүсэлт (PIN-гүй). Үр дүнг
|
|
421
|
+
* `session.waitForResult` → `validator.validateCertificateChoice`. CERT эрх шаардана.
|
|
422
|
+
*/
|
|
423
|
+
async certificateChoiceByEtsi(id, opts) {
|
|
424
|
+
return this.notification(routePath(RP_ROUTES.certChoiceEtsi, id), this.certChoiceBody(opts));
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* QR: иргэн хэн болохыг урьдчилж мэдэхгүй үед гэрчилгээ сонгуулах. CA-ийн certificate-choice хүсэлт
|
|
428
|
+
* initialCallbackUrl авдаггүй тул ЗӨВХӨН QR холбоос (`deviceLinkInput(s, "QR", lang)`). CERT эрх.
|
|
429
|
+
*/
|
|
430
|
+
async certificateChoiceDeviceLink(opts) {
|
|
431
|
+
return this.deviceLink(routePath(RP_ROUTES.certChoiceDeviceLink), this.certChoiceBody(opts), {
|
|
432
|
+
sessionType: "cert",
|
|
433
|
+
relyingPartyName: this.creds.rpName
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
// ── Гэрчилгээ урьдчилж мэдэгдэх гарын үсэг (client-side PAdES / CMS) ──
|
|
437
|
+
signerBody(signer, digestB64, interactions, opts) {
|
|
438
|
+
if (!signer?.documentNumber?.trim()) {
|
|
439
|
+
throw new Error("eid: signer.documentNumber \u0445\u043E\u043E\u0441\u043E\u043D \u2014 validateCertificateChoice-\u0438\u0439\u043D \u04AF\u0440 \u0434\u04AF\u043D\u0433 \u04E9\u0433");
|
|
440
|
+
}
|
|
441
|
+
const out = this.body(digestB64, interactions, opts, "sha256WithRSAEncryption");
|
|
442
|
+
const want = hashLen(out.sign.hashAlgorithm);
|
|
443
|
+
const d = /^[A-Za-z0-9+/]*={0,2}$/.test(digestB64) ? Buffer.from(digestB64, "base64") : null;
|
|
444
|
+
if (!d || d.length !== want || d.toString("base64") !== digestB64) {
|
|
445
|
+
throw new Error(`eid: digest \u043D\u044C \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442 base64, ${out.sign.hashAlgorithm}-\u0438\u0439\u043D \u0443\u0440\u0442 (${want} \u0431\u0430\u0439\u0442) \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439`);
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Certificate-choice-оор сонгосон ЯГ ТЭР төхөөрөмж (`signer.documentNumber`) рүү push sign. Олон утастай иргэнд
|
|
451
|
+
* etsi-ээр эхлүүлбэл өөр утас (өөр гэрчилгээ) зурж, гэрчилгээг урьдчилж оруулсан CMS (PAdES signingCertificateV2)
|
|
452
|
+
* хүчингүй болдог — тиймээс digest-ийг ҮРГЭЛЖ documentNumber-аар зуруулна.
|
|
453
|
+
*
|
|
454
|
+
* `signer.certChoiceSessionId` байвал `/signature/notification/linked/{documentNumber}` (`linkedSessionID`), эс
|
|
455
|
+
* бөгөөс `/signature/notification/document/{documentNumber}`. Default `sha256WithRSAEncryption` + SHA-256 (digest =
|
|
456
|
+
* 32 байт DTBS); digest-ийн урт буруу бол хүсэлт явахгүй. Хариуг:
|
|
457
|
+
* `validator.validateSign(r, s.sign.digest, { hashAlgorithm: s.sign.hashAlgorithm, expectedAlgorithm:
|
|
458
|
+
* s.sign.signatureAlgorithm, expectedCertificate: signer.certificate, expectedFlowType: "Notification" })`.
|
|
459
|
+
*/
|
|
460
|
+
async digestForSigner(signer, digestB64, interactions, opts) {
|
|
461
|
+
const { body, sign } = this.signerBody(signer, digestB64, interactions, opts);
|
|
462
|
+
const linked = signer.certChoiceSessionId?.trim();
|
|
463
|
+
if (linked) {
|
|
464
|
+
body.linkedSessionID = linked;
|
|
465
|
+
return this.notification(routePath(RP_ROUTES.signNotificationLinked, signer.documentNumber), body, sign);
|
|
466
|
+
}
|
|
467
|
+
return this.notification(routePath(RP_ROUTES.signNotificationDocument, signer.documentNumber), body, sign);
|
|
468
|
+
}
|
|
469
|
+
/** {@link digestForSigner}-ийн QR / Web2App / App2App хувилбар: `/signature/device-link/document/{documentNumber}`. */
|
|
470
|
+
async deviceLinkForSigner(signer, digestB64, interactions, opts) {
|
|
471
|
+
const { body, sign } = this.signerBody(signer, digestB64, interactions, opts);
|
|
472
|
+
return this.deviceLink(routePath(RP_ROUTES.signDeviceLinkDocument, signer.documentNumber), body, { sessionType: "sign", sign });
|
|
473
|
+
}
|
|
356
474
|
};
|
|
357
475
|
|
|
358
476
|
// src/session.ts
|
|
@@ -1113,13 +1231,19 @@ var ResponseValidator = class {
|
|
|
1113
1231
|
* @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
|
|
1114
1232
|
*/
|
|
1115
1233
|
validateSign(result, digestB64, opts = {}) {
|
|
1116
|
-
const
|
|
1234
|
+
const o = typeof opts === "string" ? { hashAlgorithm: opts } : opts;
|
|
1235
|
+
const { hashAlgorithm = "SHA-256", expectedFlowType } = o;
|
|
1117
1236
|
const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
|
|
1118
1237
|
if (expectedFlowType && result.flowType !== expectedFlowType) {
|
|
1119
1238
|
throw new ValidationError(`flowType ${result.flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
|
|
1120
1239
|
}
|
|
1240
|
+
if (o.expectedCertificate && !leaf.raw.equals(o.expectedCertificate.raw)) {
|
|
1241
|
+
throw new ValidationError(
|
|
1242
|
+
`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D certificate-choice-\u043E\u043E\u0440 \u0441\u043E\u043D\u0433\u043E\u0441\u043E\u043D\u0442\u043E\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439 (serial ${leaf.serialNumber} \u2260 ${o.expectedCertificate.serialNumber})`
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1121
1245
|
const digest = Buffer.from(digestB64, "base64");
|
|
1122
|
-
const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
|
|
1246
|
+
const { alg, hash } = this.algorithmOf(result, leaf, o.expectedAlgorithm ?? null, hashAlgorithm);
|
|
1123
1247
|
if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1124
1248
|
if (!verifyPrehashedSignature(digest, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
1125
1249
|
throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u04E9\u0433\u0441\u04E9\u043D digest-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
@@ -1133,6 +1257,24 @@ var ResponseValidator = class {
|
|
|
1133
1257
|
parsedSubject: parseSubject(leaf)
|
|
1134
1258
|
};
|
|
1135
1259
|
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Certificate-choice session-ий хариуг шалгана: COMPLETE/OK (эс бөгөөс `SessionFailedError`), гэрчилгээ ба
|
|
1262
|
+
* documentNumber байгаа, хугацаа, certLevel, зориулалт (гарын үсгийн — contentCommitment, `checkKeyUsage` үед), гинж,
|
|
1263
|
+
* issuer. Энэ хариунд гарын үсэг БАЙХГҮЙ — иргэнийг таниагүй (нэвтрэлт биш); зөвхөн дараагийн sign session-ий
|
|
1264
|
+
* гэрчилгээг урьдчилж мэдэхэд. `sessionId` — certificate-choice session.
|
|
1265
|
+
*/
|
|
1266
|
+
validateCertificateChoice(result, sessionId = null) {
|
|
1267
|
+
this.sessionDone(result);
|
|
1268
|
+
const leaf = this.leafOf(result, "RAW_DIGEST_SIGNATURE");
|
|
1269
|
+
return {
|
|
1270
|
+
documentNumber: result.documentNumber,
|
|
1271
|
+
certificate: leaf,
|
|
1272
|
+
certificateDerB64: leaf.raw.toString("base64"),
|
|
1273
|
+
certificateLevel: String(result.certificateLevel ?? ""),
|
|
1274
|
+
parsedSubject: parseSubject(leaf),
|
|
1275
|
+
certChoiceSessionId: sessionId
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1136
1278
|
/**
|
|
1137
1279
|
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
|
|
1138
1280
|
* (downgrade / alg confusion хаалт).
|
|
@@ -1182,13 +1324,22 @@ var ResponseValidator = class {
|
|
|
1182
1324
|
// ── дотоод ──
|
|
1183
1325
|
/** state/endResult, cert байгаа эсэх, chain, validity, certLevel — нийтлэг шалгалт. */
|
|
1184
1326
|
precheck(result, protocol) {
|
|
1185
|
-
|
|
1186
|
-
if (result.endResult !== "OK") throw new SessionFailedError(result.endResult ?? "UNKNOWN");
|
|
1327
|
+
this.sessionDone(result);
|
|
1187
1328
|
if (result.signatureProtocol && result.signatureProtocol !== protocol) {
|
|
1188
1329
|
throw new ValidationError(`signatureProtocol ${result.signatureProtocol} \u2260 ${protocol}`);
|
|
1189
1330
|
}
|
|
1190
1331
|
if (!result.certificateDerB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
|
|
1191
1332
|
if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
|
|
1333
|
+
return this.leafOf(result, protocol);
|
|
1334
|
+
}
|
|
1335
|
+
/** COMPLETE + OK; амжилтгүй endResult → SessionFailedError. */
|
|
1336
|
+
sessionDone(result) {
|
|
1337
|
+
if (result.state !== "COMPLETE") throw new ValidationError(`Session \u0434\u0443\u0443\u0441\u0430\u0430\u0433\u04AF\u0439 (state=${result.state})`);
|
|
1338
|
+
if (result.endResult !== "OK") throw new SessionFailedError(result.endResult ?? "UNKNOWN");
|
|
1339
|
+
}
|
|
1340
|
+
/** Хариуны гэрчилгээ: documentNumber, задлалт, хугацаа, certLevel, зориулалт (protocol), гинж, issuer. */
|
|
1341
|
+
leafOf(result, protocol) {
|
|
1342
|
+
if (!result.certificateDerB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
|
|
1192
1343
|
if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
|
|
1193
1344
|
const leaf = new X509Certificate3(Buffer.from(result.certificateDerB64, "base64"));
|
|
1194
1345
|
this.checkValidity(leaf);
|
|
@@ -1590,6 +1741,8 @@ var EidClient = class {
|
|
|
1590
1741
|
token;
|
|
1591
1742
|
session;
|
|
1592
1743
|
validator;
|
|
1744
|
+
/** `credentials.rpName` — иргэний утсанд харагдах RP нэр. */
|
|
1745
|
+
rpName;
|
|
1593
1746
|
constructor(cfg) {
|
|
1594
1747
|
if (!cfg.credentials?.apiSecret) throw new Error("EidClient: credentials.apiSecret \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
1595
1748
|
const base = resolveBaseUrl(cfg.baseUrl);
|
|
@@ -1603,7 +1756,7 @@ var EidClient = class {
|
|
|
1603
1756
|
});
|
|
1604
1757
|
const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
|
|
1605
1758
|
this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
|
|
1606
|
-
this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
|
|
1759
|
+
this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level, sessionTtlMs: cfg.signSessionTtlMs });
|
|
1607
1760
|
this.pdf = new PdfApi(http, cfg.credentials, {
|
|
1608
1761
|
certificateLevel: level,
|
|
1609
1762
|
timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
|
|
@@ -1614,6 +1767,7 @@ var EidClient = class {
|
|
|
1614
1767
|
this.token = new TokenApi(http, cfg.credentials);
|
|
1615
1768
|
this.session = new SessionApi(http);
|
|
1616
1769
|
this.validator = new ResponseValidator(cfg.trust ?? {});
|
|
1770
|
+
this.rpName = cfg.credentials.rpName;
|
|
1617
1771
|
}
|
|
1618
1772
|
};
|
|
1619
1773
|
|
|
@@ -1678,6 +1832,37 @@ function buildDeviceLink(p) {
|
|
|
1678
1832
|
const authCode = createHmac("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
1679
1833
|
return `${unprotected}&authCode=${authCode}`;
|
|
1680
1834
|
}
|
|
1835
|
+
function deviceLinkInput(s, deviceLinkType, lang) {
|
|
1836
|
+
const base = {
|
|
1837
|
+
deviceLinkBase: s.deviceLinkBase ?? "",
|
|
1838
|
+
deviceLinkType,
|
|
1839
|
+
sessionToken: s.sessionToken ?? "",
|
|
1840
|
+
sessionSecret: s.sessionSecret ?? "",
|
|
1841
|
+
lang,
|
|
1842
|
+
receivedAt: s.receivedAt
|
|
1843
|
+
};
|
|
1844
|
+
if (s.sessionType === "sign" && s.sign) {
|
|
1845
|
+
return {
|
|
1846
|
+
...base,
|
|
1847
|
+
sessionType: "sign",
|
|
1848
|
+
digest: s.sign.digest,
|
|
1849
|
+
relyingPartyName: s.sign.relyingPartyName,
|
|
1850
|
+
interactions: s.sign.interactions,
|
|
1851
|
+
initialCallbackUrl: s.sign.initialCallbackUrl
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
if (s.sessionType === "cert") {
|
|
1855
|
+
return { ...base, sessionType: "cert", relyingPartyName: s.relyingPartyName ?? "", interactions: "" };
|
|
1856
|
+
}
|
|
1857
|
+
return {
|
|
1858
|
+
...base,
|
|
1859
|
+
sessionType: "auth",
|
|
1860
|
+
rpChallenge: s.acsp?.rpChallenge ?? s.rpChallenge,
|
|
1861
|
+
relyingPartyName: s.acsp?.relyingPartyName ?? "",
|
|
1862
|
+
interactions: s.acsp?.interactions ?? "",
|
|
1863
|
+
initialCallbackUrl: s.acsp?.initialCallbackUrl ?? ""
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1681
1866
|
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
1682
1867
|
const clock = opts?.clock ?? Date.now;
|
|
1683
1868
|
const tick = () => onLink(buildDeviceLink({ ...input, deviceLinkType: "QR", now: clock() }));
|
|
@@ -1687,293 +1872,392 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
1687
1872
|
return () => clearInterval(t);
|
|
1688
1873
|
}
|
|
1689
1874
|
|
|
1690
|
-
// src/
|
|
1691
|
-
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
"invalid_request",
|
|
1702
|
-
"invalid_scope",
|
|
1703
|
-
"unauthorized_client",
|
|
1704
|
-
"unsupported_response_type",
|
|
1705
|
-
"request_not_supported",
|
|
1706
|
-
"temporarily_unavailable",
|
|
1707
|
-
"server_error",
|
|
1708
|
-
"invalid_client",
|
|
1709
|
-
"invalid_grant",
|
|
1710
|
-
"unsupported_grant_type"
|
|
1711
|
-
]);
|
|
1712
|
-
var EidOidcError = class extends EidError {
|
|
1713
|
-
constructor(code, message, description) {
|
|
1714
|
-
super(message);
|
|
1715
|
-
this.code = code;
|
|
1716
|
-
this.description = description;
|
|
1717
|
-
}
|
|
1718
|
-
code;
|
|
1719
|
-
description;
|
|
1720
|
-
};
|
|
1721
|
-
var EidOidcClient = class {
|
|
1722
|
-
constructor(cfg) {
|
|
1723
|
-
this.cfg = cfg;
|
|
1724
|
-
if (!cfg.clientId) throw new EidError("EidOidcClient: clientId \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
1725
|
-
if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
1726
|
-
if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
|
|
1727
|
-
this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
|
|
1728
|
-
this.scope = cfg.scope ?? OIDC_EXPECT.defaultScope;
|
|
1729
|
-
}
|
|
1730
|
-
cfg;
|
|
1731
|
-
issuer;
|
|
1732
|
-
scope;
|
|
1733
|
-
discovery;
|
|
1734
|
-
jwks;
|
|
1735
|
-
/** Discovery document (1 цаг cache). `issuer` нь тохиргоотой яг таарах ёстой (OIDC Discovery §4.3). */
|
|
1736
|
-
discover() {
|
|
1737
|
-
const c = this.discovery;
|
|
1738
|
-
if (c && Date.now() - c.at < CACHE_MS) return c.d;
|
|
1739
|
-
const d = this.fetchDiscovery();
|
|
1740
|
-
this.discovery = { at: Date.now(), d };
|
|
1741
|
-
d.catch(() => {
|
|
1742
|
-
if (this.discovery?.d === d) this.discovery = void 0;
|
|
1743
|
-
});
|
|
1744
|
-
return d;
|
|
1745
|
-
}
|
|
1746
|
-
async fetchDiscovery() {
|
|
1747
|
-
const d = await this.getJson(this.issuer + "/.well-known/openid-configuration");
|
|
1748
|
-
if (!d || d.issuer !== this.issuer) {
|
|
1749
|
-
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})`);
|
|
1750
|
-
}
|
|
1751
|
-
for (const k of ["authorization_endpoint", "token_endpoint", "jwks_uri"]) {
|
|
1752
|
-
if (typeof d[k] !== "string" || !d[k].startsWith("https://")) {
|
|
1753
|
-
throw new EidOidcError("discovery_failed", `OIDC discovery: ${k} \u0431\u0443\u0440\u0443\u0443`);
|
|
1875
|
+
// src/login.ts
|
|
1876
|
+
import { createHash as createHash5, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
1877
|
+
function memoryLoginStore() {
|
|
1878
|
+
const m = /* @__PURE__ */ new Map();
|
|
1879
|
+
return {
|
|
1880
|
+
get(k) {
|
|
1881
|
+
const e = m.get(k);
|
|
1882
|
+
if (!e) return void 0;
|
|
1883
|
+
if (e.exp <= Date.now()) {
|
|
1884
|
+
m.delete(k);
|
|
1885
|
+
return void 0;
|
|
1754
1886
|
}
|
|
1887
|
+
return e.tx;
|
|
1888
|
+
},
|
|
1889
|
+
set(k, tx, ttl) {
|
|
1890
|
+
const now = Date.now();
|
|
1891
|
+
if (m.size > 1e3) {
|
|
1892
|
+
for (const [key, e] of m) if (e.exp <= now) m.delete(key);
|
|
1893
|
+
}
|
|
1894
|
+
m.set(k, { tx, exp: now + ttl });
|
|
1895
|
+
},
|
|
1896
|
+
delete(k) {
|
|
1897
|
+
return m.delete(k);
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
var CALLBACK_PATH = "/auth/eid/callback";
|
|
1902
|
+
function forwardedClientIp(headers, trustProxy) {
|
|
1903
|
+
if (trustProxy === false || trustProxy === 0) return void 0;
|
|
1904
|
+
const xff = header(headers, "x-forwarded-for").split(",").map((x) => x.trim()).filter(Boolean);
|
|
1905
|
+
if (xff.length) {
|
|
1906
|
+
if (trustProxy === true) return xff[0];
|
|
1907
|
+
const n = Math.floor(trustProxy);
|
|
1908
|
+
return n >= 1 && n <= xff.length ? xff[xff.length - n] : void 0;
|
|
1909
|
+
}
|
|
1910
|
+
return header(headers, "x-real-ip").trim() || void 0;
|
|
1911
|
+
}
|
|
1912
|
+
var ID_RE = /^(\p{L}{2}[0-9]{8}|[0-9]{8,14}|PNOMN-[0-9]{8,14})$/u;
|
|
1913
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
1914
|
+
var B64URL_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
1915
|
+
var IOS_BROWSERS = [
|
|
1916
|
+
[/CriOS/i, "googlechromes"],
|
|
1917
|
+
[/EdgiOS/i, "microsoft-edge-https"],
|
|
1918
|
+
[/FxiOS/i, "firefox"],
|
|
1919
|
+
[/OPT\//i, "touch-https"],
|
|
1920
|
+
[/OPiOS|OPR\//i, "opera-https"],
|
|
1921
|
+
[/DuckDuckGo/i, "ddgQuickLink"]
|
|
1922
|
+
];
|
|
1923
|
+
var ANDROID_BROWSERS = [
|
|
1924
|
+
[/EdgA\//i, "com.microsoft.emmx"],
|
|
1925
|
+
[/Opera Mini/i, "com.opera.mini.native"],
|
|
1926
|
+
[/OPR\//i, "com.opera.browser"],
|
|
1927
|
+
[/SamsungBrowser\//i, "com.sec.android.app.sbrowser"],
|
|
1928
|
+
[/YaBrowser\//i, "com.yandex.browser"],
|
|
1929
|
+
[/DuckDuckGo\//i, "com.duckduckgo.mobile.android"],
|
|
1930
|
+
[/Vivaldi\//i, "com.vivaldi.browser"],
|
|
1931
|
+
[/Firefox\//i, "org.mozilla.firefox"],
|
|
1932
|
+
[/Chrome\//i, "com.android.chrome"]
|
|
1933
|
+
];
|
|
1934
|
+
function withReturnHint(callbackUrl, userAgent) {
|
|
1935
|
+
const u = new URL(callbackUrl);
|
|
1936
|
+
const pick = (t) => t.find(([re]) => re.test(userAgent))?.[1];
|
|
1937
|
+
if (/iPhone|iPad|iPod/i.test(userAgent)) {
|
|
1938
|
+
const v = pick(IOS_BROWSERS);
|
|
1939
|
+
if (v) u.searchParams.set("retScheme", v);
|
|
1940
|
+
} else if (/Android/i.test(userAgent)) {
|
|
1941
|
+
const v = pick(ANDROID_BROWSERS);
|
|
1942
|
+
if (v) u.searchParams.set("retPkg", v);
|
|
1943
|
+
}
|
|
1944
|
+
return u.toString();
|
|
1945
|
+
}
|
|
1946
|
+
function header(h, name) {
|
|
1947
|
+
const v = h[name] ?? h[name.toLowerCase()];
|
|
1948
|
+
return (Array.isArray(v) ? v[0] : v) ?? "";
|
|
1949
|
+
}
|
|
1950
|
+
function readCookie(h, name) {
|
|
1951
|
+
for (const part of header(h, "cookie").split(";")) {
|
|
1952
|
+
const i = part.indexOf("=");
|
|
1953
|
+
if (i > 0 && part.slice(0, i).trim() === name) return part.slice(i + 1).trim();
|
|
1954
|
+
}
|
|
1955
|
+
return "";
|
|
1956
|
+
}
|
|
1957
|
+
var sha256hex = (s) => createHash5("sha256").update(s).digest("hex");
|
|
1958
|
+
function safeEqualHex(a, b) {
|
|
1959
|
+
const x = Buffer.from(a, "hex");
|
|
1960
|
+
const y = Buffer.from(b, "hex");
|
|
1961
|
+
return x.length === y.length && x.length > 0 && timingSafeEqual2(x, y);
|
|
1962
|
+
}
|
|
1963
|
+
function toIdentity(sessionId, r, v, flowType) {
|
|
1964
|
+
const s = v.parsedSubject;
|
|
1965
|
+
const etsi = s.serialNumber ?? "";
|
|
1966
|
+
const m = /^PNOMN-([0-9]+)$/.exec(etsi);
|
|
1967
|
+
return {
|
|
1968
|
+
sessionId,
|
|
1969
|
+
etsi,
|
|
1970
|
+
civilId: m ? m[1] : null,
|
|
1971
|
+
givenName: s.givenName ?? null,
|
|
1972
|
+
surname: s.surname ?? null,
|
|
1973
|
+
name: s.name,
|
|
1974
|
+
country: s.country ?? null,
|
|
1975
|
+
documentNumber: v.documentNumber,
|
|
1976
|
+
certificateLevel: v.certificateLevel,
|
|
1977
|
+
certificateDerB64: r.certificateDerB64 ?? "",
|
|
1978
|
+
flowType,
|
|
1979
|
+
person: personMatchesCertificate(r.person, s) ? r.person ?? null : null
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
function createEidLogin(opts) {
|
|
1983
|
+
const client = opts.client;
|
|
1984
|
+
if (!opts.onSuccess) throw new Error("createEidLogin: onSuccess \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
1985
|
+
if (opts.callbackUrl) {
|
|
1986
|
+
const u = new URL(opts.callbackUrl);
|
|
1987
|
+
if (u.protocol !== "https:" || u.pathname !== CALLBACK_PATH) {
|
|
1988
|
+
throw new Error(`createEidLogin: callbackUrl \u043D\u044C https://<\u0442\u0430\u043D\u044B host>${CALLBACK_PATH} \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439`);
|
|
1755
1989
|
}
|
|
1756
|
-
return d;
|
|
1757
1990
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1991
|
+
const store = opts.store ?? memoryLoginStore();
|
|
1992
|
+
const ttl = opts.ttlMs ?? 12e4;
|
|
1993
|
+
const pollMs = opts.pollMs ?? 1e3;
|
|
1994
|
+
const cookieName = opts.cookie?.name ?? "eid_login";
|
|
1995
|
+
const cookiePath = opts.cookie?.path ?? "/";
|
|
1996
|
+
const secure = opts.cookie?.secure ?? true;
|
|
1997
|
+
const successRedirect = opts.successRedirect ?? "/";
|
|
1998
|
+
const failureRedirect = opts.failureRedirect ?? "/";
|
|
1999
|
+
const onError = opts.onError ?? ((e) => console.error("eid-login:", e instanceof Error ? e.message : e));
|
|
2000
|
+
const pushHits = /* @__PURE__ */ new Map();
|
|
2001
|
+
const rateLimit = opts.rateLimit ?? ((key) => {
|
|
2002
|
+
if (!key.startsWith("push:")) return true;
|
|
2003
|
+
const now = Date.now();
|
|
2004
|
+
const hits = (pushHits.get(key) ?? []).filter((t) => now - t < 6e4);
|
|
2005
|
+
if (pushHits.size > 1e4) pushHits.clear();
|
|
2006
|
+
if (hits.length >= 3) return false;
|
|
2007
|
+
hits.push(now);
|
|
2008
|
+
pushHits.set(key, hits);
|
|
2009
|
+
return true;
|
|
2010
|
+
});
|
|
2011
|
+
const displayText = (opts.displayText ?? `${client.rpName}-\u0434 \u043D\u044D\u0432\u0442\u0440\u044D\u0445`).slice(0, 60);
|
|
2012
|
+
const interactions = [{ type: "displayTextAndPIN", displayText60: displayText }];
|
|
2013
|
+
const json = (status2, body, extra = {}) => ({
|
|
2014
|
+
status: status2,
|
|
2015
|
+
headers: { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", ...extra },
|
|
2016
|
+
body: JSON.stringify(body)
|
|
2017
|
+
});
|
|
2018
|
+
const cookieAttrs = (maxAge) => `Path=${cookiePath}; Max-Age=${maxAge}; HttpOnly; SameSite=Lax${secure ? "; Secure" : ""}` + (opts.cookie?.domain ? `; Domain=${opts.cookie.domain}` : "");
|
|
2019
|
+
const clearCookie = `${cookieName}=; ${cookieAttrs(0)}`;
|
|
2020
|
+
const redirect = (to, extra = {}) => ({
|
|
2021
|
+
status: 303,
|
|
2022
|
+
headers: { Location: to, "Cache-Control": "no-store", "Referrer-Policy": "no-referrer", ...extra },
|
|
2023
|
+
body: ""
|
|
2024
|
+
});
|
|
2025
|
+
const failTo = (code) => {
|
|
2026
|
+
const u = new URL(failureRedirect, "http://x");
|
|
2027
|
+
u.searchParams.set("eid_error", code);
|
|
2028
|
+
return /^https?:/i.test(failureRedirect) ? u.toString() : u.pathname + u.search + u.hash;
|
|
2029
|
+
};
|
|
2030
|
+
const mergeHeaders = (a, b) => {
|
|
2031
|
+
const out = { ...a };
|
|
2032
|
+
for (const [k, v] of Object.entries(b ?? {})) {
|
|
2033
|
+
const key = Object.keys(out).find((x) => x.toLowerCase() === k.toLowerCase()) ?? k;
|
|
2034
|
+
const prev = out[key];
|
|
2035
|
+
out[key] = key.toLowerCase() === "set-cookie" && prev ? [...[prev].flat(), ...[v].flat()] : v;
|
|
2036
|
+
}
|
|
2037
|
+
return out;
|
|
2038
|
+
};
|
|
2039
|
+
async function loadTx(req) {
|
|
2040
|
+
const raw = readCookie(req.headers, cookieName);
|
|
2041
|
+
const dot = raw.indexOf(".");
|
|
2042
|
+
const sid = raw.slice(0, dot);
|
|
2043
|
+
const binding = raw.slice(dot + 1);
|
|
2044
|
+
if (dot < 0 || !UUID_RE.test(sid) || !B64URL_RE.test(binding)) return void 0;
|
|
2045
|
+
const tx = await store.get(sid);
|
|
2046
|
+
if (!tx || !safeEqualHex(tx.bindingHash, sha256hex(binding))) return void 0;
|
|
2047
|
+
if (tx.expiresAt <= Date.now()) {
|
|
2048
|
+
await store.delete(sid);
|
|
2049
|
+
return void 0;
|
|
2050
|
+
}
|
|
2051
|
+
return tx;
|
|
2052
|
+
}
|
|
2053
|
+
function qrLink(tx) {
|
|
2054
|
+
if (tx.mode !== "qr" || !tx.link) return void 0;
|
|
2055
|
+
return buildDeviceLink({
|
|
2056
|
+
deviceLinkBase: tx.link.base,
|
|
2057
|
+
deviceLinkType: "QR",
|
|
2058
|
+
sessionToken: tx.link.token,
|
|
2059
|
+
sessionSecret: tx.link.secret,
|
|
2060
|
+
sessionType: "auth",
|
|
2061
|
+
lang: tx.link.lang,
|
|
2062
|
+
receivedAt: tx.link.receivedAt,
|
|
2063
|
+
rpChallenge: tx.acsp.rpChallenge,
|
|
2064
|
+
relyingPartyName: tx.acsp.relyingPartyName,
|
|
2065
|
+
interactions: tx.acsp.interactions
|
|
1777
2066
|
});
|
|
1778
|
-
if (opts.display) p.set("display", opts.display);
|
|
1779
|
-
u.search = p.toString();
|
|
1780
|
-
return { url: u.toString(), state, nonce, codeVerifier };
|
|
1781
2067
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
2068
|
+
async function begin(req, mode, idCode, lang) {
|
|
2069
|
+
const level = opts.certificateLevel ? { certificateLevel: opts.certificateLevel } : {};
|
|
2070
|
+
let tx;
|
|
2071
|
+
let appLink;
|
|
2072
|
+
if (mode === "push") {
|
|
2073
|
+
const s = await client.auth.notificationByEtsi(idCode, interactions, level);
|
|
2074
|
+
tx = { sessionId: s.sessionId, mode, vc: s.vc, acsp: s.acsp };
|
|
2075
|
+
} else {
|
|
2076
|
+
const callbackUrl = mode === "web2app" ? withReturnHint(opts.callbackUrl, header(req.headers, "user-agent")) : void 0;
|
|
2077
|
+
const s = await client.auth.deviceLinkAnonymous(interactions, { ...level, callbackUrl });
|
|
2078
|
+
if (!s.deviceLinkBase || !s.sessionToken || !s.sessionSecret || !s.acsp) throw new Error("eID \u0445\u0430\u0440\u0438\u0443\u043D\u0434 device link \u0442\u0430\u043B\u0431\u0430\u0440 \u0434\u0443\u0442\u0443\u0443");
|
|
2079
|
+
tx = {
|
|
2080
|
+
sessionId: s.sessionId,
|
|
2081
|
+
mode,
|
|
2082
|
+
vc: s.vc,
|
|
2083
|
+
acsp: s.acsp,
|
|
2084
|
+
link: { base: s.deviceLinkBase, token: s.sessionToken, secret: s.sessionSecret, receivedAt: s.receivedAt, lang }
|
|
2085
|
+
};
|
|
2086
|
+
if (mode === "web2app") {
|
|
2087
|
+
appLink = buildDeviceLink({
|
|
2088
|
+
deviceLinkBase: s.deviceLinkBase,
|
|
2089
|
+
deviceLinkType: "Web2App",
|
|
2090
|
+
sessionToken: s.sessionToken,
|
|
2091
|
+
sessionSecret: s.sessionSecret,
|
|
2092
|
+
sessionType: "auth",
|
|
2093
|
+
lang,
|
|
2094
|
+
rpChallenge: s.acsp.rpChallenge,
|
|
2095
|
+
relyingPartyName: s.acsp.relyingPartyName,
|
|
2096
|
+
interactions: s.acsp.interactions,
|
|
2097
|
+
initialCallbackUrl: s.acsp.initialCallbackUrl
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
1796
2100
|
}
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
2101
|
+
const binding = randomBytes2(32).toString("base64url");
|
|
2102
|
+
const full = { ...tx, bindingHash: sha256hex(binding), expiresAt: Date.now() + ttl };
|
|
2103
|
+
await store.set(full.sessionId, full, ttl);
|
|
2104
|
+
const setCookie = `${cookieName}=${full.sessionId}.${binding}; ${cookieAttrs(Math.ceil(ttl / 1e3))}`;
|
|
2105
|
+
return { tx: full, setCookie, appLink };
|
|
2106
|
+
}
|
|
2107
|
+
async function start(req) {
|
|
2108
|
+
const url = new URL(req.url, "http://x");
|
|
2109
|
+
const site = header(req.headers, "sec-fetch-site");
|
|
2110
|
+
if (site && site !== "same-origin" && site !== "same-site" && site !== "none") return json(403, { error: "cross_site" });
|
|
2111
|
+
let body = {};
|
|
2112
|
+
if (req.method === "GET") {
|
|
2113
|
+
if (url.searchParams.get("mode") !== "web2app") return json(405, { error: "method_not_allowed" });
|
|
2114
|
+
body = { mode: "web2app", lang: url.searchParams.get("lang") ?? void 0 };
|
|
2115
|
+
} else if (req.method === "POST") {
|
|
2116
|
+
if (!header(req.headers, "content-type").toLowerCase().startsWith("application/json")) {
|
|
2117
|
+
return json(415, { error: "json_required" });
|
|
2118
|
+
}
|
|
2119
|
+
try {
|
|
2120
|
+
const b = typeof req.body === "string" ? JSON.parse(req.body) : req.body;
|
|
2121
|
+
if (b && typeof b === "object") body = b;
|
|
2122
|
+
} catch {
|
|
2123
|
+
return json(400, { error: "invalid_body" });
|
|
2124
|
+
}
|
|
2125
|
+
} else {
|
|
2126
|
+
return json(405, { error: "method_not_allowed" });
|
|
1801
2127
|
}
|
|
1802
|
-
const
|
|
1803
|
-
if (
|
|
1804
|
-
const
|
|
1805
|
-
const
|
|
1806
|
-
return {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
headers: {
|
|
1815
|
-
Authorization: "Basic " + basic,
|
|
1816
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1817
|
-
Accept: "application/json"
|
|
1818
|
-
},
|
|
1819
|
-
body: new URLSearchParams({
|
|
1820
|
-
grant_type: "authorization_code",
|
|
1821
|
-
code,
|
|
1822
|
-
redirect_uri: this.cfg.redirectUri,
|
|
1823
|
-
code_verifier: codeVerifier
|
|
1824
|
-
}).toString()
|
|
1825
|
-
});
|
|
1826
|
-
let j = {};
|
|
2128
|
+
const mode = body.mode;
|
|
2129
|
+
if (mode !== "qr" && mode !== "push" && mode !== "web2app") return json(400, { error: "invalid_mode" });
|
|
2130
|
+
const lang = body.lang === "en" ? "eng" : "mon";
|
|
2131
|
+
const ctx = { request: req, mode };
|
|
2132
|
+
if (mode === "web2app" && !opts.callbackUrl) return json(400, { error: "web2app_not_configured" });
|
|
2133
|
+
const idCode = typeof body.idCode === "string" ? body.idCode.replace(/\s+/g, "").toUpperCase() : "";
|
|
2134
|
+
if (mode === "push" && !ID_RE.test(idCode)) return json(400, { error: "invalid_id" });
|
|
2135
|
+
const limited = () => json(429, { error: "rate_limited" }, { "Retry-After": "60" });
|
|
2136
|
+
const ip = req.ip || (opts.trustProxy ? forwardedClientIp(req.headers, opts.trustProxy) : void 0);
|
|
2137
|
+
if (ip && !await rateLimit(`start:${ip}`)) return limited();
|
|
2138
|
+
if (mode === "push" && !await rateLimit(`push:${idCode}`)) return limited();
|
|
2139
|
+
let r;
|
|
1827
2140
|
try {
|
|
1828
|
-
|
|
1829
|
-
} catch {
|
|
2141
|
+
r = await begin(req, mode, idCode, lang);
|
|
2142
|
+
} catch (e) {
|
|
2143
|
+
onError(e, ctx);
|
|
2144
|
+
const status2 = e instanceof ApiError && e.status === 429 ? 429 : 502;
|
|
2145
|
+
return json(status2, { error: status2 === 429 ? "rate_limited" : "eid_unavailable" });
|
|
1830
2146
|
}
|
|
1831
|
-
if (
|
|
1832
|
-
|
|
1833
|
-
throw new EidOidcError(
|
|
1834
|
-
e,
|
|
1835
|
-
`OIDC token ${res.status}: ${e}`,
|
|
1836
|
-
typeof j.error_description === "string" ? j.error_description : void 0
|
|
1837
|
-
);
|
|
2147
|
+
if (mode === "web2app") {
|
|
2148
|
+
return { status: 302, headers: { Location: r.appLink, "Cache-Control": "no-store", "Referrer-Policy": "no-referrer", "Set-Cookie": r.setCookie }, body: "" };
|
|
1838
2149
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
2150
|
+
return json(
|
|
2151
|
+
200,
|
|
2152
|
+
{ mode, vc: r.tx.vc, ...mode === "qr" ? { qr: qrLink(r.tx) } : {}, expiresIn: Math.ceil(ttl / 1e3) },
|
|
2153
|
+
{ "Set-Cookie": r.setCookie }
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
async function finish(req, tx, r, flow, verifier) {
|
|
2157
|
+
const v = client.validator.validateAuth(r, tx.acsp, { expectedFlowType: flow, userChallengeVerifier: verifier });
|
|
2158
|
+
if (!await store.delete(tx.sessionId)) return void 0;
|
|
2159
|
+
const out = await opts.onSuccess(toIdentity(tx.sessionId, r, v, flow), { request: req, mode: tx.mode }) ?? {};
|
|
2160
|
+
return out;
|
|
2161
|
+
}
|
|
2162
|
+
async function status(req) {
|
|
2163
|
+
if (req.method !== "GET") return json(405, { error: "method_not_allowed" });
|
|
2164
|
+
const tx = await loadTx(req);
|
|
2165
|
+
if (!tx) return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2166
|
+
const ctx = { request: req, mode: tx.mode };
|
|
2167
|
+
let r;
|
|
2168
|
+
try {
|
|
2169
|
+
r = await client.session.poll(tx.sessionId, pollMs);
|
|
2170
|
+
} catch (e) {
|
|
2171
|
+
if (e instanceof ApiError && e.status === 404) {
|
|
2172
|
+
await store.delete(tx.sessionId);
|
|
2173
|
+
return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2174
|
+
}
|
|
2175
|
+
onError(e, ctx);
|
|
2176
|
+
return json(502, { error: "eid_unavailable" });
|
|
1841
2177
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
const alias = d.mtls_endpoint_aliases?.token_endpoint;
|
|
1848
|
-
if (alias === void 0) return d.token_endpoint;
|
|
1849
|
-
if (typeof alias !== "string" || !alias.startsWith("https://")) {
|
|
1850
|
-
throw new EidOidcError("discovery_failed", "OIDC discovery: mtls_endpoint_aliases.token_endpoint \u0431\u0443\u0440\u0443\u0443");
|
|
2178
|
+
const expiresIn = Math.max(0, Math.ceil((tx.expiresAt - Date.now()) / 1e3));
|
|
2179
|
+
if (r.state !== "COMPLETE") return json(200, { state: "PENDING", qr: qrLink(tx), vc: tx.vc, expiresIn });
|
|
2180
|
+
if (r.endResult !== "OK") {
|
|
2181
|
+
await store.delete(tx.sessionId);
|
|
2182
|
+
return json(200, { state: "FAILED", error: r.endResult ?? "UNKNOWN" }, { "Set-Cookie": clearCookie });
|
|
1851
2183
|
}
|
|
1852
|
-
return
|
|
1853
|
-
}
|
|
1854
|
-
/**
|
|
1855
|
-
* id_token шалгана: JWKS (`kid`, cache; танихгүй kid → нэг удаа дахин татна) ES256 гарын үсэг, `iss`, `aud`
|
|
1856
|
-
* (= clientId; олон aud бол `azp`), `exp`/`iat` (±60 сек), `nonce`. Амжилтгүй бол `invalid_id_token`.
|
|
1857
|
-
*/
|
|
1858
|
-
async verifyIdToken(idToken, opts) {
|
|
1859
|
-
const bad = (why) => new EidOidcError("invalid_id_token", `id_token: ${why}`);
|
|
1860
|
-
const parts = idToken.split(".");
|
|
1861
|
-
if (parts.length !== 3) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1862
|
-
const header = decodeJsonPart(parts[0]);
|
|
1863
|
-
const payload = decodeJsonPart(parts[1]);
|
|
1864
|
-
if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1865
|
-
if (header.alg !== OIDC_EXPECT.idTokenAlg) throw bad("alg ES256 \u0431\u0438\u0448");
|
|
1866
|
-
const kid = typeof header.kid === "string" ? header.kid : "";
|
|
1867
|
-
if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
|
|
1868
|
-
const pick = (ks) => ks.find(
|
|
1869
|
-
(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")
|
|
1870
|
-
);
|
|
1871
|
-
const jwk = pick(await this.keys(false)) ?? pick(await this.keys(true));
|
|
1872
|
-
if (!jwk || !jwk.x || !jwk.y) throw bad(`\u0442\u0430\u043D\u0438\u0445\u0433\u04AF\u0439 kid ${kid}`);
|
|
1873
|
-
const sig = Buffer.from(parts[2], "base64url");
|
|
1874
|
-
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");
|
|
1875
|
-
let ok = false;
|
|
2184
|
+
if (tx.mode === "web2app") return json(200, { state: "WAIT_CALLBACK" });
|
|
1876
2185
|
try {
|
|
1877
|
-
const
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
);
|
|
1884
|
-
|
|
1885
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
1886
|
-
key,
|
|
1887
|
-
sig,
|
|
1888
|
-
new TextEncoder().encode(parts[0] + "." + parts[1])
|
|
1889
|
-
);
|
|
1890
|
-
} catch {
|
|
1891
|
-
ok = false;
|
|
2186
|
+
const out = await finish(req, tx, r, tx.mode === "qr" ? "QR" : "Notification");
|
|
2187
|
+
if (!out) return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2188
|
+
return json(200, { state: "OK", redirect: out.redirect, data: out.data }, mergeHeaders({ "Set-Cookie": clearCookie }, out.headers));
|
|
2189
|
+
} catch (e) {
|
|
2190
|
+
onError(e, ctx);
|
|
2191
|
+
if (!(e instanceof ValidationError || e instanceof SessionFailedError)) return json(500, { error: "internal" });
|
|
2192
|
+
await store.delete(tx.sessionId);
|
|
2193
|
+
return json(200, { state: "FAILED", error: "VALIDATION" }, { "Set-Cookie": clearCookie });
|
|
1892
2194
|
}
|
|
1893
|
-
if (!ok) throw bad("\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
1894
|
-
const c = payload;
|
|
1895
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1896
|
-
if (c.iss !== this.issuer) throw bad("iss \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1897
|
-
const aud = Array.isArray(c.aud) ? c.aud : [c.aud];
|
|
1898
|
-
if (!aud.includes(this.cfg.clientId)) throw bad("aud \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1899
|
-
if (aud.length > 1 && c.azp !== this.cfg.clientId) throw bad("azp \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1900
|
-
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");
|
|
1901
|
-
if (typeof c.iat !== "number" || c.iat > now + CLOCK_SKEW_S) throw bad("iat \u0438\u0440\u044D\u044D\u0434\u04AF\u0439\u0434");
|
|
1902
|
-
if (typeof c.nonce !== "string" || !opts.nonce || !timingSafeEqualStr(c.nonce, opts.nonce)) throw bad("nonce \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1903
|
-
if (typeof c.sub !== "string" || c.sub === "") throw bad("sub \u0430\u043B\u0433\u0430");
|
|
1904
|
-
return c;
|
|
1905
|
-
}
|
|
1906
|
-
keys(force) {
|
|
1907
|
-
const c = this.jwks;
|
|
1908
|
-
if (!force && c && Date.now() - c.at < CACHE_MS) return c.keys;
|
|
1909
|
-
const keys = this.discover().then(async (d) => {
|
|
1910
|
-
const j = await this.getJson(d.jwks_uri);
|
|
1911
|
-
return Array.isArray(j?.keys) ? j.keys : [];
|
|
1912
|
-
});
|
|
1913
|
-
this.jwks = { at: Date.now(), keys };
|
|
1914
|
-
keys.catch(() => {
|
|
1915
|
-
if (this.jwks?.keys === keys) this.jwks = void 0;
|
|
1916
|
-
});
|
|
1917
|
-
return keys;
|
|
1918
2195
|
}
|
|
1919
|
-
async
|
|
1920
|
-
|
|
1921
|
-
|
|
2196
|
+
async function callback(req) {
|
|
2197
|
+
if (req.method !== "GET") return json(405, { error: "method_not_allowed" });
|
|
2198
|
+
const q = new URL(req.url, "http://x").searchParams;
|
|
2199
|
+
const sid = q.get("sessionId") ?? "";
|
|
2200
|
+
const verifier = q.get("userChallengeVerifier") ?? "";
|
|
2201
|
+
const fail2 = (code) => redirect(failTo(code), { "Set-Cookie": clearCookie });
|
|
2202
|
+
const tx = await loadTx(req);
|
|
2203
|
+
if (!tx || tx.sessionId !== sid || tx.mode !== "web2app") return fail2(tx ? "unbound" : "expired");
|
|
2204
|
+
if (!verifier) return fail2("unbound");
|
|
2205
|
+
const ctx = { request: req, mode: tx.mode };
|
|
2206
|
+
let r;
|
|
1922
2207
|
try {
|
|
1923
|
-
|
|
1924
|
-
} catch {
|
|
1925
|
-
|
|
2208
|
+
r = await client.session.poll(tx.sessionId, 1e4);
|
|
2209
|
+
} catch (e) {
|
|
2210
|
+
onError(e, ctx);
|
|
2211
|
+
return fail2("eid_unavailable");
|
|
2212
|
+
}
|
|
2213
|
+
if (r.state !== "COMPLETE") return fail2("not_complete");
|
|
2214
|
+
if (r.endResult !== "OK") {
|
|
2215
|
+
await store.delete(tx.sessionId);
|
|
2216
|
+
return fail2(r.endResult ?? "UNKNOWN");
|
|
1926
2217
|
}
|
|
1927
|
-
}
|
|
1928
|
-
async fetch(url, init) {
|
|
1929
|
-
const f = this.cfg.fetchImpl ?? fetch;
|
|
1930
|
-
const req = {
|
|
1931
|
-
...init,
|
|
1932
|
-
redirect: "error",
|
|
1933
|
-
signal: AbortSignal.timeout(this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
1934
|
-
};
|
|
1935
|
-
if (this.cfg.dispatcher) req.dispatcher = this.cfg.dispatcher;
|
|
1936
2218
|
try {
|
|
1937
|
-
|
|
2219
|
+
const out = await finish(req, tx, r, "Web2App", verifier);
|
|
2220
|
+
if (!out) return fail2("expired");
|
|
2221
|
+
return redirect(out.redirect ?? successRedirect, mergeHeaders({ "Set-Cookie": clearCookie }, out.headers));
|
|
1938
2222
|
} catch (e) {
|
|
1939
|
-
|
|
1940
|
-
if (
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
throw new NetworkError(`OIDC \u0441\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${url}): ${err.message}`);
|
|
2223
|
+
onError(e, ctx);
|
|
2224
|
+
if (!(e instanceof ValidationError || e instanceof SessionFailedError)) return fail2("internal");
|
|
2225
|
+
await store.delete(tx.sessionId);
|
|
2226
|
+
return fail2("VALIDATION");
|
|
1944
2227
|
}
|
|
1945
2228
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2229
|
+
async function handleRequest(req) {
|
|
2230
|
+
const seg = new URL(req.url, "http://x").pathname.replace(/\/+$/, "").split("/").pop();
|
|
2231
|
+
try {
|
|
2232
|
+
if (seg === "start") return await start(req);
|
|
2233
|
+
if (seg === "status") return await status(req);
|
|
2234
|
+
if (seg === "callback") return await callback(req);
|
|
2235
|
+
} catch (e) {
|
|
2236
|
+
onError(e, { request: req, mode: "qr" });
|
|
2237
|
+
if (e instanceof AuthenticationError || e instanceof ForbiddenError) return json(502, { error: "eid_unavailable" });
|
|
2238
|
+
return json(500, { error: "internal" });
|
|
2239
|
+
}
|
|
2240
|
+
return json(404, { error: "not_found" });
|
|
2241
|
+
}
|
|
2242
|
+
let warnedNoIp = false;
|
|
2243
|
+
async function handle(req) {
|
|
2244
|
+
const headers = {};
|
|
2245
|
+
req.headers.forEach((v, k) => headers[k] = v);
|
|
2246
|
+
const body = req.method === "POST" ? await req.text() : void 0;
|
|
2247
|
+
const ip = opts.getClientIp?.(req) || void 0;
|
|
2248
|
+
if (!ip && !opts.getClientIp && !opts.trustProxy && !warnedNoIp) {
|
|
2249
|
+
warnedNoIp = true;
|
|
2250
|
+
onError(new Error("eid-login: handle(Request)-\u0434 \u043A\u043B\u0438\u0435\u043D\u0442 IP \u0430\u043B\u0433\u0430 \u2014 `start:<ip>` \u0445\u044F\u0437\u0433\u0430\u0430\u0440 \u0430\u0436\u0438\u043B\u043B\u0430\u0445\u0433\u04AF\u0439; getClientIp \u044D\u0441\u0432\u044D\u043B trustProxy \u04E9\u0433"), {
|
|
2251
|
+
request: { method: req.method, url: req.url, headers },
|
|
2252
|
+
mode: "qr"
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
const r = await handleRequest({ method: req.method, url: req.url, headers, body, ip });
|
|
2256
|
+
const h = new Headers();
|
|
2257
|
+
for (const [k, v] of Object.entries(r.headers)) for (const x of [v].flat()) h.append(k, x);
|
|
2258
|
+
return new Response(r.status === 302 || r.status === 303 || r.body === "" ? null : r.body, { status: r.status, headers: h });
|
|
1968
2259
|
}
|
|
1969
|
-
}
|
|
1970
|
-
function callbackParams(input) {
|
|
1971
|
-
if (input instanceof URLSearchParams) return input;
|
|
1972
|
-
if (input instanceof URL) return input.searchParams;
|
|
1973
|
-
const s = input.trim();
|
|
1974
|
-
if (/^https?:\/\//i.test(s)) return new URL(s).searchParams;
|
|
1975
|
-
const i = s.indexOf("?");
|
|
1976
|
-
return new URLSearchParams(i >= 0 ? s.slice(i + 1) : s);
|
|
2260
|
+
return { handleRequest, handle };
|
|
1977
2261
|
}
|
|
1978
2262
|
export {
|
|
1979
2263
|
ApiError,
|
|
@@ -1983,14 +2267,10 @@ export {
|
|
|
1983
2267
|
DEFAULT_BASE_URL,
|
|
1984
2268
|
DEFAULT_PDF_MAX_BYTES,
|
|
1985
2269
|
DEFAULT_PDF_TIMEOUT_MS,
|
|
2270
|
+
DEFAULT_SIGN_SESSION_TTL_MS,
|
|
1986
2271
|
DeviceLinkError,
|
|
1987
|
-
EID_ACR_ADVANCED,
|
|
1988
|
-
EID_ACR_QUALIFIED,
|
|
1989
|
-
EID_OIDC_ISSUER,
|
|
1990
2272
|
EidClient,
|
|
1991
2273
|
EidError,
|
|
1992
|
-
EidOidcClient,
|
|
1993
|
-
EidOidcError,
|
|
1994
2274
|
ForbiddenError,
|
|
1995
2275
|
Http,
|
|
1996
2276
|
NetworkError,
|
|
@@ -2009,23 +2289,28 @@ export {
|
|
|
2009
2289
|
acspV2Digest,
|
|
2010
2290
|
buildAcspV2Payload,
|
|
2011
2291
|
buildDeviceLink,
|
|
2292
|
+
createEidLogin,
|
|
2012
2293
|
deviceLink,
|
|
2294
|
+
deviceLinkInput,
|
|
2295
|
+
forwardedClientIp,
|
|
2013
2296
|
interactionsToBase64,
|
|
2014
2297
|
isMobileUserAgent,
|
|
2298
|
+
memoryLoginStore,
|
|
2015
2299
|
openOrShowQR,
|
|
2016
2300
|
parseRetryAfter,
|
|
2017
2301
|
parseSessionResult,
|
|
2018
2302
|
parseSubject,
|
|
2019
2303
|
personMatchesCertificate,
|
|
2020
|
-
pkceChallenge,
|
|
2021
2304
|
qrDeviceLinkTicker,
|
|
2022
2305
|
randomChallenge,
|
|
2023
2306
|
resolveBaseUrl,
|
|
2307
|
+
sessionExpiry,
|
|
2024
2308
|
sha256Base64,
|
|
2025
2309
|
tokenAuthDigest,
|
|
2026
2310
|
tokenLimitOf,
|
|
2027
2311
|
userChallengeOf,
|
|
2028
2312
|
verifyPayloadSignature,
|
|
2029
2313
|
verifyPrehashedSignature,
|
|
2030
|
-
verifyTokenCertificates
|
|
2314
|
+
verifyTokenCertificates,
|
|
2315
|
+
withReturnHint
|
|
2031
2316
|
};
|