@gerege-systems/eid-mongolia-sdk 0.4.1 → 0.6.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 +76 -30
- package/dist/{browser-BS8JwSQk.d.cts → browser-PhjcbuAN.d.cts} +16 -2
- package/dist/{browser-BS8JwSQk.d.ts → browser-PhjcbuAN.d.ts} +16 -2
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/index.cjs +92 -2
- package/dist/index.d.cts +99 -4
- package/dist/index.d.ts +99 -4
- package/dist/index.js +89 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(authentication) ба хууль ёсны цахим гарын үсэг (qualified signature) хийлгэх Node.js/TypeScript сан.
|
|
5
5
|
RP-API v3, Smart-ID нийцтэй. Бүрэн баримт: <https://developer.eidmongolia.mn/>.
|
|
6
6
|
|
|
7
|
-
> ⚠️ Зөвхөн **backend**-д ажиллана (`@gerege-systems/eid-mongolia-sdk/browser`-ийн device-link туслахаас бусад).
|
|
7
|
+
> ⚠️ Зөвхөн **backend**-д ажиллана (`@gerege-systems/eid-mongolia-sdk/browser`-ийн legacy device-link туслахаас бусад).
|
|
8
8
|
> API secret (`rp_sk_…`) браузер/гар утсанд хэзээ ч задлахгүй.
|
|
9
9
|
|
|
10
10
|
## Суулгах
|
|
@@ -67,41 +67,86 @@ const sig = eid.validator.validateSign(result, digest); // digest-ийн эср
|
|
|
67
67
|
console.log(sig.signatureValueB64, sig.subject);
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
## 4.
|
|
70
|
+
## 4. QR ба ижил төхөөрөмж — device link v3
|
|
71
71
|
|
|
72
|
-
Иргэн
|
|
73
|
-
|
|
72
|
+
Иргэн өөр төхөөрөмжийн дэлгэц дээрх **QR**-ийг утсаараа уншина, эсвэл RP-ийн сайт/аппыг eID апптай **ижил
|
|
73
|
+
утсан** дээр нээсэн бол товчоор аппыг нээнэ (**Web2App** — утасны браузер, **App2App** — RP-ийн апп). Холбоосыг
|
|
74
|
+
ЗӨВХӨН RP backend `buildDeviceLink`-ээр угсарна (Smart-ID RP-API v3 «dynamic link»):
|
|
74
75
|
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
```
|
|
77
|
+
QR: {deviceLinkBase}?deviceLinkType=QR&elapsedSeconds={E}&sessionToken={T}&sessionType=auth&version=1.0&lang=mon&authCode={A}
|
|
78
|
+
Web2App: {deviceLinkBase}?deviceLinkType=Web2App&sessionToken={T}&sessionType=auth&version=1.0&lang=mon&authCode={A}
|
|
79
|
+
```
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
- `authCode = BASE64URL(HMAC-SHA256(sessionSecret, …))` — холбоосын төрөл, session, RP-ийн challenge, callback-ийг
|
|
82
|
+
хамгаална. Сервер төрлийг session-д бэхэлж, гарын үсгийн `flowType` = тэр төрөл (хуурамчлах боломжгүй).
|
|
83
|
+
- **QR секунд тутам шинэчлэгдэнэ** (`elapsedSeconds = floor(now − receivedAt)`); ~20 секундээс хуучин QR-ийг
|
|
84
|
+
сервер татгалзана → SMS/мессенжерээр дамжуулсан холбоос ажиллахгүй.
|
|
85
|
+
- `sessionToken`, **`sessionSecret`-ийг зөвхөн backend-д** хадгална — браузер/апп руу ХЭЗЭЭ Ч бүү гарга.
|
|
86
|
+
Браузер QR-ийн бэлэн холбоосыг backend endpoint-оос секунд тутам авна.
|
|
87
|
+
- QR ба товч хоёуланг нэг хуудсанд харуулах бол НЭГ session (`callbackUrl`-тай) эхлүүлж хоёр холбоос угсарна.
|
|
88
|
+
Web2App/App2App нь `callbackUrl`-тай session-д л хүчинтэй.
|
|
89
|
+
- Гэрээ: [developer.eidmongolia.mn](https://developer.eidmongolia.mn/) ба ca-eidmongolia-mn `docs/DEVICE_LINK_V3.md`.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { buildDeviceLink, type DeviceLinkSession } from "@gerege-systems/eid-mongolia-sdk";
|
|
93
|
+
|
|
94
|
+
// backend — session эхлүүлээд бүх хариуг СЕРВЕРТ хадгална (sessionToken/sessionSecret/receivedAt/acsp)
|
|
95
|
+
const s = await eid.auth.deviceLinkAnonymous(
|
|
96
|
+
[{ type: "displayTextAndPIN", displayText60: "Хаан Банк-д нэвтрэх" }],
|
|
97
|
+
{ callbackUrl: "https://bank.example.mn/eid/callback?state=…" }, // товч харуулах бол
|
|
98
|
+
);
|
|
99
|
+
store.put(s.sessionId, s);
|
|
100
|
+
res.json({ sessionId: s.sessionId, vc: s.vc }); // браузерт ЗӨВХӨН эдгээр
|
|
101
|
+
|
|
102
|
+
const link = (s: DeviceLinkSession, type: "QR" | "Web2App" | "App2App") =>
|
|
103
|
+
buildDeviceLink({
|
|
104
|
+
deviceLinkBase: s.deviceLinkBase!,
|
|
105
|
+
deviceLinkType: type,
|
|
106
|
+
sessionToken: s.sessionToken!,
|
|
107
|
+
sessionSecret: s.sessionSecret!,
|
|
108
|
+
sessionType: "auth",
|
|
109
|
+
lang: "mon",
|
|
110
|
+
receivedAt: s.receivedAt, // SDK хариу хүлээн авсан мөчийг тэмдэглэнэ
|
|
111
|
+
rpChallenge: s.acsp!.rpChallenge,
|
|
112
|
+
relyingPartyName: s.acsp!.relyingPartyName,
|
|
113
|
+
interactions: s.acsp!.interactions,
|
|
114
|
+
initialCallbackUrl: s.acsp!.initialCallbackUrl, // QR-д authCode-д орохгүй
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// GET /eid/qr?sessionId=… → браузер 1 секунд тутам дуудаж QR-ийг дахин зурна (Cache-Control: no-store)
|
|
118
|
+
app.get("/eid/qr", (req, res) => {
|
|
119
|
+
const s = store.get(String(req.query.sessionId)); // өөрийн browser session-д хамаарахыг шалга
|
|
120
|
+
if (!s) return res.sendStatus(404);
|
|
121
|
+
res.set("Cache-Control", "no-store").json({ link: link(s, "QR") });
|
|
122
|
+
});
|
|
123
|
+
// гар утсанд: товчны href = link(s, "Web2App") (нэг удаа, шинэчлэхгүй)
|
|
83
124
|
```
|
|
84
125
|
|
|
126
|
+
SSE/WebSocket-оор түлхэх бол `qrDeviceLinkTicker(input, (link) => send(link))` — шууд нэг, дараа нь секунд тутам
|
|
127
|
+
холбоос гаргаж, буцаасан функцээр зогсооно (session дуусах/клиент салахад заавал).
|
|
128
|
+
|
|
129
|
+
**Харуулсан төрлөө заавал тулга (relay хаалт).** `expectedFlowType` = RP-ийн харуулсан холбоосын төрөл:
|
|
130
|
+
|
|
85
131
|
```ts
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
94
|
-
//
|
|
132
|
+
// QR хуудас → poll-оор дууссан үр дүн
|
|
133
|
+
eid.validator.validateAuth(result, s.acsp, { expectedFlowType: "QR" });
|
|
134
|
+
|
|
135
|
+
// Web2App/App2App товч → апп callback URL-д `userChallengeVerifier` нэмж буцна
|
|
136
|
+
eid.validator.validateAuth(result, s.acsp, {
|
|
137
|
+
expectedFlowType: "Web2App", // RP-ийн апп дахь товч бол "App2App"
|
|
138
|
+
userChallengeVerifier: callbackQuery.get("userChallengeVerifier") ?? undefined,
|
|
139
|
+
});
|
|
140
|
+
// push (notificationBy*) → "Notification"
|
|
95
141
|
```
|
|
96
142
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
таахгүй. Апп руу custom scheme-ээр шууд бүү холбо — зөвхөн `deviceLink()`-ийн https холбоос.
|
|
143
|
+
QR ба товч хоёуланг харуулсан хуудсанд: callback-аар ирсэн үр дүнг `Web2App`/`App2App`-аар, poll-оор ирснийг
|
|
144
|
+
`QR`-аар шалга (товчоор нээгдсэн session зөвхөн callback-аар дуусна).
|
|
145
|
+
|
|
146
|
+
> ⚠️ **Шилжилт.** eID Mongolia 2.2.3 (build 59)-өөс өмнөх апп v3 холбоосыг танихгүй. RP-ийн v3 рүү шилжилтийг
|
|
147
|
+
> тохиргооны унтраалгаар (анхдагч legacy) хийж, build 59 store-д гарч `min_version=59` тавигдах мөчид асаана.
|
|
148
|
+
> Legacy `deviceLink(session)` (`{deviceLinkBase}?sessionId=…&vc=…`) болон `/browser` entry-ийн
|
|
149
|
+
> `openOrShowQR` **deprecated** — тэр мөчөөс сервер статик холбоосыг хүлээн авахгүй.
|
|
105
150
|
|
|
106
151
|
## Аюулгүй байдал — яагаад `validator` заавал
|
|
107
152
|
|
|
@@ -113,7 +158,8 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
113
158
|
(Root → National Issuing CA → Gerege Issuing CA → eID Mongolia Issuing CA → иргэн)
|
|
114
159
|
3. Гэрчилгээ хүчинтэй хугацаанд + шаардсан `certificateLevel` (default `QUALIFIED`)
|
|
115
160
|
4. Гарын үсгийг иргэний public key-ээр шалгана:
|
|
116
|
-
- **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-ийг мөн
|
|
161
|
+
- **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
|
+
`expectedFlowType` өгвөл `flowType` эхлүүлсэн урсгалтай ЯГ таарах, Web2App/App2App-д `userChallengeVerifier` заавал
|
|
117
163
|
- **sign** → RP-ийн өгсөн digest дээр
|
|
118
164
|
|
|
119
165
|
Аль нэг алхам бүтэлгүйтвэл `ValidationError` шиднэ — хариунд **итгэхгүй**.
|
|
@@ -128,7 +174,7 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
128
174
|
| `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
|
|
129
175
|
| `ApiError` | бусад HTTP алдаа (`.status`, `.body`) |
|
|
130
176
|
| `NetworkError` | timeout/сүлжээ |
|
|
131
|
-
| `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase`
|
|
177
|
+
| `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
|
|
132
178
|
|
|
133
179
|
## mTLS (eIDAS qualified орчин)
|
|
134
180
|
|
|
@@ -73,14 +73,17 @@ interface NotificationSession {
|
|
|
73
73
|
/** Auth session-д validator-т өгөх контекст (sign session-д undefined). */
|
|
74
74
|
acsp?: AcspContext;
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Device-link (QR/Web2App/App2App) session. Иргэн QR уншиж эсвэл товч дарж нэгдэнэ.
|
|
78
|
+
* `sessionToken`, `sessionSecret`-ийг ЗӨВХӨН backend-д хадгална — браузер руу бүү гарга.
|
|
79
|
+
*/
|
|
77
80
|
interface DeviceLinkSession {
|
|
78
81
|
sessionId: string;
|
|
79
82
|
sessionToken: string | null;
|
|
80
83
|
sessionSecret: string | null;
|
|
81
84
|
/**
|
|
82
85
|
* QR/App2App/Web2App холбоосын суурь URL (ж: `https://ca.eidmongolia.mn/dl`). Холбоосыг
|
|
83
|
-
* өөрөө угсрахгүй — {@link
|
|
86
|
+
* өөрөө угсрахгүй — backend-д {@link buildDeviceLink}-ээр (v3).
|
|
84
87
|
*/
|
|
85
88
|
deviceLinkBase: string | null;
|
|
86
89
|
/**
|
|
@@ -90,6 +93,12 @@ interface DeviceLinkSession {
|
|
|
90
93
|
vc: string | null;
|
|
91
94
|
rpChallenge: string;
|
|
92
95
|
acsp?: AcspContext;
|
|
96
|
+
/**
|
|
97
|
+
* RP backend энэ хариуг хүлээн авсан мөч (ms epoch, SDK `Date.now()`-оор тэмдэглэнэ) — v3 QR-ийн
|
|
98
|
+
* `elapsedSeconds`-ийн эх ({@link buildDeviceLink} `receivedAt`). sessionToken/sessionSecret-тэй хамт
|
|
99
|
+
* ЗӨВХӨН backend-д хадгална.
|
|
100
|
+
*/
|
|
101
|
+
receivedAt: number;
|
|
93
102
|
}
|
|
94
103
|
/** Session poll-ийн нэгтгэсэн үр дүн (validator-т орох түүхий материал). */
|
|
95
104
|
interface SessionResult {
|
|
@@ -145,6 +154,11 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
|
|
|
145
154
|
* Device-link session-ийн холбоос: `${deviceLinkBase}?sessionId=<uuid>&vc=<код>`.
|
|
146
155
|
* QR-д эсвэл «eID аппаар нээх» товчинд тавина.
|
|
147
156
|
*
|
|
157
|
+
* @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
|
|
158
|
+
* eID Mongolia 2.2.3 (build 59) гармагц сервер унтраана (`EID_DEVICE_LINK_LEGACY=false`). Шинэ код
|
|
159
|
+
* backend-д {@link buildDeviceLink} (v3) ашиглана; build 59-өөс өмнөх апп v3 холбоосыг танихгүй тул
|
|
160
|
+
* RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
|
|
161
|
+
*
|
|
148
162
|
* @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
|
|
149
163
|
*/
|
|
150
164
|
declare function deviceLink(session: DeviceLinkInput): string;
|
|
@@ -73,14 +73,17 @@ interface NotificationSession {
|
|
|
73
73
|
/** Auth session-д validator-т өгөх контекст (sign session-д undefined). */
|
|
74
74
|
acsp?: AcspContext;
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Device-link (QR/Web2App/App2App) session. Иргэн QR уншиж эсвэл товч дарж нэгдэнэ.
|
|
78
|
+
* `sessionToken`, `sessionSecret`-ийг ЗӨВХӨН backend-д хадгална — браузер руу бүү гарга.
|
|
79
|
+
*/
|
|
77
80
|
interface DeviceLinkSession {
|
|
78
81
|
sessionId: string;
|
|
79
82
|
sessionToken: string | null;
|
|
80
83
|
sessionSecret: string | null;
|
|
81
84
|
/**
|
|
82
85
|
* QR/App2App/Web2App холбоосын суурь URL (ж: `https://ca.eidmongolia.mn/dl`). Холбоосыг
|
|
83
|
-
* өөрөө угсрахгүй — {@link
|
|
86
|
+
* өөрөө угсрахгүй — backend-д {@link buildDeviceLink}-ээр (v3).
|
|
84
87
|
*/
|
|
85
88
|
deviceLinkBase: string | null;
|
|
86
89
|
/**
|
|
@@ -90,6 +93,12 @@ interface DeviceLinkSession {
|
|
|
90
93
|
vc: string | null;
|
|
91
94
|
rpChallenge: string;
|
|
92
95
|
acsp?: AcspContext;
|
|
96
|
+
/**
|
|
97
|
+
* RP backend энэ хариуг хүлээн авсан мөч (ms epoch, SDK `Date.now()`-оор тэмдэглэнэ) — v3 QR-ийн
|
|
98
|
+
* `elapsedSeconds`-ийн эх ({@link buildDeviceLink} `receivedAt`). sessionToken/sessionSecret-тэй хамт
|
|
99
|
+
* ЗӨВХӨН backend-д хадгална.
|
|
100
|
+
*/
|
|
101
|
+
receivedAt: number;
|
|
93
102
|
}
|
|
94
103
|
/** Session poll-ийн нэгтгэсэн үр дүн (validator-т орох түүхий материал). */
|
|
95
104
|
interface SessionResult {
|
|
@@ -145,6 +154,11 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
|
|
|
145
154
|
* Device-link session-ийн холбоос: `${deviceLinkBase}?sessionId=<uuid>&vc=<код>`.
|
|
146
155
|
* QR-д эсвэл «eID аппаар нээх» товчинд тавина.
|
|
147
156
|
*
|
|
157
|
+
* @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
|
|
158
|
+
* eID Mongolia 2.2.3 (build 59) гармагц сервер унтраана (`EID_DEVICE_LINK_LEGACY=false`). Шинэ код
|
|
159
|
+
* backend-д {@link buildDeviceLink} (v3) ашиглана; build 59-өөс өмнөх апп v3 холбоосыг танихгүй тул
|
|
160
|
+
* RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
|
|
161
|
+
*
|
|
148
162
|
* @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
|
|
149
163
|
*/
|
|
150
164
|
declare function deviceLink(session: DeviceLinkInput): string;
|
package/dist/browser.d.cts
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-
|
|
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';
|
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-
|
|
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';
|
package/dist/index.cjs
CHANGED
|
@@ -37,14 +37,17 @@ __export(index_exports, {
|
|
|
37
37
|
ValidationError: () => ValidationError,
|
|
38
38
|
acspV2Digest: () => acspV2Digest,
|
|
39
39
|
buildAcspV2Payload: () => buildAcspV2Payload,
|
|
40
|
+
buildDeviceLink: () => buildDeviceLink,
|
|
40
41
|
deviceLink: () => deviceLink,
|
|
41
42
|
interactionsToBase64: () => interactionsToBase64,
|
|
42
43
|
isMobileUserAgent: () => isMobileUserAgent,
|
|
43
44
|
openOrShowQR: () => openOrShowQR,
|
|
44
45
|
parseSessionResult: () => parseSessionResult,
|
|
46
|
+
qrDeviceLinkTicker: () => qrDeviceLinkTicker,
|
|
45
47
|
randomChallenge: () => randomChallenge,
|
|
46
48
|
resolveBaseUrl: () => resolveBaseUrl,
|
|
47
49
|
sha256Base64: () => sha256Base64,
|
|
50
|
+
userChallengeOf: () => userChallengeOf,
|
|
48
51
|
verifyPayloadSignature: () => verifyPayloadSignature,
|
|
49
52
|
verifyPrehashedSignature: () => verifyPrehashedSignature
|
|
50
53
|
});
|
|
@@ -223,7 +226,8 @@ var AuthApi = class {
|
|
|
223
226
|
deviceLinkBase: r.deviceLinkBase != null ? String(r.deviceLinkBase) : null,
|
|
224
227
|
vc: vc != null ? String(vc) : null,
|
|
225
228
|
rpChallenge: acsp.rpChallenge,
|
|
226
|
-
acsp
|
|
229
|
+
acsp,
|
|
230
|
+
receivedAt: Date.now()
|
|
227
231
|
};
|
|
228
232
|
}
|
|
229
233
|
/** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
@@ -415,7 +419,7 @@ var ResponseValidator = class {
|
|
|
415
419
|
* Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
|
|
416
420
|
* @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
|
|
417
421
|
*/
|
|
418
|
-
validateAuth(result, acsp) {
|
|
422
|
+
validateAuth(result, acsp, opts = {}) {
|
|
419
423
|
const leaf = this.precheck(result, "ACSP_V2");
|
|
420
424
|
const { serverRandom, userChallenge, flowType, interactionTypeUsed } = result;
|
|
421
425
|
if (!serverRandom || Buffer.from(serverRandom, "base64").length < 18) throw new ValidationError("serverRandom \u0430\u043B\u0433\u0430/\u0431\u043E\u0433\u0438\u043D\u043E");
|
|
@@ -423,6 +427,16 @@ var ResponseValidator = class {
|
|
|
423
427
|
if (!flowType || !acsp.flowTypes.includes(flowType)) {
|
|
424
428
|
throw new ValidationError(`flowType ${flowType} \u043D\u044C \u0441\u0430\u043D\u0430\u043B \u0431\u043E\u043B\u0433\u043E\u0441\u043E\u043D \u0443\u0440\u0441\u0433\u0430\u043B\u0434 \u0431\u0430\u0439\u0445\u0433\u04AF\u0439`);
|
|
425
429
|
}
|
|
430
|
+
if (opts.expectedFlowType && flowType !== opts.expectedFlowType) {
|
|
431
|
+
throw new ValidationError(`flowType ${flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${opts.expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
|
|
432
|
+
}
|
|
433
|
+
const sameDevice = opts.expectedFlowType === "Web2App" || opts.expectedFlowType === "App2App";
|
|
434
|
+
if (sameDevice && !opts.userChallengeVerifier) {
|
|
435
|
+
throw new ValidationError(`${opts.expectedFlowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 callback-\u0438\u0439\u043D userChallengeVerifier \u0437\u0430\u0430\u0432\u0430\u043B`);
|
|
436
|
+
}
|
|
437
|
+
if (opts.userChallengeVerifier !== void 0 && userChallengeOf(opts.userChallengeVerifier) !== userChallenge) {
|
|
438
|
+
throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
439
|
+
}
|
|
426
440
|
if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
|
|
427
441
|
const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
428
442
|
const payload = buildAcspV2Payload({
|
|
@@ -570,6 +584,9 @@ var ResponseValidator = class {
|
|
|
570
584
|
}
|
|
571
585
|
}
|
|
572
586
|
};
|
|
587
|
+
function userChallengeOf(verifier) {
|
|
588
|
+
return (0, import_node_crypto2.createHash)("sha256").update(verifier, "utf8").digest("base64url");
|
|
589
|
+
}
|
|
573
590
|
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
574
591
|
const sig = Buffer.from(sigB64, "base64");
|
|
575
592
|
const h = nodeHash(hash);
|
|
@@ -782,6 +799,76 @@ function openOrShowQR(session, opts) {
|
|
|
782
799
|
opts.showQR(link);
|
|
783
800
|
return "qr";
|
|
784
801
|
}
|
|
802
|
+
|
|
803
|
+
// src/devicelinkv3.ts
|
|
804
|
+
var import_node_crypto3 = require("crypto");
|
|
805
|
+
var TYPES = ["QR", "Web2App", "App2App"];
|
|
806
|
+
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
807
|
+
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
808
|
+
var LANG_RE = /^[a-z]{3}$/;
|
|
809
|
+
var STD_B64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
810
|
+
var b642 = (s) => Buffer.from(s, "utf8").toString("base64");
|
|
811
|
+
var ms = (t) => t instanceof Date ? t.getTime() : t;
|
|
812
|
+
function fail(msg) {
|
|
813
|
+
throw new DeviceLinkError(`buildDeviceLink: ${msg}`);
|
|
814
|
+
}
|
|
815
|
+
function buildDeviceLink(p) {
|
|
816
|
+
const base = p.deviceLinkBase?.trim();
|
|
817
|
+
if (!base) fail("deviceLinkBase \u0445\u043E\u043E\u0441\u043E\u043D \u2014 device-link session-\u0438\u0439 \u0445\u0430\u0440\u0438\u0443\u0433 \u0448\u0430\u043B\u0433\u0430");
|
|
818
|
+
let u;
|
|
819
|
+
try {
|
|
820
|
+
u = new URL(base);
|
|
821
|
+
} catch {
|
|
822
|
+
fail(`deviceLinkBase \u0431\u0443\u0440\u0443\u0443 URL: ${base}`);
|
|
823
|
+
}
|
|
824
|
+
if (u.protocol !== "https:" || /[?#]/.test(base)) fail("deviceLinkBase \u043D\u044C query/fragment-\u0433\u04AF\u0439 https URL \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
|
|
825
|
+
if (!TYPES.includes(p.deviceLinkType)) fail(`deviceLinkType ${String(p.deviceLinkType)} (QR | Web2App | App2App)`);
|
|
826
|
+
if (!(p.sessionType in PROTOCOL)) fail(`sessionType ${String(p.sessionType)} (auth | sign | cert)`);
|
|
827
|
+
if (!p.sessionToken || !TOKEN_RE.test(p.sessionToken)) fail("sessionToken \u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
828
|
+
if (!LANG_RE.test(p.lang ?? "")) fail("lang \u043D\u044C ISO 639-2 \u0433\u0443\u0440\u0432\u0430\u043D \u0436\u0438\u0436\u0438\u0433 \u04AF\u0441\u044D\u0433 (mon, eng)");
|
|
829
|
+
if (!p.sessionSecret || !STD_B64_RE.test(p.sessionSecret)) fail("sessionSecret \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442 Base64 (padding-\u0442\u044D\u0439) \u0431\u0438\u0448");
|
|
830
|
+
let challenge = "";
|
|
831
|
+
if (p.sessionType === "auth") {
|
|
832
|
+
if (!p.rpChallenge) fail("auth session-\u0434 rpChallenge \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
833
|
+
challenge = p.rpChallenge;
|
|
834
|
+
} else if (p.sessionType === "sign") {
|
|
835
|
+
if (!p.digest) fail("sign session-\u0434 digest \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
836
|
+
challenge = p.digest;
|
|
837
|
+
} else if (p.rpChallenge || p.digest) {
|
|
838
|
+
fail("cert session-\u0434 rpChallenge/digest \u0431\u0430\u0439\u0445\u0433\u04AF\u0439");
|
|
839
|
+
}
|
|
840
|
+
const qr = p.deviceLinkType === "QR";
|
|
841
|
+
const callback = qr ? "" : p.initialCallbackUrl ?? "";
|
|
842
|
+
if (!qr && !callback) fail(`${p.deviceLinkType} \u0445\u043E\u043B\u0431\u043E\u043E\u0441 initialCallbackUrl-\u0442\u0430\u0439 session-\u0434 \u043B \u0445\u04AF\u0447\u0438\u043D\u0442\u044D\u0439`);
|
|
843
|
+
let elapsed = "";
|
|
844
|
+
if (qr) {
|
|
845
|
+
if (p.receivedAt == null) fail("QR \u0445\u043E\u043B\u0431\u043E\u043E\u0441\u043E\u043D\u0434 receivedAt \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
846
|
+
const e = Math.max(0, Math.floor((ms(p.now ?? Date.now()) - ms(p.receivedAt)) / 1e3));
|
|
847
|
+
if (!Number.isSafeInteger(e)) fail("receivedAt/now \u0431\u0443\u0440\u0443\u0443");
|
|
848
|
+
elapsed = `&elapsedSeconds=${e}`;
|
|
849
|
+
}
|
|
850
|
+
const unprotected = `${base}?deviceLinkType=${p.deviceLinkType}${elapsed}&sessionToken=${p.sessionToken}&sessionType=${p.sessionType}&version=1.0&lang=${p.lang}`;
|
|
851
|
+
const payload = [
|
|
852
|
+
p.schemeName ?? "smart-id",
|
|
853
|
+
PROTOCOL[p.sessionType],
|
|
854
|
+
challenge,
|
|
855
|
+
b642(p.relyingPartyName),
|
|
856
|
+
b642(p.brokeredRpName ?? ""),
|
|
857
|
+
p.interactions ?? "",
|
|
858
|
+
callback,
|
|
859
|
+
unprotected
|
|
860
|
+
].join("|");
|
|
861
|
+
const authCode = (0, import_node_crypto3.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
862
|
+
return `${unprotected}&authCode=${authCode}`;
|
|
863
|
+
}
|
|
864
|
+
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
865
|
+
const clock = opts?.clock ?? Date.now;
|
|
866
|
+
const tick = () => onLink(buildDeviceLink({ ...input, deviceLinkType: "QR", now: clock() }));
|
|
867
|
+
tick();
|
|
868
|
+
const t = setInterval(tick, opts?.intervalMs ?? 1e3);
|
|
869
|
+
t.unref?.();
|
|
870
|
+
return () => clearInterval(t);
|
|
871
|
+
}
|
|
785
872
|
// Annotate the CommonJS export names for ESM import in node:
|
|
786
873
|
0 && (module.exports = {
|
|
787
874
|
ApiError,
|
|
@@ -801,14 +888,17 @@ function openOrShowQR(session, opts) {
|
|
|
801
888
|
ValidationError,
|
|
802
889
|
acspV2Digest,
|
|
803
890
|
buildAcspV2Payload,
|
|
891
|
+
buildDeviceLink,
|
|
804
892
|
deviceLink,
|
|
805
893
|
interactionsToBase64,
|
|
806
894
|
isMobileUserAgent,
|
|
807
895
|
openOrShowQR,
|
|
808
896
|
parseSessionResult,
|
|
897
|
+
qrDeviceLinkTicker,
|
|
809
898
|
randomChallenge,
|
|
810
899
|
resolveBaseUrl,
|
|
811
900
|
sha256Base64,
|
|
901
|
+
userChallengeOf,
|
|
812
902
|
verifyPayloadSignature,
|
|
813
903
|
verifyPrehashedSignature
|
|
814
904
|
});
|
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-
|
|
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-
|
|
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';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -131,6 +131,32 @@ declare class SessionApi {
|
|
|
131
131
|
}): Promise<SessionResult>;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
|
|
136
|
+
* (device-link relay хаалт).
|
|
137
|
+
*/
|
|
138
|
+
interface ValidateAuthOptions {
|
|
139
|
+
/**
|
|
140
|
+
* RP-ийн ХАРУУЛСАН device link-ийн төрөл (эсвэл push). Өгвөл гарын үсэг зурагдсан ACSP_V2 `flowType`
|
|
141
|
+
* ЯГ ИЖИЛ байх ёстой: QR хуудас → "QR"; гар утасны браузер дахь товч → "Web2App"; RP-ийн апп дахь
|
|
142
|
+
* товч → "App2App"; push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
|
|
143
|
+
*
|
|
144
|
+
* v3 холбоосонд (`buildDeviceLink`) төрөл нь authCode-оор хамгаалагдсан — сервер түүнийг session-д
|
|
145
|
+
* бэхэлж, `flowType` = тэр төрөл. Нэг хуудсанд QR ба товч хоёуланг харуулсан бол: callback-аар
|
|
146
|
+
* (verifier-тэй) ирсэн үр дүнг "Web2App"/"App2App"-аар, poll-оор ирснийг "QR"-аар шалга — товчоор
|
|
147
|
+
* нээгдсэн session poll замаар OK болохгүй, callback-аар л дуусна.
|
|
148
|
+
*
|
|
149
|
+
* ⚠️ Legacy `/dl?sessionId&vc` холбоосонд төрөл хамгаалагдаагүй — build 59 + `min_version=59` хүртэл
|
|
150
|
+
* энэ шалгалт relay-ээс бүрэн хамгаалахгүй.
|
|
151
|
+
*/
|
|
152
|
+
expectedFlowType?: FlowType;
|
|
153
|
+
/**
|
|
154
|
+
* Callback URL-ийн `userChallengeVerifier` параметр (same-device урсгалд апп RP руу буцаахдаа
|
|
155
|
+
* нэмдэг). Өгвөл `BASE64URL(SHA-256(UTF-8(verifier)))` нь гарын үсэг зурагдсан `userChallenge`-тэй
|
|
156
|
+
* таарах ёстой.
|
|
157
|
+
*/
|
|
158
|
+
userChallengeVerifier?: string;
|
|
159
|
+
}
|
|
134
160
|
/**
|
|
135
161
|
* RevocationChecker — иргэний cert revoke хийгдсэн эсэхийг шалгах hook (OCSP/CRL).
|
|
136
162
|
* Сүлжээний I/O шаардсан тул async. RP нь cert доторх AIA (OCSP) / CDP (CRL) URL-аар
|
|
@@ -197,7 +223,7 @@ declare class ResponseValidator {
|
|
|
197
223
|
* Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
|
|
198
224
|
* @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
|
|
199
225
|
*/
|
|
200
|
-
validateAuth(result: SessionResult, acsp: AcspContext): VerifiedIdentity;
|
|
226
|
+
validateAuth(result: SessionResult, acsp: AcspContext, opts?: ValidateAuthOptions): VerifiedIdentity;
|
|
201
227
|
/**
|
|
202
228
|
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
203
229
|
* @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
|
|
@@ -225,6 +251,8 @@ declare class ResponseValidator {
|
|
|
225
251
|
checkRevocation(cert: X509Certificate): Promise<void>;
|
|
226
252
|
}
|
|
227
253
|
/** Payload (auth) дээрх гарын үсэг — алгоритмаар салаалж node:crypto verify. */
|
|
254
|
+
/** Smart-ID: userChallenge = BASE64URL(SHA-256(UTF-8(userChallengeVerifier))), padding-гүй. */
|
|
255
|
+
declare function userChallengeOf(verifier: string): string;
|
|
228
256
|
declare function verifyPayloadSignature(payload: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
|
|
229
257
|
/** Prehashed digest (sign) дээрх гарын үсэг. RSA: raw public op + EMSA-PSS-VERIFY / PKCS#1 DigestInfo. */
|
|
230
258
|
declare function verifyPrehashedSignature(digest: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
|
|
@@ -288,4 +316,71 @@ declare function buildAcspV2Payload(p: AcspV2Params): Buffer;
|
|
|
288
316
|
/** HASH_{hashAlgorithm}(payload). */
|
|
289
317
|
declare function acspV2Digest(p: AcspV2Params, hashAlgorithm: HashAlgorithm | string): Buffer;
|
|
290
318
|
|
|
291
|
-
|
|
319
|
+
/** Холбоосын төрөл — апп гарын үсгийн `flowType`-д яг энэ утгыг зурна (authCode-оор хамгаалагдсан). */
|
|
320
|
+
type DeviceLinkType = "QR" | "Web2App" | "App2App";
|
|
321
|
+
/** Session-ий урсгал: auth = нэвтрэлт, sign = гарын үсэг, cert = гэрчилгээ сонгох. */
|
|
322
|
+
type DeviceLinkSessionType = "auth" | "sign" | "cert";
|
|
323
|
+
interface BuildDeviceLinkInput {
|
|
324
|
+
/** Session start хариуны `deviceLinkBase` (ж: `https://ca.eidmongolia.mn/dl`). */
|
|
325
|
+
deviceLinkBase: string;
|
|
326
|
+
deviceLinkType: DeviceLinkType;
|
|
327
|
+
/** Session start хариуны `sessionToken` — ЯГ ХЭВЭЭР. */
|
|
328
|
+
sessionToken: string;
|
|
329
|
+
/** Session start хариуны `sessionSecret` (стандарт Base64, padding-тэй). ЗӨВХӨН backend-д. */
|
|
330
|
+
sessionSecret: string;
|
|
331
|
+
sessionType: DeviceLinkSessionType;
|
|
332
|
+
/** ISO 639-2 гурван жижиг үсэг (`mon`, `eng`) — зөвхөн `/dl` fallback хуудасны хэл. */
|
|
333
|
+
lang: string;
|
|
334
|
+
/**
|
|
335
|
+
* RP backend session start хариуг ХҮЛЭЭН АВСАН мөч (өөрийн цагаар; ms epoch эсвэл Date).
|
|
336
|
+
* QR-д заавал (`elapsedSeconds = floor(now − receivedAt)`); Web2App/App2App-д ашиглагдахгүй.
|
|
337
|
+
*/
|
|
338
|
+
receivedAt?: number | Date;
|
|
339
|
+
/** Одоогийн цаг (тест/тогтмол цагт). Анхдагч `Date.now()`. */
|
|
340
|
+
now?: number | Date;
|
|
341
|
+
/** auth: RP-ийн илгээсэн `rpChallenge` (base64 ТЕКСТЭЭРЭЭ). */
|
|
342
|
+
rpChallenge?: string;
|
|
343
|
+
/** sign: RP-ийн илгээсэн `digest` (base64 текст). */
|
|
344
|
+
digest?: string;
|
|
345
|
+
/** Session start хүсэлтэд илгээсэн ЯГ тэр `relyingPartyName`. */
|
|
346
|
+
relyingPartyName: string;
|
|
347
|
+
/** auth/sign: хүсэлтэд илгээсэн interactions-ийн base64 МӨР (дахин serialize хийхгүй). cert: `""`. */
|
|
348
|
+
interactions: string;
|
|
349
|
+
/**
|
|
350
|
+
* Хүсэлтэд илгээсэн түүхий `initialCallbackUrl`. Web2App/App2App-д заавал; QR-д authCode-д ОРОХГҮЙ
|
|
351
|
+
* (`""`), тиймээс QR ба товчинд ижил session-ий утгыг өгч болно.
|
|
352
|
+
*/
|
|
353
|
+
initialCallbackUrl?: string;
|
|
354
|
+
/** Анхдагч `"smart-id"` (серверийн EID_ACSP_SCHEME_NAME). */
|
|
355
|
+
schemeName?: string;
|
|
356
|
+
/** Broker RP-ийн нэр. eID broker дэмждэггүй — анхдагч `""`. */
|
|
357
|
+
brokeredRpName?: string;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Device link v3-ийг бүтнээр нь угсарна (`…&authCode=…`). Цэвэр функц — `now`-оос өөр гаднын төлөвгүй.
|
|
361
|
+
*
|
|
362
|
+
* ```
|
|
363
|
+
* QR: {base}?deviceLinkType=QR&elapsedSeconds={E}&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
|
|
364
|
+
* Web2App: {base}?deviceLinkType=Web2App&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
|
|
365
|
+
* ```
|
|
366
|
+
*
|
|
367
|
+
* QR холбоос ~20 секундэд хүчингүй болдог тул секунд тутам дахин дуудаж харуул ({@link qrDeviceLinkTicker}).
|
|
368
|
+
* Web2App/App2App холбоосыг нэг удаа угсарна.
|
|
369
|
+
*
|
|
370
|
+
* @throws {DeviceLinkError} оролт буруу (base https биш, token/lang/secret хэлбэр, төрөлд хэрэгтэй талбар дутуу).
|
|
371
|
+
*/
|
|
372
|
+
declare function buildDeviceLink(p: BuildDeviceLinkInput): string;
|
|
373
|
+
interface QrDeviceLinkTickerOptions {
|
|
374
|
+
/** Шинэчлэх давтамж (мс). Анхдагч 1000 — Smart-ID-ийн шаардлага. */
|
|
375
|
+
intervalMs?: number;
|
|
376
|
+
/** Цагийн эх үүсвэр (тест). Анхдагч `Date.now`. */
|
|
377
|
+
clock?: () => number;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Node сервер (SSE/WebSocket)-т: QR холбоосыг шууд нэг удаа, дараа нь секунд тутам `onLink`-ээр гаргана.
|
|
381
|
+
* Буцаах функцээр зогсооно (session дуусах/клиент салахад заавал дууд). Timer процессыг барихгүй (unref).
|
|
382
|
+
* HTTP poll ашиглавал энэ хэрэггүй — хүсэлт бүрд {@link buildDeviceLink}-ийг `deviceLinkType: "QR"`-оор дууд.
|
|
383
|
+
*/
|
|
384
|
+
declare function qrDeviceLinkTicker(input: Omit<BuildDeviceLinkInput, "deviceLinkType" | "now">, onLink: (link: string) => void, opts?: QrDeviceLinkTickerOptions): () => void;
|
|
385
|
+
|
|
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 };
|
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-
|
|
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-
|
|
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';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -131,6 +131,32 @@ declare class SessionApi {
|
|
|
131
131
|
}): Promise<SessionResult>;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
|
|
136
|
+
* (device-link relay хаалт).
|
|
137
|
+
*/
|
|
138
|
+
interface ValidateAuthOptions {
|
|
139
|
+
/**
|
|
140
|
+
* RP-ийн ХАРУУЛСАН device link-ийн төрөл (эсвэл push). Өгвөл гарын үсэг зурагдсан ACSP_V2 `flowType`
|
|
141
|
+
* ЯГ ИЖИЛ байх ёстой: QR хуудас → "QR"; гар утасны браузер дахь товч → "Web2App"; RP-ийн апп дахь
|
|
142
|
+
* товч → "App2App"; push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
|
|
143
|
+
*
|
|
144
|
+
* v3 холбоосонд (`buildDeviceLink`) төрөл нь authCode-оор хамгаалагдсан — сервер түүнийг session-д
|
|
145
|
+
* бэхэлж, `flowType` = тэр төрөл. Нэг хуудсанд QR ба товч хоёуланг харуулсан бол: callback-аар
|
|
146
|
+
* (verifier-тэй) ирсэн үр дүнг "Web2App"/"App2App"-аар, poll-оор ирснийг "QR"-аар шалга — товчоор
|
|
147
|
+
* нээгдсэн session poll замаар OK болохгүй, callback-аар л дуусна.
|
|
148
|
+
*
|
|
149
|
+
* ⚠️ Legacy `/dl?sessionId&vc` холбоосонд төрөл хамгаалагдаагүй — build 59 + `min_version=59` хүртэл
|
|
150
|
+
* энэ шалгалт relay-ээс бүрэн хамгаалахгүй.
|
|
151
|
+
*/
|
|
152
|
+
expectedFlowType?: FlowType;
|
|
153
|
+
/**
|
|
154
|
+
* Callback URL-ийн `userChallengeVerifier` параметр (same-device урсгалд апп RP руу буцаахдаа
|
|
155
|
+
* нэмдэг). Өгвөл `BASE64URL(SHA-256(UTF-8(verifier)))` нь гарын үсэг зурагдсан `userChallenge`-тэй
|
|
156
|
+
* таарах ёстой.
|
|
157
|
+
*/
|
|
158
|
+
userChallengeVerifier?: string;
|
|
159
|
+
}
|
|
134
160
|
/**
|
|
135
161
|
* RevocationChecker — иргэний cert revoke хийгдсэн эсэхийг шалгах hook (OCSP/CRL).
|
|
136
162
|
* Сүлжээний I/O шаардсан тул async. RP нь cert доторх AIA (OCSP) / CDP (CRL) URL-аар
|
|
@@ -197,7 +223,7 @@ declare class ResponseValidator {
|
|
|
197
223
|
* Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
|
|
198
224
|
* @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
|
|
199
225
|
*/
|
|
200
|
-
validateAuth(result: SessionResult, acsp: AcspContext): VerifiedIdentity;
|
|
226
|
+
validateAuth(result: SessionResult, acsp: AcspContext, opts?: ValidateAuthOptions): VerifiedIdentity;
|
|
201
227
|
/**
|
|
202
228
|
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
203
229
|
* @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
|
|
@@ -225,6 +251,8 @@ declare class ResponseValidator {
|
|
|
225
251
|
checkRevocation(cert: X509Certificate): Promise<void>;
|
|
226
252
|
}
|
|
227
253
|
/** Payload (auth) дээрх гарын үсэг — алгоритмаар салаалж node:crypto verify. */
|
|
254
|
+
/** Smart-ID: userChallenge = BASE64URL(SHA-256(UTF-8(userChallengeVerifier))), padding-гүй. */
|
|
255
|
+
declare function userChallengeOf(verifier: string): string;
|
|
228
256
|
declare function verifyPayloadSignature(payload: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
|
|
229
257
|
/** Prehashed digest (sign) дээрх гарын үсэг. RSA: raw public op + EMSA-PSS-VERIFY / PKCS#1 DigestInfo. */
|
|
230
258
|
declare function verifyPrehashedSignature(digest: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
|
|
@@ -288,4 +316,71 @@ declare function buildAcspV2Payload(p: AcspV2Params): Buffer;
|
|
|
288
316
|
/** HASH_{hashAlgorithm}(payload). */
|
|
289
317
|
declare function acspV2Digest(p: AcspV2Params, hashAlgorithm: HashAlgorithm | string): Buffer;
|
|
290
318
|
|
|
291
|
-
|
|
319
|
+
/** Холбоосын төрөл — апп гарын үсгийн `flowType`-д яг энэ утгыг зурна (authCode-оор хамгаалагдсан). */
|
|
320
|
+
type DeviceLinkType = "QR" | "Web2App" | "App2App";
|
|
321
|
+
/** Session-ий урсгал: auth = нэвтрэлт, sign = гарын үсэг, cert = гэрчилгээ сонгох. */
|
|
322
|
+
type DeviceLinkSessionType = "auth" | "sign" | "cert";
|
|
323
|
+
interface BuildDeviceLinkInput {
|
|
324
|
+
/** Session start хариуны `deviceLinkBase` (ж: `https://ca.eidmongolia.mn/dl`). */
|
|
325
|
+
deviceLinkBase: string;
|
|
326
|
+
deviceLinkType: DeviceLinkType;
|
|
327
|
+
/** Session start хариуны `sessionToken` — ЯГ ХЭВЭЭР. */
|
|
328
|
+
sessionToken: string;
|
|
329
|
+
/** Session start хариуны `sessionSecret` (стандарт Base64, padding-тэй). ЗӨВХӨН backend-д. */
|
|
330
|
+
sessionSecret: string;
|
|
331
|
+
sessionType: DeviceLinkSessionType;
|
|
332
|
+
/** ISO 639-2 гурван жижиг үсэг (`mon`, `eng`) — зөвхөн `/dl` fallback хуудасны хэл. */
|
|
333
|
+
lang: string;
|
|
334
|
+
/**
|
|
335
|
+
* RP backend session start хариуг ХҮЛЭЭН АВСАН мөч (өөрийн цагаар; ms epoch эсвэл Date).
|
|
336
|
+
* QR-д заавал (`elapsedSeconds = floor(now − receivedAt)`); Web2App/App2App-д ашиглагдахгүй.
|
|
337
|
+
*/
|
|
338
|
+
receivedAt?: number | Date;
|
|
339
|
+
/** Одоогийн цаг (тест/тогтмол цагт). Анхдагч `Date.now()`. */
|
|
340
|
+
now?: number | Date;
|
|
341
|
+
/** auth: RP-ийн илгээсэн `rpChallenge` (base64 ТЕКСТЭЭРЭЭ). */
|
|
342
|
+
rpChallenge?: string;
|
|
343
|
+
/** sign: RP-ийн илгээсэн `digest` (base64 текст). */
|
|
344
|
+
digest?: string;
|
|
345
|
+
/** Session start хүсэлтэд илгээсэн ЯГ тэр `relyingPartyName`. */
|
|
346
|
+
relyingPartyName: string;
|
|
347
|
+
/** auth/sign: хүсэлтэд илгээсэн interactions-ийн base64 МӨР (дахин serialize хийхгүй). cert: `""`. */
|
|
348
|
+
interactions: string;
|
|
349
|
+
/**
|
|
350
|
+
* Хүсэлтэд илгээсэн түүхий `initialCallbackUrl`. Web2App/App2App-д заавал; QR-д authCode-д ОРОХГҮЙ
|
|
351
|
+
* (`""`), тиймээс QR ба товчинд ижил session-ий утгыг өгч болно.
|
|
352
|
+
*/
|
|
353
|
+
initialCallbackUrl?: string;
|
|
354
|
+
/** Анхдагч `"smart-id"` (серверийн EID_ACSP_SCHEME_NAME). */
|
|
355
|
+
schemeName?: string;
|
|
356
|
+
/** Broker RP-ийн нэр. eID broker дэмждэггүй — анхдагч `""`. */
|
|
357
|
+
brokeredRpName?: string;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Device link v3-ийг бүтнээр нь угсарна (`…&authCode=…`). Цэвэр функц — `now`-оос өөр гаднын төлөвгүй.
|
|
361
|
+
*
|
|
362
|
+
* ```
|
|
363
|
+
* QR: {base}?deviceLinkType=QR&elapsedSeconds={E}&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
|
|
364
|
+
* Web2App: {base}?deviceLinkType=Web2App&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
|
|
365
|
+
* ```
|
|
366
|
+
*
|
|
367
|
+
* QR холбоос ~20 секундэд хүчингүй болдог тул секунд тутам дахин дуудаж харуул ({@link qrDeviceLinkTicker}).
|
|
368
|
+
* Web2App/App2App холбоосыг нэг удаа угсарна.
|
|
369
|
+
*
|
|
370
|
+
* @throws {DeviceLinkError} оролт буруу (base https биш, token/lang/secret хэлбэр, төрөлд хэрэгтэй талбар дутуу).
|
|
371
|
+
*/
|
|
372
|
+
declare function buildDeviceLink(p: BuildDeviceLinkInput): string;
|
|
373
|
+
interface QrDeviceLinkTickerOptions {
|
|
374
|
+
/** Шинэчлэх давтамж (мс). Анхдагч 1000 — Smart-ID-ийн шаардлага. */
|
|
375
|
+
intervalMs?: number;
|
|
376
|
+
/** Цагийн эх үүсвэр (тест). Анхдагч `Date.now`. */
|
|
377
|
+
clock?: () => number;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Node сервер (SSE/WebSocket)-т: QR холбоосыг шууд нэг удаа, дараа нь секунд тутам `onLink`-ээр гаргана.
|
|
381
|
+
* Буцаах функцээр зогсооно (session дуусах/клиент салахад заавал дууд). Timer процессыг барихгүй (unref).
|
|
382
|
+
* HTTP poll ашиглавал энэ хэрэггүй — хүсэлт бүрд {@link buildDeviceLink}-ийг `deviceLinkType: "QR"`-оор дууд.
|
|
383
|
+
*/
|
|
384
|
+
declare function qrDeviceLinkTicker(input: Omit<BuildDeviceLinkInput, "deviceLinkType" | "now">, onLink: (link: string) => void, opts?: QrDeviceLinkTickerOptions): () => void;
|
|
385
|
+
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -151,7 +151,8 @@ var AuthApi = class {
|
|
|
151
151
|
deviceLinkBase: r.deviceLinkBase != null ? String(r.deviceLinkBase) : null,
|
|
152
152
|
vc: vc != null ? String(vc) : null,
|
|
153
153
|
rpChallenge: acsp.rpChallenge,
|
|
154
|
-
acsp
|
|
154
|
+
acsp,
|
|
155
|
+
receivedAt: Date.now()
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
/** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
@@ -343,7 +344,7 @@ var ResponseValidator = class {
|
|
|
343
344
|
* Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
|
|
344
345
|
* @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
|
|
345
346
|
*/
|
|
346
|
-
validateAuth(result, acsp) {
|
|
347
|
+
validateAuth(result, acsp, opts = {}) {
|
|
347
348
|
const leaf = this.precheck(result, "ACSP_V2");
|
|
348
349
|
const { serverRandom, userChallenge, flowType, interactionTypeUsed } = result;
|
|
349
350
|
if (!serverRandom || Buffer.from(serverRandom, "base64").length < 18) throw new ValidationError("serverRandom \u0430\u043B\u0433\u0430/\u0431\u043E\u0433\u0438\u043D\u043E");
|
|
@@ -351,6 +352,16 @@ var ResponseValidator = class {
|
|
|
351
352
|
if (!flowType || !acsp.flowTypes.includes(flowType)) {
|
|
352
353
|
throw new ValidationError(`flowType ${flowType} \u043D\u044C \u0441\u0430\u043D\u0430\u043B \u0431\u043E\u043B\u0433\u043E\u0441\u043E\u043D \u0443\u0440\u0441\u0433\u0430\u043B\u0434 \u0431\u0430\u0439\u0445\u0433\u04AF\u0439`);
|
|
353
354
|
}
|
|
355
|
+
if (opts.expectedFlowType && flowType !== opts.expectedFlowType) {
|
|
356
|
+
throw new ValidationError(`flowType ${flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${opts.expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
|
|
357
|
+
}
|
|
358
|
+
const sameDevice = opts.expectedFlowType === "Web2App" || opts.expectedFlowType === "App2App";
|
|
359
|
+
if (sameDevice && !opts.userChallengeVerifier) {
|
|
360
|
+
throw new ValidationError(`${opts.expectedFlowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 callback-\u0438\u0439\u043D userChallengeVerifier \u0437\u0430\u0430\u0432\u0430\u043B`);
|
|
361
|
+
}
|
|
362
|
+
if (opts.userChallengeVerifier !== void 0 && userChallengeOf(opts.userChallengeVerifier) !== userChallenge) {
|
|
363
|
+
throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
364
|
+
}
|
|
354
365
|
if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
|
|
355
366
|
const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
356
367
|
const payload = buildAcspV2Payload({
|
|
@@ -498,6 +509,9 @@ var ResponseValidator = class {
|
|
|
498
509
|
}
|
|
499
510
|
}
|
|
500
511
|
};
|
|
512
|
+
function userChallengeOf(verifier) {
|
|
513
|
+
return createHash2("sha256").update(verifier, "utf8").digest("base64url");
|
|
514
|
+
}
|
|
501
515
|
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
502
516
|
const sig = Buffer.from(sigB64, "base64");
|
|
503
517
|
const h = nodeHash(hash);
|
|
@@ -668,6 +682,76 @@ var EidClient = class {
|
|
|
668
682
|
this.validator = new ResponseValidator(cfg.trust ?? {});
|
|
669
683
|
}
|
|
670
684
|
};
|
|
685
|
+
|
|
686
|
+
// src/devicelinkv3.ts
|
|
687
|
+
import { createHmac } from "crypto";
|
|
688
|
+
var TYPES = ["QR", "Web2App", "App2App"];
|
|
689
|
+
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
690
|
+
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
691
|
+
var LANG_RE = /^[a-z]{3}$/;
|
|
692
|
+
var STD_B64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
693
|
+
var b642 = (s) => Buffer.from(s, "utf8").toString("base64");
|
|
694
|
+
var ms = (t) => t instanceof Date ? t.getTime() : t;
|
|
695
|
+
function fail(msg) {
|
|
696
|
+
throw new DeviceLinkError(`buildDeviceLink: ${msg}`);
|
|
697
|
+
}
|
|
698
|
+
function buildDeviceLink(p) {
|
|
699
|
+
const base = p.deviceLinkBase?.trim();
|
|
700
|
+
if (!base) fail("deviceLinkBase \u0445\u043E\u043E\u0441\u043E\u043D \u2014 device-link session-\u0438\u0439 \u0445\u0430\u0440\u0438\u0443\u0433 \u0448\u0430\u043B\u0433\u0430");
|
|
701
|
+
let u;
|
|
702
|
+
try {
|
|
703
|
+
u = new URL(base);
|
|
704
|
+
} catch {
|
|
705
|
+
fail(`deviceLinkBase \u0431\u0443\u0440\u0443\u0443 URL: ${base}`);
|
|
706
|
+
}
|
|
707
|
+
if (u.protocol !== "https:" || /[?#]/.test(base)) fail("deviceLinkBase \u043D\u044C query/fragment-\u0433\u04AF\u0439 https URL \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
|
|
708
|
+
if (!TYPES.includes(p.deviceLinkType)) fail(`deviceLinkType ${String(p.deviceLinkType)} (QR | Web2App | App2App)`);
|
|
709
|
+
if (!(p.sessionType in PROTOCOL)) fail(`sessionType ${String(p.sessionType)} (auth | sign | cert)`);
|
|
710
|
+
if (!p.sessionToken || !TOKEN_RE.test(p.sessionToken)) fail("sessionToken \u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
711
|
+
if (!LANG_RE.test(p.lang ?? "")) fail("lang \u043D\u044C ISO 639-2 \u0433\u0443\u0440\u0432\u0430\u043D \u0436\u0438\u0436\u0438\u0433 \u04AF\u0441\u044D\u0433 (mon, eng)");
|
|
712
|
+
if (!p.sessionSecret || !STD_B64_RE.test(p.sessionSecret)) fail("sessionSecret \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442 Base64 (padding-\u0442\u044D\u0439) \u0431\u0438\u0448");
|
|
713
|
+
let challenge = "";
|
|
714
|
+
if (p.sessionType === "auth") {
|
|
715
|
+
if (!p.rpChallenge) fail("auth session-\u0434 rpChallenge \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
716
|
+
challenge = p.rpChallenge;
|
|
717
|
+
} else if (p.sessionType === "sign") {
|
|
718
|
+
if (!p.digest) fail("sign session-\u0434 digest \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
719
|
+
challenge = p.digest;
|
|
720
|
+
} else if (p.rpChallenge || p.digest) {
|
|
721
|
+
fail("cert session-\u0434 rpChallenge/digest \u0431\u0430\u0439\u0445\u0433\u04AF\u0439");
|
|
722
|
+
}
|
|
723
|
+
const qr = p.deviceLinkType === "QR";
|
|
724
|
+
const callback = qr ? "" : p.initialCallbackUrl ?? "";
|
|
725
|
+
if (!qr && !callback) fail(`${p.deviceLinkType} \u0445\u043E\u043B\u0431\u043E\u043E\u0441 initialCallbackUrl-\u0442\u0430\u0439 session-\u0434 \u043B \u0445\u04AF\u0447\u0438\u043D\u0442\u044D\u0439`);
|
|
726
|
+
let elapsed = "";
|
|
727
|
+
if (qr) {
|
|
728
|
+
if (p.receivedAt == null) fail("QR \u0445\u043E\u043B\u0431\u043E\u043E\u0441\u043E\u043D\u0434 receivedAt \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
729
|
+
const e = Math.max(0, Math.floor((ms(p.now ?? Date.now()) - ms(p.receivedAt)) / 1e3));
|
|
730
|
+
if (!Number.isSafeInteger(e)) fail("receivedAt/now \u0431\u0443\u0440\u0443\u0443");
|
|
731
|
+
elapsed = `&elapsedSeconds=${e}`;
|
|
732
|
+
}
|
|
733
|
+
const unprotected = `${base}?deviceLinkType=${p.deviceLinkType}${elapsed}&sessionToken=${p.sessionToken}&sessionType=${p.sessionType}&version=1.0&lang=${p.lang}`;
|
|
734
|
+
const payload = [
|
|
735
|
+
p.schemeName ?? "smart-id",
|
|
736
|
+
PROTOCOL[p.sessionType],
|
|
737
|
+
challenge,
|
|
738
|
+
b642(p.relyingPartyName),
|
|
739
|
+
b642(p.brokeredRpName ?? ""),
|
|
740
|
+
p.interactions ?? "",
|
|
741
|
+
callback,
|
|
742
|
+
unprotected
|
|
743
|
+
].join("|");
|
|
744
|
+
const authCode = createHmac("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
745
|
+
return `${unprotected}&authCode=${authCode}`;
|
|
746
|
+
}
|
|
747
|
+
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
748
|
+
const clock = opts?.clock ?? Date.now;
|
|
749
|
+
const tick = () => onLink(buildDeviceLink({ ...input, deviceLinkType: "QR", now: clock() }));
|
|
750
|
+
tick();
|
|
751
|
+
const t = setInterval(tick, opts?.intervalMs ?? 1e3);
|
|
752
|
+
t.unref?.();
|
|
753
|
+
return () => clearInterval(t);
|
|
754
|
+
}
|
|
671
755
|
export {
|
|
672
756
|
ApiError,
|
|
673
757
|
AuthApi,
|
|
@@ -686,14 +770,17 @@ export {
|
|
|
686
770
|
ValidationError,
|
|
687
771
|
acspV2Digest,
|
|
688
772
|
buildAcspV2Payload,
|
|
773
|
+
buildDeviceLink,
|
|
689
774
|
deviceLink,
|
|
690
775
|
interactionsToBase64,
|
|
691
776
|
isMobileUserAgent,
|
|
692
777
|
openOrShowQR,
|
|
693
778
|
parseSessionResult,
|
|
779
|
+
qrDeviceLinkTicker,
|
|
694
780
|
randomChallenge,
|
|
695
781
|
resolveBaseUrl,
|
|
696
782
|
sha256Base64,
|
|
783
|
+
userChallengeOf,
|
|
697
784
|
verifyPayloadSignature,
|
|
698
785
|
verifyPrehashedSignature
|
|
699
786
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gerege-systems/eid-mongolia-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|