@orangecheck/auth-client 2.0.0 → 2.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.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import * as React2 from 'react';
1
+ import * as React4 from 'react';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
3
4
 
4
5
  // src/provider.tsx
5
6
 
@@ -20,7 +21,7 @@ function buildSignInUrl(cfg, returnTo) {
20
21
  u.searchParams.set("return_to", returnTo);
21
22
  return u.toString();
22
23
  }
23
- var SessionContext = React2.createContext(null);
24
+ var SessionContext = React4.createContext(null);
24
25
  function normalizeAccount(raw) {
25
26
  if (!raw) return null;
26
27
  const didOc = raw.did_oc ?? raw.didOc;
@@ -42,11 +43,11 @@ function OcSessionProvider({
42
43
  config,
43
44
  defaultReturnTo
44
45
  }) {
45
- const cfg = React2.useMemo(() => resolveConfig(config), [config]);
46
- const [account, setAccount] = React2.useState(null);
47
- const [status, setStatus] = React2.useState("loading");
48
- const [error, setError] = React2.useState(null);
49
- const refresh = React2.useCallback(async () => {
46
+ const cfg = React4.useMemo(() => resolveConfig(config), [config]);
47
+ const [account, setAccount] = React4.useState(null);
48
+ const [status, setStatus] = React4.useState("loading");
49
+ const [error, setError] = React4.useState(null);
50
+ const refresh = React4.useCallback(async () => {
50
51
  if (typeof window === "undefined") return;
51
52
  try {
52
53
  const res = await fetch(cfg.mePath, {
@@ -75,10 +76,10 @@ function OcSessionProvider({
75
76
  setError(err instanceof Error ? err : new Error(String(err)));
76
77
  }
77
78
  }, [cfg.mePath]);
78
- React2.useEffect(() => {
79
+ React4.useEffect(() => {
79
80
  void refresh();
80
81
  }, [refresh]);
81
- const signOut = React2.useCallback(async () => {
82
+ const signOut = React4.useCallback(async () => {
82
83
  try {
83
84
  await fetch(`${cfg.authOrigin}${cfg.logoutPath}`, {
84
85
  method: "POST",
@@ -89,7 +90,7 @@ function OcSessionProvider({
89
90
  setAccount(null);
90
91
  setStatus("anonymous");
91
92
  }, [cfg.authOrigin, cfg.logoutPath]);
92
- const value = React2.useMemo(() => {
93
+ const value = React4.useMemo(() => {
93
94
  const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
94
95
  return {
95
96
  status,
@@ -103,7 +104,7 @@ function OcSessionProvider({
103
104
  return /* @__PURE__ */ jsx(SessionContext.Provider, { value, children });
104
105
  }
105
106
  function useOcSession() {
106
- const ctx = React2.useContext(SessionContext);
107
+ const ctx = React4.useContext(SessionContext);
107
108
  if (!ctx) {
108
109
  throw new Error(
109
110
  "[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
@@ -112,7 +113,7 @@ function useOcSession() {
112
113
  return ctx;
113
114
  }
114
115
  function useOptionalOcSession() {
115
- return React2.useContext(SessionContext);
116
+ return React4.useContext(SessionContext);
116
117
  }
117
118
  function shortenAddress(addr) {
118
119
  if (addr.length <= 12) return addr;
@@ -127,7 +128,7 @@ function isPrefixOf(value, target) {
127
128
  }
128
129
  var listboxIdCounter = 0;
129
130
  function useUniqueId(prefix) {
130
- const [id] = React2.useState(() => `${prefix}-${++listboxIdCounter}`);
131
+ const [id] = React4.useState(() => `${prefix}-${++listboxIdCounter}`);
131
132
  return id;
132
133
  }
133
134
  function OcAccountChip({
@@ -141,9 +142,9 @@ function OcAccountChip({
141
142
  }) {
142
143
  const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
143
144
  const signInUrl = signInUrlOverride ?? sessionSignInUrl;
144
- const [open, setOpen] = React2.useState(false);
145
- const wrapRef = React2.useRef(null);
146
- React2.useEffect(() => {
145
+ const [open, setOpen] = React4.useState(false);
146
+ const wrapRef = React4.useRef(null);
147
+ React4.useEffect(() => {
147
148
  if (!open) return;
148
149
  function handleClickOutside(e) {
149
150
  if (wrapRef.current && !wrapRef.current.contains(e.target)) {
@@ -466,9 +467,9 @@ function useOcAddressSuggestion(options) {
466
467
  } = options;
467
468
  const { status, account } = useOcSession();
468
469
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
469
- const [open, setOpen] = React2.useState(false);
470
- const [highlighted, setHighlighted] = React2.useState(false);
471
- const blurTimer = React2.useRef(null);
470
+ const [open, setOpen] = React4.useState(false);
471
+ const [highlighted, setHighlighted] = React4.useState(false);
472
+ const blurTimer = React4.useRef(null);
472
473
  const listboxId = useUniqueId("oc-addr-listbox");
473
474
  const optionId = `${listboxId}-opt`;
474
475
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -480,7 +481,7 @@ function useOcAddressSuggestion(options) {
480
481
  setOpen(false);
481
482
  setHighlighted(false);
482
483
  }
483
- React2.useEffect(() => {
484
+ React4.useEffect(() => {
484
485
  return () => {
485
486
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
486
487
  };
@@ -583,7 +584,7 @@ function useOcAddressSuggestion(options) {
583
584
  ) : null;
584
585
  return { inputProps, popover };
585
586
  }
586
- var OcAddressInput = React2.forwardRef(
587
+ var OcAddressInput = React4.forwardRef(
587
588
  function OcAddressInput2({
588
589
  value,
589
590
  onValueChange,
@@ -598,15 +599,15 @@ var OcAddressInput = React2.forwardRef(
598
599
  }, forwardedRef) {
599
600
  const { status, account } = useOcSession();
600
601
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
601
- const [open, setOpen] = React2.useState(false);
602
- const [highlighted, setHighlighted] = React2.useState(false);
603
- const innerRef = React2.useRef(null);
602
+ const [open, setOpen] = React4.useState(false);
603
+ const [highlighted, setHighlighted] = React4.useState(false);
604
+ const innerRef = React4.useRef(null);
604
605
  const setRef = (node) => {
605
606
  innerRef.current = node;
606
607
  if (typeof forwardedRef === "function") forwardedRef(node);
607
608
  else if (forwardedRef) forwardedRef.current = node;
608
609
  };
609
- const blurTimer = React2.useRef(null);
610
+ const blurTimer = React4.useRef(null);
610
611
  const listboxId = useUniqueId("oc-addr-listbox");
611
612
  const optionId = `${listboxId}-opt`;
612
613
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -653,7 +654,7 @@ var OcAddressInput = React2.forwardRef(
653
654
  }
654
655
  onKeyDown?.(e);
655
656
  }
656
- React2.useEffect(() => {
657
+ React4.useEffect(() => {
657
658
  return () => {
658
659
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
659
660
  };
@@ -768,8 +769,8 @@ function OcSignIn({
768
769
  const walletEnabled = paths?.wallet ?? true;
769
770
  const emailEnabled = paths?.email ?? true;
770
771
  const safeReturn = safeReturnTo(returnTo);
771
- const [path, setPath] = React2.useState(initialPath);
772
- const handleSuccess = React2.useCallback(
772
+ const [path, setPath] = React4.useState(initialPath);
773
+ const handleSuccess = React4.useCallback(
773
774
  (account) => {
774
775
  if (onSuccess) {
775
776
  onSuccess(account);
@@ -877,10 +878,10 @@ function SigninTab({
877
878
  );
878
879
  }
879
880
  function WalletFlow({ authOrigin, audience, onSuccess }) {
880
- const [address, setAddress] = React2.useState("");
881
- const [error, setError] = React2.useState(null);
882
- const [submitting, setSubmitting] = React2.useState(false);
883
- const [stage, setStage] = React2.useState("enter");
881
+ const [address, setAddress] = React4.useState("");
882
+ const [error, setError] = React4.useState(null);
883
+ const [submitting, setSubmitting] = React4.useState(false);
884
+ const [stage, setStage] = React4.useState("enter");
884
885
  async function signIn(e) {
885
886
  e.preventDefault();
886
887
  const addr = address.trim();
@@ -979,13 +980,13 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
979
980
  ] });
980
981
  }
981
982
  function EmailFlow({ authOrigin, onSuccess }) {
982
- const [stage, setStage] = React2.useState("enter");
983
- const [email, setEmail] = React2.useState("");
984
- const [emailError, setEmailError] = React2.useState(null);
985
- const [code, setCode] = React2.useState("");
986
- const [codeError, setCodeError] = React2.useState(null);
987
- const [token, setToken] = React2.useState(null);
988
- const [submitting, setSubmitting] = React2.useState(false);
983
+ const [stage, setStage] = React4.useState("enter");
984
+ const [email, setEmail] = React4.useState("");
985
+ const [emailError, setEmailError] = React4.useState(null);
986
+ const [code, setCode] = React4.useState("");
987
+ const [codeError, setCodeError] = React4.useState(null);
988
+ const [token, setToken] = React4.useState(null);
989
+ const [submitting, setSubmitting] = React4.useState(false);
989
990
  async function start(e) {
990
991
  e.preventDefault();
991
992
  const trimmed = email.trim().toLowerCase();
@@ -1249,7 +1250,215 @@ function Hint({ children }) {
1249
1250
  }
1250
1251
  );
1251
1252
  }
1253
+ function resolveAuthOrigin(opts) {
1254
+ if (!opts) return resolveConfig(void 0).authOrigin;
1255
+ return resolveConfig(opts).authOrigin;
1256
+ }
1257
+ function isAbortError(err) {
1258
+ if (!(err instanceof Error)) return false;
1259
+ return /NotAllowed|aborted|cancel|user cancelled/i.test(err.message);
1260
+ }
1261
+ function useWebAuthnRegister(opts) {
1262
+ const authOrigin = resolveAuthOrigin(opts);
1263
+ const [status, setStatus] = React4.useState("idle");
1264
+ const [error, setError] = React4.useState(null);
1265
+ const reset = React4.useCallback(() => {
1266
+ setStatus("idle");
1267
+ setError(null);
1268
+ }, []);
1269
+ const register = React4.useCallback(
1270
+ async (args) => {
1271
+ setError(null);
1272
+ setStatus("requesting-options");
1273
+ try {
1274
+ const optsRes = await fetch(`${authOrigin}/api/auth/webauthn/register/options`, {
1275
+ method: "POST",
1276
+ credentials: "include",
1277
+ headers: { "Content-Type": "application/json" },
1278
+ body: JSON.stringify({ label: args?.label })
1279
+ });
1280
+ const optsBody = await optsRes.json();
1281
+ if (!optsRes.ok || !optsBody.ok || !optsBody.options || !optsBody.challenge_token) {
1282
+ throw new Error(optsBody.reason ?? `options_failed_${optsRes.status}`);
1283
+ }
1284
+ setStatus("authenticating");
1285
+ const attestation = await startRegistration({
1286
+ optionsJSON: optsBody.options
1287
+ });
1288
+ setStatus("verifying");
1289
+ const verifyRes = await fetch(`${authOrigin}/api/auth/webauthn/register/verify`, {
1290
+ method: "POST",
1291
+ credentials: "include",
1292
+ headers: { "Content-Type": "application/json" },
1293
+ body: JSON.stringify({
1294
+ challenge_token: optsBody.challenge_token,
1295
+ response: attestation,
1296
+ label: args?.label
1297
+ })
1298
+ });
1299
+ const verifyBody = await verifyRes.json();
1300
+ if (!verifyRes.ok || !verifyBody.ok || !verifyBody.credential) {
1301
+ throw new Error(verifyBody.reason ?? `verify_failed_${verifyRes.status}`);
1302
+ }
1303
+ setStatus("success");
1304
+ return verifyBody.credential;
1305
+ } catch (e) {
1306
+ const err = e instanceof Error ? e : new Error(String(e));
1307
+ setError(err);
1308
+ setStatus("error");
1309
+ if (isAbortError(err)) {
1310
+ setError(new Error("cancelled"));
1311
+ }
1312
+ return null;
1313
+ }
1314
+ },
1315
+ [authOrigin]
1316
+ );
1317
+ return { status, error, register, reset };
1318
+ }
1319
+ function useWebAuthnList(opts) {
1320
+ const authOrigin = resolveAuthOrigin(opts);
1321
+ const { status: sessionStatus } = useOcSession();
1322
+ const [credentials, setCredentials] = React4.useState([]);
1323
+ const [status, setStatus] = React4.useState("loading");
1324
+ const [error, setError] = React4.useState(null);
1325
+ const refetch = React4.useCallback(async () => {
1326
+ setError(null);
1327
+ if (sessionStatus !== "authenticated") {
1328
+ setCredentials([]);
1329
+ setStatus("ready");
1330
+ return;
1331
+ }
1332
+ setStatus("loading");
1333
+ try {
1334
+ const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials`, {
1335
+ credentials: "include"
1336
+ });
1337
+ if (r.status === 401) {
1338
+ setCredentials([]);
1339
+ setStatus("ready");
1340
+ return;
1341
+ }
1342
+ const j = await r.json();
1343
+ if (!j.ok || !j.credentials) {
1344
+ throw new Error(j.reason ?? "list_failed");
1345
+ }
1346
+ setCredentials(j.credentials);
1347
+ setStatus("ready");
1348
+ } catch (e) {
1349
+ setError(e instanceof Error ? e : new Error(String(e)));
1350
+ setStatus("error");
1351
+ }
1352
+ }, [authOrigin, sessionStatus]);
1353
+ React4.useEffect(() => {
1354
+ void refetch();
1355
+ }, [refetch]);
1356
+ const rename = React4.useCallback(
1357
+ async (id, label) => {
1358
+ try {
1359
+ const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
1360
+ method: "PATCH",
1361
+ credentials: "include",
1362
+ headers: { "Content-Type": "application/json" },
1363
+ body: JSON.stringify({ label })
1364
+ });
1365
+ const j = await r.json();
1366
+ if (!j.ok || !j.credential) {
1367
+ setError(new Error(j.reason ?? "rename_failed"));
1368
+ return null;
1369
+ }
1370
+ await refetch();
1371
+ return j.credential;
1372
+ } catch (e) {
1373
+ setError(e instanceof Error ? e : new Error(String(e)));
1374
+ return null;
1375
+ }
1376
+ },
1377
+ [authOrigin, refetch]
1378
+ );
1379
+ const remove = React4.useCallback(
1380
+ async (id) => {
1381
+ try {
1382
+ const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
1383
+ method: "DELETE",
1384
+ credentials: "include"
1385
+ });
1386
+ const j = await r.json();
1387
+ if (!j.ok) {
1388
+ setError(new Error(j.reason ?? "delete_failed"));
1389
+ return false;
1390
+ }
1391
+ await refetch();
1392
+ return true;
1393
+ } catch (e) {
1394
+ setError(e instanceof Error ? e : new Error(String(e)));
1395
+ return false;
1396
+ }
1397
+ },
1398
+ [authOrigin, refetch]
1399
+ );
1400
+ return { status, credentials, error, refetch, rename, remove };
1401
+ }
1402
+ function useStepUpAuth(opts) {
1403
+ const authOrigin = resolveAuthOrigin(opts);
1404
+ const { refresh } = useOcSession();
1405
+ const [status, setStatus] = React4.useState("idle");
1406
+ const [error, setError] = React4.useState(null);
1407
+ const reset = React4.useCallback(() => {
1408
+ setStatus("idle");
1409
+ setError(null);
1410
+ }, []);
1411
+ const stepUp = React4.useCallback(
1412
+ async (args) => {
1413
+ setError(null);
1414
+ setStatus("requesting-options");
1415
+ try {
1416
+ const optsRes = await fetch(`${authOrigin}/api/auth/webauthn/assertion/options`, {
1417
+ method: "POST",
1418
+ credentials: "include",
1419
+ headers: { "Content-Type": "application/json" },
1420
+ body: JSON.stringify({ purpose: args.purpose })
1421
+ });
1422
+ const optsBody = await optsRes.json();
1423
+ if (!optsRes.ok || !optsBody.ok || !optsBody.options || !optsBody.challenge_token) {
1424
+ throw new Error(optsBody.reason ?? `options_failed_${optsRes.status}`);
1425
+ }
1426
+ setStatus("authenticating");
1427
+ const assertion = await startAuthentication({
1428
+ optionsJSON: optsBody.options
1429
+ });
1430
+ setStatus("verifying");
1431
+ const verifyRes = await fetch(
1432
+ `${authOrigin}/api/auth/webauthn/assertion/verify`,
1433
+ {
1434
+ method: "POST",
1435
+ credentials: "include",
1436
+ headers: { "Content-Type": "application/json" },
1437
+ body: JSON.stringify({
1438
+ challenge_token: optsBody.challenge_token,
1439
+ response: assertion
1440
+ })
1441
+ }
1442
+ );
1443
+ const verifyBody = await verifyRes.json();
1444
+ if (!verifyRes.ok || !verifyBody.ok || typeof verifyBody.step_up_at !== "number") {
1445
+ throw new Error(verifyBody.reason ?? `verify_failed_${verifyRes.status}`);
1446
+ }
1447
+ await refresh();
1448
+ setStatus("success");
1449
+ return { step_up_at: verifyBody.step_up_at };
1450
+ } catch (e) {
1451
+ const err = isAbortError(e) ? new Error("cancelled") : e instanceof Error ? e : new Error(String(e));
1452
+ setError(err);
1453
+ setStatus("error");
1454
+ return null;
1455
+ }
1456
+ },
1457
+ [authOrigin, refresh]
1458
+ );
1459
+ return { status, error, stepUp, reset };
1460
+ }
1252
1461
 
1253
- export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcSessionProvider, OcSignIn, OcSignInButton, buildSignInUrl, useOcAddressSuggestion, useOcSession, useOptionalOcSession };
1462
+ export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcSessionProvider, OcSignIn, OcSignInButton, buildSignInUrl, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister };
1254
1463
  //# sourceMappingURL=index.mjs.map
1255
1464
  //# sourceMappingURL=index.mjs.map