@zoreal/oauth2-react 0.1.5 → 0.1.7

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 CHANGED
@@ -213,6 +213,136 @@ requested it. That is the raw material for a KYC check. You remain the controlle
213
213
  store, use, and justify collecting it; ZOREAL provides the verified attributes,
214
214
  not a regulatory determination.
215
215
 
216
+ ## Assurance levels — `acr` and requiring a liveness check
217
+
218
+ ### What `acr` is
219
+
220
+ `acr` is an OpenID Connect standard claim — *Authentication Context Class
221
+ Reference*. It is a string in the ID token that says **how strongly this login
222
+ was authenticated**. Every ZOREAL login carries one. `sub` tells you *who* (a
223
+ stable, pairwise identifier for this person at your site); `acr` tells you *how
224
+ sure ZOREAL is that the person is really there for this login*. A stolen,
225
+ unlocked phone can still produce a `sub`; it cannot produce a fresh
226
+ `zoreal.live`.
227
+
228
+ This SDK is the **request** side of `acr`: it is where you ask for a level, which
229
+ decides what the holder's ZOREAL ID app makes them do. Whether that level was
230
+ actually reached is decided by the signed token and checked on your backend
231
+ (below).
232
+
233
+ ### The three levels
234
+
235
+ Weakest to strongest. `acr` reports what actually happened, never what was asked.
236
+
237
+ | `acr` | What the holder did | `amr` | Proves | Does **not** prove |
238
+ |---|---|---|---|---|
239
+ | `zoreal.session` | Nothing — a returning holder resumed silently from an existing ZOREAL session, no phone interaction | `[]` | Continuity | Presence |
240
+ | `zoreal.device` | Approved on their enrolled phone: a secure-element key signature released by a local biometric/passcode unlock | `["hwk","user"]` | Possession of the enrolled device **and** a local unlock | That a live face was captured for *this* login |
241
+ | `zoreal.live` | The above **plus** a fresh face capture this login — a flash-plus-zoom video scored for presentation attacks and screen replay, matched 1:1 to the government document read at enrolment | `["hwk","face","user"]` | A live, real, unique human, verified to be the enrolled person, **at the moment of this login** | — (strongest) |
242
+
243
+ `amr` (*Authentication Methods References*) lists the factors: `hwk` a hardware
244
+ key, `user` a presence/unlock gesture, `face` a face biometric. `zoreal.live` is
245
+ `zoreal.device` with `face` added. The default is `zoreal.device` — a login that
246
+ asks for nothing still requires the enrolled phone and a local unlock.
247
+
248
+ ### When to request which
249
+
250
+ - **`zoreal.device`** (the default): a forum, a community, a normal login. Pass
251
+ no `acr_values` at all.
252
+ - **`zoreal.live`**: a bank onboarding, a high-value transaction, an age-gated
253
+ purchase, a first login, a "confirm it is really you" step. Anywhere a fresh,
254
+ unforgeable proof of the live, right human is worth the few seconds a capture
255
+ costs.
256
+ - **`zoreal.session`** is never something you *request*; it is the silent
257
+ convenience re-auth (`prompt: 'none'`) a returning holder gets at a site they
258
+ have already consented to.
259
+
260
+ ### Requesting it here
261
+
262
+ `acr_values` is a request option on `useZorealLogin`, `useZorealAutoLogin` and
263
+ `<ZorealLogin>`. Its type is `AcrValue | AcrValue[]` where
264
+ `AcrValue = 'zoreal.live' | 'zoreal.device' | 'zoreal.session'`.
265
+
266
+ ```tsx
267
+ const login = useZorealLogin({
268
+ flow: 'auth-code',
269
+ acr_values: 'zoreal.live', // the app now makes the holder pass a face capture
270
+ onSuccess: async ({ code, code_verifier, nonce }) => {
271
+ // Post all three to your backend, which verifies the signed acr claim.
272
+ },
273
+ onNonOAuthError: (e) => {
274
+ // A device that cannot meet the requirement (e.g. no capture on the
275
+ // platform, or the holder declines) arrives here as
276
+ // { type: 'request_denied' }, with the provider's reason in e.description.
277
+ },
278
+ });
279
+ ```
280
+
281
+ In `browser-direct` mode the resolved level comes back on the credential
282
+ response as `response.acr`, parsed from the ID token; the token stays the
283
+ authority.
284
+
285
+ ### Requesting is not verifying — the rule that matters
286
+
287
+ `acr_values` here is **advisory**. It shapes what the holder is asked to do; it
288
+ is not a security control, because a browser is attacker-controlled and a value
289
+ that only travels through it proves nothing. The proof is the **signed `acr`
290
+ claim** in the ID token, minted by ZOREAL, and it must be verified **on your
291
+ backend**:
292
+
293
+ - Auth-code flow: your backend receives the code and, after exchanging it, reads
294
+ the token's `acr`. The ZOREAL backend libraries (`zoreal-oauth2` for Ruby and
295
+ its siblings for Node, Python, PHP, Go, JVM and .NET) take a required-acr
296
+ argument at exchange for exactly this, refusing a token below the level.
297
+ - Browser-direct flow: the `credential` is an ID token you still verify
298
+ server-side against the JWKS, `acr` included.
299
+
300
+ With the Ruby backend library the required-acr argument is `acr:`; every sibling
301
+ takes the same argument in its own idiom:
302
+
303
+ ```ruby
304
+ login = ZOREAL_OAUTH.authenticate(
305
+ code:, code_verifier:, nonce:,
306
+ acr: 'zoreal.live' # refuses the login unless the signed token says zoreal.live
307
+ )
308
+ login.acr # "zoreal.live" — what actually happened, read from the signed token
309
+ ```
310
+
311
+ **A relying party that requests `zoreal.live` but never verifies the claim has
312
+ checked nothing.** It has only asked the holder nicely.
313
+
314
+ ### How the check behaves (on your backend)
315
+
316
+ The requirement you hand a backend library is satisfied **upward**:
317
+ `zoreal.session < zoreal.device < zoreal.live`. Requiring `zoreal.device`
318
+ therefore accepts a `zoreal.live` token — the holder gave you *more* assurance
319
+ than you demanded. A token whose `acr` is below the requirement, missing, or
320
+ outside the vocabulary is refused (in Ruby, a `VerificationError`); an unknown
321
+ *required* value — a typo like `'zoreal.liveness'` — is a bug in your code rather
322
+ than a bad token, so the libraries raise a configuration error instead and fail
323
+ loudly rather than rejecting every login in silence.
324
+
325
+ If you would rather branch than have the exchange refuse, require nothing and
326
+ inspect the result — the backend libraries expose a predicate for the same upward
327
+ comparison (in Ruby, `login.satisfies_acr?`, with `login.live?` as shorthand):
328
+
329
+ ```ruby
330
+ login = ZOREAL_OAUTH.authenticate(code:, code_verifier:, nonce:)
331
+ unless login.satisfies_acr?('zoreal.live')
332
+ # step the user up, or refuse the sensitive action
333
+ end
334
+ ```
335
+
336
+ The frontend's only job is the request; each of these checks happens where
337
+ verification means something, which is your server.
338
+
339
+ ### `acr` versus the assurance block
340
+
341
+ `acr` grades *this login event*. The assurance block in the token (uniqueness
342
+ basis, verification month, chip-liveness, trust tier, key protection) describes
343
+ the *identity behind it* — how the person was proofed at enrolment. One is about
344
+ now; the other about who they are. A high-value flow wants both.
345
+
216
346
  ## What your page needs to allow
217
347
 
218
348
  The package loads no third-party script, no stylesheet, no font, and has zero
package/dist/index.cjs CHANGED
@@ -37,7 +37,7 @@ var import_react = require("react");
37
37
 
38
38
  // src/wire.ts
39
39
  var WIRE_VERSION = 1;
40
- var SDK_VERSION = "0.1.5";
40
+ var SDK_VERSION = "0.1.7";
41
41
  var DEFAULT_ISSUER = "https://id.zoreal.com";
42
42
  var POLL_INTERVAL_MS = 2e3;
