@zoreal/oauth2-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,487 @@
1
+ 'use client';
2
+
3
+ // src/context.tsx
4
+ import { createContext, useContext, useMemo } from "react";
5
+
6
+ // src/wire.ts
7
+ var WIRE_VERSION = 1;
8
+ var SDK_VERSION = "0.1.0";
9
+ var DEFAULT_ISSUER = "https://id.zoreal.com";
10
+ var POLL_INTERVAL_MS = 2e3;
11
+ var POLL_INTERVAL_ENROLLING_MS = 5e3;
12
+
13
+ // src/context.tsx
14
+ import { jsx } from "react/jsx-runtime";
15
+ var ZorealOAuthContext = createContext(null);
16
+ function ZorealOAuthProvider({
17
+ clientId,
18
+ issuer = DEFAULT_ISSUER,
19
+ locale,
20
+ children
21
+ }) {
22
+ const value = useMemo(
23
+ () => ({ clientId, issuer: issuer.replace(/\/$/, ""), locale }),
24
+ [clientId, issuer, locale]
25
+ );
26
+ return /* @__PURE__ */ jsx(ZorealOAuthContext.Provider, { value, children });
27
+ }
28
+ function useZorealOAuth() {
29
+ const ctx = useContext(ZorealOAuthContext);
30
+ if (!ctx) {
31
+ throw new Error(
32
+ "useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. Wrap your app (or the part that logs in) in the provider."
33
+ );
34
+ }
35
+ return ctx;
36
+ }
37
+
38
+ // src/ZorealLogin.tsx
39
+ import { useMemo as useMemo2 } from "react";
40
+
41
+ // src/useZorealLogin.ts
42
+ import { useCallback, useEffect, useRef, useState } from "react";
43
+
44
+ // src/jwt.ts
45
+ function unsafeClaims(idToken) {
46
+ try {
47
+ const payload = idToken.split(".")[1] ?? "";
48
+ const b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
49
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
50
+ return JSON.parse(
51
+ new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))
52
+ );
53
+ } catch {
54
+ return {};
55
+ }
56
+ }
57
+
58
+ // src/pairing.ts
59
+ var OAuthFlowError = class extends Error {
60
+ constructor(error, description) {
61
+ super(description ?? error);
62
+ this.error = error;
63
+ this.description = description;
64
+ }
65
+ };
66
+ var FlowAbandonedError = class extends Error {
67
+ constructor(reason) {
68
+ super(reason.description ?? reason.type);
69
+ this.reason = reason;
70
+ }
71
+ };
72
+ async function parseJson(response) {
73
+ try {
74
+ return await response.json();
75
+ } catch {
76
+ return {};
77
+ }
78
+ }
79
+ async function startPairing(issuer, params) {
80
+ const response = await fetch(`${issuer}/pair`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({
84
+ ...params,
85
+ code_challenge_method: "S256",
86
+ wire_version: WIRE_VERSION,
87
+ sdk: `@zoreal/oauth2-react/${SDK_VERSION}`
88
+ })
89
+ });
90
+ const body = await parseJson(response);
91
+ if (!response.ok) {
92
+ throw new OAuthFlowError(
93
+ body.error ?? "server_error",
94
+ body.error_description ?? `The provider refused the request (${response.status})`
95
+ );
96
+ }
97
+ return body;
98
+ }
99
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
100
+ const t = setTimeout(resolve, ms);
101
+ signal?.addEventListener("abort", () => {
102
+ clearTimeout(t);
103
+ reject(new DOMException("aborted", "AbortError"));
104
+ });
105
+ });
106
+ async function pollUntilApproved(issuer, requestId, onState, signal) {
107
+ for (; ; ) {
108
+ const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {
109
+ signal
110
+ });
111
+ const body = await parseJson(response);
112
+ if (!response.ok) {
113
+ throw new OAuthFlowError(
114
+ body.error ?? "server_error",
115
+ body.error_description ?? `Pairing status failed (${response.status})`
116
+ );
117
+ }
118
+ onState?.({
119
+ status: body.status,
120
+ expiresIn: body.expires_in,
121
+ enrolmentDeadline: body.enrolment_deadline
122
+ });
123
+ switch (body.status) {
124
+ case "approved":
125
+ if (!body.code) {
126
+ throw new OAuthFlowError("server_error", "approved with no authorization code");
127
+ }
128
+ return body.code;
129
+ case "denied":
130
+ throw new FlowAbandonedError({ type: "request_denied", description: body.error_description });
131
+ case "expired":
132
+ throw new FlowAbandonedError({ type: "request_expired", description: body.error_description });
133
+ case "enrolling":
134
+ await sleep(POLL_INTERVAL_ENROLLING_MS, signal);
135
+ break;
136
+ default:
137
+ await sleep(POLL_INTERVAL_MS, signal);
138
+ }
139
+ }
140
+ }
141
+ async function exchangeCode(issuer, input) {
142
+ const response = await fetch(`${issuer}/token`, {
143
+ method: "POST",
144
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
145
+ body: new URLSearchParams({
146
+ grant_type: "authorization_code",
147
+ code: input.code,
148
+ code_verifier: input.code_verifier,
149
+ client_id: input.client_id
150
+ })
151
+ });
152
+ const body = await parseJson(response);
153
+ if (!response.ok || body.error) {
154
+ throw new OAuthFlowError(
155
+ body.error ?? "server_error",
156
+ body.error_description ?? `Token exchange failed (${response.status})`
157
+ );
158
+ }
159
+ return body;
160
+ }
161
+ function isMobileUserAgent() {
162
+ if (typeof navigator === "undefined") return false;
163
+ return /android|iphone|ipad|ipod/i.test(navigator.userAgent);
164
+ }
165
+
166
+ // src/pkce.ts
167
+ var VERIFIER_BYTES = 32;
168
+ var base64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
169
+ function generateVerifier() {
170
+ const bytes = new Uint8Array(VERIFIER_BYTES);
171
+ crypto.getRandomValues(bytes);
172
+ return base64url(bytes);
173
+ }
174
+ async function challengeS256(verifier) {
175
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
176
+ return base64url(new Uint8Array(digest));
177
+ }
178
+ function generateState() {
179
+ const bytes = new Uint8Array(16);
180
+ crypto.getRandomValues(bytes);
181
+ return base64url(bytes);
182
+ }
183
+
184
+ // src/useZorealLogin.ts
185
+ function useZorealFlow(options) {
186
+ const { clientId, issuer, locale } = useZorealOAuth();
187
+ const [pairing, setPairing] = useState(null);
188
+ const abortRef = useRef(null);
189
+ const optionsRef = useRef(options);
190
+ optionsRef.current = options;
191
+ useEffect(() => () => abortRef.current?.abort(), []);
192
+ const login = useCallback(() => {
193
+ const opts = optionsRef.current;
194
+ const run = async () => {
195
+ abortRef.current?.abort();
196
+ const controller = new AbortController();
197
+ abortRef.current = controller;
198
+ const flow = opts.flow;
199
+ const verifier = generateVerifier();
200
+ const state = generateState();
201
+ const nonce = generateState();
202
+ try {
203
+ const started = await startPairing(issuer, {
204
+ client_id: clientId,
205
+ scope: opts.scope ?? "openid",
206
+ state,
207
+ nonce,
208
+ code_challenge: await challengeS256(verifier),
209
+ redirect_uri: flow === "auth-code" ? opts.redirect_uri : void 0,
210
+ acr_values: Array.isArray(opts.acr_values) ? opts.acr_values.join(" ") : opts.acr_values,
211
+ max_age: opts.max_age,
212
+ prompt: opts.prompt,
213
+ locale
214
+ });
215
+ let code;
216
+ let selectBy = "device";
217
+ if ("code" in started) {
218
+ code = started.code;
219
+ selectBy = "session";
220
+ } else {
221
+ const useAppLink = opts.display === "link" || opts.display !== "qr" && isMobileUserAgent();
222
+ selectBy = useAppLink ? "app_link" : "qr";
223
+ const active = {
224
+ requestId: started.request_id,
225
+ pairUrl: started.pair_url,
226
+ qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,
227
+ state: { status: "pending", expiresIn: started.expires_in },
228
+ appLink: useAppLink,
229
+ cancel: () => {
230
+ controller.abort();
231
+ setPairing(null);
232
+ }
233
+ };
234
+ setPairing(active);
235
+ if (useAppLink) {
236
+ window.location.assign(started.pair_url);
237
+ }
238
+ code = await pollUntilApproved(
239
+ issuer,
240
+ started.request_id,
241
+ (s) => {
242
+ setPairing((p) => p && p.requestId === started.request_id ? { ...p, state: s } : p);
243
+ opts.onPairingStateChange?.(s);
244
+ },
245
+ controller.signal
246
+ );
247
+ }
248
+ setPairing(null);
249
+ if (flow === "auth-code") {
250
+ opts.onCode?.({
251
+ code,
252
+ scope: opts.scope ?? "openid",
253
+ app_state: opts.app_state,
254
+ code_verifier: verifier
255
+ });
256
+ return;
257
+ }
258
+ const tokens = await exchangeCode(issuer, {
259
+ code,
260
+ code_verifier: verifier,
261
+ client_id: clientId
262
+ });
263
+ const claims = unsafeClaims(tokens.id_token);
264
+ const response = {
265
+ credential: tokens.id_token,
266
+ clientId,
267
+ select_by: selectBy,
268
+ acr: claims.acr ?? "zoreal.device"
269
+ };
270
+ opts.onCredential?.(response);
271
+ } catch (e) {
272
+ setPairing(null);
273
+ if (e instanceof DOMException && e.name === "AbortError") return;
274
+ if (e instanceof FlowAbandonedError) {
275
+ opts.onNonOAuthError?.(e.reason);
276
+ return;
277
+ }
278
+ if (e instanceof OAuthFlowError) {
279
+ opts.onError?.({ error: e.error, description: e.description });
280
+ return;
281
+ }
282
+ opts.onNonOAuthError?.({
283
+ type: "unknown",
284
+ description: e instanceof Error ? e.message : String(e)
285
+ });
286
+ }
287
+ };
288
+ void run();
289
+ }, [clientId, issuer, locale]);
290
+ return { login, internals: { pairing } };
291
+ }
292
+ function useZorealLogin(options) {
293
+ if (options.ux_mode === "redirect") {
294
+ throw new Error(
295
+ "@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default 'popup' shape and post the code and code_verifier from onSuccess to your backend."
296
+ );
297
+ }
298
+ const flow = options.flow ?? "browser-direct";
299
+ return useZorealFlow({
300
+ ...options,
301
+ flow,
302
+ onCredential: flow === "browser-direct" ? options.onSuccess : void 0,
303
+ onCode: flow === "auth-code" ? options.onSuccess : void 0
304
+ }).login;
305
+ }
306
+
307
+ // src/ZorealLogin.tsx
308
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
309
+ var TEXTS = {
310
+ continue_with: "Continue with ZOREAL",
311
+ signin_with: "Sign in with ZOREAL",
312
+ signup_with: "Sign up with ZOREAL",
313
+ signin: "Sign in"
314
+ };
315
+ var SIZES = {
316
+ large: { height: 44, font: 15, pad: 20 },
317
+ medium: { height: 38, font: 14, pad: 16 },
318
+ small: { height: 32, font: 12, pad: 12 }
319
+ };
320
+ var Mark = ({ size }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": true, focusable: "false", children: [
321
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "9", fill: "none", stroke: "currentColor", strokeWidth: "2.6" }),
322
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "3.4", fill: "currentColor" })
323
+ ] });
324
+ var PairingPanel = ({ pairing }) => {
325
+ const { status } = pairing.state;
326
+ const line = status === "claimed" ? "Approve the login in your ZOREAL ID app." : status === "enrolling" ? "Finishing enrolment. This screen will continue by itself." : pairing.appLink ? "Continue in the ZOREAL ID app, then return to this tab." : "Scan with your phone camera or the ZOREAL ID app.";
327
+ return /* @__PURE__ */ jsxs(
328
+ "div",
329
+ {
330
+ role: "dialog",
331
+ "aria-label": "Log in with ZOREAL",
332
+ style: {
333
+ marginTop: 8,
334
+ padding: 16,
335
+ width: 232,
336
+ borderRadius: 12,
337
+ border: "1px solid rgba(128,128,128,0.35)",
338
+ background: "Canvas",
339
+ color: "CanvasText",
340
+ textAlign: "center",
341
+ fontFamily: "inherit"
342
+ },
343
+ children: [
344
+ !pairing.appLink && /* @__PURE__ */ jsx2(
345
+ "img",
346
+ {
347
+ src: pairing.qrUrl,
348
+ alt: `QR code for ${pairing.pairUrl}`,
349
+ width: 200,
350
+ height: 200,
351
+ style: { display: "block", margin: "0 auto", borderRadius: 8 }
352
+ }
353
+ ),
354
+ /* @__PURE__ */ jsx2("p", { style: { margin: "10px 0 0", fontSize: 12, lineHeight: 1.5 }, children: line }),
355
+ /* @__PURE__ */ jsx2(
356
+ "button",
357
+ {
358
+ type: "button",
359
+ onClick: pairing.cancel,
360
+ style: {
361
+ marginTop: 10,
362
+ border: "none",
363
+ background: "none",
364
+ color: "inherit",
365
+ opacity: 0.6,
366
+ fontSize: 12,
367
+ cursor: "pointer",
368
+ textDecoration: "underline"
369
+ },
370
+ children: "Cancel"
371
+ }
372
+ )
373
+ ]
374
+ }
375
+ );
376
+ };
377
+ function ZorealLogin(props) {
378
+ const {
379
+ onSuccess,
380
+ onError,
381
+ containerProps,
382
+ type = "standard",
383
+ theme = "filled",
384
+ size = "large",
385
+ text = "continue_with",
386
+ shape = "rectangular",
387
+ logo_alignment = "left",
388
+ width,
389
+ click_listener,
390
+ ...request
391
+ } = props;
392
+ const { login, internals } = useZorealFlow({
393
+ ...request,
394
+ flow: "browser-direct",
395
+ onCredential: onSuccess,
396
+ onError: (e) => onError?.({ type: "unknown", description: e.description ?? e.error }),
397
+ onNonOAuthError: (e) => onError?.(e)
398
+ });
399
+ const s = SIZES[size];
400
+ const style = useMemo2(
401
+ () => ({
402
+ display: "inline-flex",
403
+ alignItems: "center",
404
+ justifyContent: logo_alignment === "center" ? "center" : "flex-start",
405
+ gap: 10,
406
+ height: s.height,
407
+ padding: `0 ${s.pad}px`,
408
+ width,
409
+ fontSize: s.font,
410
+ fontFamily: "inherit",
411
+ fontWeight: 500,
412
+ cursor: "pointer",
413
+ borderRadius: shape === "pill" ? s.height / 2 : shape === "square" ? 4 : 8,
414
+ ...theme === "outline" ? { background: "transparent", color: "inherit", border: "1px solid rgba(128,128,128,0.5)" } : theme === "filled_black" ? { background: "#111", color: "#fff", border: "1px solid #111" } : { background: "#00b4d9", color: "#fff", border: "1px solid #00b4d9" }
415
+ }),
416
+ [logo_alignment, s, shape, theme, width]
417
+ );
418
+ return /* @__PURE__ */ jsxs("div", { ...containerProps, children: [
419
+ /* @__PURE__ */ jsxs(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ style,
424
+ onClick: () => {
425
+ click_listener?.();
426
+ login();
427
+ },
428
+ children: [
429
+ /* @__PURE__ */ jsx2(Mark, { size: Math.round(s.font * 1.25) }),
430
+ type === "standard" && TEXTS[text]
431
+ ]
432
+ }
433
+ ),
434
+ internals.pairing && !internals.pairing.appLink && /* @__PURE__ */ jsx2(PairingPanel, { pairing: internals.pairing })
435
+ ] });
436
+ }
437
+
438
+ // src/useZorealAutoLogin.ts
439
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
440
+ function useZorealAutoLogin(options) {
441
+ const { login } = useZorealFlow({
442
+ flow: "browser-direct",
443
+ scope: options.scope,
444
+ prompt: "none",
445
+ onCredential: options.onSuccess,
446
+ onError: (e) => {
447
+ const quiet = ["login_required", "consent_required", "interaction_required"];
448
+ if (quiet.includes(e.error)) {
449
+ options.onUnavailable?.();
450
+ } else {
451
+ options.onError?.({ type: "unknown", description: e.description ?? e.error });
452
+ }
453
+ },
454
+ onNonOAuthError: (e) => options.onError?.(e)
455
+ });
456
+ const fired = useRef2(false);
457
+ useEffect2(() => {
458
+ if (options.disabled || fired.current) return;
459
+ fired.current = true;
460
+ login();
461
+ }, [options.disabled, login]);
462
+ }
463
+
464
+ // src/logout.ts
465
+ function zorealLogout() {
466
+ }
467
+
468
+ // src/scopes.ts
469
+ function hasGrantedAllScopesZoreal(response, firstScope, ...restScopes) {
470
+ const granted = new Set((response.scope ?? "").split(/\s+/).filter(Boolean));
471
+ return [firstScope, ...restScopes].every((s) => granted.has(s));
472
+ }
473
+ function hasGrantedAnyScopeZoreal(response, firstScope, ...restScopes) {
474
+ const granted = new Set((response.scope ?? "").split(/\s+/).filter(Boolean));
475
+ return [firstScope, ...restScopes].some((s) => granted.has(s));
476
+ }
477
+ export {
478
+ ZorealLogin,
479
+ ZorealOAuthProvider,
480
+ hasGrantedAllScopesZoreal,
481
+ hasGrantedAnyScopeZoreal,
482
+ useZorealAutoLogin,
483
+ useZorealLogin,
484
+ useZorealOAuth,
485
+ zorealLogout
486
+ };
487
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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, because the package is self-contained by decision (01 section 4):\n * a shipped version keeps working until the provider explicitly refuses it,\n * and the provider CAN refuse it, with a reason this package surfaces verbatim\n * (06 section 2: the compensating control for the npm supply chain). Both the\n * wire version and the package version travel on every pairing request so the\n * refusal can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (04 section 1.1):\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. 02 section 1: 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 (01 section 4) 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.0';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** 02 section 1: pending TTL 120s. Poll gently; over-polling cancels. */\nexport const POLL_INTERVAL_MS = 2000;\n/** 02 section 4: enrolling extends the window to 30 minutes; 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' | '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, browser-direct and therefore Tier A only (03 section 1:\n * it receives no access token, so there is nothing for a personal-data claim\n * to arrive on).\n *\n * The copy is neutral by decision (01 section 5): the button asserts nothing\n * about a person who has not yet authenticated. Styling is inline and\n * self-contained; no stylesheet, no font, no external asset, because this\n * renders on the most attacked page the integrator owns (01 section 4).\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 mark: a filled ring, the brand geometry reduced to currentColor. */\nconst Mark = ({ size }: { size: number }) => (\n <svg width={size} height={size} viewBox=\"0 0 24 24\" aria-hidden focusable=\"false\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.6\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.4\" fill=\"currentColor\" />\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 active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n state: { status: 'pending', expiresIn: started.expires_in },\n appLink: useAppLink,\n cancel: () => {\n controller.abort();\n setPairing(null);\n },\n };\n setPairing(active);\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 setPairing((p) => (p && p.requestId === started.request_id ? { ...p, state: s } : p));\n opts.onPairingStateChange?.(s);\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 });\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. 02 section 1 is the authority for the\n * object; 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 (02 section 1), 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. 06 section 2: a refused package version\n // arrives here, and rewriting its reason would disable the one remediation\n // path that does not depend on integrators upgrading.\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 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 '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 (03 section 1): personal data lives at\n * /userinfo behind an access token this mode is never issued for Tier B\n * scopes, because those are 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/** 02 section 3: 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, per docs/14 section 3: mandatory for every client,\n * 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 STATUS, stated because the spec leaves it open (06 section 7, O5):\n * mounting this on a page sends a request to ZOREAL on page load, before the\n * user does anything. Until that question is decided, `disabled` defaults are\n * conservative: the hook fires once per mount, never retries, and does nothing\n * at all when `disabled` is true.\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 (05 section 8). 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: name-for-name portability (05 section 1). */\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 ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@zoreal/oauth2-react",
3
+ "version": "0.1.0",
4
+ "description": "Login with ZOREAL for React. A chip-verified human behind every sign-in.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Bynn-Intelligence/zoreal-oauth2-react.git"
9
+ },
10
+ "homepage": "https://zoreal.com",
11
+ "keywords": ["zoreal", "oauth2", "oidc", "react", "login", "identity", "liveness"],
12
+ "type": "module",
13
+ "main": "./dist/index.cjs",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "require": "./dist/index.cjs"
21
+ }
22
+ },
23
+ "files": ["dist"],
24
+ "sideEffects": false,
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "test": "vitest run",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=18",
32
+ "react-dom": ">=18"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^19.0.0",
36
+ "react": "^19.0.0",
37
+ "react-dom": "^19.0.0",
38
+ "tsup": "^8.3.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^3.0.0"
41
+ }
42
+ }