@gerege-systems/eid-mongolia-sdk 0.15.0 → 0.15.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 +49 -12
- package/dist/button.cjs +55 -45
- package/dist/button.js +55 -45
- package/dist/index.cjs +48 -11
- package/dist/index.d.cts +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +47 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ export const eidLogin = createEidLogin({
|
|
|
72
72
|
};
|
|
73
73
|
},
|
|
74
74
|
// store: redisStore, // олон instance / serverless бол (анхдагч — процессын санах ой)
|
|
75
|
-
// rateLimit: (key) => redisLimiter.allow(key), //
|
|
75
|
+
// rateLimit: { start: (key) => redisLimiter.allow(key) }, // анхдагчтай нэгтгэгдэнэ (push 3/мин хэвээр) — доор «Хязгаарлалт»
|
|
76
76
|
trustProxy: 1, // handle(Request)-д клиент IP: nginx/LB нэг hop бол 1 (доор «Клиент IP»)
|
|
77
77
|
});
|
|
78
78
|
```
|
|
@@ -95,8 +95,27 @@ export const POST = (req: Request) => eidLogin.handle(req);
|
|
|
95
95
|
Vercel `req.headers.get("x-real-ip")`). `trustProxy`-оос давуу.
|
|
96
96
|
|
|
97
97
|
Аль нь ч үгүй бол IP-ээр хязгаарлахгүй (эхний хүсэлтэд `onError`-оор нэг удаа анхааруулна). `handleRequest`-д `req.ip`
|
|
98
|
-
(Express `req.ip`, `app.set("trust proxy", …)`-тэй) шууд хэрэглэгдэнэ.
|
|
99
|
-
|
|
98
|
+
(Express `req.ip`, `app.set("trust proxy", …)`-тэй) шууд хэрэглэгдэнэ. Go: `eidlogin.Config.ClientIP` (`eidlogin.ForwardedClientIP(n)`) — ижил дүрэм.
|
|
99
|
+
|
|
100
|
+
**Хязгаарлалт** (`rateLimit`). Түлхүүр: `start:<ip>` (IP мэдэгдэж байвал), `push:<idCode>`; татгалзвал 429 + `Retry-After: 60`.
|
|
101
|
+
Анхдагч `defaultRateLimit` = `{ push: { limit: 3, windowMs: 60_000 } }` — нэг дугаарт минутад 3 мэдэгдэл (MFA fatigue);
|
|
102
|
+
`start`-ийг анхдагчаар хязгаарладаггүй.
|
|
103
|
+
|
|
104
|
+
- **Объект** `{ start?, push? }` — анхдагчтай **нэгтгэгдэнэ**: өгсөн төрөл анхдагчийг дарна, өгөөгүй нь анхдагчаараа үлдэнэ.
|
|
105
|
+
Утга: `{ limit, windowMs }` (процессын санах ойн гулсах цонх), `(key) => boolean | Promise<boolean>` (Redis г.м.),
|
|
106
|
+
эсвэл `false` (тухайн төрлийг хязгаарлахгүй). Өөр түлхүүр → `createEidLogin` throw.
|
|
107
|
+
- **Функц** `(key) => …` — бүх түлхүүрт дуудагдана, анхдагч хязгаар **дээр нэмэгдэнэ** (хоёулаа зөвшөөрөх ёстой).
|
|
108
|
+
Анхдагчийг сулруулах/солих бол объектоор тухайн төрлийг өг (`{ push: … }`).
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { defaultRateLimit } from "@gerege-systems/eid-mongolia-sdk";
|
|
112
|
+
rateLimit: { start: { limit: 20, windowMs: 60_000 } }, // push 3/мин хэвээр
|
|
113
|
+
rateLimit: { ...defaultRateLimit, start: (k) => redis.allow(k) }, // ижил — анхдагчийг илэрхий бичсэн
|
|
114
|
+
rateLimit: { push: (k) => redis.allow(k, 3, 60) }, // олон instance: push-ийг Redis-ээр солино
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Go: `eidlogin.Config.RateLimits` (`map[string]eidlogin.RateRule{"start": {Max: 20, Window: time.Minute}}`,
|
|
118
|
+
`eidlogin.DefaultRateLimit()`) — ижил нэгтгэл; `Config.RateLimit` функц мөн анхдагч дээр нэмэгдэнэ.
|
|
100
119
|
|
|
101
120
|
Express:
|
|
102
121
|
|
|
@@ -128,21 +147,39 @@ app.use("/auth/eid", express.json(), async (req, res) => {
|
|
|
128
147
|
**Дизайн, theme.** Дизайн нь eID нэвтрэх хуудсынх (лого, өнгө). Фонт нь хуудаснаас өвлөгдөнө (товч, цонх, input бүгд).
|
|
129
148
|
`theme="auto"` үед дараалал: хуудасны `html[data-theme="dark|light"]` → `html.dark` / `html.light` → OS
|
|
130
149
|
(`prefers-color-scheme`); хуудас theme солиход (MutationObserver) шууд дагана. `theme="light|dark"` нь хуудсыг давна.
|
|
131
|
-
Өнгө, радиусыг CSS хувьсагчаар
|
|
150
|
+
Өнгө, радиусыг CSS хувьсагчаар дарна. `--eid-*` нь зөвхөн **оролт** (shadow дотор тодорхойлогддоггүй) тул хуудасны
|
|
151
|
+
аль ч түвшинд (`:root`, эцэг элемент, `eid-login-button`) өгсөн утга өвлөгдөж **light ба dark хоёуланд** үйлчилнэ;
|
|
152
|
+
`--eid-<нэр>-dark` нь зөвхөн dark-д, `--eid-<нэр>`-ээс давуу. Аль нь ч үгүй бол eID-ийн анхдагч (доорх хүснэгт) — fallback л.
|
|
153
|
+
Dark-ийн дараалал: `--eid-<нэр>-dark` → `--eid-<нэр>` → dark анхдагч.
|
|
132
154
|
|
|
133
155
|
```css
|
|
134
|
-
eid-login-button {
|
|
135
|
-
--eid-accent:
|
|
136
|
-
--eid-
|
|
137
|
-
--eid-fg: …; --eid-fg-muted: …; --eid-fg-subtle: …; /* текст */
|
|
138
|
-
--eid-border: …; --eid-border-strong: …; --eid-ring: …; /* хүрээ, focus */
|
|
139
|
-
--eid-warning-bg: …; --eid-warning-border: …; --eid-warning-fg: …; --eid-danger-fg: …;
|
|
156
|
+
:root { /* эсвэл eid-login-button { … } */
|
|
157
|
+
--eid-accent: var(--brand); /* хоёр theme-д */
|
|
158
|
+
--eid-accent-dark: var(--brand-400); /* зөвхөн dark-д (өгөхгүй бол --eid-accent) */
|
|
140
159
|
--eid-radius: 6px; --eid-radius-lg: 8px;
|
|
141
160
|
}
|
|
142
|
-
|
|
143
|
-
eid-login-button::part(button) { width: 100%; } /* part: button, backdrop, dialog */
|
|
161
|
+
eid-login-button::part(button) { width: 100%; } /* part: button, backdrop, dialog */
|
|
144
162
|
```
|
|
145
163
|
|
|
164
|
+
| Хувьсагч (`-dark` хувилбартай) | Юунд | Light анхдагч | Dark анхдагч |
|
|
165
|
+
|---|---|---|---|
|
|
166
|
+
| `--eid-accent` | үндсэн товч | `oklch(0.534 0.2 259)` | `oklch(0.64 0.15 259)` |
|
|
167
|
+
| `--eid-accent-fg` | үндсэн товчны текст | `#fff` | `hsl(229 50% 6%)` |
|
|
168
|
+
| `--eid-accent-subtle` | алхмын дугаарын дэвсгэр | `oklch(0.97 0.03 259)` | `oklch(0.24 0.06 259)` |
|
|
169
|
+
| `--eid-accent-subtle-fg` | алхмын дугаар | `oklch(0.474 0.19 259)` | `oklch(0.82 0.1 259)` |
|
|
170
|
+
| `--eid-ring` | focus | `oklch(0.604 0.2 259)` | `oklch(0.68 0.15 259)` |
|
|
171
|
+
| `--eid-bg` | цонх, outline товч, input | `hsl(0 0% 100%)` | `hsl(229 50% 6%)` |
|
|
172
|
+
| `--eid-bg-subtle` | — (нөөц) | `hsl(210 40% 98%)` | `hsl(222 47% 9%)` |
|
|
173
|
+
| `--eid-bg-muted` | hover | `hsl(210 40% 96%)` | `hsl(217 33% 13%)` |
|
|
174
|
+
| `--eid-fg` | текст | `hsl(222 47% 11%)` | `hsl(210 40% 98%)` |
|
|
175
|
+
| `--eid-fg-muted` | хоёрдогч текст | `hsl(215 19% 35%)` | `hsl(213 27% 84%)` |
|
|
176
|
+
| `--eid-fg-subtle` | тайлбар, placeholder | `hsl(215 16% 45%)` | `hsl(215 20% 65%)` |
|
|
177
|
+
| `--eid-border` | цонх, QR, тусгаарлагч | `hsl(214 32% 91%)` | `hsl(217 33% 17%)` |
|
|
178
|
+
| `--eid-border-strong` | outline товч, input | `hsl(213 27% 84%)` | `hsl(215 25% 27%)` |
|
|
179
|
+
| `--eid-warning-bg` / `-border` / `-fg` | сүлжээний анхааруулга | `hsl(48 100% 96%)` / `hsl(45 95% 78%)` / `hsl(23 75% 33%)` | `hsl(20 60% 10%)` / `hsl(22 70% 26%)` / `hsl(40 92% 65%)` |
|
|
180
|
+
| `--eid-danger-fg` | алдаа | `hsl(0 74% 42%)` | `hsl(0 91% 71%)` |
|
|
181
|
+
| `--eid-radius` / `--eid-radius-lg` (theme-гүй) | булан | `6px` / `8px` | — |
|
|
182
|
+
|
|
146
183
|
**eID аппын доод хувилбар.** `flowType` бэхлэлт (device link v3) ба Web2App-ийн `userChallengeVerifier` нь
|
|
147
184
|
**iOS 2.2.3 (build 59)**, **Android 2.2.3 (versionCode 60)**-аас эхэлсэн. eID сервер 2026-10-07-оос legacy холбоосыг
|
|
148
185
|
бүрэн хассан (v3-гүй session-д утас `DEVICE_LINK_REQUIRED`) бөгөөд доод хувилбарыг `/mobile/app-config`-оор албадан
|
package/dist/button.cjs
CHANGED
|
@@ -465,87 +465,97 @@ var T = {
|
|
|
465
465
|
minute: "1 minute left"
|
|
466
466
|
}
|
|
467
467
|
};
|
|
468
|
+
var EID_COLOR_TOKENS = [
|
|
469
|
+
["bg", "hsl(0 0% 100%)", "hsl(229 50% 6%)"],
|
|
470
|
+
["bg-subtle", "hsl(210 40% 98%)", "hsl(222 47% 9%)"],
|
|
471
|
+
["bg-muted", "hsl(210 40% 96%)", "hsl(217 33% 13%)"],
|
|
472
|
+
["fg", "hsl(222 47% 11%)", "hsl(210 40% 98%)"],
|
|
473
|
+
["fg-muted", "hsl(215 19% 35%)", "hsl(213 27% 84%)"],
|
|
474
|
+
["fg-subtle", "hsl(215 16% 45%)", "hsl(215 20% 65%)"],
|
|
475
|
+
["border", "hsl(214 32% 91%)", "hsl(217 33% 17%)"],
|
|
476
|
+
["border-strong", "hsl(213 27% 84%)", "hsl(215 25% 27%)"],
|
|
477
|
+
["accent", "oklch(0.534 0.2 259)", "oklch(0.64 0.15 259)"],
|
|
478
|
+
["accent-fg", "#fff", "hsl(229 50% 6%)"],
|
|
479
|
+
["accent-subtle", "oklch(0.97 0.03 259)", "oklch(0.24 0.06 259)"],
|
|
480
|
+
["accent-subtle-fg", "oklch(0.474 0.19 259)", "oklch(0.82 0.1 259)"],
|
|
481
|
+
["ring", "oklch(0.604 0.2 259)", "oklch(0.68 0.15 259)"],
|
|
482
|
+
["warning-bg", "hsl(48 100% 96%)", "hsl(20 60% 10%)"],
|
|
483
|
+
["warning-border", "hsl(45 95% 78%)", "hsl(22 70% 26%)"],
|
|
484
|
+
["warning-fg", "hsl(23 75% 33%)", "hsl(40 92% 65%)"],
|
|
485
|
+
["danger-fg", "hsl(0 74% 42%)", "hsl(0 91% 71%)"]
|
|
486
|
+
];
|
|
487
|
+
var lightVars = EID_COLOR_TOKENS.map(([n, l]) => `--_eid-${n}: var(--eid-${n}, ${l});`).join("\n ");
|
|
488
|
+
var darkVars = EID_COLOR_TOKENS.map(([n, , d]) => `--_eid-${n}: var(--eid-${n}-dark, var(--eid-${n}, ${d}));`).join("\n ");
|
|
468
489
|
var CSS_BASE = `
|
|
469
|
-
:host { display: inline-block;
|
|
470
|
-
|
|
471
|
-
--
|
|
472
|
-
--eid-border: hsl(214 32% 91%); --eid-border-strong: hsl(213 27% 84%);
|
|
473
|
-
--eid-accent: oklch(0.534 0.2 259); --eid-accent-fg: #fff; --eid-accent-subtle: oklch(0.97 0.03 259);
|
|
474
|
-
--eid-accent-subtle-fg: oklch(0.474 0.19 259); --eid-ring: oklch(0.604 0.2 259);
|
|
475
|
-
--eid-warning-bg: hsl(48 100% 96%); --eid-warning-border: hsl(45 95% 78%); --eid-warning-fg: hsl(23 75% 33%);
|
|
476
|
-
--eid-danger-fg: hsl(0 74% 42%); --eid-radius: 6px; --eid-radius-lg: 8px; font-family: inherit; color-scheme: light; }
|
|
490
|
+
:host { display: inline-block; font-family: inherit; color-scheme: light;
|
|
491
|
+
${lightVars}
|
|
492
|
+
--_eid-radius: var(--eid-radius, 6px); --_eid-radius-lg: var(--eid-radius-lg, 8px); }
|
|
477
493
|
button, input { font-family: inherit; }
|
|
478
494
|
`;
|
|
479
495
|
var DARK_CSS = `:host { color-scheme: dark;
|
|
480
|
-
|
|
481
|
-
--eid-fg: hsl(210 40% 98%); --eid-fg-muted: hsl(213 27% 84%); --eid-fg-subtle: hsl(215 20% 65%);
|
|
482
|
-
--eid-border: hsl(217 33% 17%); --eid-border-strong: hsl(215 25% 27%);
|
|
483
|
-
--eid-accent: oklch(0.64 0.15 259); --eid-accent-fg: hsl(229 50% 6%); --eid-accent-subtle: oklch(0.24 0.06 259);
|
|
484
|
-
--eid-accent-subtle-fg: oklch(0.82 0.1 259); --eid-ring: oklch(0.68 0.15 259);
|
|
485
|
-
--eid-warning-bg: hsl(20 60% 10%); --eid-warning-border: hsl(22 70% 26%); --eid-warning-fg: hsl(40 92% 65%);
|
|
486
|
-
--eid-danger-fg: hsl(0 91% 71%); }
|
|
496
|
+
${darkVars} }
|
|
487
497
|
`;
|
|
488
498
|
var CSS_REST = `
|
|
489
499
|
*, *::before, *::after { box-sizing: border-box; }
|
|
490
500
|
[hidden] { display: none !important; }
|
|
491
501
|
h2, p, ol { margin: 0; }
|
|
492
|
-
:focus-visible { outline: 2px solid var(--
|
|
502
|
+
:focus-visible { outline: 2px solid var(--_eid-ring); outline-offset: 2px; }
|
|
493
503
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
494
504
|
.btn { display: inline-flex; width: 100%; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 20px;
|
|
495
|
-
border-radius: var(--
|
|
505
|
+
border-radius: var(--_eid-radius); font-family: inherit; font-weight: 500; font-size: 1rem; line-height: 1.25; text-decoration: none; border: 1px solid transparent; cursor: pointer;
|
|
496
506
|
transition: background-color 160ms cubic-bezier(0,0,0.2,1); }
|
|
497
507
|
.btn:disabled { opacity: 0.6; cursor: progress; }
|
|
498
|
-
.btn-primary { background: var(--
|
|
499
|
-
.btn-primary:hover { background: color-mix(in oklab, var(--
|
|
500
|
-
.btn-outline { background: var(--
|
|
501
|
-
.btn-outline:hover { background: var(--
|
|
508
|
+
.btn-primary { background: var(--_eid-accent); color: var(--_eid-accent-fg); }
|
|
509
|
+
.btn-primary:hover { background: color-mix(in oklab, var(--_eid-accent) 88%, black); }
|
|
510
|
+
.btn-outline { background: var(--_eid-bg); color: var(--_eid-fg); border-color: var(--_eid-border-strong); }
|
|
511
|
+
.btn-outline:hover { background: var(--_eid-bg-muted); }
|
|
502
512
|
.btn-sm { width: auto; min-height: 36px; padding: 0 14px; font-size: 0.8125rem; }
|
|
503
513
|
.trigger { width: auto; }
|
|
504
514
|
.trigger img { width: 20px; height: 20px; border-radius: 4px; }
|
|
505
515
|
.backdrop { position: fixed; inset: 0; z-index: 2147483000; background: hsl(222 47% 11% / 0.5); display: flex; align-items: center;
|
|
506
516
|
justify-content: center; padding: 16px; overflow-y: auto; }
|
|
507
|
-
.dialog { position: relative; width: 100%; max-width: 26rem; max-height: calc(100dvh - 32px); overflow-y: auto; background: var(--
|
|
508
|
-
color: var(--
|
|
517
|
+
.dialog { position: relative; width: 100%; max-width: 26rem; max-height: calc(100dvh - 32px); overflow-y: auto; background: var(--_eid-bg);
|
|
518
|
+
color: var(--_eid-fg); border: 1px solid var(--_eid-border); border-radius: var(--_eid-radius-lg); padding: 24px; font-size: 0.875rem; line-height: 1.5rem; }
|
|
509
519
|
@media (max-width: 639px) { .dialog { padding: 16px; } }
|
|
510
520
|
.head { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
|
|
511
|
-
.head img { width: 28px; height: 28px; border-radius: var(--
|
|
521
|
+
.head img { width: 28px; height: 28px; border-radius: var(--_eid-radius); }
|
|
512
522
|
.head h2 { flex: 1; font-size: 1.125rem; line-height: 1.75rem; font-weight: 600; }
|
|
513
|
-
.x { width: 36px; height: 36px; border: 0; border-radius: var(--
|
|
514
|
-
.x:hover { background: var(--
|
|
515
|
-
.qr { position: relative; width: 256px; max-width: 100%; aspect-ratio: 1; margin: 0 auto; border: 1px solid var(--
|
|
516
|
-
border-radius: var(--
|
|
523
|
+
.x { width: 36px; height: 36px; border: 0; border-radius: var(--_eid-radius); background: transparent; color: var(--_eid-fg-muted); cursor: pointer; font-size: 1.25rem; line-height: 1; }
|
|
524
|
+
.x:hover { background: var(--_eid-bg-muted); color: var(--_eid-fg); }
|
|
525
|
+
.qr { position: relative; width: 256px; max-width: 100%; aspect-ratio: 1; margin: 0 auto; border: 1px solid var(--_eid-border);
|
|
526
|
+
border-radius: var(--_eid-radius-lg); background: #fff; overflow: hidden; }
|
|
517
527
|
.qr svg { display: block; width: 100%; height: 100%; }
|
|
518
528
|
.qr.stale svg { opacity: 0.12; }
|
|
519
529
|
.qr-stale { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
|
|
520
530
|
color: hsl(222 47% 11%); font-weight: 500; }
|
|
521
|
-
.qr-vc { margin-top: 8px; text-align: center; font-size: 0.8125rem; color: var(--
|
|
522
|
-
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.08em; color: var(--
|
|
531
|
+
.qr-vc { margin-top: 8px; text-align: center; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
|
|
532
|
+
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.08em; color: var(--_eid-fg-muted); }
|
|
523
533
|
.steps { margin-top: 16px; padding: 0; list-style: none; counter-reset: st; display: grid; gap: 8px; }
|
|
524
534
|
.steps li { counter-increment: st; display: flex; align-items: center; gap: 12px; }
|
|
525
|
-
.steps li::before { content: counter(st); flex: none; width: 24px; height: 24px; border-radius: 9999px; background: var(--
|
|
526
|
-
color: var(--
|
|
527
|
-
.or { display: flex; align-items: center; gap: 12px; margin: 24px 0; color: var(--
|
|
528
|
-
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--
|
|
535
|
+
.steps li::before { content: counter(st); flex: none; width: 24px; height: 24px; border-radius: 9999px; background: var(--_eid-accent-subtle);
|
|
536
|
+
color: var(--_eid-accent-subtle-fg); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 600; }
|
|
537
|
+
.or { display: flex; align-items: center; gap: 12px; margin: 24px 0; color: var(--_eid-fg-subtle); font-size: 0.8125rem; }
|
|
538
|
+
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--_eid-border); }
|
|
529
539
|
.field { display: grid; gap: 8px; }
|
|
530
540
|
.field label { font-weight: 500; }
|
|
531
|
-
.field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--
|
|
532
|
-
background: var(--
|
|
533
|
-
.field input::placeholder { color: var(--
|
|
534
|
-
.field input[aria-invalid="true"] { border-color: var(--
|
|
535
|
-
.hint { font-size: 0.8125rem; color: var(--
|
|
536
|
-
.err { font-size: 0.8125rem; color: var(--
|
|
541
|
+
.field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--_eid-border-strong); border-radius: var(--_eid-radius);
|
|
542
|
+
background: var(--_eid-bg); color: var(--_eid-fg); font-family: inherit; font-size: 1rem; line-height: 1.5rem; letter-spacing: 0.02em; text-transform: uppercase; }
|
|
543
|
+
.field input::placeholder { color: var(--_eid-fg-subtle); text-transform: none; }
|
|
544
|
+
.field input[aria-invalid="true"] { border-color: var(--_eid-danger-fg); }
|
|
545
|
+
.hint { font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
|
|
546
|
+
.err { font-size: 0.8125rem; color: var(--_eid-danger-fg); font-weight: 500; }
|
|
537
547
|
.push { margin-top: 16px; }
|
|
538
548
|
.center { text-align: center; }
|
|
539
549
|
.vc { font: 600 2.25rem/2.5rem ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.16em; padding-left: 0.16em; }
|
|
540
550
|
.vc:focus, h2:focus { outline: none; }
|
|
541
|
-
.text { margin-top: 12px; color: var(--
|
|
542
|
-
.pending { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; color: var(--
|
|
551
|
+
.text { margin-top: 12px; color: var(--_eid-fg-muted); }
|
|
552
|
+
.pending { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; color: var(--_eid-fg-muted); }
|
|
543
553
|
.end .btn, .center .btn { margin-top: 24px; }
|
|
544
554
|
.spinner { width: 16px; height: 16px; flex: none; border: 2px solid currentColor; border-right-color: transparent; border-radius: 9999px; animation: spin 0.8s linear infinite; }
|
|
545
555
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
546
|
-
.netwarn { margin-top: 16px; padding: 8px 12px; border: 1px solid var(--
|
|
547
|
-
color: var(--
|
|
548
|
-
.meta { margin-top: 16px; font-size: 0.8125rem; color: var(--
|
|
556
|
+
.netwarn { margin-top: 16px; padding: 8px 12px; border: 1px solid var(--_eid-warning-border); border-radius: var(--_eid-radius); background: var(--_eid-warning-bg);
|
|
557
|
+
color: var(--_eid-warning-fg); font-size: 0.8125rem; }
|
|
558
|
+
.meta { margin-top: 16px; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
|
|
549
559
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; }
|
|
550
560
|
.spinner { animation: none; border-right-color: currentColor; opacity: 0.6; } }
|
|
551
561
|
`;
|
package/dist/button.js
CHANGED
|
@@ -436,87 +436,97 @@ var T = {
|
|
|
436
436
|
minute: "1 minute left"
|
|
437
437
|
}
|
|
438
438
|
};
|
|
439
|
+
var EID_COLOR_TOKENS = [
|
|
440
|
+
["bg", "hsl(0 0% 100%)", "hsl(229 50% 6%)"],
|
|
441
|
+
["bg-subtle", "hsl(210 40% 98%)", "hsl(222 47% 9%)"],
|
|
442
|
+
["bg-muted", "hsl(210 40% 96%)", "hsl(217 33% 13%)"],
|
|
443
|
+
["fg", "hsl(222 47% 11%)", "hsl(210 40% 98%)"],
|
|
444
|
+
["fg-muted", "hsl(215 19% 35%)", "hsl(213 27% 84%)"],
|
|
445
|
+
["fg-subtle", "hsl(215 16% 45%)", "hsl(215 20% 65%)"],
|
|
446
|
+
["border", "hsl(214 32% 91%)", "hsl(217 33% 17%)"],
|
|
447
|
+
["border-strong", "hsl(213 27% 84%)", "hsl(215 25% 27%)"],
|
|
448
|
+
["accent", "oklch(0.534 0.2 259)", "oklch(0.64 0.15 259)"],
|
|
449
|
+
["accent-fg", "#fff", "hsl(229 50% 6%)"],
|
|
450
|
+
["accent-subtle", "oklch(0.97 0.03 259)", "oklch(0.24 0.06 259)"],
|
|
451
|
+
["accent-subtle-fg", "oklch(0.474 0.19 259)", "oklch(0.82 0.1 259)"],
|
|
452
|
+
["ring", "oklch(0.604 0.2 259)", "oklch(0.68 0.15 259)"],
|
|
453
|
+
["warning-bg", "hsl(48 100% 96%)", "hsl(20 60% 10%)"],
|
|
454
|
+
["warning-border", "hsl(45 95% 78%)", "hsl(22 70% 26%)"],
|
|
455
|
+
["warning-fg", "hsl(23 75% 33%)", "hsl(40 92% 65%)"],
|
|
456
|
+
["danger-fg", "hsl(0 74% 42%)", "hsl(0 91% 71%)"]
|
|
457
|
+
];
|
|
458
|
+
var lightVars = EID_COLOR_TOKENS.map(([n, l]) => `--_eid-${n}: var(--eid-${n}, ${l});`).join("\n ");
|
|
459
|
+
var darkVars = EID_COLOR_TOKENS.map(([n, , d]) => `--_eid-${n}: var(--eid-${n}-dark, var(--eid-${n}, ${d}));`).join("\n ");
|
|
439
460
|
var CSS_BASE = `
|
|
440
|
-
:host { display: inline-block;
|
|
441
|
-
|
|
442
|
-
--
|
|
443
|
-
--eid-border: hsl(214 32% 91%); --eid-border-strong: hsl(213 27% 84%);
|
|
444
|
-
--eid-accent: oklch(0.534 0.2 259); --eid-accent-fg: #fff; --eid-accent-subtle: oklch(0.97 0.03 259);
|
|
445
|
-
--eid-accent-subtle-fg: oklch(0.474 0.19 259); --eid-ring: oklch(0.604 0.2 259);
|
|
446
|
-
--eid-warning-bg: hsl(48 100% 96%); --eid-warning-border: hsl(45 95% 78%); --eid-warning-fg: hsl(23 75% 33%);
|
|
447
|
-
--eid-danger-fg: hsl(0 74% 42%); --eid-radius: 6px; --eid-radius-lg: 8px; font-family: inherit; color-scheme: light; }
|
|
461
|
+
:host { display: inline-block; font-family: inherit; color-scheme: light;
|
|
462
|
+
${lightVars}
|
|
463
|
+
--_eid-radius: var(--eid-radius, 6px); --_eid-radius-lg: var(--eid-radius-lg, 8px); }
|
|
448
464
|
button, input { font-family: inherit; }
|
|
449
465
|
`;
|
|
450
466
|
var DARK_CSS = `:host { color-scheme: dark;
|
|
451
|
-
|
|
452
|
-
--eid-fg: hsl(210 40% 98%); --eid-fg-muted: hsl(213 27% 84%); --eid-fg-subtle: hsl(215 20% 65%);
|
|
453
|
-
--eid-border: hsl(217 33% 17%); --eid-border-strong: hsl(215 25% 27%);
|
|
454
|
-
--eid-accent: oklch(0.64 0.15 259); --eid-accent-fg: hsl(229 50% 6%); --eid-accent-subtle: oklch(0.24 0.06 259);
|
|
455
|
-
--eid-accent-subtle-fg: oklch(0.82 0.1 259); --eid-ring: oklch(0.68 0.15 259);
|
|
456
|
-
--eid-warning-bg: hsl(20 60% 10%); --eid-warning-border: hsl(22 70% 26%); --eid-warning-fg: hsl(40 92% 65%);
|
|
457
|
-
--eid-danger-fg: hsl(0 91% 71%); }
|
|
467
|
+
${darkVars} }
|
|
458
468
|
`;
|
|
459
469
|
var CSS_REST = `
|
|
460
470
|
*, *::before, *::after { box-sizing: border-box; }
|
|
461
471
|
[hidden] { display: none !important; }
|
|
462
472
|
h2, p, ol { margin: 0; }
|
|
463
|
-
:focus-visible { outline: 2px solid var(--
|
|
473
|
+
:focus-visible { outline: 2px solid var(--_eid-ring); outline-offset: 2px; }
|
|
464
474
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
465
475
|
.btn { display: inline-flex; width: 100%; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 20px;
|
|
466
|
-
border-radius: var(--
|
|
476
|
+
border-radius: var(--_eid-radius); font-family: inherit; font-weight: 500; font-size: 1rem; line-height: 1.25; text-decoration: none; border: 1px solid transparent; cursor: pointer;
|
|
467
477
|
transition: background-color 160ms cubic-bezier(0,0,0.2,1); }
|
|
468
478
|
.btn:disabled { opacity: 0.6; cursor: progress; }
|
|
469
|
-
.btn-primary { background: var(--
|
|
470
|
-
.btn-primary:hover { background: color-mix(in oklab, var(--
|
|
471
|
-
.btn-outline { background: var(--
|
|
472
|
-
.btn-outline:hover { background: var(--
|
|
479
|
+
.btn-primary { background: var(--_eid-accent); color: var(--_eid-accent-fg); }
|
|
480
|
+
.btn-primary:hover { background: color-mix(in oklab, var(--_eid-accent) 88%, black); }
|
|
481
|
+
.btn-outline { background: var(--_eid-bg); color: var(--_eid-fg); border-color: var(--_eid-border-strong); }
|
|
482
|
+
.btn-outline:hover { background: var(--_eid-bg-muted); }
|
|
473
483
|
.btn-sm { width: auto; min-height: 36px; padding: 0 14px; font-size: 0.8125rem; }
|
|
474
484
|
.trigger { width: auto; }
|
|
475
485
|
.trigger img { width: 20px; height: 20px; border-radius: 4px; }
|
|
476
486
|
.backdrop { position: fixed; inset: 0; z-index: 2147483000; background: hsl(222 47% 11% / 0.5); display: flex; align-items: center;
|
|
477
487
|
justify-content: center; padding: 16px; overflow-y: auto; }
|
|
478
|
-
.dialog { position: relative; width: 100%; max-width: 26rem; max-height: calc(100dvh - 32px); overflow-y: auto; background: var(--
|
|
479
|
-
color: var(--
|
|
488
|
+
.dialog { position: relative; width: 100%; max-width: 26rem; max-height: calc(100dvh - 32px); overflow-y: auto; background: var(--_eid-bg);
|
|
489
|
+
color: var(--_eid-fg); border: 1px solid var(--_eid-border); border-radius: var(--_eid-radius-lg); padding: 24px; font-size: 0.875rem; line-height: 1.5rem; }
|
|
480
490
|
@media (max-width: 639px) { .dialog { padding: 16px; } }
|
|
481
491
|
.head { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
|
|
482
|
-
.head img { width: 28px; height: 28px; border-radius: var(--
|
|
492
|
+
.head img { width: 28px; height: 28px; border-radius: var(--_eid-radius); }
|
|
483
493
|
.head h2 { flex: 1; font-size: 1.125rem; line-height: 1.75rem; font-weight: 600; }
|
|
484
|
-
.x { width: 36px; height: 36px; border: 0; border-radius: var(--
|
|
485
|
-
.x:hover { background: var(--
|
|
486
|
-
.qr { position: relative; width: 256px; max-width: 100%; aspect-ratio: 1; margin: 0 auto; border: 1px solid var(--
|
|
487
|
-
border-radius: var(--
|
|
494
|
+
.x { width: 36px; height: 36px; border: 0; border-radius: var(--_eid-radius); background: transparent; color: var(--_eid-fg-muted); cursor: pointer; font-size: 1.25rem; line-height: 1; }
|
|
495
|
+
.x:hover { background: var(--_eid-bg-muted); color: var(--_eid-fg); }
|
|
496
|
+
.qr { position: relative; width: 256px; max-width: 100%; aspect-ratio: 1; margin: 0 auto; border: 1px solid var(--_eid-border);
|
|
497
|
+
border-radius: var(--_eid-radius-lg); background: #fff; overflow: hidden; }
|
|
488
498
|
.qr svg { display: block; width: 100%; height: 100%; }
|
|
489
499
|
.qr.stale svg { opacity: 0.12; }
|
|
490
500
|
.qr-stale { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
|
|
491
501
|
color: hsl(222 47% 11%); font-weight: 500; }
|
|
492
|
-
.qr-vc { margin-top: 8px; text-align: center; font-size: 0.8125rem; color: var(--
|
|
493
|
-
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.08em; color: var(--
|
|
502
|
+
.qr-vc { margin-top: 8px; text-align: center; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
|
|
503
|
+
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.08em; color: var(--_eid-fg-muted); }
|
|
494
504
|
.steps { margin-top: 16px; padding: 0; list-style: none; counter-reset: st; display: grid; gap: 8px; }
|
|
495
505
|
.steps li { counter-increment: st; display: flex; align-items: center; gap: 12px; }
|
|
496
|
-
.steps li::before { content: counter(st); flex: none; width: 24px; height: 24px; border-radius: 9999px; background: var(--
|
|
497
|
-
color: var(--
|
|
498
|
-
.or { display: flex; align-items: center; gap: 12px; margin: 24px 0; color: var(--
|
|
499
|
-
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--
|
|
506
|
+
.steps li::before { content: counter(st); flex: none; width: 24px; height: 24px; border-radius: 9999px; background: var(--_eid-accent-subtle);
|
|
507
|
+
color: var(--_eid-accent-subtle-fg); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 600; }
|
|
508
|
+
.or { display: flex; align-items: center; gap: 12px; margin: 24px 0; color: var(--_eid-fg-subtle); font-size: 0.8125rem; }
|
|
509
|
+
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--_eid-border); }
|
|
500
510
|
.field { display: grid; gap: 8px; }
|
|
501
511
|
.field label { font-weight: 500; }
|
|
502
|
-
.field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--
|
|
503
|
-
background: var(--
|
|
504
|
-
.field input::placeholder { color: var(--
|
|
505
|
-
.field input[aria-invalid="true"] { border-color: var(--
|
|
506
|
-
.hint { font-size: 0.8125rem; color: var(--
|
|
507
|
-
.err { font-size: 0.8125rem; color: var(--
|
|
512
|
+
.field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--_eid-border-strong); border-radius: var(--_eid-radius);
|
|
513
|
+
background: var(--_eid-bg); color: var(--_eid-fg); font-family: inherit; font-size: 1rem; line-height: 1.5rem; letter-spacing: 0.02em; text-transform: uppercase; }
|
|
514
|
+
.field input::placeholder { color: var(--_eid-fg-subtle); text-transform: none; }
|
|
515
|
+
.field input[aria-invalid="true"] { border-color: var(--_eid-danger-fg); }
|
|
516
|
+
.hint { font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
|
|
517
|
+
.err { font-size: 0.8125rem; color: var(--_eid-danger-fg); font-weight: 500; }
|
|
508
518
|
.push { margin-top: 16px; }
|
|
509
519
|
.center { text-align: center; }
|
|
510
520
|
.vc { font: 600 2.25rem/2.5rem ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.16em; padding-left: 0.16em; }
|
|
511
521
|
.vc:focus, h2:focus { outline: none; }
|
|
512
|
-
.text { margin-top: 12px; color: var(--
|
|
513
|
-
.pending { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; color: var(--
|
|
522
|
+
.text { margin-top: 12px; color: var(--_eid-fg-muted); }
|
|
523
|
+
.pending { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; color: var(--_eid-fg-muted); }
|
|
514
524
|
.end .btn, .center .btn { margin-top: 24px; }
|
|
515
525
|
.spinner { width: 16px; height: 16px; flex: none; border: 2px solid currentColor; border-right-color: transparent; border-radius: 9999px; animation: spin 0.8s linear infinite; }
|
|
516
526
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
517
|
-
.netwarn { margin-top: 16px; padding: 8px 12px; border: 1px solid var(--
|
|
518
|
-
color: var(--
|
|
519
|
-
.meta { margin-top: 16px; font-size: 0.8125rem; color: var(--
|
|
527
|
+
.netwarn { margin-top: 16px; padding: 8px 12px; border: 1px solid var(--_eid-warning-border); border-radius: var(--_eid-radius); background: var(--_eid-warning-bg);
|
|
528
|
+
color: var(--_eid-warning-fg); font-size: 0.8125rem; }
|
|
529
|
+
.meta { margin-top: 16px; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
|
|
520
530
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; }
|
|
521
531
|
.spinner { animation: none; border-right-color: currentColor; opacity: 0.6; } }
|
|
522
532
|
`;
|
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(index_exports, {
|
|
|
52
52
|
buildDeviceLink: () => buildDeviceLink,
|
|
53
53
|
buildRegisterBody: () => buildRegisterBody,
|
|
54
54
|
createEidLogin: () => createEidLogin,
|
|
55
|
+
defaultRateLimit: () => defaultRateLimit,
|
|
55
56
|
deviceLink: () => deviceLink,
|
|
56
57
|
deviceLinkInput: () => deviceLinkInput,
|
|
57
58
|
forwardedClientIp: () => forwardedClientIp,
|
|
@@ -2131,6 +2132,51 @@ function memoryLoginStore() {
|
|
|
2131
2132
|
}
|
|
2132
2133
|
};
|
|
2133
2134
|
}
|
|
2135
|
+
var defaultRateLimit = Object.freeze({
|
|
2136
|
+
push: Object.freeze({ limit: 3, windowMs: 6e4 })
|
|
2137
|
+
});
|
|
2138
|
+
var RATE_KINDS = ["start", "push"];
|
|
2139
|
+
function windowLimiter(limit, windowMs) {
|
|
2140
|
+
const hits = /* @__PURE__ */ new Map();
|
|
2141
|
+
return (key) => {
|
|
2142
|
+
const now = Date.now();
|
|
2143
|
+
const h = (hits.get(key) ?? []).filter((t) => now - t < windowMs);
|
|
2144
|
+
if (hits.size > 1e4) hits.clear();
|
|
2145
|
+
if (h.length >= limit) {
|
|
2146
|
+
hits.set(key, h);
|
|
2147
|
+
return false;
|
|
2148
|
+
}
|
|
2149
|
+
h.push(now);
|
|
2150
|
+
hits.set(key, h);
|
|
2151
|
+
return true;
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
function buildRateLimit(opt) {
|
|
2155
|
+
const extra = typeof opt === "function" ? opt : void 0;
|
|
2156
|
+
const user = opt && typeof opt === "object" ? opt : {};
|
|
2157
|
+
for (const k of Object.keys(user)) {
|
|
2158
|
+
if (!RATE_KINDS.includes(k)) throw new Error(`createEidLogin: rateLimit.${k} \u2014 \u0437\u04E9\u0432\u0445\u04E9\u043D start | push`);
|
|
2159
|
+
}
|
|
2160
|
+
const merged = { ...defaultRateLimit, ...user };
|
|
2161
|
+
const perKind = /* @__PURE__ */ new Map();
|
|
2162
|
+
for (const k of RATE_KINDS) {
|
|
2163
|
+
const r = merged[k];
|
|
2164
|
+
if (r === void 0 || r === false) continue;
|
|
2165
|
+
if (typeof r === "function") {
|
|
2166
|
+
perKind.set(k, r);
|
|
2167
|
+
continue;
|
|
2168
|
+
}
|
|
2169
|
+
if (!(Number.isInteger(r.limit) && r.limit > 0 && r.windowMs > 0)) {
|
|
2170
|
+
throw new Error(`createEidLogin: rateLimit.${k} \u2014 limit (\u0431\u04AF\u0445\u044D\u043B > 0), windowMs (> 0)`);
|
|
2171
|
+
}
|
|
2172
|
+
perKind.set(k, windowLimiter(r.limit, r.windowMs));
|
|
2173
|
+
}
|
|
2174
|
+
return async (key) => {
|
|
2175
|
+
const fn = perKind.get(key.slice(0, key.indexOf(":")));
|
|
2176
|
+
if (fn && !await fn(key)) return false;
|
|
2177
|
+
return extra ? extra(key) : true;
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2134
2180
|
var CALLBACK_PATH = "/auth/eid/callback";
|
|
2135
2181
|
function forwardedClientIp(headers, trustProxy) {
|
|
2136
2182
|
if (trustProxy === false || trustProxy === 0) return void 0;
|
|
@@ -2230,17 +2276,7 @@ function createEidLogin(opts) {
|
|
|
2230
2276
|
const successRedirect = opts.successRedirect ?? "/";
|
|
2231
2277
|
const failureRedirect = opts.failureRedirect ?? "/";
|
|
2232
2278
|
const onError = opts.onError ?? ((e) => console.error("eid-login:", e instanceof Error ? e.message : e));
|
|
2233
|
-
const
|
|
2234
|
-
const rateLimit = opts.rateLimit ?? ((key) => {
|
|
2235
|
-
if (!key.startsWith("push:")) return true;
|
|
2236
|
-
const now = Date.now();
|
|
2237
|
-
const hits = (pushHits.get(key) ?? []).filter((t) => now - t < 6e4);
|
|
2238
|
-
if (pushHits.size > 1e4) pushHits.clear();
|
|
2239
|
-
if (hits.length >= 3) return false;
|
|
2240
|
-
hits.push(now);
|
|
2241
|
-
pushHits.set(key, hits);
|
|
2242
|
-
return true;
|
|
2243
|
-
});
|
|
2279
|
+
const rateLimit = buildRateLimit(opts.rateLimit);
|
|
2244
2280
|
const displayText = (opts.displayText ?? `${client.rpName}-\u0434 \u043D\u044D\u0432\u0442\u0440\u044D\u0445`).slice(0, 60);
|
|
2245
2281
|
const interactions = [{ type: "displayTextAndPIN", displayText60: displayText }];
|
|
2246
2282
|
const json = (status2, body, extra = {}) => ({
|
|
@@ -2526,6 +2562,7 @@ function createEidLogin(opts) {
|
|
|
2526
2562
|
buildDeviceLink,
|
|
2527
2563
|
buildRegisterBody,
|
|
2528
2564
|
createEidLogin,
|
|
2565
|
+
defaultRateLimit,
|
|
2529
2566
|
deviceLink,
|
|
2530
2567
|
deviceLinkInput,
|
|
2531
2568
|
forwardedClientIp,
|
package/dist/index.d.cts
CHANGED
|
@@ -1092,6 +1092,20 @@ interface EidLoginSuccess {
|
|
|
1092
1092
|
data?: unknown;
|
|
1093
1093
|
headers?: Record<string, string | string[]>;
|
|
1094
1094
|
}
|
|
1095
|
+
/** Хязгаарлагч: true — зөвшөөрөх, false — 429. */
|
|
1096
|
+
type EidLoginRateLimitFn = (key: string) => boolean | Promise<boolean>;
|
|
1097
|
+
/** Нэг төрлийн хязгаар: санах ойн гулсах цонх, өөрийн функц, эсвэл `false` (хязгааргүй). */
|
|
1098
|
+
type EidLoginRateRule = {
|
|
1099
|
+
limit: number;
|
|
1100
|
+
windowMs: number;
|
|
1101
|
+
} | EidLoginRateLimitFn | false;
|
|
1102
|
+
/** Төрөл тус бүрийн хязгаар: `start` — `start:<ip>`, `push` — `push:<idCode>`. */
|
|
1103
|
+
interface EidLoginRateLimits {
|
|
1104
|
+
start?: EidLoginRateRule;
|
|
1105
|
+
push?: EidLoginRateRule;
|
|
1106
|
+
}
|
|
1107
|
+
/** Анхдагч хязгаар — `rateLimit`-ийн объектод өгөөгүй төрөл энүүгээр үлдэнэ. `{ ...defaultRateLimit, start: … }`. */
|
|
1108
|
+
declare const defaultRateLimit: Readonly<EidLoginRateLimits>;
|
|
1095
1109
|
interface EidLoginOptions {
|
|
1096
1110
|
client: EidClient;
|
|
1097
1111
|
/**
|
|
@@ -1123,10 +1137,15 @@ interface EidLoginOptions {
|
|
|
1123
1137
|
domain?: string;
|
|
1124
1138
|
};
|
|
1125
1139
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1140
|
+
* Хязгаарлалт. Түлхүүр: `start:<ip>` (ip мэдэгдэж байвал), `push:<idCode>`; false → 429.
|
|
1141
|
+
* Анхдагч {@link defaultRateLimit}: push-д нэг дугаарт 60 секундэд 3 (санах ойд) — MFA fatigue-ээс.
|
|
1142
|
+
*
|
|
1143
|
+
* - Объект `{ start?, push? }` — анхдагчтай НЭГТГЭГДЭНЭ: өгсөн төрөл анхдагчийг дарна, өгөөгүй нь анхдагчаараа.
|
|
1144
|
+
* Утга: `{ limit, windowMs }` (санах ойн гулсах цонх), функц (Redis г.м.) эсвэл `false` (тухайн төрлийг хязгаарлахгүй).
|
|
1145
|
+
* - Функц `(key) => boolean` — бүх түлхүүрт дуудагдана, анхдагч хязгаар ДЭЭР НЭМЭГДЭНЭ (хоёулаа зөвшөөрөх ёстой).
|
|
1146
|
+
* Анхдагчийг сулруулах/солих бол объект хэлбэрээр тухайн төрлийг өг.
|
|
1128
1147
|
*/
|
|
1129
|
-
rateLimit?:
|
|
1148
|
+
rateLimit?: EidLoginRateLimitFn | EidLoginRateLimits;
|
|
1130
1149
|
/**
|
|
1131
1150
|
* `handle(Request)`-ийн клиент IP (`start:<ip>` хязгаарт). Web-standard `Request`-д IP байдаггүй тул эсвэл энийг, эсвэл
|
|
1132
1151
|
* {@link EidLoginOptions.trustProxy}-г өг; аль нь ч үгүй бол IP-ээр хязгаарлахгүй (анхны дуудлагад onError-оор анхааруулна).
|
|
@@ -1178,4 +1197,4 @@ declare function withReturnHint(callbackUrl: string, userAgent: string): string;
|
|
|
1178
1197
|
*/
|
|
1179
1198
|
declare function createEidLogin(opts: EidLoginOptions): EidLogin;
|
|
1180
1199
|
|
|
1181
|
-
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertChoiceOptions, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CompletedForRegistration, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, type EidLogin, type EidLoginContext, type EidLoginIdentity, type EidLoginMode, type EidLoginOptions, type EidLoginRequest, type EidLoginResponse, type EidLoginStore, type EidLoginSuccess, type EidLoginTx, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PDF_SIGNATURE_REGISTER_FIELDS, type PdfDigestInput, type PdfDigestSession, type PdfDigestSigner, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, type RegisterSignatureInput, type RegisterSignatureResult, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type Signer, type SignerRef, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, buildRegisterBody, createEidLogin, deviceLinkInput, forwardedClientIp, interactionsToBase64, memoryLoginStore, parseSessionResult, parseSubject, personMatchesCertificate, qrDeviceLinkTicker, randomChallenge, registrationFromCompleted, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates, withReturnHint };
|
|
1200
|
+
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertChoiceOptions, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CompletedForRegistration, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, type EidLogin, type EidLoginContext, type EidLoginIdentity, type EidLoginMode, type EidLoginOptions, type EidLoginRateLimitFn, type EidLoginRateLimits, type EidLoginRateRule, type EidLoginRequest, type EidLoginResponse, type EidLoginStore, type EidLoginSuccess, type EidLoginTx, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PDF_SIGNATURE_REGISTER_FIELDS, type PdfDigestInput, type PdfDigestSession, type PdfDigestSigner, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, type RegisterSignatureInput, type RegisterSignatureResult, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type Signer, type SignerRef, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, buildRegisterBody, createEidLogin, defaultRateLimit, deviceLinkInput, forwardedClientIp, interactionsToBase64, memoryLoginStore, parseSessionResult, parseSubject, personMatchesCertificate, qrDeviceLinkTicker, randomChallenge, registrationFromCompleted, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates, withReturnHint };
|
package/dist/index.d.ts
CHANGED
|
@@ -1092,6 +1092,20 @@ interface EidLoginSuccess {
|
|
|
1092
1092
|
data?: unknown;
|
|
1093
1093
|
headers?: Record<string, string | string[]>;
|
|
1094
1094
|
}
|
|
1095
|
+
/** Хязгаарлагч: true — зөвшөөрөх, false — 429. */
|
|
1096
|
+
type EidLoginRateLimitFn = (key: string) => boolean | Promise<boolean>;
|
|
1097
|
+
/** Нэг төрлийн хязгаар: санах ойн гулсах цонх, өөрийн функц, эсвэл `false` (хязгааргүй). */
|
|
1098
|
+
type EidLoginRateRule = {
|
|
1099
|
+
limit: number;
|
|
1100
|
+
windowMs: number;
|
|
1101
|
+
} | EidLoginRateLimitFn | false;
|
|
1102
|
+
/** Төрөл тус бүрийн хязгаар: `start` — `start:<ip>`, `push` — `push:<idCode>`. */
|
|
1103
|
+
interface EidLoginRateLimits {
|
|
1104
|
+
start?: EidLoginRateRule;
|
|
1105
|
+
push?: EidLoginRateRule;
|
|
1106
|
+
}
|
|
1107
|
+
/** Анхдагч хязгаар — `rateLimit`-ийн объектод өгөөгүй төрөл энүүгээр үлдэнэ. `{ ...defaultRateLimit, start: … }`. */
|
|
1108
|
+
declare const defaultRateLimit: Readonly<EidLoginRateLimits>;
|
|
1095
1109
|
interface EidLoginOptions {
|
|
1096
1110
|
client: EidClient;
|
|
1097
1111
|
/**
|
|
@@ -1123,10 +1137,15 @@ interface EidLoginOptions {
|
|
|
1123
1137
|
domain?: string;
|
|
1124
1138
|
};
|
|
1125
1139
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1140
|
+
* Хязгаарлалт. Түлхүүр: `start:<ip>` (ip мэдэгдэж байвал), `push:<idCode>`; false → 429.
|
|
1141
|
+
* Анхдагч {@link defaultRateLimit}: push-д нэг дугаарт 60 секундэд 3 (санах ойд) — MFA fatigue-ээс.
|
|
1142
|
+
*
|
|
1143
|
+
* - Объект `{ start?, push? }` — анхдагчтай НЭГТГЭГДЭНЭ: өгсөн төрөл анхдагчийг дарна, өгөөгүй нь анхдагчаараа.
|
|
1144
|
+
* Утга: `{ limit, windowMs }` (санах ойн гулсах цонх), функц (Redis г.м.) эсвэл `false` (тухайн төрлийг хязгаарлахгүй).
|
|
1145
|
+
* - Функц `(key) => boolean` — бүх түлхүүрт дуудагдана, анхдагч хязгаар ДЭЭР НЭМЭГДЭНЭ (хоёулаа зөвшөөрөх ёстой).
|
|
1146
|
+
* Анхдагчийг сулруулах/солих бол объект хэлбэрээр тухайн төрлийг өг.
|
|
1128
1147
|
*/
|
|
1129
|
-
rateLimit?:
|
|
1148
|
+
rateLimit?: EidLoginRateLimitFn | EidLoginRateLimits;
|
|
1130
1149
|
/**
|
|
1131
1150
|
* `handle(Request)`-ийн клиент IP (`start:<ip>` хязгаарт). Web-standard `Request`-д IP байдаггүй тул эсвэл энийг, эсвэл
|
|
1132
1151
|
* {@link EidLoginOptions.trustProxy}-г өг; аль нь ч үгүй бол IP-ээр хязгаарлахгүй (анхны дуудлагад onError-оор анхааруулна).
|
|
@@ -1178,4 +1197,4 @@ declare function withReturnHint(callbackUrl: string, userAgent: string): string;
|
|
|
1178
1197
|
*/
|
|
1179
1198
|
declare function createEidLogin(opts: EidLoginOptions): EidLogin;
|
|
1180
1199
|
|
|
1181
|
-
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertChoiceOptions, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CompletedForRegistration, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, type EidLogin, type EidLoginContext, type EidLoginIdentity, type EidLoginMode, type EidLoginOptions, type EidLoginRequest, type EidLoginResponse, type EidLoginStore, type EidLoginSuccess, type EidLoginTx, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PDF_SIGNATURE_REGISTER_FIELDS, type PdfDigestInput, type PdfDigestSession, type PdfDigestSigner, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, type RegisterSignatureInput, type RegisterSignatureResult, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type Signer, type SignerRef, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, buildRegisterBody, createEidLogin, deviceLinkInput, forwardedClientIp, interactionsToBase64, memoryLoginStore, parseSessionResult, parseSubject, personMatchesCertificate, qrDeviceLinkTicker, randomChallenge, registrationFromCompleted, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates, withReturnHint };
|
|
1200
|
+
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertChoiceOptions, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CompletedForRegistration, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EidClient, type EidLogin, type EidLoginContext, type EidLoginIdentity, type EidLoginMode, type EidLoginOptions, type EidLoginRateLimitFn, type EidLoginRateLimits, type EidLoginRateRule, type EidLoginRequest, type EidLoginResponse, type EidLoginStore, type EidLoginSuccess, type EidLoginTx, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PDF_SIGNATURE_REGISTER_FIELDS, type PdfDigestInput, type PdfDigestSession, type PdfDigestSigner, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, type RegisterSignatureInput, type RegisterSignatureResult, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type Signer, type SignerRef, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, buildRegisterBody, createEidLogin, defaultRateLimit, deviceLinkInput, forwardedClientIp, interactionsToBase64, memoryLoginStore, parseSessionResult, parseSubject, personMatchesCertificate, qrDeviceLinkTicker, randomChallenge, registrationFromCompleted, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates, withReturnHint };
|
package/dist/index.js
CHANGED
|
@@ -1899,6 +1899,51 @@ function memoryLoginStore() {
|
|
|
1899
1899
|
}
|
|
1900
1900
|
};
|
|
1901
1901
|
}
|
|
1902
|
+
var defaultRateLimit = Object.freeze({
|
|
1903
|
+
push: Object.freeze({ limit: 3, windowMs: 6e4 })
|
|
1904
|
+
});
|
|
1905
|
+
var RATE_KINDS = ["start", "push"];
|
|
1906
|
+
function windowLimiter(limit, windowMs) {
|
|
1907
|
+
const hits = /* @__PURE__ */ new Map();
|
|
1908
|
+
return (key) => {
|
|
1909
|
+
const now = Date.now();
|
|
1910
|
+
const h = (hits.get(key) ?? []).filter((t) => now - t < windowMs);
|
|
1911
|
+
if (hits.size > 1e4) hits.clear();
|
|
1912
|
+
if (h.length >= limit) {
|
|
1913
|
+
hits.set(key, h);
|
|
1914
|
+
return false;
|
|
1915
|
+
}
|
|
1916
|
+
h.push(now);
|
|
1917
|
+
hits.set(key, h);
|
|
1918
|
+
return true;
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
function buildRateLimit(opt) {
|
|
1922
|
+
const extra = typeof opt === "function" ? opt : void 0;
|
|
1923
|
+
const user = opt && typeof opt === "object" ? opt : {};
|
|
1924
|
+
for (const k of Object.keys(user)) {
|
|
1925
|
+
if (!RATE_KINDS.includes(k)) throw new Error(`createEidLogin: rateLimit.${k} \u2014 \u0437\u04E9\u0432\u0445\u04E9\u043D start | push`);
|
|
1926
|
+
}
|
|
1927
|
+
const merged = { ...defaultRateLimit, ...user };
|
|
1928
|
+
const perKind = /* @__PURE__ */ new Map();
|
|
1929
|
+
for (const k of RATE_KINDS) {
|
|
1930
|
+
const r = merged[k];
|
|
1931
|
+
if (r === void 0 || r === false) continue;
|
|
1932
|
+
if (typeof r === "function") {
|
|
1933
|
+
perKind.set(k, r);
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (!(Number.isInteger(r.limit) && r.limit > 0 && r.windowMs > 0)) {
|
|
1937
|
+
throw new Error(`createEidLogin: rateLimit.${k} \u2014 limit (\u0431\u04AF\u0445\u044D\u043B > 0), windowMs (> 0)`);
|
|
1938
|
+
}
|
|
1939
|
+
perKind.set(k, windowLimiter(r.limit, r.windowMs));
|
|
1940
|
+
}
|
|
1941
|
+
return async (key) => {
|
|
1942
|
+
const fn = perKind.get(key.slice(0, key.indexOf(":")));
|
|
1943
|
+
if (fn && !await fn(key)) return false;
|
|
1944
|
+
return extra ? extra(key) : true;
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1902
1947
|
var CALLBACK_PATH = "/auth/eid/callback";
|
|
1903
1948
|
function forwardedClientIp(headers, trustProxy) {
|
|
1904
1949
|
if (trustProxy === false || trustProxy === 0) return void 0;
|
|
@@ -1998,17 +2043,7 @@ function createEidLogin(opts) {
|
|
|
1998
2043
|
const successRedirect = opts.successRedirect ?? "/";
|
|
1999
2044
|
const failureRedirect = opts.failureRedirect ?? "/";
|
|
2000
2045
|
const onError = opts.onError ?? ((e) => console.error("eid-login:", e instanceof Error ? e.message : e));
|
|
2001
|
-
const
|
|
2002
|
-
const rateLimit = opts.rateLimit ?? ((key) => {
|
|
2003
|
-
if (!key.startsWith("push:")) return true;
|
|
2004
|
-
const now = Date.now();
|
|
2005
|
-
const hits = (pushHits.get(key) ?? []).filter((t) => now - t < 6e4);
|
|
2006
|
-
if (pushHits.size > 1e4) pushHits.clear();
|
|
2007
|
-
if (hits.length >= 3) return false;
|
|
2008
|
-
hits.push(now);
|
|
2009
|
-
pushHits.set(key, hits);
|
|
2010
|
-
return true;
|
|
2011
|
-
});
|
|
2046
|
+
const rateLimit = buildRateLimit(opts.rateLimit);
|
|
2012
2047
|
const displayText = (opts.displayText ?? `${client.rpName}-\u0434 \u043D\u044D\u0432\u0442\u0440\u044D\u0445`).slice(0, 60);
|
|
2013
2048
|
const interactions = [{ type: "displayTextAndPIN", displayText60: displayText }];
|
|
2014
2049
|
const json = (status2, body, extra = {}) => ({
|
|
@@ -2293,6 +2328,7 @@ export {
|
|
|
2293
2328
|
buildDeviceLink,
|
|
2294
2329
|
buildRegisterBody,
|
|
2295
2330
|
createEidLogin,
|
|
2331
|
+
defaultRateLimit,
|
|
2296
2332
|
deviceLink,
|
|
2297
2333
|
deviceLinkInput,
|
|
2298
2334
|
forwardedClientIp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gerege-systems/eid-mongolia-sdk",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|