43
43
  var POLL_INTERVAL_ENROLLING_MS = 5e3;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/wire.ts","../src/ZorealLogin.tsx","../src/useZorealLogin.ts","../src/jwt.ts","../src/pairing.ts","../src/pkce.ts","../src/useZorealAutoLogin.ts","../src/logout.ts","../src/scopes.ts"],"sourcesContent":["export { ZorealOAuthProvider, useZorealOAuth } from './context';\nexport type { ZorealOAuthProviderProps, ZorealOAuthContextProps } from './context';\nexport { ZorealLogin } from './ZorealLogin';\nexport { useZorealLogin } from './useZorealLogin';\nexport { useZorealAutoLogin } from './useZorealAutoLogin';\nexport { zorealLogout } from './logout';\nexport { hasGrantedAllScopesZoreal, hasGrantedAnyScopeZoreal } from './scopes';\nexport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n UseZorealAutoLoginOptions,\n ZorealButtonConfiguration,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginProps,\n ZorealLoginRequestOptions,\n} from './types';\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { DEFAULT_ISSUER } from './wire';\n\nexport interface ZorealOAuthProviderProps {\n /** From the zoreal-web dashboard: the asset ID. */\n clientId: string;\n /** Override the provider origin. Sandbox and self-hosted testing only. */\n issuer?: string;\n /** BCP 47. Drives button text and the pairing page. */\n locale?: string;\n children: ReactNode;\n}\n\nexport interface ZorealOAuthContextProps {\n clientId: string;\n issuer: string;\n locale?: string;\n}\n\nconst ZorealOAuthContext = createContext<ZorealOAuthContextProps | null>(null);\n\nexport function ZorealOAuthProvider({\n clientId,\n issuer = DEFAULT_ISSUER,\n locale,\n children,\n}: ZorealOAuthProviderProps) {\n const value = useMemo(\n () => ({ clientId, issuer: issuer.replace(/\\/$/, ''), locale }),\n [clientId, issuer, locale]\n );\n return <ZorealOAuthContext.Provider value={value}>{children}</ZorealOAuthContext.Provider>;\n}\n\nexport function useZorealOAuth(): ZorealOAuthContextProps {\n const ctx = useContext(ZorealOAuthContext);\n if (!ctx) {\n throw new Error(\n 'useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. ' +\n 'Wrap your app (or the part that logs in) in the provider.'\n );\n }\n return ctx;\n}\n","/**\n * The wire protocol between this package and the ZOREAL OpenID Provider.\n *\n * VERSIONED: a shipped version keeps working until the provider explicitly\n * refuses it, and when it does, the reason is surfaced verbatim. Both the wire\n * version and the package version travel on every pairing request so a refusal\n * can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (the dashboard):\n *\n * POST /pair start a pairing request. Body carries the\n * authorize parameters plus PKCE challenge.\n * Returns { request_id, pair_url, expires_in }\n * or, for prompt=none with a live consented\n * session, { code } immediately.\n * GET /pair/:id/status poll: pending | claimed |\n * approved (with code) | denied | expired |\n * enrolling. Over-polling cancels the request\n * rather than throttling it, so the cadence\n * below is not a suggestion.\n * GET /pair/:id/qr.svg the QR image for the pairing URL, served by\n * the provider so the pairing surface stays\n * changeable at runtime and\n * this package keeps zero dependencies.\n * POST /token the code exchange. Browser-direct mode uses\n * it directly with PKCE and no client secret;\n * auth-code mode leaves it to the RP backend.\n */\n\nexport const WIRE_VERSION = 1;\nexport const SDK_VERSION = '0.1.5';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** Pending TTL is short. Poll gently; over-polling cancels the request. */\nexport const POLL_INTERVAL_MS = 2000;\n/** Enrolling extends the window well beyond a normal login; poll slower. */\nexport const POLL_INTERVAL_ENROLLING_MS = 5000;\n\nexport interface PairCreated {\n request_id: string;\n /** https://zoreal.com/qr/<request_id>. The same URL in QR and app link. */\n pair_url: string;\n expires_in: number;\n}\n\nexport interface PairImmediate {\n /** prompt=none resolved silently: consented sector, live session. */\n code: string;\n}\n\nexport type PairStartResponse = PairCreated | PairImmediate;\n\nexport interface PairStatusResponse {\n status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';\n code?: string;\n expires_in?: number;\n enrolment_deadline?: number;\n /** The provider's reason on denial or refusal. Surfaced verbatim, never rewritten. */\n error?: string;\n error_description?: string;\n}\n\nexport interface TokenResponse {\n id_token: string;\n access_token?: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n","import { useMemo, type CSSProperties } from 'react';\nimport { useZorealFlow, type ActivePairing } from './useZorealLogin';\nimport type { NonOAuthError, ZorealLoginProps } from './types';\n\n/**\n * The drop-in button. It receives no access token, so it returns the\n * pseudonymous identity only; personal data needs the auth-code flow.\n *\n * The copy is neutral: the button asserts nothing about a person who has not\n * yet authenticated. Styling is inline and self-contained; no stylesheet, no\n * font, no external asset, because this renders on a sign-in page.\n */\n\nconst TEXTS: Record<NonNullable<ZorealLoginProps['text']>, string> = {\n continue_with: 'Continue with ZOREAL',\n signin_with: 'Sign in with ZOREAL',\n signup_with: 'Sign up with ZOREAL',\n signin: 'Sign in',\n};\n\nconst SIZES = {\n large: { height: 44, font: 15, pad: 20 },\n medium: { height: 38, font: 14, pad: 16 },\n small: { height: 32, font: 12, pad: 12 },\n} as const;\n\n/** The ZOREAL mark, in currentColor so every theme carries it correctly. */\nconst Mark = ({ size }: { size: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"8.4 7.4 62.2 62.2\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n aria-hidden\n focusable=\"false\"\n >\n <path d=\"M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z\" />\n <path d=\"M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z\" />\n <path d=\"M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z\" />\n <path d=\"M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z\" />\n <path d=\"M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z\" />\n <path d=\"M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z\" />\n <path d=\"M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z\" />\n </svg>\n);\n\nconst PairingPanel = ({ pairing }: { pairing: ActivePairing }) => {\n const { status } = pairing.state;\n const line =\n status === 'claimed'\n ? 'Approve the login in your ZOREAL ID app.'\n : status === 'enrolling'\n ? 'Finishing enrolment. This screen will continue by itself.'\n : pairing.appLink\n ? 'Continue in the ZOREAL ID app, then return to this tab.'\n : 'Scan with your phone camera or the ZOREAL ID app.';\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Log in with ZOREAL\"\n style={{\n marginTop: 8,\n padding: 16,\n width: 232,\n borderRadius: 12,\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'Canvas',\n color: 'CanvasText',\n textAlign: 'center',\n fontFamily: 'inherit',\n }}\n >\n {!pairing.appLink && (\n <img\n src={pairing.qrUrl}\n alt={`QR code for ${pairing.pairUrl}`}\n width={200}\n height={200}\n style={{ display: 'block', margin: '0 auto', borderRadius: 8 }}\n />\n )}\n <p style={{ margin: '10px 0 0', fontSize: 12, lineHeight: 1.5 }}>{line}</p>\n <button\n type=\"button\"\n onClick={pairing.cancel}\n style={{\n marginTop: 10,\n border: 'none',\n background: 'none',\n color: 'inherit',\n opacity: 0.6,\n fontSize: 12,\n cursor: 'pointer',\n textDecoration: 'underline',\n }}\n >\n Cancel\n </button>\n </div>\n );\n};\n\nexport function ZorealLogin(props: ZorealLoginProps) {\n const {\n onSuccess,\n onError,\n containerProps,\n type = 'standard',\n theme = 'filled',\n size = 'large',\n text = 'continue_with',\n shape = 'rectangular',\n logo_alignment = 'left',\n width,\n click_listener,\n ...request\n } = props;\n\n const { login, internals } = useZorealFlow({\n ...request,\n flow: 'browser-direct',\n onCredential: onSuccess,\n onError: (e) => onError?.({ type: 'unknown', description: e.description ?? e.error }),\n onNonOAuthError: (e: NonOAuthError) => onError?.(e),\n });\n\n const s = SIZES[size];\n const style: CSSProperties = useMemo(\n () => ({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: logo_alignment === 'center' ? 'center' : 'flex-start',\n gap: 10,\n height: s.height,\n padding: `0 ${s.pad}px`,\n width,\n fontSize: s.font,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: shape === 'pill' ? s.height / 2 : shape === 'square' ? 4 : 8,\n ...(theme === 'outline'\n ? { background: 'transparent', color: 'inherit', border: '1px solid rgba(128,128,128,0.5)' }\n : theme === 'filled_black'\n ? { background: '#111', color: '#fff', border: '1px solid #111' }\n : { background: '#00b4d9', color: '#fff', border: '1px solid #00b4d9' }),\n }),\n [logo_alignment, s, shape, theme, width]\n );\n\n return (\n <div {...containerProps}>\n <button\n type=\"button\"\n style={style}\n onClick={() => {\n click_listener?.();\n login();\n }}\n >\n <Mark size={Math.round(s.font * 1.25)} />\n {type === 'standard' && TEXTS[text]}\n </button>\n {internals.pairing && !internals.pairing.appLink && (\n <PairingPanel pairing={internals.pairing} />\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useZorealOAuth } from './context';\nimport { unsafeClaims } from './jwt';\nimport {\n FlowAbandonedError,\n OAuthFlowError,\n exchangeCode,\n isMobileUserAgent,\n pollUntilApproved,\n startPairing,\n} from './pairing';\nimport { challengeS256, generateState, generateVerifier } from './pkce';\nimport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginRequestOptions,\n} from './types';\n\nexport interface ActivePairing {\n requestId: string;\n pairUrl: string;\n qrUrl: string;\n state: PairingState;\n /** True when display resolved to the app link rather than the QR. */\n appLink: boolean;\n cancel: () => void;\n}\n\ninterface FlowInternals {\n /** Non-null while a pairing is on screen. ZorealLogin renders from this. */\n pairing: ActivePairing | null;\n}\n\n/**\n * The internal option shape: one flow discriminator, one success callback per\n * mode. The public API keeps Google's single overloaded onSuccess; this type\n * exists because an intersection of those two signatures is uninhabitable, and\n * the mapping from public to internal happens once, in useZorealLogin.\n */\nexport interface InternalFlowOptions extends ZorealLoginRequestOptions {\n flow: 'browser-direct' | 'auth-code';\n redirect_uri?: string;\n onCredential?: (response: ZorealCredentialResponse) => void;\n onCode?: (response: ZorealCodeResponse) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n}\n\n/**\n * The one flow, shared by the hook and the button. Starts a pairing, exposes\n * it for rendering, polls, and finishes per mode: browser-direct exchanges the\n * code here (public client, PKCE, no secret) and hands over an ID token;\n * auth-code hands the code and the PKCE verifier to the caller, whose backend\n * does the exchange with its client authentication.\n */\nexport function useZorealFlow(options: InternalFlowOptions): {\n login: () => void;\n internals: FlowInternals;\n} {\n const { clientId, issuer, locale } = useZorealOAuth();\n const [pairing, setPairing] = useState<ActivePairing | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // A component unmounting mid-login must stop the poll: the provider cancels\n // over-polled requests, and an orphaned interval is exactly how one happens.\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const login = useCallback(() => {\n const opts = optionsRef.current;\n const run = async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const flow = opts.flow;\n const verifier = generateVerifier();\n const state = generateState();\n const nonce = generateState();\n\n try {\n const started = await startPairing(issuer, {\n client_id: clientId,\n scope: opts.scope ?? 'openid',\n state,\n nonce,\n code_challenge: await challengeS256(verifier),\n redirect_uri: flow === 'auth-code' ? opts.redirect_uri : undefined,\n acr_values: Array.isArray(opts.acr_values)\n ? opts.acr_values.join(' ')\n : opts.acr_values,\n max_age: opts.max_age,\n prompt: opts.prompt,\n locale,\n });\n\n let code: string;\n let selectBy: SelectBy = 'device';\n\n if ('code' in started) {\n // prompt=none resolved silently: consented sector, live session.\n code = started.code;\n selectBy = 'session';\n } else {\n const useAppLink =\n opts.display === 'link' || (opts.display !== 'qr' && isMobileUserAgent());\n selectBy = useAppLink ? 'app_link' : 'qr';\n\n const cancel = () => {\n controller.abort();\n setPairing(null);\n };\n // Everything a caller-rendered pairing UI needs, on every state it\n // sees: the QR flow cannot complete unless SOMETHING renders\n // pairUrl, and for the auth-code flow that something is the caller.\n const surface = {\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n appLink: useAppLink,\n cancel,\n };\n const active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: surface.pairUrl,\n qrUrl: surface.qrUrl,\n state: { status: 'pending', expiresIn: started.expires_in, ...surface },\n appLink: useAppLink,\n cancel,\n };\n setPairing(active);\n // The initial state, immediately: the first poll response is one\n // round-trip away, and a UI that waits for it opens visibly empty.\n opts.onPairingStateChange?.(active.state);\n\n if (useAppLink) {\n // The universal link, in the same tab: the app claims it, and with\n // no app installed the same URL is the real pairing page which can\n // enrol (02 sections 3 and 4). A popup here would be blocked more\n // often than it would help.\n window.location.assign(started.pair_url);\n }\n\n code = await pollUntilApproved(\n issuer,\n started.request_id,\n (s) => {\n const enriched = { ...s, ...surface };\n setPairing((p) =>\n p && p.requestId === started.request_id ? { ...p, state: enriched } : p\n );\n opts.onPairingStateChange?.(enriched);\n },\n controller.signal\n );\n }\n\n setPairing(null);\n\n if (flow === 'auth-code') {\n opts.onCode?.({\n code,\n scope: opts.scope ?? 'openid',\n app_state: opts.app_state,\n code_verifier: verifier,\n nonce,\n });\n return;\n }\n\n const tokens = await exchangeCode(issuer, {\n code,\n code_verifier: verifier,\n client_id: clientId,\n });\n const claims = unsafeClaims(tokens.id_token);\n const response: ZorealCredentialResponse = {\n credential: tokens.id_token,\n clientId,\n select_by: selectBy,\n acr: (claims.acr as AcrValue) ?? 'zoreal.device',\n };\n opts.onCredential?.(response);\n } catch (e) {\n setPairing(null);\n if (e instanceof DOMException && e.name === 'AbortError') return;\n if (e instanceof FlowAbandonedError) {\n opts.onNonOAuthError?.(e.reason);\n return;\n }\n if (e instanceof OAuthFlowError) {\n opts.onError?.({ error: e.error, description: e.description });\n return;\n }\n opts.onNonOAuthError?.({\n type: 'unknown',\n description: e instanceof Error ? e.message : String(e),\n });\n }\n };\n void run();\n }, [clientId, issuer, locale]);\n\n return { login, internals: { pairing } };\n}\n\nexport function useZorealLogin(\n options: { flow?: 'browser-direct' } & BrowserDirectFlowOptions\n): () => void;\nexport function useZorealLogin(options: { flow: 'auth-code' } & AuthCodeFlowOptions): () => void;\nexport function useZorealLogin(\n options: ({ flow?: 'browser-direct' | 'auth-code' } & ZorealLoginRequestOptions) &\n Partial<Pick<AuthCodeFlowOptions, 'redirect_uri' | 'ux_mode'>> & {\n onSuccess?: (response: never) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n }\n): () => void {\n if (options.ux_mode === 'redirect') {\n // v1 supports the popup shape only: the code and PKCE verifier go to your\n // onSuccess and from there to your backend over TLS. A redirect would have\n // to carry the verifier in a URL, which is a credential in every access\n // log on the path. Refused loudly rather than implemented badly.\n throw new Error(\n \"@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default \" +\n \"'popup' shape and post the code and code_verifier from onSuccess to your backend.\"\n );\n }\n const flow = options.flow ?? 'browser-direct';\n return useZorealFlow({\n ...options,\n flow,\n onCredential:\n flow === 'browser-direct'\n ? (options.onSuccess as unknown as (r: ZorealCredentialResponse) => void)\n : undefined,\n onCode:\n flow === 'auth-code'\n ? (options.onSuccess as unknown as (r: ZorealCodeResponse) => void)\n : undefined,\n }).login;\n}\n","/**\n * Reads claims OUT of an ID token without verifying it.\n *\n * That is not a shortcut, it is the design: this code runs in a browser the\n * threat model assumes is attacker-controlled (02), so a signature check here\n * proves nothing to anyone. The token is verified where verification means\n * something: server-side against the JWKS. What this parser feeds is\n * convenience fields (acr on the response object) that the types document as\n * convenience, with the token staying the authority.\n */\n\nexport function unsafeClaims(idToken: string): Record<string, unknown> {\n try {\n const payload = idToken.split('.')[1] ?? '';\n const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n return JSON.parse(\n new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))\n );\n } catch {\n return {};\n }\n}\n","/**\n * The pairing channel, client side. wire.ts pins the endpoints.\n *\n * The browser polls; the phone never talks to the browser. Everything here is\n * therefore plain fetch against the issuer, CORS-gated on the client's\n * authorized origins, with the poll cadence fixed: the provider cancels an\n * over-polling request rather than throttling it, so a \"retry\n * faster on error\" strategy here would kill the login it is trying to save.\n */\n\nimport {\n POLL_INTERVAL_ENROLLING_MS,\n POLL_INTERVAL_MS,\n SDK_VERSION,\n WIRE_VERSION,\n type PairStartResponse,\n type PairStatusResponse,\n type TokenResponse,\n} from './wire';\nimport type { ErrorCode, NonOAuthError, PairingState } from './types';\n\nexport class OAuthFlowError extends Error {\n constructor(\n public error: ErrorCode,\n public description?: string\n ) {\n super(description ?? error);\n }\n}\n\nexport class FlowAbandonedError extends Error {\n constructor(public reason: NonOAuthError) {\n super(reason.description ?? reason.type);\n }\n}\n\nexport interface StartPairingParams {\n client_id: string;\n scope: string;\n state: string;\n nonce: string;\n code_challenge: string;\n redirect_uri?: string;\n acr_values?: string;\n max_age?: number;\n prompt?: string;\n locale?: string;\n}\n\nasync function parseJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function startPairing(\n issuer: string,\n params: StartPairingParams\n): Promise<PairStartResponse> {\n const response = await fetch(`${issuer}/pair`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...params,\n code_challenge_method: 'S256',\n wire_version: WIRE_VERSION,\n sdk: `@zoreal/oauth2-react/${SDK_VERSION}`,\n }),\n });\n\n const body = await parseJson(response);\n if (!response.ok) {\n // The provider's words, verbatim. A refused package version arrives here,\n // and rewriting its reason would hide the only signal telling an integrator\n // to upgrade.\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n (body.error_description as string) ?? `The provider refused the request (${response.status})`\n );\n }\n return body as unknown as PairStartResponse;\n}\n\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n // An already-aborted signal never fires its abort event, so check first\n // or the sleep runs to term and the poll takes one extra swing.\n if (signal?.aborted) {\n reject(new DOMException('aborted', 'AbortError'));\n return;\n }\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('aborted', 'AbortError'));\n });\n });\n\n/**\n * Polls until the request resolves. Returns the authorization code.\n * Throws FlowAbandonedError for the human outcomes (denied, expired,\n * enrolment abandoned) and OAuthFlowError for protocol ones.\n */\nexport async function pollUntilApproved(\n issuer: string,\n requestId: string,\n onState?: (state: PairingState) => void,\n signal?: AbortSignal\n): Promise<string> {\n for (;;) {\n const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {\n signal,\n });\n const body = (await parseJson(response)) as unknown as PairStatusResponse;\n\n if (!response.ok) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Pairing status failed (${response.status})`\n );\n }\n\n onState?.({\n status: body.status,\n expiresIn: body.expires_in,\n enrolmentDeadline: body.enrolment_deadline,\n });\n\n switch (body.status) {\n case 'approved':\n if (!body.code) {\n throw new OAuthFlowError('server_error', 'approved with no authorization code');\n }\n return body.code;\n case 'denied':\n throw new FlowAbandonedError({ type: 'request_denied', description: body.error_description });\n case 'expired':\n throw new FlowAbandonedError({ type: 'request_expired', description: body.error_description });\n case 'cancelled':\n // The provider cancels an over-polled or abandoned request outright\n // (its pairing rows have a real cancelled state). Before 0.1.4 this\n // fell through to the default branch and polled a dead request\n // forever.\n throw new FlowAbandonedError({\n type: 'request_expired',\n description: body.error_description ?? 'the provider cancelled the pairing request',\n });\n case 'enrolling':\n await sleep(POLL_INTERVAL_ENROLLING_MS, signal);\n break;\n default:\n await sleep(POLL_INTERVAL_MS, signal);\n }\n }\n}\n\n/**\n * The code exchange, browser-direct mode only: a public client, PKCE and no\n * secret. What comes back can only ever be the pseudonymous tier, by\n * construction rather than by rule: personal data lives at /userinfo behind an\n * access token this mode is never issued, because personal-data scopes are\n * refused for public clients at the pairing step.\n */\nexport async function exchangeCode(\n issuer: string,\n input: { code: string; code_verifier: string; client_id: string }\n): Promise<TokenResponse> {\n const response = await fetch(`${issuer}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code,\n code_verifier: input.code_verifier,\n client_id: input.client_id,\n }),\n });\n\n const body = (await parseJson(response)) as unknown as TokenResponse;\n if (!response.ok || body.error) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Token exchange failed (${response.status})`\n );\n }\n return body;\n}\n\n/** A mobile user agent gets the app link, not a QR of its own screen. */\nexport function isMobileUserAgent(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /android|iphone|ipad|ipod/i.test(navigator.userAgent);\n}\n","/**\n * PKCE, S256 only: mandatory for every client, confidential ones included. There is no plain fallback and there must never\n * be one; a provider seeing method=plain is seeing a bug or an attack.\n */\n\nconst VERIFIER_BYTES = 32; // 43 base64url chars, the RFC 7636 minimum length\n\nconst base64url = (bytes: Uint8Array): string =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nexport function generateVerifier(): string {\n const bytes = new Uint8Array(VERIFIER_BYTES);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\nexport async function challengeS256(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n\nexport function generateState(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n","import { useEffect, useRef } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport type { UseZorealAutoLoginOptions } from './types';\n\n/**\n * Silent re-auth with prompt=none. NOT One Tap and never could be: there is no\n * ZOREAL session cookie in the browser to read, the credential is on a phone.\n * This succeeds only for a returning user at a consented sector with a live\n * session, the resulting acr is zoreal.session with an empty amr, and a\n * relying party that needs a live human must not build on this hook.\n *\n * PRIVACY NOTE: mounting this on a page sends a request to ZOREAL on page\n * load, before the user does anything. The hook is therefore conservative: it\n * fires once per mount, never retries, and does nothing when `disabled`.\n */\nexport function useZorealAutoLogin(options: UseZorealAutoLoginOptions): void {\n const { login } = useZorealFlow({\n flow: 'browser-direct',\n scope: options.scope,\n prompt: 'none',\n onCredential: options.onSuccess,\n onError: (e) => {\n // The provider's honest answer when no silent session exists (the\n // common case): unavailable, not an error, and never surfaced.\n const quiet = ['login_required', 'consent_required', 'interaction_required'];\n if (quiet.includes(e.error)) {\n options.onUnavailable?.();\n } else {\n options.onError?.({ type: 'unknown', description: e.description ?? e.error });\n }\n },\n onNonOAuthError: (e) => options.onError?.(e),\n });\n\n const fired = useRef(false);\n useEffect(() => {\n if (options.disabled || fired.current) return;\n fired.current = true;\n login();\n }, [options.disabled, login]);\n}\n","/**\n * Clears SDK-held local state. Named for parity with googleLogout and, like\n * it, LOCAL ONLY: it does not end the holder's ZOREAL session, which lives on\n * their phone and at the provider. A relying party that believes this signs\n * the user out of ZOREAL has a security misunderstanding, not a naming\n * complaint. The relying party's own session is the relying\n * party's to end.\n *\n * The SDK deliberately persists nothing (no localStorage, no cookies), so\n * today this has nothing to clear and exists as the stable API surface for a\n * future that does.\n */\nexport function zorealLogout(): void {\n // Intentionally empty until the SDK holds state worth clearing.\n}\n","import type { ZorealCodeResponse } from './types';\n\n/** Mirrors hasGrantedAllScopesGoogle for name-for-name portability. */\nexport function hasGrantedAllScopesZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].every((s) => granted.has(s));\n}\n\nexport function hasGrantedAnyScopeZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].some((s) => granted.has(s));\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/wire.ts","../src/ZorealLogin.tsx","../src/useZorealLogin.ts","../src/jwt.ts","../src/pairing.ts","../src/pkce.ts","../src/useZorealAutoLogin.ts","../src/logout.ts","../src/scopes.ts"],"sourcesContent":["export { ZorealOAuthProvider, useZorealOAuth } from './context';\nexport type { ZorealOAuthProviderProps, ZorealOAuthContextProps } from './context';\nexport { ZorealLogin } from './ZorealLogin';\nexport { useZorealLogin } from './useZorealLogin';\nexport { useZorealAutoLogin } from './useZorealAutoLogin';\nexport { zorealLogout } from './logout';\nexport { hasGrantedAllScopesZoreal, hasGrantedAnyScopeZoreal } from './scopes';\nexport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n UseZorealAutoLoginOptions,\n ZorealButtonConfiguration,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginProps,\n ZorealLoginRequestOptions,\n} from './types';\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { DEFAULT_ISSUER } from './wire';\n\nexport interface ZorealOAuthProviderProps {\n /** From the ZOREAL dashboard: the asset ID. */\n clientId: string;\n /** Override the provider origin. Sandbox and self-hosted testing only. */\n issuer?: string;\n /** BCP 47. Drives button text and the pairing page. */\n locale?: string;\n children: ReactNode;\n}\n\nexport interface ZorealOAuthContextProps {\n clientId: string;\n issuer: string;\n locale?: string;\n}\n\nconst ZorealOAuthContext = createContext<ZorealOAuthContextProps | null>(null);\n\nexport function ZorealOAuthProvider({\n clientId,\n issuer = DEFAULT_ISSUER,\n locale,\n children,\n}: ZorealOAuthProviderProps) {\n const value = useMemo(\n () => ({ clientId, issuer: issuer.replace(/\\/$/, ''), locale }),\n [clientId, issuer, locale]\n );\n return <ZorealOAuthContext.Provider value={value}>{children}</ZorealOAuthContext.Provider>;\n}\n\nexport function useZorealOAuth(): ZorealOAuthContextProps {\n const ctx = useContext(ZorealOAuthContext);\n if (!ctx) {\n throw new Error(\n 'useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. ' +\n 'Wrap your app (or the part that logs in) in the provider.'\n );\n }\n return ctx;\n}\n","/**\n * The wire protocol between this package and the ZOREAL OpenID Provider.\n *\n * VERSIONED: a shipped version keeps working until the provider explicitly\n * refuses it, and when it does, the reason is surfaced verbatim. Both the wire\n * version and the package version travel on every pairing request so a refusal\n * can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (the dashboard):\n *\n * POST /pair start a pairing request. Body carries the\n * authorize parameters plus PKCE challenge.\n * Returns { request_id, pair_url, expires_in }\n * or, for prompt=none with a live consented\n * session, { code } immediately.\n * GET /pair/:id/status poll: pending | claimed |\n * approved (with code) | denied | expired |\n * enrolling. Over-polling cancels the request\n * rather than throttling it, so the cadence\n * below is not a suggestion.\n * GET /pair/:id/qr.svg the QR image for the pairing URL, served by\n * the provider so the pairing surface stays\n * changeable at runtime and\n * this package keeps zero dependencies.\n * POST /token the code exchange. Browser-direct mode uses\n * it directly with PKCE and no client secret;\n * auth-code mode leaves it to the RP backend.\n */\n\nexport const WIRE_VERSION = 1;\nexport const SDK_VERSION = '0.1.7';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** Pending TTL is short. Poll gently; over-polling cancels the request. */\nexport const POLL_INTERVAL_MS = 2000;\n/** Enrolling extends the window well beyond a normal login; poll slower. */\nexport const POLL_INTERVAL_ENROLLING_MS = 5000;\n\nexport interface PairCreated {\n request_id: string;\n /** https://zoreal.com/qr/<request_id>. The same URL in QR and app link. */\n pair_url: string;\n expires_in: number;\n}\n\nexport interface PairImmediate {\n /** prompt=none resolved silently: consented sector, live session. */\n code: string;\n}\n\nexport type PairStartResponse = PairCreated | PairImmediate;\n\nexport interface PairStatusResponse {\n status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';\n code?: string;\n expires_in?: number;\n enrolment_deadline?: number;\n /** The provider's reason on denial or refusal. Surfaced verbatim, never rewritten. */\n error?: string;\n error_description?: string;\n}\n\nexport interface TokenResponse {\n id_token: string;\n access_token?: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n","import { useMemo, type CSSProperties } from 'react';\nimport { useZorealFlow, type ActivePairing } from './useZorealLogin';\nimport type { NonOAuthError, ZorealLoginProps } from './types';\n\n/**\n * The drop-in button. It receives no access token, so it returns the\n * pseudonymous identity only; personal data needs the auth-code flow.\n *\n * The copy is neutral: the button asserts nothing about a person who has not\n * yet authenticated. Styling is inline and self-contained; no stylesheet, no\n * font, no external asset, because this renders on a sign-in page.\n */\n\nconst TEXTS: Record<NonNullable<ZorealLoginProps['text']>, string> = {\n continue_with: 'Continue with ZOREAL',\n signin_with: 'Sign in with ZOREAL',\n signup_with: 'Sign up with ZOREAL',\n signin: 'Sign in',\n};\n\nconst SIZES = {\n large: { height: 44, font: 15, pad: 20 },\n medium: { height: 38, font: 14, pad: 16 },\n small: { height: 32, font: 12, pad: 12 },\n} as const;\n\n/** The ZOREAL mark, in currentColor so every theme carries it correctly. */\nconst Mark = ({ size }: { size: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"8.4 7.4 62.2 62.2\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n aria-hidden\n focusable=\"false\"\n >\n <path d=\"M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z\" />\n <path d=\"M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z\" />\n <path d=\"M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z\" />\n <path d=\"M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z\" />\n <path d=\"M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z\" />\n <path d=\"M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z\" />\n <path d=\"M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z\" />\n </svg>\n);\n\nconst PairingPanel = ({ pairing }: { pairing: ActivePairing }) => {\n const { status } = pairing.state;\n const line =\n status === 'claimed'\n ? 'Approve the login in your ZOREAL ID app.'\n : status === 'enrolling'\n ? 'Finishing enrolment. This screen will continue by itself.'\n : pairing.appLink\n ? 'Continue in the ZOREAL ID app, then return to this tab.'\n : 'Scan with your phone camera or the ZOREAL ID app.';\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Log in with ZOREAL\"\n style={{\n marginTop: 8,\n padding: 16,\n width: 232,\n borderRadius: 12,\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'Canvas',\n color: 'CanvasText',\n textAlign: 'center',\n fontFamily: 'inherit',\n }}\n >\n {!pairing.appLink && (\n <img\n src={pairing.qrUrl}\n alt={`QR code for ${pairing.pairUrl}`}\n width={200}\n height={200}\n style={{ display: 'block', margin: '0 auto', borderRadius: 8 }}\n />\n )}\n <p style={{ margin: '10px 0 0', fontSize: 12, lineHeight: 1.5 }}>{line}</p>\n <button\n type=\"button\"\n onClick={pairing.cancel}\n style={{\n marginTop: 10,\n border: 'none',\n background: 'none',\n color: 'inherit',\n opacity: 0.6,\n fontSize: 12,\n cursor: 'pointer',\n textDecoration: 'underline',\n }}\n >\n Cancel\n </button>\n </div>\n );\n};\n\nexport function ZorealLogin(props: ZorealLoginProps) {\n const {\n onSuccess,\n onError,\n containerProps,\n type = 'standard',\n theme = 'filled',\n size = 'large',\n text = 'continue_with',\n shape = 'rectangular',\n logo_alignment = 'left',\n width,\n click_listener,\n ...request\n } = props;\n\n const { login, internals } = useZorealFlow({\n ...request,\n flow: 'browser-direct',\n onCredential: onSuccess,\n onError: (e) => onError?.({ type: 'unknown', description: e.description ?? e.error }),\n onNonOAuthError: (e: NonOAuthError) => onError?.(e),\n });\n\n const s = SIZES[size];\n const style: CSSProperties = useMemo(\n () => ({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: logo_alignment === 'center' ? 'center' : 'flex-start',\n gap: 10,\n height: s.height,\n padding: `0 ${s.pad}px`,\n width,\n fontSize: s.font,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: shape === 'pill' ? s.height / 2 : shape === 'square' ? 4 : 8,\n ...(theme === 'outline'\n ? { background: 'transparent', color: 'inherit', border: '1px solid rgba(128,128,128,0.5)' }\n : theme === 'filled_black'\n ? { background: '#111', color: '#fff', border: '1px solid #111' }\n : { background: '#00b4d9', color: '#fff', border: '1px solid #00b4d9' }),\n }),\n [logo_alignment, s, shape, theme, width]\n );\n\n return (\n <div {...containerProps}>\n <button\n type=\"button\"\n style={style}\n onClick={() => {\n click_listener?.();\n login();\n }}\n >\n <Mark size={Math.round(s.font * 1.25)} />\n {type === 'standard' && TEXTS[text]}\n </button>\n {internals.pairing && !internals.pairing.appLink && (\n <PairingPanel pairing={internals.pairing} />\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useZorealOAuth } from './context';\nimport { unsafeClaims } from './jwt';\nimport {\n FlowAbandonedError,\n OAuthFlowError,\n exchangeCode,\n isMobileUserAgent,\n pollUntilApproved,\n startPairing,\n} from './pairing';\nimport { challengeS256, generateState, generateVerifier } from './pkce';\nimport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginRequestOptions,\n} from './types';\n\nexport interface ActivePairing {\n requestId: string;\n pairUrl: string;\n qrUrl: string;\n state: PairingState;\n /** True when display resolved to the app link rather than the QR. */\n appLink: boolean;\n cancel: () => void;\n}\n\ninterface FlowInternals {\n /** Non-null while a pairing is on screen. ZorealLogin renders from this. */\n pairing: ActivePairing | null;\n}\n\n/**\n * The internal option shape: one flow discriminator, one success callback per\n * mode. The public API keeps Google's single overloaded onSuccess; this type\n * exists because an intersection of those two signatures is uninhabitable, and\n * the mapping from public to internal happens once, in useZorealLogin.\n */\nexport interface InternalFlowOptions extends ZorealLoginRequestOptions {\n flow: 'browser-direct' | 'auth-code';\n redirect_uri?: string;\n onCredential?: (response: ZorealCredentialResponse) => void;\n onCode?: (response: ZorealCodeResponse) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n}\n\n/**\n * The one flow, shared by the hook and the button. Starts a pairing, exposes\n * it for rendering, polls, and finishes per mode: browser-direct exchanges the\n * code here (public client, PKCE, no secret) and hands over an ID token;\n * auth-code hands the code and the PKCE verifier to the caller, whose backend\n * does the exchange with its client authentication.\n */\nexport function useZorealFlow(options: InternalFlowOptions): {\n login: () => void;\n internals: FlowInternals;\n} {\n const { clientId, issuer, locale } = useZorealOAuth();\n const [pairing, setPairing] = useState<ActivePairing | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // A component unmounting mid-login must stop the poll: the provider cancels\n // over-polled requests, and an orphaned interval is exactly how one happens.\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const login = useCallback(() => {\n const opts = optionsRef.current;\n const run = async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const flow = opts.flow;\n const verifier = generateVerifier();\n const state = generateState();\n const nonce = generateState();\n\n try {\n const started = await startPairing(issuer, {\n client_id: clientId,\n scope: opts.scope ?? 'openid',\n state,\n nonce,\n code_challenge: await challengeS256(verifier),\n redirect_uri: flow === 'auth-code' ? opts.redirect_uri : undefined,\n acr_values: Array.isArray(opts.acr_values)\n ? opts.acr_values.join(' ')\n : opts.acr_values,\n max_age: opts.max_age,\n prompt: opts.prompt,\n locale,\n });\n\n let code: string;\n let selectBy: SelectBy = 'device';\n\n if ('code' in started) {\n // prompt=none resolved silently: consented sector, live session.\n code = started.code;\n selectBy = 'session';\n } else {\n const useAppLink =\n opts.display === 'link' || (opts.display !== 'qr' && isMobileUserAgent());\n selectBy = useAppLink ? 'app_link' : 'qr';\n\n const cancel = () => {\n controller.abort();\n setPairing(null);\n };\n // Everything a caller-rendered pairing UI needs, on every state it\n // sees: the QR flow cannot complete unless SOMETHING renders\n // pairUrl, and for the auth-code flow that something is the caller.\n const surface = {\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n appLink: useAppLink,\n cancel,\n };\n const active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: surface.pairUrl,\n qrUrl: surface.qrUrl,\n state: { status: 'pending', expiresIn: started.expires_in, ...surface },\n appLink: useAppLink,\n cancel,\n };\n setPairing(active);\n // The initial state, immediately: the first poll response is one\n // round-trip away, and a UI that waits for it opens visibly empty.\n opts.onPairingStateChange?.(active.state);\n\n if (useAppLink) {\n // The universal link, in the same tab: the app claims it, and with\n // no app installed the same URL is the real pairing page which can\n // enrol (02 sections 3 and 4). A popup here would be blocked more\n // often than it would help.\n window.location.assign(started.pair_url);\n }\n\n code = await pollUntilApproved(\n issuer,\n started.request_id,\n (s) => {\n const enriched = { ...s, ...surface };\n setPairing((p) =>\n p && p.requestId === started.request_id ? { ...p, state: enriched } : p\n );\n opts.onPairingStateChange?.(enriched);\n },\n controller.signal\n );\n }\n\n setPairing(null);\n\n if (flow === 'auth-code') {\n opts.onCode?.({\n code,\n scope: opts.scope ?? 'openid',\n app_state: opts.app_state,\n code_verifier: verifier,\n nonce,\n });\n return;\n }\n\n const tokens = await exchangeCode(issuer, {\n code,\n code_verifier: verifier,\n client_id: clientId,\n });\n const claims = unsafeClaims(tokens.id_token);\n const response: ZorealCredentialResponse = {\n credential: tokens.id_token,\n clientId,\n select_by: selectBy,\n acr: (claims.acr as AcrValue) ?? 'zoreal.device',\n };\n opts.onCredential?.(response);\n } catch (e) {\n setPairing(null);\n if (e instanceof DOMException && e.name === 'AbortError') return;\n if (e instanceof FlowAbandonedError) {\n opts.onNonOAuthError?.(e.reason);\n return;\n }\n if (e instanceof OAuthFlowError) {\n opts.onError?.({ error: e.error, description: e.description });\n return;\n }\n opts.onNonOAuthError?.({\n type: 'unknown',\n description: e instanceof Error ? e.message : String(e),\n });\n }\n };\n void run();\n }, [clientId, issuer, locale]);\n\n return { login, internals: { pairing } };\n}\n\nexport function useZorealLogin(\n options: { flow?: 'browser-direct' } & BrowserDirectFlowOptions\n): () => void;\nexport function useZorealLogin(options: { flow: 'auth-code' } & AuthCodeFlowOptions): () => void;\nexport function useZorealLogin(\n options: ({ flow?: 'browser-direct' | 'auth-code' } & ZorealLoginRequestOptions) &\n Partial<Pick<AuthCodeFlowOptions, 'redirect_uri' | 'ux_mode'>> & {\n onSuccess?: (response: never) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n }\n): () => void {\n if (options.ux_mode === 'redirect') {\n // v1 supports the popup shape only: the code and PKCE verifier go to your\n // onSuccess and from there to your backend over TLS. A redirect would have\n // to carry the verifier in a URL, which is a credential in every access\n // log on the path. Refused loudly rather than implemented badly.\n throw new Error(\n \"@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default \" +\n \"'popup' shape and post the code and code_verifier from onSuccess to your backend.\"\n );\n }\n const flow = options.flow ?? 'browser-direct';\n return useZorealFlow({\n ...options,\n flow,\n onCredential:\n flow === 'browser-direct'\n ? (options.onSuccess as unknown as (r: ZorealCredentialResponse) => void)\n : undefined,\n onCode:\n flow === 'auth-code'\n ? (options.onSuccess as unknown as (r: ZorealCodeResponse) => void)\n : undefined,\n }).login;\n}\n","/**\n * Reads claims OUT of an ID token without verifying it.\n *\n * That is not a shortcut, it is the design: this code runs in a browser the\n * threat model assumes is attacker-controlled (02), so a signature check here\n * proves nothing to anyone. The token is verified where verification means\n * something: server-side against the JWKS. What this parser feeds is\n * convenience fields (acr on the response object) that the types document as\n * convenience, with the token staying the authority.\n */\n\nexport function unsafeClaims(idToken: string): Record<string, unknown> {\n try {\n const payload = idToken.split('.')[1] ?? '';\n const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n return JSON.parse(\n new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))\n );\n } catch {\n return {};\n }\n}\n","/**\n * The pairing channel, client side. wire.ts pins the endpoints.\n *\n * The browser polls; the phone never talks to the browser. Everything here is\n * therefore plain fetch against the issuer, CORS-gated on the client's\n * authorized origins, with the poll cadence fixed: the provider cancels an\n * over-polling request rather than throttling it, so a \"retry\n * faster on error\" strategy here would kill the login it is trying to save.\n */\n\nimport {\n POLL_INTERVAL_ENROLLING_MS,\n POLL_INTERVAL_MS,\n SDK_VERSION,\n WIRE_VERSION,\n type PairStartResponse,\n type PairStatusResponse,\n type TokenResponse,\n} from './wire';\nimport type { ErrorCode, NonOAuthError, PairingState } from './types';\n\nexport class OAuthFlowError extends Error {\n constructor(\n public error: ErrorCode,\n public description?: string\n ) {\n super(description ?? error);\n }\n}\n\nexport class FlowAbandonedError extends Error {\n constructor(public reason: NonOAuthError) {\n super(reason.description ?? reason.type);\n }\n}\n\nexport interface StartPairingParams {\n client_id: string;\n scope: string;\n state: string;\n nonce: string;\n code_challenge: string;\n redirect_uri?: string;\n acr_values?: string;\n max_age?: number;\n prompt?: string;\n locale?: string;\n}\n\nasync function parseJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function startPairing(\n issuer: string,\n params: StartPairingParams\n): Promise<PairStartResponse> {\n const response = await fetch(`${issuer}/pair`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...params,\n code_challenge_method: 'S256',\n wire_version: WIRE_VERSION,\n sdk: `@zoreal/oauth2-react/${SDK_VERSION}`,\n }),\n });\n\n const body = await parseJson(response);\n if (!response.ok) {\n // The provider's words, verbatim. A refused package version arrives here,\n // and rewriting its reason would hide the only signal telling an integrator\n // to upgrade.\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n (body.error_description as string) ?? `The provider refused the request (${response.status})`\n );\n }\n return body as unknown as PairStartResponse;\n}\n\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n // An already-aborted signal never fires its abort event, so check first\n // or the sleep runs to term and the poll takes one extra swing.\n if (signal?.aborted) {\n reject(new DOMException('aborted', 'AbortError'));\n return;\n }\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('aborted', 'AbortError'));\n });\n });\n\n/**\n * Polls until the request resolves. Returns the authorization code.\n * Throws FlowAbandonedError for the human outcomes (denied, expired,\n * enrolment abandoned) and OAuthFlowError for protocol ones.\n */\nexport async function pollUntilApproved(\n issuer: string,\n requestId: string,\n onState?: (state: PairingState) => void,\n signal?: AbortSignal\n): Promise<string> {\n for (;;) {\n const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {\n signal,\n });\n const body = (await parseJson(response)) as unknown as PairStatusResponse;\n\n if (!response.ok) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Pairing status failed (${response.status})`\n );\n }\n\n onState?.({\n status: body.status,\n expiresIn: body.expires_in,\n enrolmentDeadline: body.enrolment_deadline,\n });\n\n switch (body.status) {\n case 'approved':\n if (!body.code) {\n throw new OAuthFlowError('server_error', 'approved with no authorization code');\n }\n return body.code;\n case 'denied':\n throw new FlowAbandonedError({ type: 'request_denied', description: body.error_description });\n case 'expired':\n throw new FlowAbandonedError({ type: 'request_expired', description: body.error_description });\n case 'cancelled':\n // The provider cancels an over-polled or abandoned request outright\n // (its pairing rows have a real cancelled state). Before 0.1.4 this\n // fell through to the default branch and polled a dead request\n // forever.\n throw new FlowAbandonedError({\n type: 'request_expired',\n description: body.error_description ?? 'the provider cancelled the pairing request',\n });\n case 'enrolling':\n await sleep(POLL_INTERVAL_ENROLLING_MS, signal);\n break;\n default:\n await sleep(POLL_INTERVAL_MS, signal);\n }\n }\n}\n\n/**\n * The code exchange, browser-direct mode only: a public client, PKCE and no\n * secret. What comes back can only ever be the pseudonymous tier, by\n * construction rather than by rule: personal data lives at /userinfo behind an\n * access token this mode is never issued, because personal-data scopes are\n * refused for public clients at the pairing step.\n */\nexport async function exchangeCode(\n issuer: string,\n input: { code: string; code_verifier: string; client_id: string }\n): Promise<TokenResponse> {\n const response = await fetch(`${issuer}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code,\n code_verifier: input.code_verifier,\n client_id: input.client_id,\n }),\n });\n\n const body = (await parseJson(response)) as unknown as TokenResponse;\n if (!response.ok || body.error) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Token exchange failed (${response.status})`\n );\n }\n return body;\n}\n\n/** A mobile user agent gets the app link, not a QR of its own screen. */\nexport function isMobileUserAgent(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /android|iphone|ipad|ipod/i.test(navigator.userAgent);\n}\n","/**\n * PKCE, S256 only: mandatory for every client, confidential ones included. There is no plain fallback and there must never\n * be one; a provider seeing method=plain is seeing a bug or an attack.\n */\n\nconst VERIFIER_BYTES = 32; // 43 base64url chars, the RFC 7636 minimum length\n\nconst base64url = (bytes: Uint8Array): string =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nexport function generateVerifier(): string {\n const bytes = new Uint8Array(VERIFIER_BYTES);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\nexport async function challengeS256(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n\nexport function generateState(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n","import { useEffect, useRef } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport type { UseZorealAutoLoginOptions } from './types';\n\n/**\n * Silent re-auth with prompt=none. NOT One Tap and never could be: there is no\n * ZOREAL session cookie in the browser to read, the credential is on a phone.\n * This succeeds only for a returning user at a consented sector with a live\n * session, the resulting acr is zoreal.session with an empty amr, and a\n * relying party that needs a live human must not build on this hook.\n *\n * PRIVACY NOTE: mounting this on a page sends a request to ZOREAL on page\n * load, before the user does anything. The hook is therefore conservative: it\n * fires once per mount, never retries, and does nothing when `disabled`.\n */\nexport function useZorealAutoLogin(options: UseZorealAutoLoginOptions): void {\n const { login } = useZorealFlow({\n flow: 'browser-direct',\n scope: options.scope,\n prompt: 'none',\n onCredential: options.onSuccess,\n onError: (e) => {\n // The provider's honest answer when no silent session exists (the\n // common case): unavailable, not an error, and never surfaced.\n const quiet = ['login_required', 'consent_required', 'interaction_required'];\n if (quiet.includes(e.error)) {\n options.onUnavailable?.();\n } else {\n options.onError?.({ type: 'unknown', description: e.description ?? e.error });\n }\n },\n onNonOAuthError: (e) => options.onError?.(e),\n });\n\n const fired = useRef(false);\n useEffect(() => {\n if (options.disabled || fired.current) return;\n fired.current = true;\n login();\n }, [options.disabled, login]);\n}\n","/**\n * Clears SDK-held local state. Named for parity with googleLogout and, like\n * it, LOCAL ONLY: it does not end the holder's ZOREAL session, which lives on\n * their phone and at the provider. A relying party that believes this signs\n * the user out of ZOREAL has a security misunderstanding, not a naming\n * complaint. The relying party's own session is the relying\n * party's to end.\n *\n * The SDK deliberately persists nothing (no localStorage, no cookies), so\n * today this has nothing to clear and exists as the stable API surface for a\n * future that does.\n */\nexport function zorealLogout(): void {\n // Intentionally empty until the SDK holds state worth clearing.\n}\n","import type { ZorealCodeResponse } from './types';\n\n/** Mirrors hasGrantedAllScopesGoogle for name-for-name portability. */\nexport function hasGrantedAllScopesZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].every((s) => granted.has(s));\n}\n\nexport function hasGrantedAnyScopeZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].some((s) => granted.has(s));\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react"]}
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  interface ZorealOAuthProviderProps {
5
- /** From the zoreal-web dashboard: the asset ID. */
5
+ /** From the ZOREAL dashboard: the asset ID. */
6
6
  clientId: string;
7
7
  /** Override the provider origin. Sandbox and self-hosted testing only. */
8
8
  issuer?: string;
@@ -33,7 +33,12 @@ type NonOAuthError = {
33
33
  };
34
34
  /** How the holder reached this login. Analogous to Google's select_by. */
35
35
  type SelectBy = 'qr' | 'app_link' | 'device' | 'session';
36
- /** How the login was actually authenticated. Describes what happened, never what was requested. */
36
+ /**
37
+ * How the login was actually authenticated. Describes what happened, never
38
+ * what was requested: 'zoreal.live' means a fresh liveness capture was passed
39
+ * for this login. As acr_values it is a request; only the signed acr claim in
40
+ * the ID token confirms it, so verify that claim on your backend.
41
+ */
37
42
  type AcrValue = 'zoreal.live' | 'zoreal.device' | 'zoreal.session';
38
43
  interface PairingState {
39
44
  status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';
@@ -59,7 +64,12 @@ interface PairingState {
59
64
  interface ZorealLoginRequestOptions {
60
65
  /** Defaults to 'openid'. Scopes that return personal data require flow: 'auth-code'. */
61
66
  scope?: string;
62
- /** Ask for a specific assurance. Omit to accept the default, zoreal.device. */
67
+ /**
68
+ * Ask for a specific assurance. 'zoreal.live' requires a fresh liveness
69
+ * capture in the ZOREAL ID app before the login can complete. Advisory:
70
+ * your backend must verify the signed acr claim in the ID token. Omit to
71
+ * accept the default, zoreal.device.
72
+ */
63
73
  acr_values?: AcrValue | AcrValue[];
64
74
  /** Seconds. Forces re-authentication when auth_time is older. */
65
75
  max_age?: number;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  interface ZorealOAuthProviderProps {
5
- /** From the zoreal-web dashboard: the asset ID. */
5
+ /** From the ZOREAL dashboard: the asset ID. */
6
6
  clientId: string;
7
7
  /** Override the provider origin. Sandbox and self-hosted testing only. */
8
8
  issuer?: string;
@@ -33,7 +33,12 @@ type NonOAuthError = {
33
33
  };
34
34
  /** How the holder reached this login. Analogous to Google's select_by. */
35
35
  type SelectBy = 'qr' | 'app_link' | 'device' | 'session';
36
- /** How the login was actually authenticated. Describes what happened, never what was requested. */
36
+ /**
37
+ * How the login was actually authenticated. Describes what happened, never
38
+ * what was requested: 'zoreal.live' means a fresh liveness capture was passed
39
+ * for this login. As acr_values it is a request; only the signed acr claim in
40
+ * the ID token confirms it, so verify that claim on your backend.
41
+ */
37
42
  type AcrValue = 'zoreal.live' | 'zoreal.device' | 'zoreal.session';
38
43
  interface PairingState {
39
44
  status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';
@@ -59,7 +64,12 @@ interface PairingState {
59
64
  interface ZorealLoginRequestOptions {
60
65
  /** Defaults to 'openid'. Scopes that return personal data require flow: 'auth-code'. */
61
66
  scope?: string;
62
- /** Ask for a specific assurance. Omit to accept the default, zoreal.device. */
67
+ /**
68
+ * Ask for a specific assurance. 'zoreal.live' requires a fresh liveness
69
+ * capture in the ZOREAL ID app before the login can complete. Advisory:
70
+ * your backend must verify the signed acr claim in the ID token. Omit to
71
+ * accept the default, zoreal.device.
72
+ */
63
73
  acr_values?: AcrValue | AcrValue[];
64
74
  /** Seconds. Forces re-authentication when auth_time is older. */
65
75
  max_age?: number;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { createContext, useContext, useMemo } from "react";
5
5
 
6
6
  // src/wire.ts
7
7
  var WIRE_VERSION = 1;
8
- var SDK_VERSION = "0.1.5";
8
+ var SDK_VERSION = "0.1.7";
9
9
  var DEFAULT_ISSUER = "https://id.zoreal.com";
10
10
  var POLL_INTERVAL_MS = 2e3;
11
11
  var POLL_INTERVAL_ENROLLING_MS = 5e3;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.tsx","../src/wire.ts","../src/ZorealLogin.tsx","../src/useZorealLogin.ts","../src/jwt.ts","../src/pairing.ts","../src/pkce.ts","../src/useZorealAutoLogin.ts","../src/logout.ts","../src/scopes.ts"],"sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { DEFAULT_ISSUER } from './wire';\n\nexport interface ZorealOAuthProviderProps {\n /** From the zoreal-web dashboard: the asset ID. */\n clientId: string;\n /** Override the provider origin. Sandbox and self-hosted testing only. */\n issuer?: string;\n /** BCP 47. Drives button text and the pairing page. */\n locale?: string;\n children: ReactNode;\n}\n\nexport interface ZorealOAuthContextProps {\n clientId: string;\n issuer: string;\n locale?: string;\n}\n\nconst ZorealOAuthContext = createContext<ZorealOAuthContextProps | null>(null);\n\nexport function ZorealOAuthProvider({\n clientId,\n issuer = DEFAULT_ISSUER,\n locale,\n children,\n}: ZorealOAuthProviderProps) {\n const value = useMemo(\n () => ({ clientId, issuer: issuer.replace(/\\/$/, ''), locale }),\n [clientId, issuer, locale]\n );\n return <ZorealOAuthContext.Provider value={value}>{children}</ZorealOAuthContext.Provider>;\n}\n\nexport function useZorealOAuth(): ZorealOAuthContextProps {\n const ctx = useContext(ZorealOAuthContext);\n if (!ctx) {\n throw new Error(\n 'useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. ' +\n 'Wrap your app (or the part that logs in) in the provider.'\n );\n }\n return ctx;\n}\n","/**\n * The wire protocol between this package and the ZOREAL OpenID Provider.\n *\n * VERSIONED: a shipped version keeps working until the provider explicitly\n * refuses it, and when it does, the reason is surfaced verbatim. Both the wire\n * version and the package version travel on every pairing request so a refusal\n * can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (the dashboard):\n *\n * POST /pair start a pairing request. Body carries the\n * authorize parameters plus PKCE challenge.\n * Returns { request_id, pair_url, expires_in }\n * or, for prompt=none with a live consented\n * session, { code } immediately.\n * GET /pair/:id/status poll: pending | claimed |\n * approved (with code) | denied | expired |\n * enrolling. Over-polling cancels the request\n * rather than throttling it, so the cadence\n * below is not a suggestion.\n * GET /pair/:id/qr.svg the QR image for the pairing URL, served by\n * the provider so the pairing surface stays\n * changeable at runtime and\n * this package keeps zero dependencies.\n * POST /token the code exchange. Browser-direct mode uses\n * it directly with PKCE and no client secret;\n * auth-code mode leaves it to the RP backend.\n */\n\nexport const WIRE_VERSION = 1;\nexport const SDK_VERSION = '0.1.5';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** Pending TTL is short. Poll gently; over-polling cancels the request. */\nexport const POLL_INTERVAL_MS = 2000;\n/** Enrolling extends the window well beyond a normal login; poll slower. */\nexport const POLL_INTERVAL_ENROLLING_MS = 5000;\n\nexport interface PairCreated {\n request_id: string;\n /** https://zoreal.com/qr/<request_id>. The same URL in QR and app link. */\n pair_url: string;\n expires_in: number;\n}\n\nexport interface PairImmediate {\n /** prompt=none resolved silently: consented sector, live session. */\n code: string;\n}\n\nexport type PairStartResponse = PairCreated | PairImmediate;\n\nexport interface PairStatusResponse {\n status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';\n code?: string;\n expires_in?: number;\n enrolment_deadline?: number;\n /** The provider's reason on denial or refusal. Surfaced verbatim, never rewritten. */\n error?: string;\n error_description?: string;\n}\n\nexport interface TokenResponse {\n id_token: string;\n access_token?: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n","import { useMemo, type CSSProperties } from 'react';\nimport { useZorealFlow, type ActivePairing } from './useZorealLogin';\nimport type { NonOAuthError, ZorealLoginProps } from './types';\n\n/**\n * The drop-in button. It receives no access token, so it returns the\n * pseudonymous identity only; personal data needs the auth-code flow.\n *\n * The copy is neutral: the button asserts nothing about a person who has not\n * yet authenticated. Styling is inline and self-contained; no stylesheet, no\n * font, no external asset, because this renders on a sign-in page.\n */\n\nconst TEXTS: Record<NonNullable<ZorealLoginProps['text']>, string> = {\n continue_with: 'Continue with ZOREAL',\n signin_with: 'Sign in with ZOREAL',\n signup_with: 'Sign up with ZOREAL',\n signin: 'Sign in',\n};\n\nconst SIZES = {\n large: { height: 44, font: 15, pad: 20 },\n medium: { height: 38, font: 14, pad: 16 },\n small: { height: 32, font: 12, pad: 12 },\n} as const;\n\n/** The ZOREAL mark, in currentColor so every theme carries it correctly. */\nconst Mark = ({ size }: { size: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"8.4 7.4 62.2 62.2\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n aria-hidden\n focusable=\"false\"\n >\n <path d=\"M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z\" />\n <path d=\"M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z\" />\n <path d=\"M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z\" />\n <path d=\"M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z\" />\n <path d=\"M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z\" />\n <path d=\"M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z\" />\n <path d=\"M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z\" />\n </svg>\n);\n\nconst PairingPanel = ({ pairing }: { pairing: ActivePairing }) => {\n const { status } = pairing.state;\n const line =\n status === 'claimed'\n ? 'Approve the login in your ZOREAL ID app.'\n : status === 'enrolling'\n ? 'Finishing enrolment. This screen will continue by itself.'\n : pairing.appLink\n ? 'Continue in the ZOREAL ID app, then return to this tab.'\n : 'Scan with your phone camera or the ZOREAL ID app.';\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Log in with ZOREAL\"\n style={{\n marginTop: 8,\n padding: 16,\n width: 232,\n borderRadius: 12,\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'Canvas',\n color: 'CanvasText',\n textAlign: 'center',\n fontFamily: 'inherit',\n }}\n >\n {!pairing.appLink && (\n <img\n src={pairing.qrUrl}\n alt={`QR code for ${pairing.pairUrl}`}\n width={200}\n height={200}\n style={{ display: 'block', margin: '0 auto', borderRadius: 8 }}\n />\n )}\n <p style={{ margin: '10px 0 0', fontSize: 12, lineHeight: 1.5 }}>{line}</p>\n <button\n type=\"button\"\n onClick={pairing.cancel}\n style={{\n marginTop: 10,\n border: 'none',\n background: 'none',\n color: 'inherit',\n opacity: 0.6,\n fontSize: 12,\n cursor: 'pointer',\n textDecoration: 'underline',\n }}\n >\n Cancel\n </button>\n </div>\n );\n};\n\nexport function ZorealLogin(props: ZorealLoginProps) {\n const {\n onSuccess,\n onError,\n containerProps,\n type = 'standard',\n theme = 'filled',\n size = 'large',\n text = 'continue_with',\n shape = 'rectangular',\n logo_alignment = 'left',\n width,\n click_listener,\n ...request\n } = props;\n\n const { login, internals } = useZorealFlow({\n ...request,\n flow: 'browser-direct',\n onCredential: onSuccess,\n onError: (e) => onError?.({ type: 'unknown', description: e.description ?? e.error }),\n onNonOAuthError: (e: NonOAuthError) => onError?.(e),\n });\n\n const s = SIZES[size];\n const style: CSSProperties = useMemo(\n () => ({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: logo_alignment === 'center' ? 'center' : 'flex-start',\n gap: 10,\n height: s.height,\n padding: `0 ${s.pad}px`,\n width,\n fontSize: s.font,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: shape === 'pill' ? s.height / 2 : shape === 'square' ? 4 : 8,\n ...(theme === 'outline'\n ? { background: 'transparent', color: 'inherit', border: '1px solid rgba(128,128,128,0.5)' }\n : theme === 'filled_black'\n ? { background: '#111', color: '#fff', border: '1px solid #111' }\n : { background: '#00b4d9', color: '#fff', border: '1px solid #00b4d9' }),\n }),\n [logo_alignment, s, shape, theme, width]\n );\n\n return (\n <div {...containerProps}>\n <button\n type=\"button\"\n style={style}\n onClick={() => {\n click_listener?.();\n login();\n }}\n >\n <Mark size={Math.round(s.font * 1.25)} />\n {type === 'standard' && TEXTS[text]}\n </button>\n {internals.pairing && !internals.pairing.appLink && (\n <PairingPanel pairing={internals.pairing} />\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useZorealOAuth } from './context';\nimport { unsafeClaims } from './jwt';\nimport {\n FlowAbandonedError,\n OAuthFlowError,\n exchangeCode,\n isMobileUserAgent,\n pollUntilApproved,\n startPairing,\n} from './pairing';\nimport { challengeS256, generateState, generateVerifier } from './pkce';\nimport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginRequestOptions,\n} from './types';\n\nexport interface ActivePairing {\n requestId: string;\n pairUrl: string;\n qrUrl: string;\n state: PairingState;\n /** True when display resolved to the app link rather than the QR. */\n appLink: boolean;\n cancel: () => void;\n}\n\ninterface FlowInternals {\n /** Non-null while a pairing is on screen. ZorealLogin renders from this. */\n pairing: ActivePairing | null;\n}\n\n/**\n * The internal option shape: one flow discriminator, one success callback per\n * mode. The public API keeps Google's single overloaded onSuccess; this type\n * exists because an intersection of those two signatures is uninhabitable, and\n * the mapping from public to internal happens once, in useZorealLogin.\n */\nexport interface InternalFlowOptions extends ZorealLoginRequestOptions {\n flow: 'browser-direct' | 'auth-code';\n redirect_uri?: string;\n onCredential?: (response: ZorealCredentialResponse) => void;\n onCode?: (response: ZorealCodeResponse) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n}\n\n/**\n * The one flow, shared by the hook and the button. Starts a pairing, exposes\n * it for rendering, polls, and finishes per mode: browser-direct exchanges the\n * code here (public client, PKCE, no secret) and hands over an ID token;\n * auth-code hands the code and the PKCE verifier to the caller, whose backend\n * does the exchange with its client authentication.\n */\nexport function useZorealFlow(options: InternalFlowOptions): {\n login: () => void;\n internals: FlowInternals;\n} {\n const { clientId, issuer, locale } = useZorealOAuth();\n const [pairing, setPairing] = useState<ActivePairing | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // A component unmounting mid-login must stop the poll: the provider cancels\n // over-polled requests, and an orphaned interval is exactly how one happens.\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const login = useCallback(() => {\n const opts = optionsRef.current;\n const run = async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const flow = opts.flow;\n const verifier = generateVerifier();\n const state = generateState();\n const nonce = generateState();\n\n try {\n const started = await startPairing(issuer, {\n client_id: clientId,\n scope: opts.scope ?? 'openid',\n state,\n nonce,\n code_challenge: await challengeS256(verifier),\n redirect_uri: flow === 'auth-code' ? opts.redirect_uri : undefined,\n acr_values: Array.isArray(opts.acr_values)\n ? opts.acr_values.join(' ')\n : opts.acr_values,\n max_age: opts.max_age,\n prompt: opts.prompt,\n locale,\n });\n\n let code: string;\n let selectBy: SelectBy = 'device';\n\n if ('code' in started) {\n // prompt=none resolved silently: consented sector, live session.\n code = started.code;\n selectBy = 'session';\n } else {\n const useAppLink =\n opts.display === 'link' || (opts.display !== 'qr' && isMobileUserAgent());\n selectBy = useAppLink ? 'app_link' : 'qr';\n\n const cancel = () => {\n controller.abort();\n setPairing(null);\n };\n // Everything a caller-rendered pairing UI needs, on every state it\n // sees: the QR flow cannot complete unless SOMETHING renders\n // pairUrl, and for the auth-code flow that something is the caller.\n const surface = {\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n appLink: useAppLink,\n cancel,\n };\n const active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: surface.pairUrl,\n qrUrl: surface.qrUrl,\n state: { status: 'pending', expiresIn: started.expires_in, ...surface },\n appLink: useAppLink,\n cancel,\n };\n setPairing(active);\n // The initial state, immediately: the first poll response is one\n // round-trip away, and a UI that waits for it opens visibly empty.\n opts.onPairingStateChange?.(active.state);\n\n if (useAppLink) {\n // The universal link, in the same tab: the app claims it, and with\n // no app installed the same URL is the real pairing page which can\n // enrol (02 sections 3 and 4). A popup here would be blocked more\n // often than it would help.\n window.location.assign(started.pair_url);\n }\n\n code = await pollUntilApproved(\n issuer,\n started.request_id,\n (s) => {\n const enriched = { ...s, ...surface };\n setPairing((p) =>\n p && p.requestId === started.request_id ? { ...p, state: enriched } : p\n );\n opts.onPairingStateChange?.(enriched);\n },\n controller.signal\n );\n }\n\n setPairing(null);\n\n if (flow === 'auth-code') {\n opts.onCode?.({\n code,\n scope: opts.scope ?? 'openid',\n app_state: opts.app_state,\n code_verifier: verifier,\n nonce,\n });\n return;\n }\n\n const tokens = await exchangeCode(issuer, {\n code,\n code_verifier: verifier,\n client_id: clientId,\n });\n const claims = unsafeClaims(tokens.id_token);\n const response: ZorealCredentialResponse = {\n credential: tokens.id_token,\n clientId,\n select_by: selectBy,\n acr: (claims.acr as AcrValue) ?? 'zoreal.device',\n };\n opts.onCredential?.(response);\n } catch (e) {\n setPairing(null);\n if (e instanceof DOMException && e.name === 'AbortError') return;\n if (e instanceof FlowAbandonedError) {\n opts.onNonOAuthError?.(e.reason);\n return;\n }\n if (e instanceof OAuthFlowError) {\n opts.onError?.({ error: e.error, description: e.description });\n return;\n }\n opts.onNonOAuthError?.({\n type: 'unknown',\n description: e instanceof Error ? e.message : String(e),\n });\n }\n };\n void run();\n }, [clientId, issuer, locale]);\n\n return { login, internals: { pairing } };\n}\n\nexport function useZorealLogin(\n options: { flow?: 'browser-direct' } & BrowserDirectFlowOptions\n): () => void;\nexport function useZorealLogin(options: { flow: 'auth-code' } & AuthCodeFlowOptions): () => void;\nexport function useZorealLogin(\n options: ({ flow?: 'browser-direct' | 'auth-code' } & ZorealLoginRequestOptions) &\n Partial<Pick<AuthCodeFlowOptions, 'redirect_uri' | 'ux_mode'>> & {\n onSuccess?: (response: never) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n }\n): () => void {\n if (options.ux_mode === 'redirect') {\n // v1 supports the popup shape only: the code and PKCE verifier go to your\n // onSuccess and from there to your backend over TLS. A redirect would have\n // to carry the verifier in a URL, which is a credential in every access\n // log on the path. Refused loudly rather than implemented badly.\n throw new Error(\n \"@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default \" +\n \"'popup' shape and post the code and code_verifier from onSuccess to your backend.\"\n );\n }\n const flow = options.flow ?? 'browser-direct';\n return useZorealFlow({\n ...options,\n flow,\n onCredential:\n flow === 'browser-direct'\n ? (options.onSuccess as unknown as (r: ZorealCredentialResponse) => void)\n : undefined,\n onCode:\n flow === 'auth-code'\n ? (options.onSuccess as unknown as (r: ZorealCodeResponse) => void)\n : undefined,\n }).login;\n}\n","/**\n * Reads claims OUT of an ID token without verifying it.\n *\n * That is not a shortcut, it is the design: this code runs in a browser the\n * threat model assumes is attacker-controlled (02), so a signature check here\n * proves nothing to anyone. The token is verified where verification means\n * something: server-side against the JWKS. What this parser feeds is\n * convenience fields (acr on the response object) that the types document as\n * convenience, with the token staying the authority.\n */\n\nexport function unsafeClaims(idToken: string): Record<string, unknown> {\n try {\n const payload = idToken.split('.')[1] ?? '';\n const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n return JSON.parse(\n new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))\n );\n } catch {\n return {};\n }\n}\n","/**\n * The pairing channel, client side. wire.ts pins the endpoints.\n *\n * The browser polls; the phone never talks to the browser. Everything here is\n * therefore plain fetch against the issuer, CORS-gated on the client's\n * authorized origins, with the poll cadence fixed: the provider cancels an\n * over-polling request rather than throttling it, so a \"retry\n * faster on error\" strategy here would kill the login it is trying to save.\n */\n\nimport {\n POLL_INTERVAL_ENROLLING_MS,\n POLL_INTERVAL_MS,\n SDK_VERSION,\n WIRE_VERSION,\n type PairStartResponse,\n type PairStatusResponse,\n type TokenResponse,\n} from './wire';\nimport type { ErrorCode, NonOAuthError, PairingState } from './types';\n\nexport class OAuthFlowError extends Error {\n constructor(\n public error: ErrorCode,\n public description?: string\n ) {\n super(description ?? error);\n }\n}\n\nexport class FlowAbandonedError extends Error {\n constructor(public reason: NonOAuthError) {\n super(reason.description ?? reason.type);\n }\n}\n\nexport interface StartPairingParams {\n client_id: string;\n scope: string;\n state: string;\n nonce: string;\n code_challenge: string;\n redirect_uri?: string;\n acr_values?: string;\n max_age?: number;\n prompt?: string;\n locale?: string;\n}\n\nasync function parseJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function startPairing(\n issuer: string,\n params: StartPairingParams\n): Promise<PairStartResponse> {\n const response = await fetch(`${issuer}/pair`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...params,\n code_challenge_method: 'S256',\n wire_version: WIRE_VERSION,\n sdk: `@zoreal/oauth2-react/${SDK_VERSION}`,\n }),\n });\n\n const body = await parseJson(response);\n if (!response.ok) {\n // The provider's words, verbatim. A refused package version arrives here,\n // and rewriting its reason would hide the only signal telling an integrator\n // to upgrade.\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n (body.error_description as string) ?? `The provider refused the request (${response.status})`\n );\n }\n return body as unknown as PairStartResponse;\n}\n\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n // An already-aborted signal never fires its abort event, so check first\n // or the sleep runs to term and the poll takes one extra swing.\n if (signal?.aborted) {\n reject(new DOMException('aborted', 'AbortError'));\n return;\n }\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('aborted', 'AbortError'));\n });\n });\n\n/**\n * Polls until the request resolves. Returns the authorization code.\n * Throws FlowAbandonedError for the human outcomes (denied, expired,\n * enrolment abandoned) and OAuthFlowError for protocol ones.\n */\nexport async function pollUntilApproved(\n issuer: string,\n requestId: string,\n onState?: (state: PairingState) => void,\n signal?: AbortSignal\n): Promise<string> {\n for (;;) {\n const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {\n signal,\n });\n const body = (await parseJson(response)) as unknown as PairStatusResponse;\n\n if (!response.ok) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Pairing status failed (${response.status})`\n );\n }\n\n onState?.({\n status: body.status,\n expiresIn: body.expires_in,\n enrolmentDeadline: body.enrolment_deadline,\n });\n\n switch (body.status) {\n case 'approved':\n if (!body.code) {\n throw new OAuthFlowError('server_error', 'approved with no authorization code');\n }\n return body.code;\n case 'denied':\n throw new FlowAbandonedError({ type: 'request_denied', description: body.error_description });\n case 'expired':\n throw new FlowAbandonedError({ type: 'request_expired', description: body.error_description });\n case 'cancelled':\n // The provider cancels an over-polled or abandoned request outright\n // (its pairing rows have a real cancelled state). Before 0.1.4 this\n // fell through to the default branch and polled a dead request\n // forever.\n throw new FlowAbandonedError({\n type: 'request_expired',\n description: body.error_description ?? 'the provider cancelled the pairing request',\n });\n case 'enrolling':\n await sleep(POLL_INTERVAL_ENROLLING_MS, signal);\n break;\n default:\n await sleep(POLL_INTERVAL_MS, signal);\n }\n }\n}\n\n/**\n * The code exchange, browser-direct mode only: a public client, PKCE and no\n * secret. What comes back can only ever be the pseudonymous tier, by\n * construction rather than by rule: personal data lives at /userinfo behind an\n * access token this mode is never issued, because personal-data scopes are\n * refused for public clients at the pairing step.\n */\nexport async function exchangeCode(\n issuer: string,\n input: { code: string; code_verifier: string; client_id: string }\n): Promise<TokenResponse> {\n const response = await fetch(`${issuer}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code,\n code_verifier: input.code_verifier,\n client_id: input.client_id,\n }),\n });\n\n const body = (await parseJson(response)) as unknown as TokenResponse;\n if (!response.ok || body.error) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Token exchange failed (${response.status})`\n );\n }\n return body;\n}\n\n/** A mobile user agent gets the app link, not a QR of its own screen. */\nexport function isMobileUserAgent(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /android|iphone|ipad|ipod/i.test(navigator.userAgent);\n}\n","/**\n * PKCE, S256 only: mandatory for every client, confidential ones included. There is no plain fallback and there must never\n * be one; a provider seeing method=plain is seeing a bug or an attack.\n */\n\nconst VERIFIER_BYTES = 32; // 43 base64url chars, the RFC 7636 minimum length\n\nconst base64url = (bytes: Uint8Array): string =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nexport function generateVerifier(): string {\n const bytes = new Uint8Array(VERIFIER_BYTES);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\nexport async function challengeS256(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n\nexport function generateState(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n","import { useEffect, useRef } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport type { UseZorealAutoLoginOptions } from './types';\n\n/**\n * Silent re-auth with prompt=none. NOT One Tap and never could be: there is no\n * ZOREAL session cookie in the browser to read, the credential is on a phone.\n * This succeeds only for a returning user at a consented sector with a live\n * session, the resulting acr is zoreal.session with an empty amr, and a\n * relying party that needs a live human must not build on this hook.\n *\n * PRIVACY NOTE: mounting this on a page sends a request to ZOREAL on page\n * load, before the user does anything. The hook is therefore conservative: it\n * fires once per mount, never retries, and does nothing when `disabled`.\n */\nexport function useZorealAutoLogin(options: UseZorealAutoLoginOptions): void {\n const { login } = useZorealFlow({\n flow: 'browser-direct',\n scope: options.scope,\n prompt: 'none',\n onCredential: options.onSuccess,\n onError: (e) => {\n // The provider's honest answer when no silent session exists (the\n // common case): unavailable, not an error, and never surfaced.\n const quiet = ['login_required', 'consent_required', 'interaction_required'];\n if (quiet.includes(e.error)) {\n options.onUnavailable?.();\n } else {\n options.onError?.({ type: 'unknown', description: e.description ?? e.error });\n }\n },\n onNonOAuthError: (e) => options.onError?.(e),\n });\n\n const fired = useRef(false);\n useEffect(() => {\n if (options.disabled || fired.current) return;\n fired.current = true;\n login();\n }, [options.disabled, login]);\n}\n","/**\n * Clears SDK-held local state. Named for parity with googleLogout and, like\n * it, LOCAL ONLY: it does not end the holder's ZOREAL session, which lives on\n * their phone and at the provider. A relying party that believes this signs\n * the user out of ZOREAL has a security misunderstanding, not a naming\n * complaint. The relying party's own session is the relying\n * party's to end.\n *\n * The SDK deliberately persists nothing (no localStorage, no cookies), so\n * today this has nothing to clear and exists as the stable API surface for a\n * future that does.\n */\nexport function zorealLogout(): void {\n // Intentionally empty until the SDK holds state worth clearing.\n}\n","import type { ZorealCodeResponse } from './types';\n\n/** Mirrors hasGrantedAllScopesGoogle for name-for-name portability. */\nexport function hasGrantedAllScopesZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].every((s) => granted.has(s));\n}\n\nexport function hasGrantedAnyScopeZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].some((s) => granted.has(s));\n}\n"],"mappings":"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","names":["useMemo","jsx","useMemo","useEffect","useRef","useRef","useEffect"]}
1
+ {"version":3,"sources":["../src/context.tsx","../src/wire.ts","../src/ZorealLogin.tsx","../src/useZorealLogin.ts","../src/jwt.ts","../src/pairing.ts","../src/pkce.ts","../src/useZorealAutoLogin.ts","../src/logout.ts","../src/scopes.ts"],"sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { DEFAULT_ISSUER } from './wire';\n\nexport interface ZorealOAuthProviderProps {\n /** From the ZOREAL dashboard: the asset ID. */\n clientId: string;\n /** Override the provider origin. Sandbox and self-hosted testing only. */\n issuer?: string;\n /** BCP 47. Drives button text and the pairing page. */\n locale?: string;\n children: ReactNode;\n}\n\nexport interface ZorealOAuthContextProps {\n clientId: string;\n issuer: string;\n locale?: string;\n}\n\nconst ZorealOAuthContext = createContext<ZorealOAuthContextProps | null>(null);\n\nexport function ZorealOAuthProvider({\n clientId,\n issuer = DEFAULT_ISSUER,\n locale,\n children,\n}: ZorealOAuthProviderProps) {\n const value = useMemo(\n () => ({ clientId, issuer: issuer.replace(/\\/$/, ''), locale }),\n [clientId, issuer, locale]\n );\n return <ZorealOAuthContext.Provider value={value}>{children}</ZorealOAuthContext.Provider>;\n}\n\nexport function useZorealOAuth(): ZorealOAuthContextProps {\n const ctx = useContext(ZorealOAuthContext);\n if (!ctx) {\n throw new Error(\n 'useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. ' +\n 'Wrap your app (or the part that logs in) in the provider.'\n );\n }\n return ctx;\n}\n","/**\n * The wire protocol between this package and the ZOREAL OpenID Provider.\n *\n * VERSIONED: a shipped version keeps working until the provider explicitly\n * refuses it, and when it does, the reason is surfaced verbatim. Both the wire\n * version and the package version travel on every pairing request so a refusal\n * can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (the dashboard):\n *\n * POST /pair start a pairing request. Body carries the\n * authorize parameters plus PKCE challenge.\n * Returns { request_id, pair_url, expires_in }\n * or, for prompt=none with a live consented\n * session, { code } immediately.\n * GET /pair/:id/status poll: pending | claimed |\n * approved (with code) | denied | expired |\n * enrolling. Over-polling cancels the request\n * rather than throttling it, so the cadence\n * below is not a suggestion.\n * GET /pair/:id/qr.svg the QR image for the pairing URL, served by\n * the provider so the pairing surface stays\n * changeable at runtime and\n * this package keeps zero dependencies.\n * POST /token the code exchange. Browser-direct mode uses\n * it directly with PKCE and no client secret;\n * auth-code mode leaves it to the RP backend.\n */\n\nexport const WIRE_VERSION = 1;\nexport const SDK_VERSION = '0.1.7';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** Pending TTL is short. Poll gently; over-polling cancels the request. */\nexport const POLL_INTERVAL_MS = 2000;\n/** Enrolling extends the window well beyond a normal login; poll slower. */\nexport const POLL_INTERVAL_ENROLLING_MS = 5000;\n\nexport interface PairCreated {\n request_id: string;\n /** https://zoreal.com/qr/<request_id>. The same URL in QR and app link. */\n pair_url: string;\n expires_in: number;\n}\n\nexport interface PairImmediate {\n /** prompt=none resolved silently: consented sector, live session. */\n code: string;\n}\n\nexport type PairStartResponse = PairCreated | PairImmediate;\n\nexport interface PairStatusResponse {\n status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';\n code?: string;\n expires_in?: number;\n enrolment_deadline?: number;\n /** The provider's reason on denial or refusal. Surfaced verbatim, never rewritten. */\n error?: string;\n error_description?: string;\n}\n\nexport interface TokenResponse {\n id_token: string;\n access_token?: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n","import { useMemo, type CSSProperties } from 'react';\nimport { useZorealFlow, type ActivePairing } from './useZorealLogin';\nimport type { NonOAuthError, ZorealLoginProps } from './types';\n\n/**\n * The drop-in button. It receives no access token, so it returns the\n * pseudonymous identity only; personal data needs the auth-code flow.\n *\n * The copy is neutral: the button asserts nothing about a person who has not\n * yet authenticated. Styling is inline and self-contained; no stylesheet, no\n * font, no external asset, because this renders on a sign-in page.\n */\n\nconst TEXTS: Record<NonNullable<ZorealLoginProps['text']>, string> = {\n continue_with: 'Continue with ZOREAL',\n signin_with: 'Sign in with ZOREAL',\n signup_with: 'Sign up with ZOREAL',\n signin: 'Sign in',\n};\n\nconst SIZES = {\n large: { height: 44, font: 15, pad: 20 },\n medium: { height: 38, font: 14, pad: 16 },\n small: { height: 32, font: 12, pad: 12 },\n} as const;\n\n/** The ZOREAL mark, in currentColor so every theme carries it correctly. */\nconst Mark = ({ size }: { size: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"8.4 7.4 62.2 62.2\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n aria-hidden\n focusable=\"false\"\n >\n <path d=\"M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z\" />\n <path d=\"M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z\" />\n <path d=\"M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z\" />\n <path d=\"M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z\" />\n <path d=\"M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z\" />\n <path d=\"M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z\" />\n <path d=\"M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z\" />\n </svg>\n);\n\nconst PairingPanel = ({ pairing }: { pairing: ActivePairing }) => {\n const { status } = pairing.state;\n const line =\n status === 'claimed'\n ? 'Approve the login in your ZOREAL ID app.'\n : status === 'enrolling'\n ? 'Finishing enrolment. This screen will continue by itself.'\n : pairing.appLink\n ? 'Continue in the ZOREAL ID app, then return to this tab.'\n : 'Scan with your phone camera or the ZOREAL ID app.';\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Log in with ZOREAL\"\n style={{\n marginTop: 8,\n padding: 16,\n width: 232,\n borderRadius: 12,\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'Canvas',\n color: 'CanvasText',\n textAlign: 'center',\n fontFamily: 'inherit',\n }}\n >\n {!pairing.appLink && (\n <img\n src={pairing.qrUrl}\n alt={`QR code for ${pairing.pairUrl}`}\n width={200}\n height={200}\n style={{ display: 'block', margin: '0 auto', borderRadius: 8 }}\n />\n )}\n <p style={{ margin: '10px 0 0', fontSize: 12, lineHeight: 1.5 }}>{line}</p>\n <button\n type=\"button\"\n onClick={pairing.cancel}\n style={{\n marginTop: 10,\n border: 'none',\n background: 'none',\n color: 'inherit',\n opacity: 0.6,\n fontSize: 12,\n cursor: 'pointer',\n textDecoration: 'underline',\n }}\n >\n Cancel\n </button>\n </div>\n );\n};\n\nexport function ZorealLogin(props: ZorealLoginProps) {\n const {\n onSuccess,\n onError,\n containerProps,\n type = 'standard',\n theme = 'filled',\n size = 'large',\n text = 'continue_with',\n shape = 'rectangular',\n logo_alignment = 'left',\n width,\n click_listener,\n ...request\n } = props;\n\n const { login, internals } = useZorealFlow({\n ...request,\n flow: 'browser-direct',\n onCredential: onSuccess,\n onError: (e) => onError?.({ type: 'unknown', description: e.description ?? e.error }),\n onNonOAuthError: (e: NonOAuthError) => onError?.(e),\n });\n\n const s = SIZES[size];\n const style: CSSProperties = useMemo(\n () => ({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: logo_alignment === 'center' ? 'center' : 'flex-start',\n gap: 10,\n height: s.height,\n padding: `0 ${s.pad}px`,\n width,\n fontSize: s.font,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: shape === 'pill' ? s.height / 2 : shape === 'square' ? 4 : 8,\n ...(theme === 'outline'\n ? { background: 'transparent', color: 'inherit', border: '1px solid rgba(128,128,128,0.5)' }\n : theme === 'filled_black'\n ? { background: '#111', color: '#fff', border: '1px solid #111' }\n : { background: '#00b4d9', color: '#fff', border: '1px solid #00b4d9' }),\n }),\n [logo_alignment, s, shape, theme, width]\n );\n\n return (\n <div {...containerProps}>\n <button\n type=\"button\"\n style={style}\n onClick={() => {\n click_listener?.();\n login();\n }}\n >\n <Mark size={Math.round(s.font * 1.25)} />\n {type === 'standard' && TEXTS[text]}\n </button>\n {internals.pairing && !internals.pairing.appLink && (\n <PairingPanel pairing={internals.pairing} />\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useZorealOAuth } from './context';\nimport { unsafeClaims } from './jwt';\nimport {\n FlowAbandonedError,\n OAuthFlowError,\n exchangeCode,\n isMobileUserAgent,\n pollUntilApproved,\n startPairing,\n} from './pairing';\nimport { challengeS256, generateState, generateVerifier } from './pkce';\nimport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginRequestOptions,\n} from './types';\n\nexport interface ActivePairing {\n requestId: string;\n pairUrl: string;\n qrUrl: string;\n state: PairingState;\n /** True when display resolved to the app link rather than the QR. */\n appLink: boolean;\n cancel: () => void;\n}\n\ninterface FlowInternals {\n /** Non-null while a pairing is on screen. ZorealLogin renders from this. */\n pairing: ActivePairing | null;\n}\n\n/**\n * The internal option shape: one flow discriminator, one success callback per\n * mode. The public API keeps Google's single overloaded onSuccess; this type\n * exists because an intersection of those two signatures is uninhabitable, and\n * the mapping from public to internal happens once, in useZorealLogin.\n */\nexport interface InternalFlowOptions extends ZorealLoginRequestOptions {\n flow: 'browser-direct' | 'auth-code';\n redirect_uri?: string;\n onCredential?: (response: ZorealCredentialResponse) => void;\n onCode?: (response: ZorealCodeResponse) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n}\n\n/**\n * The one flow, shared by the hook and the button. Starts a pairing, exposes\n * it for rendering, polls, and finishes per mode: browser-direct exchanges the\n * code here (public client, PKCE, no secret) and hands over an ID token;\n * auth-code hands the code and the PKCE verifier to the caller, whose backend\n * does the exchange with its client authentication.\n */\nexport function useZorealFlow(options: InternalFlowOptions): {\n login: () => void;\n internals: FlowInternals;\n} {\n const { clientId, issuer, locale } = useZorealOAuth();\n const [pairing, setPairing] = useState<ActivePairing | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // A component unmounting mid-login must stop the poll: the provider cancels\n // over-polled requests, and an orphaned interval is exactly how one happens.\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const login = useCallback(() => {\n const opts = optionsRef.current;\n const run = async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const flow = opts.flow;\n const verifier = generateVerifier();\n const state = generateState();\n const nonce = generateState();\n\n try {\n const started = await startPairing(issuer, {\n client_id: clientId,\n scope: opts.scope ?? 'openid',\n state,\n nonce,\n code_challenge: await challengeS256(verifier),\n redirect_uri: flow === 'auth-code' ? opts.redirect_uri : undefined,\n acr_values: Array.isArray(opts.acr_values)\n ? opts.acr_values.join(' ')\n : opts.acr_values,\n max_age: opts.max_age,\n prompt: opts.prompt,\n locale,\n });\n\n let code: string;\n let selectBy: SelectBy = 'device';\n\n if ('code' in started) {\n // prompt=none resolved silently: consented sector, live session.\n code = started.code;\n selectBy = 'session';\n } else {\n const useAppLink =\n opts.display === 'link' || (opts.display !== 'qr' && isMobileUserAgent());\n selectBy = useAppLink ? 'app_link' : 'qr';\n\n const cancel = () => {\n controller.abort();\n setPairing(null);\n };\n // Everything a caller-rendered pairing UI needs, on every state it\n // sees: the QR flow cannot complete unless SOMETHING renders\n // pairUrl, and for the auth-code flow that something is the caller.\n const surface = {\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n appLink: useAppLink,\n cancel,\n };\n const active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: surface.pairUrl,\n qrUrl: surface.qrUrl,\n state: { status: 'pending', expiresIn: started.expires_in, ...surface },\n appLink: useAppLink,\n cancel,\n };\n setPairing(active);\n // The initial state, immediately: the first poll response is one\n // round-trip away, and a UI that waits for it opens visibly empty.\n opts.onPairingStateChange?.(active.state);\n\n if (useAppLink) {\n // The universal link, in the same tab: the app claims it, and with\n // no app installed the same URL is the real pairing page which can\n // enrol (02 sections 3 and 4). A popup here would be blocked more\n // often than it would help.\n window.location.assign(started.pair_url);\n }\n\n code = await pollUntilApproved(\n issuer,\n started.request_id,\n (s) => {\n const enriched = { ...s, ...surface };\n setPairing((p) =>\n p && p.requestId === started.request_id ? { ...p, state: enriched } : p\n );\n opts.onPairingStateChange?.(enriched);\n },\n controller.signal\n );\n }\n\n setPairing(null);\n\n if (flow === 'auth-code') {\n opts.onCode?.({\n code,\n scope: opts.scope ?? 'openid',\n app_state: opts.app_state,\n code_verifier: verifier,\n nonce,\n });\n return;\n }\n\n const tokens = await exchangeCode(issuer, {\n code,\n code_verifier: verifier,\n client_id: clientId,\n });\n const claims = unsafeClaims(tokens.id_token);\n const response: ZorealCredentialResponse = {\n credential: tokens.id_token,\n clientId,\n select_by: selectBy,\n acr: (claims.acr as AcrValue) ?? 'zoreal.device',\n };\n opts.onCredential?.(response);\n } catch (e) {\n setPairing(null);\n if (e instanceof DOMException && e.name === 'AbortError') return;\n if (e instanceof FlowAbandonedError) {\n opts.onNonOAuthError?.(e.reason);\n return;\n }\n if (e instanceof OAuthFlowError) {\n opts.onError?.({ error: e.error, description: e.description });\n return;\n }\n opts.onNonOAuthError?.({\n type: 'unknown',\n description: e instanceof Error ? e.message : String(e),\n });\n }\n };\n void run();\n }, [clientId, issuer, locale]);\n\n return { login, internals: { pairing } };\n}\n\nexport function useZorealLogin(\n options: { flow?: 'browser-direct' } & BrowserDirectFlowOptions\n): () => void;\nexport function useZorealLogin(options: { flow: 'auth-code' } & AuthCodeFlowOptions): () => void;\nexport function useZorealLogin(\n options: ({ flow?: 'browser-direct' | 'auth-code' } & ZorealLoginRequestOptions) &\n Partial<Pick<AuthCodeFlowOptions, 'redirect_uri' | 'ux_mode'>> & {\n onSuccess?: (response: never) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n }\n): () => void {\n if (options.ux_mode === 'redirect') {\n // v1 supports the popup shape only: the code and PKCE verifier go to your\n // onSuccess and from there to your backend over TLS. A redirect would have\n // to carry the verifier in a URL, which is a credential in every access\n // log on the path. Refused loudly rather than implemented badly.\n throw new Error(\n \"@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default \" +\n \"'popup' shape and post the code and code_verifier from onSuccess to your backend.\"\n );\n }\n const flow = options.flow ?? 'browser-direct';\n return useZorealFlow({\n ...options,\n flow,\n onCredential:\n flow === 'browser-direct'\n ? (options.onSuccess as unknown as (r: ZorealCredentialResponse) => void)\n : undefined,\n onCode:\n flow === 'auth-code'\n ? (options.onSuccess as unknown as (r: ZorealCodeResponse) => void)\n : undefined,\n }).login;\n}\n","/**\n * Reads claims OUT of an ID token without verifying it.\n *\n * That is not a shortcut, it is the design: this code runs in a browser the\n * threat model assumes is attacker-controlled (02), so a signature check here\n * proves nothing to anyone. The token is verified where verification means\n * something: server-side against the JWKS. What this parser feeds is\n * convenience fields (acr on the response object) that the types document as\n * convenience, with the token staying the authority.\n */\n\nexport function unsafeClaims(idToken: string): Record<string, unknown> {\n try {\n const payload = idToken.split('.')[1] ?? '';\n const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n return JSON.parse(\n new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))\n );\n } catch {\n return {};\n }\n}\n","/**\n * The pairing channel, client side. wire.ts pins the endpoints.\n *\n * The browser polls; the phone never talks to the browser. Everything here is\n * therefore plain fetch against the issuer, CORS-gated on the client's\n * authorized origins, with the poll cadence fixed: the provider cancels an\n * over-polling request rather than throttling it, so a \"retry\n * faster on error\" strategy here would kill the login it is trying to save.\n */\n\nimport {\n POLL_INTERVAL_ENROLLING_MS,\n POLL_INTERVAL_MS,\n SDK_VERSION,\n WIRE_VERSION,\n type PairStartResponse,\n type PairStatusResponse,\n type TokenResponse,\n} from './wire';\nimport type { ErrorCode, NonOAuthError, PairingState } from './types';\n\nexport class OAuthFlowError extends Error {\n constructor(\n public error: ErrorCode,\n public description?: string\n ) {\n super(description ?? error);\n }\n}\n\nexport class FlowAbandonedError extends Error {\n constructor(public reason: NonOAuthError) {\n super(reason.description ?? reason.type);\n }\n}\n\nexport interface StartPairingParams {\n client_id: string;\n scope: string;\n state: string;\n nonce: string;\n code_challenge: string;\n redirect_uri?: string;\n acr_values?: string;\n max_age?: number;\n prompt?: string;\n locale?: string;\n}\n\nasync function parseJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function startPairing(\n issuer: string,\n params: StartPairingParams\n): Promise<PairStartResponse> {\n const response = await fetch(`${issuer}/pair`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...params,\n code_challenge_method: 'S256',\n wire_version: WIRE_VERSION,\n sdk: `@zoreal/oauth2-react/${SDK_VERSION}`,\n }),\n });\n\n const body = await parseJson(response);\n if (!response.ok) {\n // The provider's words, verbatim. A refused package version arrives here,\n // and rewriting its reason would hide the only signal telling an integrator\n // to upgrade.\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n (body.error_description as string) ?? `The provider refused the request (${response.status})`\n );\n }\n return body as unknown as PairStartResponse;\n}\n\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n // An already-aborted signal never fires its abort event, so check first\n // or the sleep runs to term and the poll takes one extra swing.\n if (signal?.aborted) {\n reject(new DOMException('aborted', 'AbortError'));\n return;\n }\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('aborted', 'AbortError'));\n });\n });\n\n/**\n * Polls until the request resolves. Returns the authorization code.\n * Throws FlowAbandonedError for the human outcomes (denied, expired,\n * enrolment abandoned) and OAuthFlowError for protocol ones.\n */\nexport async function pollUntilApproved(\n issuer: string,\n requestId: string,\n onState?: (state: PairingState) => void,\n signal?: AbortSignal\n): Promise<string> {\n for (;;) {\n const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {\n signal,\n });\n const body = (await parseJson(response)) as unknown as PairStatusResponse;\n\n if (!response.ok) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Pairing status failed (${response.status})`\n );\n }\n\n onState?.({\n status: body.status,\n expiresIn: body.expires_in,\n enrolmentDeadline: body.enrolment_deadline,\n });\n\n switch (body.status) {\n case 'approved':\n if (!body.code) {\n throw new OAuthFlowError('server_error', 'approved with no authorization code');\n }\n return body.code;\n case 'denied':\n throw new FlowAbandonedError({ type: 'request_denied', description: body.error_description });\n case 'expired':\n throw new FlowAbandonedError({ type: 'request_expired', description: body.error_description });\n case 'cancelled':\n // The provider cancels an over-polled or abandoned request outright\n // (its pairing rows have a real cancelled state). Before 0.1.4 this\n // fell through to the default branch and polled a dead request\n // forever.\n throw new FlowAbandonedError({\n type: 'request_expired',\n description: body.error_description ?? 'the provider cancelled the pairing request',\n });\n case 'enrolling':\n await sleep(POLL_INTERVAL_ENROLLING_MS, signal);\n break;\n default:\n await sleep(POLL_INTERVAL_MS, signal);\n }\n }\n}\n\n/**\n * The code exchange, browser-direct mode only: a public client, PKCE and no\n * secret. What comes back can only ever be the pseudonymous tier, by\n * construction rather than by rule: personal data lives at /userinfo behind an\n * access token this mode is never issued, because personal-data scopes are\n * refused for public clients at the pairing step.\n */\nexport async function exchangeCode(\n issuer: string,\n input: { code: string; code_verifier: string; client_id: string }\n): Promise<TokenResponse> {\n const response = await fetch(`${issuer}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code,\n code_verifier: input.code_verifier,\n client_id: input.client_id,\n }),\n });\n\n const body = (await parseJson(response)) as unknown as TokenResponse;\n if (!response.ok || body.error) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Token exchange failed (${response.status})`\n );\n }\n return body;\n}\n\n/** A mobile user agent gets the app link, not a QR of its own screen. */\nexport function isMobileUserAgent(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /android|iphone|ipad|ipod/i.test(navigator.userAgent);\n}\n","/**\n * PKCE, S256 only: mandatory for every client, confidential ones included. There is no plain fallback and there must never\n * be one; a provider seeing method=plain is seeing a bug or an attack.\n */\n\nconst VERIFIER_BYTES = 32; // 43 base64url chars, the RFC 7636 minimum length\n\nconst base64url = (bytes: Uint8Array): string =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nexport function generateVerifier(): string {\n const bytes = new Uint8Array(VERIFIER_BYTES);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\nexport async function challengeS256(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n\nexport function generateState(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n","import { useEffect, useRef } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport type { UseZorealAutoLoginOptions } from './types';\n\n/**\n * Silent re-auth with prompt=none. NOT One Tap and never could be: there is no\n * ZOREAL session cookie in the browser to read, the credential is on a phone.\n * This succeeds only for a returning user at a consented sector with a live\n * session, the resulting acr is zoreal.session with an empty amr, and a\n * relying party that needs a live human must not build on this hook.\n *\n * PRIVACY NOTE: mounting this on a page sends a request to ZOREAL on page\n * load, before the user does anything. The hook is therefore conservative: it\n * fires once per mount, never retries, and does nothing when `disabled`.\n */\nexport function useZorealAutoLogin(options: UseZorealAutoLoginOptions): void {\n const { login } = useZorealFlow({\n flow: 'browser-direct',\n scope: options.scope,\n prompt: 'none',\n onCredential: options.onSuccess,\n onError: (e) => {\n // The provider's honest answer when no silent session exists (the\n // common case): unavailable, not an error, and never surfaced.\n const quiet = ['login_required', 'consent_required', 'interaction_required'];\n if (quiet.includes(e.error)) {\n options.onUnavailable?.();\n } else {\n options.onError?.({ type: 'unknown', description: e.description ?? e.error });\n }\n },\n onNonOAuthError: (e) => options.onError?.(e),\n });\n\n const fired = useRef(false);\n useEffect(() => {\n if (options.disabled || fired.current) return;\n fired.current = true;\n login();\n }, [options.disabled, login]);\n}\n","/**\n * Clears SDK-held local state. Named for parity with googleLogout and, like\n * it, LOCAL ONLY: it does not end the holder's ZOREAL session, which lives on\n * their phone and at the provider. A relying party that believes this signs\n * the user out of ZOREAL has a security misunderstanding, not a naming\n * complaint. The relying party's own session is the relying\n * party's to end.\n *\n * The SDK deliberately persists nothing (no localStorage, no cookies), so\n * today this has nothing to clear and exists as the stable API surface for a\n * future that does.\n */\nexport function zorealLogout(): void {\n // Intentionally empty until the SDK holds state worth clearing.\n}\n","import type { ZorealCodeResponse } from './types';\n\n/** Mirrors hasGrantedAllScopesGoogle for name-for-name portability. */\nexport function hasGrantedAllScopesZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].every((s) => granted.has(s));\n}\n\nexport function hasGrantedAnyScopeZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].some((s) => granted.has(s));\n}\n"],"mappings":"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","names":["useMemo","jsx","useMemo","useEffect","useRef","useRef","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoreal/oauth2-react",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Login with ZOREAL for React. A ZOREAL Verified Proof-of-Human behind every sign-in.",
5
5
  "license": "MIT",
6
6
  "repository": {