@zoreal/oauth2-js 0.1.7 → 0.1.9
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 +91 -17
- package/dist/index.cjs +1195 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -3
- package/dist/index.d.ts +67 -3
- package/dist/index.js +1193 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -89,11 +89,8 @@ const handle = startLogin({
|
|
|
89
89
|
flow: 'auth-code',
|
|
90
90
|
clientId: 'ast_your_asset_id',
|
|
91
91
|
scope: 'openid email profile.name',
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// s.cancel on your close button. Every callback carries all of it.
|
|
95
|
-
renderPairing(s);
|
|
96
|
-
},
|
|
92
|
+
// The QR, its live status, the countdown and the cancel wiring are drawn
|
|
93
|
+
// by this package. Nothing to render, nothing to translate.
|
|
97
94
|
});
|
|
98
95
|
|
|
99
96
|
const { code, code_verifier, nonce } = await handle.promise;
|
|
@@ -112,10 +109,7 @@ await fetch('/api/auth/zoreal', {
|
|
|
112
109
|
```ts
|
|
113
110
|
import { startLogin } from '@zoreal/oauth2-js';
|
|
114
111
|
|
|
115
|
-
const handle = startLogin({
|
|
116
|
-
clientId: 'ast_your_asset_id',
|
|
117
|
-
onState: (s) => renderPairing(s),
|
|
118
|
-
});
|
|
112
|
+
const handle = startLogin({ clientId: 'ast_your_asset_id' });
|
|
119
113
|
|
|
120
114
|
const { credential } = await handle.promise;
|
|
121
115
|
// `credential` is an ID token carrying a stable per-user identifier (`sub`)
|
|
@@ -124,10 +118,71 @@ const { credential } = await handle.promise;
|
|
|
124
118
|
// JWKS before trusting it.
|
|
125
119
|
```
|
|
126
120
|
|
|
127
|
-
On desktop
|
|
128
|
-
phone and approves in the ZOREAL ID app. On a phone
|
|
129
|
-
app directly through the pairing link
|
|
130
|
-
|
|
121
|
+
On desktop this package opens the [pairing modal](#the-pairing-modal); the user
|
|
122
|
+
scans the QR with their phone and approves in the ZOREAL ID app. On a phone it
|
|
123
|
+
skips the QR and opens the app directly through the pairing link. Either way
|
|
124
|
+
your page just awaits `handle.promise`.
|
|
125
|
+
|
|
126
|
+
## The pairing modal
|
|
127
|
+
|
|
128
|
+
On desktop a QR sign-in cannot complete unless something puts the pairing code
|
|
129
|
+
on screen, so this package does it. `startLogin` mounts a dialog, keeps it in
|
|
130
|
+
step with the pairing, and takes it down when the flow settles. You render
|
|
131
|
+
nothing.
|
|
132
|
+
|
|
133
|
+
| | |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| **Mobile** | No QR. The pairing link opens the ZOREAL ID app and the modal never appears. Force one or the other with `display: 'qr'` / `'link'`. |
|
|
136
|
+
| **Live status** | Copy and title follow the pairing: waiting for a scan, then waiting for approval once the code is claimed (the spent QR blurs out behind a phone glyph). |
|
|
137
|
+
| **Countdown** | Counts down to expiry, turning amber under 20s. Reads the clock each tick rather than decrementing, so a backgrounded tab comes back honest. |
|
|
138
|
+
| **Timeout** | Closes and cancels at zero. Defaults to 120s; override with `pairingTimeoutMs`. The provider's own expiry wins when it is shorter. |
|
|
139
|
+
| **Cancel** | The X, the Cancel button, `Escape`, clicking outside and the timeout are one behaviour: abort the poll, close, reject the promise with `AbortError`. |
|
|
140
|
+
| **No ZOREAL ID yet** | A footer says the same code also installs the app. Without it the panel reads as "scan this with something I do not have", and the flow dead-ends at the one moment it can still be recovered. |
|
|
141
|
+
| **Themes** | `theme: 'auto'` (default) follows `prefers-color-scheme`; `'light'` and `'dark'` force it. The QR well stays white in dark mode on purpose — an inverted QR fails on a good number of phone cameras. |
|
|
142
|
+
| **Language** | Ships its own copy in 39 locales. Pass `locale` and the modal matches the page it opened on; omit it and it follows the browser's preference list, English as the floor. Arabic, Hebrew and Urdu flip the dialog to RTL. |
|
|
143
|
+
| **Accessibility** | `role="dialog"`, `aria-modal`, labelled by its title, focus moved in on open, scroll locked, visible focus rings, and a `prefers-reduced-motion` fallback. |
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
startLogin({
|
|
147
|
+
clientId: 'ast_your_asset_id',
|
|
148
|
+
locale: 'sv', // omit to follow the browser
|
|
149
|
+
theme: 'auto', // 'light' | 'dark'
|
|
150
|
+
pairingTimeoutMs: 120_000,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Styling is a single `<style>` tag injected once, every selector prefixed `zrl-`
|
|
155
|
+
and scoped to the dialog. There is no CSS file to import and nothing to add to
|
|
156
|
+
your build, because a required import step is a required support ticket. The
|
|
157
|
+
DOM is built with `createElement`, never `innerHTML`: this renders on someone
|
|
158
|
+
else's sign-in page.
|
|
159
|
+
|
|
160
|
+
### Languages
|
|
161
|
+
|
|
162
|
+
Arabic · Bengali · Bosnian · Bulgarian · Chinese (Simplified) · Chinese (Traditional) ·
|
|
163
|
+
Croatian · Czech · Danish · Dutch · English · Filipino/Tagalog · Finnish · French ·
|
|
164
|
+
German · Greek · Hebrew · Hindi · Hungarian · Indonesian · Italian · Japanese · Korean ·
|
|
165
|
+
Malay · Norwegian · Polish · Portuguese · Portuguese (Brazil) · Romanian · Russian ·
|
|
166
|
+
Serbian · Spanish · Spanish (Latin America) · Swedish · Thai · Turkish · Ukrainian ·
|
|
167
|
+
Urdu · Vietnamese
|
|
168
|
+
|
|
169
|
+
Resolution handles the cases that usually get missed: `zh` splits by script rather
|
|
170
|
+
than region, `es-MX` and the other Latin American regions resolve to Latin American
|
|
171
|
+
Spanish instead of peninsular, `pt-BR` stays out of European Portuguese, and the
|
|
172
|
+
superseded codes (`iw`, `in`) plus `nb`/`nn` and `fil` reach the right table.
|
|
173
|
+
|
|
174
|
+
### Rendering it yourself
|
|
175
|
+
|
|
176
|
+
Framework wrappers and anyone with their own design system opt out:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
startLogin({ clientId: 'ast_your_asset_id', pairingUI: 'none' });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`onState` then carries everything you need on every state: `qrUrl`, `pairUrl`,
|
|
183
|
+
`status`, `expiresIn` and `cancel`. Render `qrUrl` in an `<img>`; do not draw
|
|
184
|
+
your own. `mountPairingModal` is also exported if you want the real dialog but
|
|
185
|
+
driven on your own terms.
|
|
131
186
|
|
|
132
187
|
## The handle
|
|
133
188
|
|
|
@@ -254,7 +309,6 @@ key, `user` a presence/unlock gesture, `face` a face biometric. `zoreal.live` is
|
|
|
254
309
|
const handle = startLogin({
|
|
255
310
|
clientId: 'ast_your_asset_id',
|
|
256
311
|
acr_values: 'zoreal.live', // the app now makes the holder pass a face capture
|
|
257
|
-
onState: (s) => renderPairing(s),
|
|
258
312
|
});
|
|
259
313
|
```
|
|
260
314
|
|
|
@@ -311,6 +365,8 @@ enforced where enforcement counts: on your backend, against the verified token.
|
|
|
311
365
|
| `generateVerifier()` / `challengeS256(v)` / `generateState()` | PKCE and state material, S256 only |
|
|
312
366
|
| `unsafeClaims(idToken)` | reads claims without verifying. Convenience only; verification happens server-side |
|
|
313
367
|
| `isMobileUserAgent()` | whether this user agent gets the app link rather than a QR |
|
|
368
|
+
| `mountPairingModal(state, { onCancel, locale?, theme?, timeoutMs? })` | mounts the dialog yourself, for `pairingUI: 'none'` callers who still want the real one. Returns `{ update, close }`, or `null` outside a browser |
|
|
369
|
+
| `DEFAULT_PAIRING_TIMEOUT_MS` | `120000`, the modal's default cap |
|
|
314
370
|
|
|
315
371
|
Errors: `OAuthFlowError` (the provider refused; `error` is the OAuth code,
|
|
316
372
|
`description` is the provider's reason verbatim) and `FlowAbandonedError` (a
|
|
@@ -318,10 +374,17 @@ human outcome: `reason.type` is `request_denied`, `request_expired`,
|
|
|
318
374
|
`enrolment_abandoned`, or `unknown` for failures that never reached the
|
|
319
375
|
provider). `cancel()` rejects with a `DOMException` named `AbortError`.
|
|
320
376
|
|
|
377
|
+
`startLogin` options controlling the built-in modal: `pairingUI`
|
|
378
|
+
(`'modal'` default, `'none'` to render your own), `theme` (`'auto'` default,
|
|
379
|
+
`'light'`, `'dark'`), `pairingTimeoutMs` (120000 default), and `locale`, which
|
|
380
|
+
is sent to the provider AND picks the modal's own language. See
|
|
381
|
+
[The pairing modal](#the-pairing-modal).
|
|
382
|
+
|
|
321
383
|
All types are exported: `PairingState`, `ZorealCredentialResponse`,
|
|
322
384
|
`ZorealCodeResponse`, `StartLoginOptions`, `BrowserDirectLoginOptions`,
|
|
323
385
|
`AuthCodeLoginOptions`, `LoginHandle`, `ErrorCode`, `NonOAuthError`,
|
|
324
|
-
`SelectBy`, `AcrValue`,
|
|
386
|
+
`SelectBy`, `AcrValue`, `PairingUI`, `ZorealTheme`, `PairingModalHandle`,
|
|
387
|
+
`PairingModalOptions`, and the wire shapes.
|
|
325
388
|
|
|
326
389
|
## Error reference
|
|
327
390
|
|
|
@@ -427,6 +490,9 @@ export function mountZorealButton(root: HTMLElement) {
|
|
|
427
490
|
flow: 'auth-code',
|
|
428
491
|
clientId: 'ast_your_asset_id',
|
|
429
492
|
scope: 'openid email profile.name',
|
|
493
|
+
// This example draws its own panel, so it opts out of the built-in
|
|
494
|
+
// modal. Drop these two lines and delete renderPairing to use it.
|
|
495
|
+
pairingUI: 'none',
|
|
430
496
|
onState: renderPairing,
|
|
431
497
|
});
|
|
432
498
|
|
|
@@ -478,8 +544,14 @@ identical in the browser.
|
|
|
478
544
|
|
|
479
545
|
A wrapper owns exactly two things: calling `startLogin` on the user's
|
|
480
546
|
gesture, and rendering what `onState` carries. Everything else - PKCE, state,
|
|
481
|
-
nonce, poll cadence, cancellation - is this package's job.
|
|
482
|
-
|
|
547
|
+
nonce, poll cadence, cancellation - is this package's job.
|
|
548
|
+
|
|
549
|
+
Decide first whether you are rendering the pairing UI at all. If the built-in
|
|
550
|
+
modal is what you want, drop `onState` and there is nothing to build. If you
|
|
551
|
+
are drawing your own, pass `pairingUI: 'none'` or your users get two QRs on
|
|
552
|
+
screen. The example below draws its own, so it opts out.
|
|
553
|
+
|
|
554
|
+
A minimal Vue 3 composable:
|
|
483
555
|
|
|
484
556
|
```ts
|
|
485
557
|
// useZorealLogin.ts
|
|
@@ -500,6 +572,8 @@ export function useZorealLogin(clientId: string) {
|
|
|
500
572
|
active?.cancel();
|
|
501
573
|
const handle = startLogin({
|
|
502
574
|
clientId,
|
|
575
|
+
pairingUI: 'none', // this wrapper renders its own
|
|
576
|
+
|
|
503
577
|
onState: (s) => (pairing.value = s),
|
|
504
578
|
});
|
|
505
579
|
active = handle;
|