@gerege-systems/eid-mongolia-sdk 0.5.0 → 0.6.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 +68 -39
- package/dist/{browser-BS8JwSQk.d.cts → browser-BU9ShPWO.d.cts} +22 -2
- package/dist/{browser-BS8JwSQk.d.ts → browser-BU9ShPWO.d.ts} +22 -2
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/index.cjs +91 -2
- package/dist/index.d.cts +80 -9
- package/dist/index.d.ts +80 -9
- 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,60 +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
|
-
res.json({ sessionId: s.sessionId, vc: s.vc, deviceLinkBase: s.deviceLinkBase }); // s.acsp-ийг серверт хадгал
|
|
79
|
-
|
|
80
|
-
// холбоос: `${deviceLinkBase}?sessionId=<uuid>&vc=<5 орон>` (ж: https://ca.eidmongolia.mn/dl?...)
|
|
81
|
-
import { deviceLink } from "@gerege-systems/eid-mongolia-sdk";
|
|
82
|
-
const link = deviceLink(s);
|
|
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}
|
|
83
79
|
```
|
|
84
80
|
|
|
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
|
+
|
|
85
91
|
```ts
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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") (нэг удаа, шинэчлэхгүй)
|
|
95
124
|
```
|
|
96
125
|
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
SSE/WebSocket-оор түлхэх бол `qrDeviceLinkTicker(input, (link) => send(link))` — шууд нэг, дараа нь секунд тутам
|
|
127
|
+
холбоос гаргаж, буцаасан функцээр зогсооно (session дуусах/клиент салахад заавал).
|
|
128
|
+
|
|
129
|
+
**Харуулсан төрлөө заавал тулга (relay хаалт).** `expectedFlowType` = RP-ийн харуулсан холбоосын төрөл:
|
|
99
130
|
|
|
100
131
|
```ts
|
|
101
|
-
//
|
|
132
|
+
// QR хуудас → poll-оор дууссан үр дүн
|
|
102
133
|
eid.validator.validateAuth(result, s.acsp, { expectedFlowType: "QR" });
|
|
103
134
|
|
|
104
|
-
//
|
|
105
|
-
// `?userChallengeVerifier=...`-ийг callback URL-д нэмдэг
|
|
135
|
+
// Web2App/App2App товч → апп callback URL-д `userChallengeVerifier` нэмж буцна
|
|
106
136
|
eid.validator.validateAuth(result, s.acsp, {
|
|
107
|
-
expectedFlowType: "Web2App", //
|
|
137
|
+
expectedFlowType: "Web2App", // RP-ийн апп дахь товч бол "App2App"
|
|
108
138
|
userChallengeVerifier: callbackQuery.get("userChallengeVerifier") ?? undefined,
|
|
109
139
|
});
|
|
140
|
+
// push (notificationBy*) → "Notification"
|
|
110
141
|
```
|
|
111
142
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
> хамгаалахгүй — гэхдээ QR урсгалд хуучин апптай ч эвдрэхгүй.
|
|
143
|
+
QR ба товч хоёуланг харуулсан хуудсанд: callback-аар ирсэн үр дүнг `Web2App`/`App2App`-аар, poll-оор ирснийг
|
|
144
|
+
`QR`-аар шалга (товчоор нээгдсэн session зөвхөн callback-аар дуусна).
|
|
115
145
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
хэрэглэгчийн үйлдэл гэж үзэхгүй, Universal Link-ийг алгасаж `/dl` хуудсыг нээдэг — тэр хуудас
|
|
121
|
-
«Аппаар нээх» товч ба App Store / Play холбоосыг санал болгоно (апп суугаагүй үед ч мөн).
|
|
122
|
-
- `deviceLinkBase` хариунд байхгүй, `sessionId` UUID биш, `vc` 5 орон биш бол `DeviceLinkError` — холбоосыг
|
|
123
|
-
таахгүй. Апп руу custom scheme-ээр шууд бүү холбо — зөвхөн `deviceLink()`-ийн https холбоос.
|
|
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** — тэр мөчөөс сервер статик холбоосыг хүлээн авахгүй.
|
|
124
150
|
|
|
125
151
|
## Аюулгүй байдал — яагаад `validator` заавал
|
|
126
152
|
|
|
@@ -134,6 +160,9 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
134
160
|
4. Гарын үсгийг иргэний public key-ээр шалгана:
|
|
135
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-ийг мөн шалгана;
|
|
136
162
|
`expectedFlowType` өгвөл `flowType` эхлүүлсэн урсгалтай ЯГ таарах, Web2App/App2App-д `userChallengeVerifier` заавал
|
|
163
|
+
`initialCallbackUrl`-ийг хариуны `signature.initialCallbackUrl`-аас авна (CA урсгалаар шийднэ: QR/Notification →
|
|
164
|
+
`""`, Web2App/App2App → таны callback; шилжилтийн үед legacy `/dl` ба push-д таны callback) — зөвхөн `""` эсвэл
|
|
165
|
+
таны илгээсэн callback байхад, Web2App/App2App-д заавал таны callback; өөр утга → татгалзана
|
|
137
166
|
- **sign** → RP-ийн өгсөн digest дээр
|
|
138
167
|
|
|
139
168
|
Аль нэг алхам бүтэлгүйтвэл `ValidationError` шиднэ — хариунд **итгэхгүй**.
|
|
@@ -148,7 +177,7 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
148
177
|
| `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
|
|
149
178
|
| `ApiError` | бусад HTTP алдаа (`.status`, `.body`) |
|
|
150
179
|
| `NetworkError` | timeout/сүлжээ |
|
|
151
|
-
| `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase`
|
|
180
|
+
| `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
|
|
152
181
|
|
|
153
182
|
## mTLS (eIDAS qualified орчин)
|
|
154
183
|
|
|
@@ -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 {
|
|
@@ -107,6 +116,12 @@ interface SessionResult {
|
|
|
107
116
|
serverRandom: string | null;
|
|
108
117
|
userChallenge: string | null;
|
|
109
118
|
flowType: string | null;
|
|
119
|
+
/**
|
|
120
|
+
* ACSP_V2 payload-д гарын үсэг зурагдсан `initialCallbackUrl` (хариуны `signature.initialCallbackUrl`,
|
|
121
|
+
* eID нэмэлт). Smart-ID: QR/Notification → `""`, Web2App/App2App → RP-ийн илгээсэн URL. null = хариунд
|
|
122
|
+
* талбар алга (хуучин CA). Validator үүнийг зөвхөн `""` эсвэл RP-ийн өөрийн callback байхад л хүлээн авна.
|
|
123
|
+
*/
|
|
124
|
+
initialCallbackUrl?: string | null;
|
|
110
125
|
signatureAlgorithmParameters: SignatureAlgorithmParameters | null;
|
|
111
126
|
interactionTypeUsed: string | null;
|
|
112
127
|
/**
|
|
@@ -145,6 +160,11 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
|
|
|
145
160
|
* Device-link session-ийн холбоос: `${deviceLinkBase}?sessionId=<uuid>&vc=<код>`.
|
|
146
161
|
* QR-д эсвэл «eID аппаар нээх» товчинд тавина.
|
|
147
162
|
*
|
|
163
|
+
* @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
|
|
164
|
+
* eID Mongolia 2.2.3 (build 59) гармагц сервер унтраана (`EID_DEVICE_LINK_LEGACY=false`). Шинэ код
|
|
165
|
+
* backend-д {@link buildDeviceLink} (v3) ашиглана; build 59-өөс өмнөх апп v3 холбоосыг танихгүй тул
|
|
166
|
+
* RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
|
|
167
|
+
*
|
|
148
168
|
* @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
|
|
149
169
|
*/
|
|
150
170
|
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 {
|
|
@@ -107,6 +116,12 @@ interface SessionResult {
|
|
|
107
116
|
serverRandom: string | null;
|
|
108
117
|
userChallenge: string | null;
|
|
109
118
|
flowType: string | null;
|
|
119
|
+
/**
|
|
120
|
+
* ACSP_V2 payload-д гарын үсэг зурагдсан `initialCallbackUrl` (хариуны `signature.initialCallbackUrl`,
|
|
121
|
+
* eID нэмэлт). Smart-ID: QR/Notification → `""`, Web2App/App2App → RP-ийн илгээсэн URL. null = хариунд
|
|
122
|
+
* талбар алга (хуучин CA). Validator үүнийг зөвхөн `""` эсвэл RP-ийн өөрийн callback байхад л хүлээн авна.
|
|
123
|
+
*/
|
|
124
|
+
initialCallbackUrl?: string | null;
|
|
110
125
|
signatureAlgorithmParameters: SignatureAlgorithmParameters | null;
|
|
111
126
|
interactionTypeUsed: string | null;
|
|
112
127
|
/**
|
|
@@ -145,6 +160,11 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
|
|
|
145
160
|
* Device-link session-ийн холбоос: `${deviceLinkBase}?sessionId=<uuid>&vc=<код>`.
|
|
146
161
|
* QR-д эсвэл «eID аппаар нээх» товчинд тавина.
|
|
147
162
|
*
|
|
163
|
+
* @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
|
|
164
|
+
* eID Mongolia 2.2.3 (build 59) гармагц сервер унтраана (`EID_DEVICE_LINK_LEGACY=false`). Шинэ код
|
|
165
|
+
* backend-д {@link buildDeviceLink} (v3) ашиглана; build 59-өөс өмнөх апп v3 холбоосыг танихгүй тул
|
|
166
|
+
* RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
|
|
167
|
+
*
|
|
148
168
|
* @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
|
|
149
169
|
*/
|
|
150
170
|
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-BU9ShPWO.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-BU9ShPWO.js';
|
package/dist/index.cjs
CHANGED
|
@@ -37,11 +37,13 @@ __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,
|
|
@@ -224,7 +226,8 @@ var AuthApi = class {
|
|
|
224
226
|
deviceLinkBase: r.deviceLinkBase != null ? String(r.deviceLinkBase) : null,
|
|
225
227
|
vc: vc != null ? String(vc) : null,
|
|
226
228
|
rpChallenge: acsp.rpChallenge,
|
|
227
|
-
acsp
|
|
229
|
+
acsp,
|
|
230
|
+
receivedAt: Date.now()
|
|
228
231
|
};
|
|
229
232
|
}
|
|
230
233
|
/** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
@@ -333,6 +336,9 @@ function parseSessionResult(raw) {
|
|
|
333
336
|
serverRandom: str(sig.serverRandom),
|
|
334
337
|
userChallenge: str(sig.userChallenge),
|
|
335
338
|
flowType: str(sig.flowType),
|
|
339
|
+
// CA-ийн eID нэмэлт (f59d14ee-оос): гарын үсэг зурагдсан ACSP_V2 initialCallbackUrl. "" нь утга —
|
|
340
|
+
// null/undefined-аас ялгана (талбар алга = хуучин CA).
|
|
341
|
+
initialCallbackUrl: typeof sig.initialCallbackUrl === "string" ? sig.initialCallbackUrl : null,
|
|
336
342
|
signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
|
|
337
343
|
interactionTypeUsed: str(r.interactionTypeUsed),
|
|
338
344
|
onBehalfOf: parseOrg(r.onBehalfOf)
|
|
@@ -444,7 +450,7 @@ var ResponseValidator = class {
|
|
|
444
450
|
brokeredRpName: acsp.brokeredRpName,
|
|
445
451
|
interactions: acsp.interactions,
|
|
446
452
|
interactionTypeUsed,
|
|
447
|
-
initialCallbackUrl: acsp.initialCallbackUrl,
|
|
453
|
+
initialCallbackUrl: signedCallbackUrl(result.initialCallbackUrl, acsp.initialCallbackUrl, flowType),
|
|
448
454
|
flowType
|
|
449
455
|
});
|
|
450
456
|
if (!verifyPayloadSignature(payload, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
@@ -581,6 +587,17 @@ var ResponseValidator = class {
|
|
|
581
587
|
}
|
|
582
588
|
}
|
|
583
589
|
};
|
|
590
|
+
function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
591
|
+
const rp = rpCallback ?? "";
|
|
592
|
+
if (fromResponse == null) return rp;
|
|
593
|
+
if (fromResponse !== "" && fromResponse !== rp) {
|
|
594
|
+
throw new ValidationError("signature.initialCallbackUrl \u043D\u044C \u0445\u043E\u043E\u0441\u043E\u043D \u0447 \u0431\u0438\u0448, \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0447 \u0431\u0438\u0448 (\u0445\u0443\u0443\u0440\u0430\u043C\u0447 \u0445\u0430\u0440\u0438\u0443 \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
|
|
595
|
+
}
|
|
596
|
+
if ((flowType === "Web2App" || flowType === "App2App") && fromResponse !== rp) {
|
|
597
|
+
throw new ValidationError(`${flowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 signature.initialCallbackUrl \u043D\u044C \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439`);
|
|
598
|
+
}
|
|
599
|
+
return fromResponse;
|
|
600
|
+
}
|
|
584
601
|
function userChallengeOf(verifier) {
|
|
585
602
|
return (0, import_node_crypto2.createHash)("sha256").update(verifier, "utf8").digest("base64url");
|
|
586
603
|
}
|
|
@@ -796,6 +813,76 @@ function openOrShowQR(session, opts) {
|
|
|
796
813
|
opts.showQR(link);
|
|
797
814
|
return "qr";
|
|
798
815
|
}
|
|
816
|
+
|
|
817
|
+
// src/devicelinkv3.ts
|
|
818
|
+
var import_node_crypto3 = require("crypto");
|
|
819
|
+
var TYPES = ["QR", "Web2App", "App2App"];
|
|
820
|
+
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
821
|
+
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
822
|
+
var LANG_RE = /^[a-z]{3}$/;
|
|
823
|
+
var STD_B64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
824
|
+
var b642 = (s) => Buffer.from(s, "utf8").toString("base64");
|
|
825
|
+
var ms = (t) => t instanceof Date ? t.getTime() : t;
|
|
826
|
+
function fail(msg) {
|
|
827
|
+
throw new DeviceLinkError(`buildDeviceLink: ${msg}`);
|
|
828
|
+
}
|
|
829
|
+
function buildDeviceLink(p) {
|
|
830
|
+
const base = p.deviceLinkBase?.trim();
|
|
831
|
+
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");
|
|
832
|
+
let u;
|
|
833
|
+
try {
|
|
834
|
+
u = new URL(base);
|
|
835
|
+
} catch {
|
|
836
|
+
fail(`deviceLinkBase \u0431\u0443\u0440\u0443\u0443 URL: ${base}`);
|
|
837
|
+
}
|
|
838
|
+
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");
|
|
839
|
+
if (!TYPES.includes(p.deviceLinkType)) fail(`deviceLinkType ${String(p.deviceLinkType)} (QR | Web2App | App2App)`);
|
|
840
|
+
if (!(p.sessionType in PROTOCOL)) fail(`sessionType ${String(p.sessionType)} (auth | sign | cert)`);
|
|
841
|
+
if (!p.sessionToken || !TOKEN_RE.test(p.sessionToken)) fail("sessionToken \u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
842
|
+
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)");
|
|
843
|
+
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");
|
|
844
|
+
let challenge = "";
|
|
845
|
+
if (p.sessionType === "auth") {
|
|
846
|
+
if (!p.rpChallenge) fail("auth session-\u0434 rpChallenge \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
847
|
+
challenge = p.rpChallenge;
|
|
848
|
+
} else if (p.sessionType === "sign") {
|
|
849
|
+
if (!p.digest) fail("sign session-\u0434 digest \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
850
|
+
challenge = p.digest;
|
|
851
|
+
} else if (p.rpChallenge || p.digest) {
|
|
852
|
+
fail("cert session-\u0434 rpChallenge/digest \u0431\u0430\u0439\u0445\u0433\u04AF\u0439");
|
|
853
|
+
}
|
|
854
|
+
const qr = p.deviceLinkType === "QR";
|
|
855
|
+
const callback = qr ? "" : p.initialCallbackUrl ?? "";
|
|
856
|
+
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`);
|
|
857
|
+
let elapsed = "";
|
|
858
|
+
if (qr) {
|
|
859
|
+
if (p.receivedAt == null) fail("QR \u0445\u043E\u043B\u0431\u043E\u043E\u0441\u043E\u043D\u0434 receivedAt \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
860
|
+
const e = Math.max(0, Math.floor((ms(p.now ?? Date.now()) - ms(p.receivedAt)) / 1e3));
|
|
861
|
+
if (!Number.isSafeInteger(e)) fail("receivedAt/now \u0431\u0443\u0440\u0443\u0443");
|
|
862
|
+
elapsed = `&elapsedSeconds=${e}`;
|
|
863
|
+
}
|
|
864
|
+
const unprotected = `${base}?deviceLinkType=${p.deviceLinkType}${elapsed}&sessionToken=${p.sessionToken}&sessionType=${p.sessionType}&version=1.0&lang=${p.lang}`;
|
|
865
|
+
const payload = [
|
|
866
|
+
p.schemeName ?? "smart-id",
|
|
867
|
+
PROTOCOL[p.sessionType],
|
|
868
|
+
challenge,
|
|
869
|
+
b642(p.relyingPartyName),
|
|
870
|
+
b642(p.brokeredRpName ?? ""),
|
|
871
|
+
p.interactions ?? "",
|
|
872
|
+
callback,
|
|
873
|
+
unprotected
|
|
874
|
+
].join("|");
|
|
875
|
+
const authCode = (0, import_node_crypto3.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
876
|
+
return `${unprotected}&authCode=${authCode}`;
|
|
877
|
+
}
|
|
878
|
+
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
879
|
+
const clock = opts?.clock ?? Date.now;
|
|
880
|
+
const tick = () => onLink(buildDeviceLink({ ...input, deviceLinkType: "QR", now: clock() }));
|
|
881
|
+
tick();
|
|
882
|
+
const t = setInterval(tick, opts?.intervalMs ?? 1e3);
|
|
883
|
+
t.unref?.();
|
|
884
|
+
return () => clearInterval(t);
|
|
885
|
+
}
|
|
799
886
|
// Annotate the CommonJS export names for ESM import in node:
|
|
800
887
|
0 && (module.exports = {
|
|
801
888
|
ApiError,
|
|
@@ -815,11 +902,13 @@ function openOrShowQR(session, opts) {
|
|
|
815
902
|
ValidationError,
|
|
816
903
|
acspV2Digest,
|
|
817
904
|
buildAcspV2Payload,
|
|
905
|
+
buildDeviceLink,
|
|
818
906
|
deviceLink,
|
|
819
907
|
interactionsToBase64,
|
|
820
908
|
isMobileUserAgent,
|
|
821
909
|
openOrShowQR,
|
|
822
910
|
parseSessionResult,
|
|
911
|
+
qrDeviceLinkTicker,
|
|
823
912
|
randomChallenge,
|
|
824
913
|
resolveBaseUrl,
|
|
825
914
|
sha256Base64,
|
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-BU9ShPWO.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-BU9ShPWO.cjs';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -137,13 +137,17 @@ declare class SessionApi {
|
|
|
137
137
|
*/
|
|
138
138
|
interface ValidateAuthOptions {
|
|
139
139
|
/**
|
|
140
|
-
* RP-ийн
|
|
141
|
-
*
|
|
142
|
-
* push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
|
|
140
|
+
* RP-ийн ХАРУУЛСАН device link-ийн төрөл (эсвэл push). Өгвөл гарын үсэг зурагдсан ACSP_V2 `flowType`
|
|
141
|
+
* ЯГ ИЖИЛ байх ёстой: QR хуудас → "QR"; гар утасны браузер дахь товч → "Web2App"; RP-ийн апп дахь
|
|
142
|
+
* товч → "App2App"; push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
|
|
143
143
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
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-ээс бүрэн хамгаалахгүй.
|
|
147
151
|
*/
|
|
148
152
|
expectedFlowType?: FlowType;
|
|
149
153
|
/**
|
|
@@ -312,4 +316,71 @@ declare function buildAcspV2Payload(p: AcspV2Params): Buffer;
|
|
|
312
316
|
/** HASH_{hashAlgorithm}(payload). */
|
|
313
317
|
declare function acspV2Digest(p: AcspV2Params, hashAlgorithm: HashAlgorithm | string): Buffer;
|
|
314
318
|
|
|
315
|
-
|
|
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-BU9ShPWO.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-BU9ShPWO.js';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -137,13 +137,17 @@ declare class SessionApi {
|
|
|
137
137
|
*/
|
|
138
138
|
interface ValidateAuthOptions {
|
|
139
139
|
/**
|
|
140
|
-
* RP-ийн
|
|
141
|
-
*
|
|
142
|
-
* push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
|
|
140
|
+
* RP-ийн ХАРУУЛСАН device link-ийн төрөл (эсвэл push). Өгвөл гарын үсэг зурагдсан ACSP_V2 `flowType`
|
|
141
|
+
* ЯГ ИЖИЛ байх ёстой: QR хуудас → "QR"; гар утасны браузер дахь товч → "Web2App"; RP-ийн апп дахь
|
|
142
|
+
* товч → "App2App"; push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
|
|
143
143
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
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-ээс бүрэн хамгаалахгүй.
|
|
147
151
|
*/
|
|
148
152
|
expectedFlowType?: FlowType;
|
|
149
153
|
/**
|
|
@@ -312,4 +316,71 @@ declare function buildAcspV2Payload(p: AcspV2Params): Buffer;
|
|
|
312
316
|
/** HASH_{hashAlgorithm}(payload). */
|
|
313
317
|
declare function acspV2Digest(p: AcspV2Params, hashAlgorithm: HashAlgorithm | string): Buffer;
|
|
314
318
|
|
|
315
|
-
|
|
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-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
@@ -260,6 +261,9 @@ function parseSessionResult(raw) {
|
|
|
260
261
|
serverRandom: str(sig.serverRandom),
|
|
261
262
|
userChallenge: str(sig.userChallenge),
|
|
262
263
|
flowType: str(sig.flowType),
|
|
264
|
+
// CA-ийн eID нэмэлт (f59d14ee-оос): гарын үсэг зурагдсан ACSP_V2 initialCallbackUrl. "" нь утга —
|
|
265
|
+
// null/undefined-аас ялгана (талбар алга = хуучин CA).
|
|
266
|
+
initialCallbackUrl: typeof sig.initialCallbackUrl === "string" ? sig.initialCallbackUrl : null,
|
|
263
267
|
signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
|
|
264
268
|
interactionTypeUsed: str(r.interactionTypeUsed),
|
|
265
269
|
onBehalfOf: parseOrg(r.onBehalfOf)
|
|
@@ -371,7 +375,7 @@ var ResponseValidator = class {
|
|
|
371
375
|
brokeredRpName: acsp.brokeredRpName,
|
|
372
376
|
interactions: acsp.interactions,
|
|
373
377
|
interactionTypeUsed,
|
|
374
|
-
initialCallbackUrl: acsp.initialCallbackUrl,
|
|
378
|
+
initialCallbackUrl: signedCallbackUrl(result.initialCallbackUrl, acsp.initialCallbackUrl, flowType),
|
|
375
379
|
flowType
|
|
376
380
|
});
|
|
377
381
|
if (!verifyPayloadSignature(payload, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
@@ -508,6 +512,17 @@ var ResponseValidator = class {
|
|
|
508
512
|
}
|
|
509
513
|
}
|
|
510
514
|
};
|
|
515
|
+
function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
516
|
+
const rp = rpCallback ?? "";
|
|
517
|
+
if (fromResponse == null) return rp;
|
|
518
|
+
if (fromResponse !== "" && fromResponse !== rp) {
|
|
519
|
+
throw new ValidationError("signature.initialCallbackUrl \u043D\u044C \u0445\u043E\u043E\u0441\u043E\u043D \u0447 \u0431\u0438\u0448, \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0447 \u0431\u0438\u0448 (\u0445\u0443\u0443\u0440\u0430\u043C\u0447 \u0445\u0430\u0440\u0438\u0443 \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
|
|
520
|
+
}
|
|
521
|
+
if ((flowType === "Web2App" || flowType === "App2App") && fromResponse !== rp) {
|
|
522
|
+
throw new ValidationError(`${flowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 signature.initialCallbackUrl \u043D\u044C \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D callback \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439`);
|
|
523
|
+
}
|
|
524
|
+
return fromResponse;
|
|
525
|
+
}
|
|
511
526
|
function userChallengeOf(verifier) {
|
|
512
527
|
return createHash2("sha256").update(verifier, "utf8").digest("base64url");
|
|
513
528
|
}
|
|
@@ -681,6 +696,76 @@ var EidClient = class {
|
|
|
681
696
|
this.validator = new ResponseValidator(cfg.trust ?? {});
|
|
682
697
|
}
|
|
683
698
|
};
|
|
699
|
+
|
|
700
|
+
// src/devicelinkv3.ts
|
|
701
|
+
import { createHmac } from "crypto";
|
|
702
|
+
var TYPES = ["QR", "Web2App", "App2App"];
|
|
703
|
+
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
704
|
+
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
705
|
+
var LANG_RE = /^[a-z]{3}$/;
|
|
706
|
+
var STD_B64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
707
|
+
var b642 = (s) => Buffer.from(s, "utf8").toString("base64");
|
|
708
|
+
var ms = (t) => t instanceof Date ? t.getTime() : t;
|
|
709
|
+
function fail(msg) {
|
|
710
|
+
throw new DeviceLinkError(`buildDeviceLink: ${msg}`);
|
|
711
|
+
}
|
|
712
|
+
function buildDeviceLink(p) {
|
|
713
|
+
const base = p.deviceLinkBase?.trim();
|
|
714
|
+
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");
|
|
715
|
+
let u;
|
|
716
|
+
try {
|
|
717
|
+
u = new URL(base);
|
|
718
|
+
} catch {
|
|
719
|
+
fail(`deviceLinkBase \u0431\u0443\u0440\u0443\u0443 URL: ${base}`);
|
|
720
|
+
}
|
|
721
|
+
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");
|
|
722
|
+
if (!TYPES.includes(p.deviceLinkType)) fail(`deviceLinkType ${String(p.deviceLinkType)} (QR | Web2App | App2App)`);
|
|
723
|
+
if (!(p.sessionType in PROTOCOL)) fail(`sessionType ${String(p.sessionType)} (auth | sign | cert)`);
|
|
724
|
+
if (!p.sessionToken || !TOKEN_RE.test(p.sessionToken)) fail("sessionToken \u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
725
|
+
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)");
|
|
726
|
+
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");
|
|
727
|
+
let challenge = "";
|
|
728
|
+
if (p.sessionType === "auth") {
|
|
729
|
+
if (!p.rpChallenge) fail("auth session-\u0434 rpChallenge \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
730
|
+
challenge = p.rpChallenge;
|
|
731
|
+
} else if (p.sessionType === "sign") {
|
|
732
|
+
if (!p.digest) fail("sign session-\u0434 digest \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
733
|
+
challenge = p.digest;
|
|
734
|
+
} else if (p.rpChallenge || p.digest) {
|
|
735
|
+
fail("cert session-\u0434 rpChallenge/digest \u0431\u0430\u0439\u0445\u0433\u04AF\u0439");
|
|
736
|
+
}
|
|
737
|
+
const qr = p.deviceLinkType === "QR";
|
|
738
|
+
const callback = qr ? "" : p.initialCallbackUrl ?? "";
|
|
739
|
+
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`);
|
|
740
|
+
let elapsed = "";
|
|
741
|
+
if (qr) {
|
|
742
|
+
if (p.receivedAt == null) fail("QR \u0445\u043E\u043B\u0431\u043E\u043E\u0441\u043E\u043D\u0434 receivedAt \u0437\u0430\u0430\u0432\u0430\u043B");
|
|
743
|
+
const e = Math.max(0, Math.floor((ms(p.now ?? Date.now()) - ms(p.receivedAt)) / 1e3));
|
|
744
|
+
if (!Number.isSafeInteger(e)) fail("receivedAt/now \u0431\u0443\u0440\u0443\u0443");
|
|
745
|
+
elapsed = `&elapsedSeconds=${e}`;
|
|
746
|
+
}
|
|
747
|
+
const unprotected = `${base}?deviceLinkType=${p.deviceLinkType}${elapsed}&sessionToken=${p.sessionToken}&sessionType=${p.sessionType}&version=1.0&lang=${p.lang}`;
|
|
748
|
+
const payload = [
|
|
749
|
+
p.schemeName ?? "smart-id",
|
|
750
|
+
PROTOCOL[p.sessionType],
|
|
751
|
+
challenge,
|
|
752
|
+
b642(p.relyingPartyName),
|
|
753
|
+
b642(p.brokeredRpName ?? ""),
|
|
754
|
+
p.interactions ?? "",
|
|
755
|
+
callback,
|
|
756
|
+
unprotected
|
|
757
|
+
].join("|");
|
|
758
|
+
const authCode = createHmac("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
759
|
+
return `${unprotected}&authCode=${authCode}`;
|
|
760
|
+
}
|
|
761
|
+
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
762
|
+
const clock = opts?.clock ?? Date.now;
|
|
763
|
+
const tick = () => onLink(buildDeviceLink({ ...input, deviceLinkType: "QR", now: clock() }));
|
|
764
|
+
tick();
|
|
765
|
+
const t = setInterval(tick, opts?.intervalMs ?? 1e3);
|
|
766
|
+
t.unref?.();
|
|
767
|
+
return () => clearInterval(t);
|
|
768
|
+
}
|
|
684
769
|
export {
|
|
685
770
|
ApiError,
|
|
686
771
|
AuthApi,
|
|
@@ -699,11 +784,13 @@ export {
|
|
|
699
784
|
ValidationError,
|
|
700
785
|
acspV2Digest,
|
|
701
786
|
buildAcspV2Payload,
|
|
787
|
+
buildDeviceLink,
|
|
702
788
|
deviceLink,
|
|
703
789
|
interactionsToBase64,
|
|
704
790
|
isMobileUserAgent,
|
|
705
791
|
openOrShowQR,
|
|
706
792
|
parseSessionResult,
|
|
793
|
+
qrDeviceLinkTicker,
|
|
707
794
|
randomChallenge,
|
|
708
795
|
resolveBaseUrl,
|
|
709
796
|
sha256Base64,
|
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.1",
|
|
4
4
|
"description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|