@orangecheck/auth-client 2.25.3 → 2.26.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.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +485 -252
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +484 -251
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as React4 from 'react';
|
|
2
2
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
|
|
4
4
|
|
|
@@ -199,7 +199,7 @@ async function consumeTabAccountHint(authOrigin) {
|
|
|
199
199
|
}
|
|
200
200
|
return null;
|
|
201
201
|
}
|
|
202
|
-
var SessionContext =
|
|
202
|
+
var SessionContext = React4.createContext(null);
|
|
203
203
|
function normalizeDisplayIdentity(raw, didOc) {
|
|
204
204
|
const di = raw.display_identity ?? raw.displayIdentity;
|
|
205
205
|
if (di && typeof di === "object" && typeof di.value === "string" && di.value.length > 0 && typeof di.kind === "string" && DISPLAY_IDENTITY_KINDS.includes(di.kind)) {
|
|
@@ -257,14 +257,14 @@ function OcSessionProvider({
|
|
|
257
257
|
config,
|
|
258
258
|
defaultReturnTo
|
|
259
259
|
}) {
|
|
260
|
-
const cfg =
|
|
261
|
-
const [account, setAccount] =
|
|
262
|
-
const [roster, setRoster] =
|
|
263
|
-
const [status, setStatus] =
|
|
264
|
-
const [error, setError] =
|
|
265
|
-
const [tabPinned, setTabPinned] =
|
|
266
|
-
const pinInFlightRef =
|
|
267
|
-
const pinThisTab =
|
|
260
|
+
const cfg = React4.useMemo(() => resolveConfig(config), [config]);
|
|
261
|
+
const [account, setAccount] = React4.useState(null);
|
|
262
|
+
const [roster, setRoster] = React4.useState([]);
|
|
263
|
+
const [status, setStatus] = React4.useState("loading");
|
|
264
|
+
const [error, setError] = React4.useState(null);
|
|
265
|
+
const [tabPinned, setTabPinned] = React4.useState(false);
|
|
266
|
+
const pinInFlightRef = React4.useRef(false);
|
|
267
|
+
const pinThisTab = React4.useCallback(
|
|
268
268
|
async (didOc) => {
|
|
269
269
|
if (pinInFlightRef.current || readTabSession()) return;
|
|
270
270
|
pinInFlightRef.current = true;
|
|
@@ -287,7 +287,7 @@ function OcSessionProvider({
|
|
|
287
287
|
},
|
|
288
288
|
[cfg.authOrigin]
|
|
289
289
|
);
|
|
290
|
-
const refresh =
|
|
290
|
+
const refresh = React4.useCallback(async () => {
|
|
291
291
|
if (typeof window === "undefined") return;
|
|
292
292
|
try {
|
|
293
293
|
let pin = readTabSession();
|
|
@@ -341,7 +341,7 @@ function OcSessionProvider({
|
|
|
341
341
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
342
342
|
}
|
|
343
343
|
}, [cfg, pinThisTab]);
|
|
344
|
-
|
|
344
|
+
React4.useEffect(() => {
|
|
345
345
|
let cancelled = false;
|
|
346
346
|
consumeTabAdoptMarker();
|
|
347
347
|
void (async () => {
|
|
@@ -352,9 +352,9 @@ function OcSessionProvider({
|
|
|
352
352
|
cancelled = true;
|
|
353
353
|
};
|
|
354
354
|
}, [refresh, cfg.authOrigin]);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const signOut =
|
|
355
|
+
React4.useEffect(() => installTabFetchInterceptor(cfg.authOrigin), [cfg.authOrigin]);
|
|
356
|
+
React4.useEffect(() => installTabLinkDecorator(cfg.authOrigin), [cfg.authOrigin]);
|
|
357
|
+
const signOut = React4.useCallback(
|
|
358
358
|
async (opts) => {
|
|
359
359
|
const scope = opts?.scope ?? "all";
|
|
360
360
|
try {
|
|
@@ -390,7 +390,7 @@ function OcSessionProvider({
|
|
|
390
390
|
},
|
|
391
391
|
[cfg.authOrigin, cfg.logoutPath, refresh]
|
|
392
392
|
);
|
|
393
|
-
const switchAccount =
|
|
393
|
+
const switchAccount = React4.useCallback(
|
|
394
394
|
async (didOc) => {
|
|
395
395
|
if (typeof window === "undefined") return;
|
|
396
396
|
const res = await fetch(`${cfg.authOrigin}/api/auth/switch`, {
|
|
@@ -420,14 +420,14 @@ function OcSessionProvider({
|
|
|
420
420
|
},
|
|
421
421
|
[cfg.authOrigin, refresh]
|
|
422
422
|
);
|
|
423
|
-
const addAccountUrl =
|
|
423
|
+
const addAccountUrl = React4.useCallback(
|
|
424
424
|
(returnTo) => {
|
|
425
425
|
const rt = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
426
426
|
return buildAddAccountUrl(cfg, rt);
|
|
427
427
|
},
|
|
428
428
|
[cfg]
|
|
429
429
|
);
|
|
430
|
-
const setDisplayIdentity =
|
|
430
|
+
const setDisplayIdentity = React4.useCallback(
|
|
431
431
|
async (kind) => {
|
|
432
432
|
if (typeof window === "undefined") return;
|
|
433
433
|
const res = await fetch(`${cfg.authOrigin}/api/auth/account`, {
|
|
@@ -457,7 +457,7 @@ function OcSessionProvider({
|
|
|
457
457
|
},
|
|
458
458
|
[cfg.authOrigin, refresh]
|
|
459
459
|
);
|
|
460
|
-
const value =
|
|
460
|
+
const value = React4.useMemo(() => {
|
|
461
461
|
const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
462
462
|
return {
|
|
463
463
|
status,
|
|
@@ -489,7 +489,7 @@ function OcSessionProvider({
|
|
|
489
489
|
return /* @__PURE__ */ jsx(SessionContext.Provider, { value, children });
|
|
490
490
|
}
|
|
491
491
|
function useOcSession() {
|
|
492
|
-
const ctx =
|
|
492
|
+
const ctx = React4.useContext(SessionContext);
|
|
493
493
|
if (!ctx) {
|
|
494
494
|
throw new Error(
|
|
495
495
|
"[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
|
|
@@ -498,7 +498,7 @@ function useOcSession() {
|
|
|
498
498
|
return ctx;
|
|
499
499
|
}
|
|
500
500
|
function useOptionalOcSession() {
|
|
501
|
-
return
|
|
501
|
+
return React4.useContext(SessionContext);
|
|
502
502
|
}
|
|
503
503
|
function shortenAddress(addr) {
|
|
504
504
|
if (addr.length <= 12) return addr;
|
|
@@ -513,7 +513,7 @@ function isPrefixOf(value, target) {
|
|
|
513
513
|
}
|
|
514
514
|
var listboxIdCounter = 0;
|
|
515
515
|
function useUniqueId(prefix) {
|
|
516
|
-
const [id] =
|
|
516
|
+
const [id] = React4.useState(() => `${prefix}-${++listboxIdCounter}`);
|
|
517
517
|
return id;
|
|
518
518
|
}
|
|
519
519
|
function OcAccountChip({
|
|
@@ -527,9 +527,9 @@ function OcAccountChip({
|
|
|
527
527
|
}) {
|
|
528
528
|
const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
|
|
529
529
|
const signInUrl = signInUrlOverride ?? sessionSignInUrl;
|
|
530
|
-
const [open, setOpen] =
|
|
531
|
-
const wrapRef =
|
|
532
|
-
|
|
530
|
+
const [open, setOpen] = React4.useState(false);
|
|
531
|
+
const wrapRef = React4.useRef(null);
|
|
532
|
+
React4.useEffect(() => {
|
|
533
533
|
if (!open) return;
|
|
534
534
|
function handleClickOutside(e) {
|
|
535
535
|
if (wrapRef.current && !wrapRef.current.contains(e.target)) {
|
|
@@ -852,9 +852,9 @@ function useOcAddressSuggestion(options) {
|
|
|
852
852
|
} = options;
|
|
853
853
|
const { status, account } = useOcSession();
|
|
854
854
|
const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
|
|
855
|
-
const [open, setOpen] =
|
|
856
|
-
const [highlighted, setHighlighted] =
|
|
857
|
-
const blurTimer =
|
|
855
|
+
const [open, setOpen] = React4.useState(false);
|
|
856
|
+
const [highlighted, setHighlighted] = React4.useState(false);
|
|
857
|
+
const blurTimer = React4.useRef(null);
|
|
858
858
|
const listboxId = useUniqueId("oc-addr-listbox");
|
|
859
859
|
const optionId = `${listboxId}-opt`;
|
|
860
860
|
const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
|
|
@@ -866,7 +866,7 @@ function useOcAddressSuggestion(options) {
|
|
|
866
866
|
setOpen(false);
|
|
867
867
|
setHighlighted(false);
|
|
868
868
|
}
|
|
869
|
-
|
|
869
|
+
React4.useEffect(() => {
|
|
870
870
|
return () => {
|
|
871
871
|
if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
|
|
872
872
|
};
|
|
@@ -969,7 +969,7 @@ function useOcAddressSuggestion(options) {
|
|
|
969
969
|
) : null;
|
|
970
970
|
return { inputProps, popover };
|
|
971
971
|
}
|
|
972
|
-
var OcAddressInput =
|
|
972
|
+
var OcAddressInput = React4.forwardRef(
|
|
973
973
|
function OcAddressInput2({
|
|
974
974
|
value,
|
|
975
975
|
onValueChange,
|
|
@@ -984,15 +984,15 @@ var OcAddressInput = React3.forwardRef(
|
|
|
984
984
|
}, forwardedRef) {
|
|
985
985
|
const { status, account } = useOcSession();
|
|
986
986
|
const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
|
|
987
|
-
const [open, setOpen] =
|
|
988
|
-
const [highlighted, setHighlighted] =
|
|
989
|
-
const innerRef =
|
|
987
|
+
const [open, setOpen] = React4.useState(false);
|
|
988
|
+
const [highlighted, setHighlighted] = React4.useState(false);
|
|
989
|
+
const innerRef = React4.useRef(null);
|
|
990
990
|
const setRef = (node) => {
|
|
991
991
|
innerRef.current = node;
|
|
992
992
|
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
993
993
|
else if (forwardedRef) forwardedRef.current = node;
|
|
994
994
|
};
|
|
995
|
-
const blurTimer =
|
|
995
|
+
const blurTimer = React4.useRef(null);
|
|
996
996
|
const listboxId = useUniqueId("oc-addr-listbox");
|
|
997
997
|
const optionId = `${listboxId}-opt`;
|
|
998
998
|
const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
|
|
@@ -1039,7 +1039,7 @@ var OcAddressInput = React3.forwardRef(
|
|
|
1039
1039
|
}
|
|
1040
1040
|
onKeyDown?.(e);
|
|
1041
1041
|
}
|
|
1042
|
-
|
|
1042
|
+
React4.useEffect(() => {
|
|
1043
1043
|
return () => {
|
|
1044
1044
|
if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
|
|
1045
1045
|
};
|
|
@@ -1173,13 +1173,14 @@ async function fetchOcLinkedIdentities(opts) {
|
|
|
1173
1173
|
function OcLinkedIdentities({
|
|
1174
1174
|
authOrigin = DEFAULT_AUTH_ORIGIN,
|
|
1175
1175
|
className,
|
|
1176
|
-
onChange
|
|
1176
|
+
onChange,
|
|
1177
|
+
attestationLink = true
|
|
1177
1178
|
}) {
|
|
1178
|
-
const [resp, setResp] =
|
|
1179
|
-
const [error, setError] =
|
|
1180
|
-
const [showEmailLink, setShowEmailLink] =
|
|
1181
|
-
const [showBtcLink, setShowBtcLink] =
|
|
1182
|
-
const refresh =
|
|
1179
|
+
const [resp, setResp] = React4.useState(null);
|
|
1180
|
+
const [error, setError] = React4.useState(null);
|
|
1181
|
+
const [showEmailLink, setShowEmailLink] = React4.useState(false);
|
|
1182
|
+
const [showBtcLink, setShowBtcLink] = React4.useState(false);
|
|
1183
|
+
const refresh = React4.useCallback(async () => {
|
|
1183
1184
|
setError(null);
|
|
1184
1185
|
try {
|
|
1185
1186
|
const r = await fetch(`${authOrigin}/api/auth/identities`, {
|
|
@@ -1198,10 +1199,10 @@ function OcLinkedIdentities({
|
|
|
1198
1199
|
setError(e instanceof Error ? e.message : "load failed");
|
|
1199
1200
|
}
|
|
1200
1201
|
}, [authOrigin]);
|
|
1201
|
-
|
|
1202
|
+
React4.useEffect(() => {
|
|
1202
1203
|
void refresh();
|
|
1203
1204
|
}, [refresh]);
|
|
1204
|
-
const afterChange =
|
|
1205
|
+
const afterChange = React4.useCallback(() => {
|
|
1205
1206
|
setShowEmailLink(false);
|
|
1206
1207
|
setShowBtcLink(false);
|
|
1207
1208
|
void refresh();
|
|
@@ -1233,11 +1234,7 @@ function OcLinkedIdentities({
|
|
|
1233
1234
|
return /* @__PURE__ */ jsxs("div", { className, "data-oc-li": "", style: { display: "grid", gap: 16 }, children: [
|
|
1234
1235
|
/* @__PURE__ */ jsxs("div", { style: panelStyle(), children: [
|
|
1235
1236
|
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 what this is" }),
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
"Your public identity is the opaque ",
|
|
1238
|
-
/* @__PURE__ */ jsx("code", { style: codeStyle, children: "did:oc:" }),
|
|
1239
|
-
" DID. Below it sit your private linked identities \u2014 your email and your Bitcoin address. They never appear in dashboard chrome and never reach an integrator unless you grant the matching scope. Linking a second identity is also your account-recovery path: if you lose one, the other still signs you in."
|
|
1240
|
-
] })
|
|
1237
|
+
/* @__PURE__ */ jsx("p", { style: bodyStyle, children: "These are the ways you can sign in to this account. An app you sign in to sees one only if you grant it. Linking a second one is also your way back in: lose one, and the other still signs you in." })
|
|
1241
1238
|
] }),
|
|
1242
1239
|
mergedFrom.length > 0 && /* @__PURE__ */ jsx(ConsolidationPanel, { entries: mergedFrom }),
|
|
1243
1240
|
/* @__PURE__ */ jsxs("div", { style: panelStyle("plain"), "data-oc-li-rows": "", children: [
|
|
@@ -1290,7 +1287,7 @@ function OcLinkedIdentities({
|
|
|
1290
1287
|
onCancel: () => setShowBtcLink(false)
|
|
1291
1288
|
}
|
|
1292
1289
|
),
|
|
1293
|
-
hasBtc && /* @__PURE__ */ jsx(AttestationLink, {})
|
|
1290
|
+
hasBtc && attestationLink && /* @__PURE__ */ jsx(AttestationLink, {})
|
|
1294
1291
|
] });
|
|
1295
1292
|
}
|
|
1296
1293
|
function IdentityGroup({
|
|
@@ -1334,10 +1331,10 @@ function IdentityRow({
|
|
|
1334
1331
|
authOrigin,
|
|
1335
1332
|
onChange
|
|
1336
1333
|
}) {
|
|
1337
|
-
const [shown, setShown] =
|
|
1338
|
-
const [confirming, setConfirming] =
|
|
1339
|
-
const [busy, setBusy] =
|
|
1340
|
-
const [err, setErr] =
|
|
1334
|
+
const [shown, setShown] = React4.useState(false);
|
|
1335
|
+
const [confirming, setConfirming] = React4.useState(false);
|
|
1336
|
+
const [busy, setBusy] = React4.useState(false);
|
|
1337
|
+
const [err, setErr] = React4.useState(null);
|
|
1341
1338
|
const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
|
|
1342
1339
|
const display = shown && row.value ? row.value : masked;
|
|
1343
1340
|
async function unlink() {
|
|
@@ -1536,13 +1533,13 @@ function EmailLinkForm({
|
|
|
1536
1533
|
onDone,
|
|
1537
1534
|
onCancel
|
|
1538
1535
|
}) {
|
|
1539
|
-
const [step, setStep] =
|
|
1540
|
-
const [email, setEmail] =
|
|
1541
|
-
const [code, setCode] =
|
|
1542
|
-
const [token, setToken] =
|
|
1543
|
-
const [busy, setBusy] =
|
|
1544
|
-
const [err, setErr] =
|
|
1545
|
-
const [transfer, setTransfer] =
|
|
1536
|
+
const [step, setStep] = React4.useState("email");
|
|
1537
|
+
const [email, setEmail] = React4.useState("");
|
|
1538
|
+
const [code, setCode] = React4.useState("");
|
|
1539
|
+
const [token, setToken] = React4.useState("");
|
|
1540
|
+
const [busy, setBusy] = React4.useState(false);
|
|
1541
|
+
const [err, setErr] = React4.useState(null);
|
|
1542
|
+
const [transfer, setTransfer] = React4.useState(false);
|
|
1546
1543
|
async function start() {
|
|
1547
1544
|
setBusy(true);
|
|
1548
1545
|
setErr(null);
|
|
@@ -1685,12 +1682,12 @@ function BtcLinkForm({
|
|
|
1685
1682
|
onDone,
|
|
1686
1683
|
onCancel
|
|
1687
1684
|
}) {
|
|
1688
|
-
const [step, setStep] =
|
|
1689
|
-
const [addr, setAddr] =
|
|
1690
|
-
const [challenge, setChallenge] =
|
|
1691
|
-
const [busy, setBusy] =
|
|
1692
|
-
const [err, setErr] =
|
|
1693
|
-
const [pendingSig, setPendingSig] =
|
|
1685
|
+
const [step, setStep] = React4.useState("request");
|
|
1686
|
+
const [addr, setAddr] = React4.useState("");
|
|
1687
|
+
const [challenge, setChallenge] = React4.useState("");
|
|
1688
|
+
const [busy, setBusy] = React4.useState(false);
|
|
1689
|
+
const [err, setErr] = React4.useState(null);
|
|
1690
|
+
const [pendingSig, setPendingSig] = React4.useState(null);
|
|
1694
1691
|
async function getChallenge() {
|
|
1695
1692
|
if (!didOc) {
|
|
1696
1693
|
setErr("session not loaded yet \xB7 refresh and retry");
|
|
@@ -1850,9 +1847,9 @@ function TransferConfirm({
|
|
|
1850
1847
|
] });
|
|
1851
1848
|
}
|
|
1852
1849
|
function LazyWalletButton(props) {
|
|
1853
|
-
const [Comp, setComp] =
|
|
1854
|
-
const [failed, setFailed] =
|
|
1855
|
-
|
|
1850
|
+
const [Comp, setComp] = React4.useState(null);
|
|
1851
|
+
const [failed, setFailed] = React4.useState(false);
|
|
1852
|
+
React4.useEffect(() => {
|
|
1856
1853
|
let alive = true;
|
|
1857
1854
|
import('@orangecheck/wallet-adapter/react').then((m) => {
|
|
1858
1855
|
if (alive) {
|
|
@@ -1960,10 +1957,6 @@ var hintStyle = {
|
|
|
1960
1957
|
fontSize: 10.5,
|
|
1961
1958
|
opacity: 0.8
|
|
1962
1959
|
};
|
|
1963
|
-
var codeStyle = {
|
|
1964
|
-
fontFamily: MONO,
|
|
1965
|
-
color: V.foreground
|
|
1966
|
-
};
|
|
1967
1960
|
var rowStyle = {
|
|
1968
1961
|
display: "flex",
|
|
1969
1962
|
flexWrap: "wrap",
|
|
@@ -2070,6 +2063,50 @@ function hardNavigate(target) {
|
|
|
2070
2063
|
if (typeof window === "undefined") return;
|
|
2071
2064
|
window.location.assign(target);
|
|
2072
2065
|
}
|
|
2066
|
+
var SIGNIN_ERRORS = {
|
|
2067
|
+
bad_request: "That doesn't look like a Bitcoin address.",
|
|
2068
|
+
invalid_challenge: "That signature doesn't match this address. Sign with the wallet that holds it.",
|
|
2069
|
+
sig_invalid: "That signature doesn't match this address. Sign with the wallet that holds it.",
|
|
2070
|
+
sig_unsupported_scheme: "We can't check this wallet's signature format yet. Try another wallet, or use email.",
|
|
2071
|
+
expired: "That request timed out. Try again for a fresh one.",
|
|
2072
|
+
not_yet_valid: "That request is not valid yet. Check your device clock, then try again.",
|
|
2073
|
+
nonce_mismatch: "That request didn't match. Try again for a fresh one.",
|
|
2074
|
+
audience_mismatch: "That request didn't match this site. Reload the page and try again.",
|
|
2075
|
+
purpose_mismatch: "That request didn't match. Try again for a fresh one.",
|
|
2076
|
+
malformed: "That signature couldn't be read. Copy it again, whole.",
|
|
2077
|
+
cross_site_blocked: "Your browser blocked this request. Reload the page and try again.",
|
|
2078
|
+
rate_limited: "Too many tries. Wait a minute, then try again.",
|
|
2079
|
+
invalid_email: "That email address doesn't look right.",
|
|
2080
|
+
mailer_unavailable: "We couldn't send the email right now. Try again in a minute.",
|
|
2081
|
+
mailer_error: "We couldn't send the email right now. Try again in a minute.",
|
|
2082
|
+
wrong_code: "That code isn't right. Check the newest email from us.",
|
|
2083
|
+
replay: "That code was already used. Send a new one.",
|
|
2084
|
+
invalid_token: "That code is no longer valid. Send a new one.",
|
|
2085
|
+
email_mismatch: "That code was sent to a different address. Send a new one."
|
|
2086
|
+
};
|
|
2087
|
+
var EMAIL_ERRORS = {
|
|
2088
|
+
expired: "That code expired. Send a new one."
|
|
2089
|
+
};
|
|
2090
|
+
var SUBMIT_ERRORS = {
|
|
2091
|
+
bad_request: "That signature couldn't be read. Copy it again, whole."
|
|
2092
|
+
};
|
|
2093
|
+
var NETWORK_ERROR = "We couldn't reach the server. Check your connection and try again.";
|
|
2094
|
+
function humanSigninError(reason, path, step = "challenge") {
|
|
2095
|
+
if (!/^[a-z0-9_]+$/.test(reason)) return reason;
|
|
2096
|
+
if (path === "wallet" && step === "submit" && SUBMIT_ERRORS[reason]) return SUBMIT_ERRORS[reason];
|
|
2097
|
+
if (path === "email" && EMAIL_ERRORS[reason]) return EMAIL_ERRORS[reason];
|
|
2098
|
+
return SIGNIN_ERRORS[reason] ?? (path === "email" ? "That didn't work. Try again, or send a new code." : "That didn't work. Try again.");
|
|
2099
|
+
}
|
|
2100
|
+
function failureMessage(err, path, step) {
|
|
2101
|
+
if (err instanceof TypeError || err instanceof SyntaxError) return NETWORK_ERROR;
|
|
2102
|
+
return humanSigninError(err instanceof Error ? err.message : "failed", path, step);
|
|
2103
|
+
}
|
|
2104
|
+
var BECH32_ADDRESS = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{8,87}$/i;
|
|
2105
|
+
var BASE58_ADDRESS = /^[123mn][1-9A-HJ-NP-Za-km-z]{25,34}$/;
|
|
2106
|
+
function looksLikeBitcoinAddress(input) {
|
|
2107
|
+
const s = input.trim();
|
|
2108
|
+
return BECH32_ADDRESS.test(s) || BASE58_ADDRESS.test(s);
|
|
2109
|
+
}
|
|
2073
2110
|
function OcSignIn({
|
|
2074
2111
|
audience,
|
|
2075
2112
|
returnTo,
|
|
@@ -2085,10 +2122,10 @@ function OcSignIn({
|
|
|
2085
2122
|
}) {
|
|
2086
2123
|
const walletEnabled = paths?.wallet ?? true;
|
|
2087
2124
|
const emailEnabled = paths?.email ?? true;
|
|
2088
|
-
const [resolvedReturn, setResolvedReturn] =
|
|
2125
|
+
const [resolvedReturn, setResolvedReturn] = React4.useState(
|
|
2089
2126
|
() => familyReturnTarget(returnTo) ?? safeReturnTo(returnTo)
|
|
2090
2127
|
);
|
|
2091
|
-
|
|
2128
|
+
React4.useEffect(() => {
|
|
2092
2129
|
const fromProp = familyReturnTarget(returnTo);
|
|
2093
2130
|
if (fromProp) {
|
|
2094
2131
|
setResolvedReturn(fromProp);
|
|
@@ -2099,8 +2136,8 @@ function OcSignIn({
|
|
|
2099
2136
|
const fromQuery = familyReturnTarget(q.get("return_to")) ?? familyReturnTarget(q.get("next"));
|
|
2100
2137
|
setResolvedReturn(fromQuery ?? "/");
|
|
2101
2138
|
}, [returnTo]);
|
|
2102
|
-
const [addMode, setAddMode] =
|
|
2103
|
-
|
|
2139
|
+
const [addMode, setAddMode] = React4.useState(Boolean(addProp));
|
|
2140
|
+
React4.useEffect(() => {
|
|
2104
2141
|
if (typeof window === "undefined") return;
|
|
2105
2142
|
if (addProp !== void 0) {
|
|
2106
2143
|
setAddMode(Boolean(addProp));
|
|
@@ -2108,19 +2145,19 @@ function OcSignIn({
|
|
|
2108
2145
|
}
|
|
2109
2146
|
setAddMode(new URLSearchParams(window.location.search).get("add") === "1");
|
|
2110
2147
|
}, [addProp]);
|
|
2111
|
-
const [path, setPath] =
|
|
2148
|
+
const [path, setPath] = React4.useState(
|
|
2112
2149
|
initialPath ?? (providersFirst ? "email" : "wallet")
|
|
2113
2150
|
);
|
|
2114
|
-
const [signedIn, setSignedIn] =
|
|
2115
|
-
const [linkAlso, setLinkAlso] =
|
|
2116
|
-
const [oauthError, setOauthError] =
|
|
2117
|
-
|
|
2151
|
+
const [signedIn, setSignedIn] = React4.useState(null);
|
|
2152
|
+
const [linkAlso, setLinkAlso] = React4.useState(false);
|
|
2153
|
+
const [oauthError, setOauthError] = React4.useState(false);
|
|
2154
|
+
React4.useEffect(() => {
|
|
2118
2155
|
if (typeof window === "undefined") return;
|
|
2119
2156
|
if (new URLSearchParams(window.location.search).has("oauth_error")) {
|
|
2120
2157
|
setOauthError(true);
|
|
2121
2158
|
}
|
|
2122
2159
|
}, []);
|
|
2123
|
-
const navigate =
|
|
2160
|
+
const navigate = React4.useCallback(
|
|
2124
2161
|
async (account) => {
|
|
2125
2162
|
if (resolveReturnTo) {
|
|
2126
2163
|
try {
|
|
@@ -2134,7 +2171,7 @@ function OcSignIn({
|
|
|
2134
2171
|
},
|
|
2135
2172
|
[resolveReturnTo, resolvedReturn]
|
|
2136
2173
|
);
|
|
2137
|
-
const handleSuccess =
|
|
2174
|
+
const handleSuccess = React4.useCallback(
|
|
2138
2175
|
async (account, token, via) => {
|
|
2139
2176
|
const proceed = () => {
|
|
2140
2177
|
clearTabSession();
|
|
@@ -2220,7 +2257,7 @@ function OcSignIn({
|
|
|
2220
2257
|
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
2221
2258
|
fontSize: 11
|
|
2222
2259
|
},
|
|
2223
|
-
children: "That sign-in didn't
|
|
2260
|
+
children: "That sign-in didn't finish. Try again, or use another way below."
|
|
2224
2261
|
}
|
|
2225
2262
|
),
|
|
2226
2263
|
addMode && /* @__PURE__ */ jsxs(
|
|
@@ -2285,7 +2322,7 @@ function OcSignIn({
|
|
|
2285
2322
|
{
|
|
2286
2323
|
active: path === "email",
|
|
2287
2324
|
onClick: () => setPath("email"),
|
|
2288
|
-
children: "email
|
|
2325
|
+
children: "email"
|
|
2289
2326
|
}
|
|
2290
2327
|
),
|
|
2291
2328
|
/* @__PURE__ */ jsx(
|
|
@@ -2293,7 +2330,7 @@ function OcSignIn({
|
|
|
2293
2330
|
{
|
|
2294
2331
|
active: path === "wallet",
|
|
2295
2332
|
onClick: () => setPath("wallet"),
|
|
2296
|
-
children: "bitcoin
|
|
2333
|
+
children: "bitcoin wallet"
|
|
2297
2334
|
}
|
|
2298
2335
|
)
|
|
2299
2336
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -2310,7 +2347,7 @@ function OcSignIn({
|
|
|
2310
2347
|
{
|
|
2311
2348
|
active: path === "email",
|
|
2312
2349
|
onClick: () => setPath("email"),
|
|
2313
|
-
children: "email
|
|
2350
|
+
children: "email"
|
|
2314
2351
|
}
|
|
2315
2352
|
)
|
|
2316
2353
|
] })
|
|
@@ -2343,7 +2380,7 @@ function OcSignIn({
|
|
|
2343
2380
|
type: "checkbox",
|
|
2344
2381
|
checked: linkAlso,
|
|
2345
2382
|
onChange: (e) => setLinkAlso(e.target.checked),
|
|
2346
|
-
style: {
|
|
2383
|
+
style: { margin: 0, accentColor: "var(--primary, #f97316)" }
|
|
2347
2384
|
}
|
|
2348
2385
|
),
|
|
2349
2386
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -2351,7 +2388,7 @@ function OcSignIn({
|
|
|
2351
2388
|
" ",
|
|
2352
2389
|
activeMethod === "email" ? "a Bitcoin wallet" : "an email",
|
|
2353
2390
|
" ",
|
|
2354
|
-
/* @__PURE__ */ jsx("span", {
|
|
2391
|
+
/* @__PURE__ */ jsx("span", { children: "\u2014 optional, one more signature." })
|
|
2355
2392
|
] })
|
|
2356
2393
|
] })
|
|
2357
2394
|
] });
|
|
@@ -2370,6 +2407,7 @@ function SigninTab({
|
|
|
2370
2407
|
onClick,
|
|
2371
2408
|
"data-oc-signin-tab": active ? "active" : "inactive",
|
|
2372
2409
|
style: {
|
|
2410
|
+
minHeight: 44,
|
|
2373
2411
|
padding: "0.6rem 0.875rem",
|
|
2374
2412
|
background: "transparent",
|
|
2375
2413
|
border: "none",
|
|
@@ -2409,20 +2447,21 @@ function ProviderSignIn({
|
|
|
2409
2447
|
add,
|
|
2410
2448
|
first = false
|
|
2411
2449
|
}) {
|
|
2412
|
-
const [providers, setProviders] =
|
|
2413
|
-
const [origin, setOrigin] =
|
|
2414
|
-
|
|
2450
|
+
const [providers, setProviders] = React4.useState(null);
|
|
2451
|
+
const [origin, setOrigin] = React4.useState("");
|
|
2452
|
+
React4.useEffect(() => {
|
|
2415
2453
|
setOrigin(window.location.origin);
|
|
2416
2454
|
let cancelled = false;
|
|
2417
2455
|
fetch(`${authOrigin}/api/auth/providers`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((body) => {
|
|
2418
|
-
if (!cancelled
|
|
2456
|
+
if (!cancelled) setProviders(body?.providers ?? []);
|
|
2419
2457
|
}).catch(() => {
|
|
2458
|
+
if (!cancelled) setProviders([]);
|
|
2420
2459
|
});
|
|
2421
2460
|
return () => {
|
|
2422
2461
|
cancelled = true;
|
|
2423
2462
|
};
|
|
2424
2463
|
}, [authOrigin]);
|
|
2425
|
-
if (providers.length === 0) return null;
|
|
2464
|
+
if (providers !== null && providers.length === 0) return null;
|
|
2426
2465
|
const providerReturnTo = returnTo.startsWith("/") ? origin ? `${origin}${returnTo}` : returnTo : returnTo;
|
|
2427
2466
|
const line = { flex: 1, height: 1, background: "var(--border, #27272a)" };
|
|
2428
2467
|
const BLOCK_GAP = 16;
|
|
@@ -2451,6 +2490,31 @@ function ProviderSignIn({
|
|
|
2451
2490
|
]
|
|
2452
2491
|
}
|
|
2453
2492
|
);
|
|
2493
|
+
const buttonStyle = (i) => ({
|
|
2494
|
+
display: "flex",
|
|
2495
|
+
alignItems: "center",
|
|
2496
|
+
justifyContent: "center",
|
|
2497
|
+
gap: 10,
|
|
2498
|
+
boxSizing: "border-box",
|
|
2499
|
+
width: "100%",
|
|
2500
|
+
minHeight: 44,
|
|
2501
|
+
marginTop: i === 0 ? 0 : BUTTON_GAP,
|
|
2502
|
+
padding: "0.6rem 0.875rem",
|
|
2503
|
+
border: "1px solid var(--border, #27272a)",
|
|
2504
|
+
borderRadius: 6,
|
|
2505
|
+
background: "transparent",
|
|
2506
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
2507
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
2508
|
+
fontSize: 12,
|
|
2509
|
+
textDecoration: "none"
|
|
2510
|
+
});
|
|
2511
|
+
if (providers === null) {
|
|
2512
|
+
if (!first) return null;
|
|
2513
|
+
return /* @__PURE__ */ jsxs("div", { "data-oc-signin-providers": "", "aria-hidden": "true", children: [
|
|
2514
|
+
[0, 1].map((i) => /* @__PURE__ */ jsx("div", { style: { ...buttonStyle(i), visibility: "hidden" }, children: "\xA0" }, i)),
|
|
2515
|
+
divider
|
|
2516
|
+
] });
|
|
2517
|
+
}
|
|
2454
2518
|
const buttons = providers.map((p, i) => /* @__PURE__ */ jsxs(
|
|
2455
2519
|
"a",
|
|
2456
2520
|
{
|
|
@@ -2458,23 +2522,7 @@ function ProviderSignIn({
|
|
|
2458
2522
|
providerReturnTo
|
|
2459
2523
|
)}${add ? "&add=1" : ""}`,
|
|
2460
2524
|
"data-oc-signin-provider": p.id,
|
|
2461
|
-
style:
|
|
2462
|
-
display: "flex",
|
|
2463
|
-
alignItems: "center",
|
|
2464
|
-
justifyContent: "center",
|
|
2465
|
-
gap: 10,
|
|
2466
|
-
boxSizing: "border-box",
|
|
2467
|
-
width: "100%",
|
|
2468
|
-
marginTop: i === 0 ? 0 : BUTTON_GAP,
|
|
2469
|
-
padding: "0.6rem 0.875rem",
|
|
2470
|
-
border: "1px solid var(--border, #27272a)",
|
|
2471
|
-
borderRadius: 6,
|
|
2472
|
-
background: "transparent",
|
|
2473
|
-
color: "var(--muted-foreground, #a1a1aa)",
|
|
2474
|
-
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
2475
|
-
fontSize: 12,
|
|
2476
|
-
textDecoration: "none"
|
|
2477
|
-
},
|
|
2525
|
+
style: buttonStyle(i),
|
|
2478
2526
|
children: [
|
|
2479
2527
|
/* @__PURE__ */ jsx(ProviderIcon, { id: p.id }),
|
|
2480
2528
|
/* @__PURE__ */ jsx("span", { children: p.label })
|
|
@@ -2497,82 +2545,214 @@ function ProviderSignIn({
|
|
|
2497
2545
|
}
|
|
2498
2546
|
);
|
|
2499
2547
|
}
|
|
2548
|
+
async function loadAdapter() {
|
|
2549
|
+
return await import('@orangecheck/wallet-adapter');
|
|
2550
|
+
}
|
|
2551
|
+
async function detectBrowserWallets() {
|
|
2552
|
+
return (await loadAdapter()).detectWallets().filter((w) => w.detected && w.id !== "manual").map((w) => ({ id: w.id, name: w.name }));
|
|
2553
|
+
}
|
|
2500
2554
|
function WalletFlow({ authOrigin, audience, add, onSuccess }) {
|
|
2501
|
-
const
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2555
|
+
const uid = React4.useId();
|
|
2556
|
+
const ids = {
|
|
2557
|
+
address: `${uid}-address`,
|
|
2558
|
+
wallet: `${uid}-wallet`,
|
|
2559
|
+
message: `${uid}-message`,
|
|
2560
|
+
signature: `${uid}-signature`
|
|
2561
|
+
};
|
|
2562
|
+
const [address, setAddress] = React4.useState("");
|
|
2563
|
+
const [error, setError] = React4.useState(null);
|
|
2564
|
+
const [submitting, setSubmitting] = React4.useState(false);
|
|
2565
|
+
const [wallets, setWallets] = React4.useState(null);
|
|
2566
|
+
const [walletId, setWalletId] = React4.useState(null);
|
|
2567
|
+
const [manual, setManual] = React4.useState(null);
|
|
2568
|
+
const [pasted, setPasted] = React4.useState("");
|
|
2569
|
+
const [copied, setCopied] = React4.useState("idle");
|
|
2570
|
+
React4.useEffect(() => {
|
|
2571
|
+
let cancelled = false;
|
|
2572
|
+
detectBrowserWallets().then((found) => {
|
|
2573
|
+
if (cancelled) return;
|
|
2574
|
+
setWallets(found);
|
|
2575
|
+
setWalletId(found[0]?.id ?? null);
|
|
2576
|
+
}).catch(() => {
|
|
2577
|
+
if (!cancelled) setWallets([]);
|
|
2578
|
+
});
|
|
2579
|
+
return () => {
|
|
2580
|
+
cancelled = true;
|
|
2581
|
+
};
|
|
2582
|
+
}, []);
|
|
2583
|
+
async function fetchChallenge(addr) {
|
|
2584
|
+
const res = await fetch(
|
|
2585
|
+
`${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
|
|
2586
|
+
);
|
|
2587
|
+
const json = await res.json();
|
|
2588
|
+
if (!res.ok || !json.message || !json.nonce) {
|
|
2589
|
+
throw new Error(json.error ?? "challenge_failed");
|
|
2590
|
+
}
|
|
2591
|
+
return { message: json.message, nonce: json.nonce };
|
|
2592
|
+
}
|
|
2593
|
+
async function submitSignature(message, nonce, signature, scheme) {
|
|
2594
|
+
const res = await fetch(`${authOrigin}/api/auth/signin`, {
|
|
2595
|
+
method: "POST",
|
|
2596
|
+
credentials: "include",
|
|
2597
|
+
headers: { "Content-Type": "application/json" },
|
|
2598
|
+
body: JSON.stringify({
|
|
2599
|
+
message,
|
|
2600
|
+
signature,
|
|
2601
|
+
...scheme ? { scheme } : {},
|
|
2602
|
+
expectedNonce: nonce,
|
|
2603
|
+
expectedAudience: audience,
|
|
2604
|
+
expectedPurpose: "login",
|
|
2605
|
+
// Multi-account add-mode · the auth host preserves the current
|
|
2606
|
+
// roster_id when set, instead of minting a fresh one.
|
|
2607
|
+
...add ? { add: true } : {}
|
|
2608
|
+
})
|
|
2609
|
+
});
|
|
2610
|
+
const json = await res.json();
|
|
2611
|
+
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
2612
|
+
throw new Error(
|
|
2613
|
+
"reason" in json && json.reason || "error" in json && json.error || "verify_failed"
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
onSuccess(json.account, json.token);
|
|
2617
|
+
}
|
|
2505
2618
|
async function signIn(e) {
|
|
2506
2619
|
e.preventDefault();
|
|
2507
2620
|
const addr = address.trim();
|
|
2508
2621
|
if (!addr) {
|
|
2509
|
-
setError("
|
|
2622
|
+
setError("Paste your Bitcoin address.");
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
if (!looksLikeBitcoinAddress(addr)) {
|
|
2626
|
+
setError(humanSigninError("bad_request", "wallet"));
|
|
2510
2627
|
return;
|
|
2511
2628
|
}
|
|
2512
2629
|
setError(null);
|
|
2513
2630
|
setSubmitting(true);
|
|
2514
|
-
|
|
2631
|
+
let step = "challenge";
|
|
2515
2632
|
try {
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2633
|
+
let id = walletId;
|
|
2634
|
+
if (wallets === null) {
|
|
2635
|
+
const found = await detectBrowserWallets().catch(() => []);
|
|
2636
|
+
setWallets(found);
|
|
2637
|
+
id = found[0]?.id ?? null;
|
|
2638
|
+
setWalletId(id);
|
|
2522
2639
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
"could not load wallet support \xB7 refresh to try again"
|
|
2529
|
-
);
|
|
2530
|
-
}
|
|
2531
|
-
const wallets = adapter.detectWallets().filter((w) => w.detected && w.id !== "manual");
|
|
2532
|
-
if (wallets.length === 0) {
|
|
2533
|
-
throw new Error("no BIP-322 wallet extension detected \xB7 install one and refresh");
|
|
2640
|
+
const challenge = await fetchChallenge(addr);
|
|
2641
|
+
if (!id) {
|
|
2642
|
+
setManual(challenge);
|
|
2643
|
+
setSubmitting(false);
|
|
2644
|
+
return;
|
|
2534
2645
|
}
|
|
2535
|
-
const
|
|
2536
|
-
const signer = adapter.getSigner(wallet.id, { address: addr });
|
|
2646
|
+
const signer = (await loadAdapter()).getSigner(id, { address: addr });
|
|
2537
2647
|
const signature = await signer(challenge.message);
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
credentials: "include",
|
|
2541
|
-
headers: { "Content-Type": "application/json" },
|
|
2542
|
-
body: JSON.stringify({
|
|
2543
|
-
message: challenge.message,
|
|
2544
|
-
signature,
|
|
2545
|
-
scheme: "bip322",
|
|
2546
|
-
expectedNonce: challenge.nonce,
|
|
2547
|
-
expectedAudience: audience,
|
|
2548
|
-
expectedPurpose: "login",
|
|
2549
|
-
// Multi-account add-mode · the auth host preserves
|
|
2550
|
-
// the current roster_id when set, instead of minting
|
|
2551
|
-
// a fresh one. Hosts that haven't deployed the
|
|
2552
|
-
// multi-account migration silently ignore the field.
|
|
2553
|
-
...add ? { add: true } : {}
|
|
2554
|
-
})
|
|
2555
|
-
});
|
|
2556
|
-
const json = await res.json();
|
|
2557
|
-
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
2558
|
-
throw new Error(
|
|
2559
|
-
"reason" in json && json.reason || "error" in json && json.error || `verify_failed_${res.status}`
|
|
2560
|
-
);
|
|
2561
|
-
}
|
|
2562
|
-
onSuccess(json.account, json.token);
|
|
2648
|
+
step = "submit";
|
|
2649
|
+
await submitSignature(challenge.message, challenge.nonce, signature, "bip322");
|
|
2563
2650
|
} catch (err) {
|
|
2564
|
-
|
|
2565
|
-
setError(msg);
|
|
2651
|
+
setError(failureMessage(err, "wallet", step));
|
|
2566
2652
|
setSubmitting(false);
|
|
2567
|
-
setStage("enter");
|
|
2568
2653
|
}
|
|
2569
2654
|
}
|
|
2655
|
+
async function signInManual(e) {
|
|
2656
|
+
e.preventDefault();
|
|
2657
|
+
if (!manual) return;
|
|
2658
|
+
const sig = pasted.trim();
|
|
2659
|
+
if (!sig) {
|
|
2660
|
+
setError("Paste the signature your wallet gave you.");
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
setError(null);
|
|
2664
|
+
setSubmitting(true);
|
|
2665
|
+
try {
|
|
2666
|
+
await submitSignature(manual.message, manual.nonce, sig, void 0);
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
setError(failureMessage(err, "wallet", "submit"));
|
|
2669
|
+
setSubmitting(false);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
async function copyMessage() {
|
|
2673
|
+
if (!manual) return;
|
|
2674
|
+
try {
|
|
2675
|
+
await navigator.clipboard.writeText(manual.message);
|
|
2676
|
+
setCopied("copied");
|
|
2677
|
+
} catch {
|
|
2678
|
+
setCopied("failed");
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
if (manual) {
|
|
2682
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: signInManual, "data-oc-signin-wallet-manual": "", children: [
|
|
2683
|
+
/* @__PURE__ */ jsxs(FlowHeader, { children: [
|
|
2684
|
+
"In your wallet, use \u201Csign message\u201D (BIP-322) with ",
|
|
2685
|
+
address.trim(),
|
|
2686
|
+
", sign the text below, and paste the signature back here. Nothing is spent."
|
|
2687
|
+
] }),
|
|
2688
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.message, children: "message to sign" }),
|
|
2689
|
+
/* @__PURE__ */ jsx(
|
|
2690
|
+
"textarea",
|
|
2691
|
+
{
|
|
2692
|
+
id: ids.message,
|
|
2693
|
+
readOnly: true,
|
|
2694
|
+
value: manual.message,
|
|
2695
|
+
rows: 5,
|
|
2696
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
2697
|
+
style: { ...inputStyle2, resize: "vertical" }
|
|
2698
|
+
}
|
|
2699
|
+
),
|
|
2700
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void copyMessage(), style: linkButtonStyle, children: copied === "copied" ? "copied" : "copy message" }),
|
|
2701
|
+
copied === "failed" && /* @__PURE__ */ jsx(Hint, { children: "Couldn't copy here. Select the text above and copy it." }),
|
|
2702
|
+
/* @__PURE__ */ jsxs("div", { style: { marginTop: 14 }, children: [
|
|
2703
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.signature, children: "signature" }),
|
|
2704
|
+
/* @__PURE__ */ jsx(
|
|
2705
|
+
"textarea",
|
|
2706
|
+
{
|
|
2707
|
+
id: ids.signature,
|
|
2708
|
+
value: pasted,
|
|
2709
|
+
onChange: (e) => {
|
|
2710
|
+
setPasted(e.target.value);
|
|
2711
|
+
if (error) setError(null);
|
|
2712
|
+
},
|
|
2713
|
+
rows: 3,
|
|
2714
|
+
placeholder: "paste the signature",
|
|
2715
|
+
spellCheck: false,
|
|
2716
|
+
autoFocus: true,
|
|
2717
|
+
"aria-invalid": error ? true : void 0,
|
|
2718
|
+
style: { ...inputStyle2, resize: "vertical" }
|
|
2719
|
+
}
|
|
2720
|
+
)
|
|
2721
|
+
] }),
|
|
2722
|
+
error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
|
|
2723
|
+
/* @__PURE__ */ jsx(
|
|
2724
|
+
"button",
|
|
2725
|
+
{
|
|
2726
|
+
type: "submit",
|
|
2727
|
+
disabled: submitting || !pasted.trim(),
|
|
2728
|
+
style: submitStyle(submitting || !pasted.trim()),
|
|
2729
|
+
children: submitting ? "checking\u2026" : "sign me in \u2192"
|
|
2730
|
+
}
|
|
2731
|
+
),
|
|
2732
|
+
/* @__PURE__ */ jsx(
|
|
2733
|
+
"button",
|
|
2734
|
+
{
|
|
2735
|
+
type: "button",
|
|
2736
|
+
onClick: () => {
|
|
2737
|
+
setManual(null);
|
|
2738
|
+
setPasted("");
|
|
2739
|
+
setError(null);
|
|
2740
|
+
setCopied("idle");
|
|
2741
|
+
},
|
|
2742
|
+
style: linkButtonStyle,
|
|
2743
|
+
children: "use a different address"
|
|
2744
|
+
}
|
|
2745
|
+
)
|
|
2746
|
+
] });
|
|
2747
|
+
}
|
|
2748
|
+
const noBrowserWallet = wallets !== null && wallets.length === 0;
|
|
2570
2749
|
return /* @__PURE__ */ jsxs("form", { onSubmit: signIn, "data-oc-signin-wallet": "", children: [
|
|
2571
|
-
/* @__PURE__ */ jsx(FlowHeader, {
|
|
2572
|
-
/* @__PURE__ */ jsx(Label, { children: "bitcoin address" }),
|
|
2750
|
+
/* @__PURE__ */ jsx(FlowHeader, { children: "Your wallet signs a short message to prove you hold the address. Nothing is spent and your key never leaves the wallet. Works with browser wallets, or any wallet that can sign a message, like Sparrow." }),
|
|
2751
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.address, children: "bitcoin address" }),
|
|
2573
2752
|
/* @__PURE__ */ jsx(
|
|
2574
2753
|
"input",
|
|
2575
2754
|
{
|
|
2755
|
+
id: ids.address,
|
|
2576
2756
|
type: "text",
|
|
2577
2757
|
value: address,
|
|
2578
2758
|
onChange: (e) => {
|
|
@@ -2588,6 +2768,19 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
|
|
|
2588
2768
|
style: inputStyle2
|
|
2589
2769
|
}
|
|
2590
2770
|
),
|
|
2771
|
+
wallets && wallets.length > 1 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
|
|
2772
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.wallet, children: "sign with" }),
|
|
2773
|
+
/* @__PURE__ */ jsx(
|
|
2774
|
+
"select",
|
|
2775
|
+
{
|
|
2776
|
+
id: ids.wallet,
|
|
2777
|
+
value: walletId ?? "",
|
|
2778
|
+
onChange: (e) => setWalletId(e.target.value),
|
|
2779
|
+
style: inputStyle2,
|
|
2780
|
+
children: wallets.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.name }, w.id))
|
|
2781
|
+
}
|
|
2782
|
+
)
|
|
2783
|
+
] }),
|
|
2591
2784
|
error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
|
|
2592
2785
|
/* @__PURE__ */ jsx(
|
|
2593
2786
|
"button",
|
|
@@ -2595,54 +2788,71 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
|
|
|
2595
2788
|
type: "submit",
|
|
2596
2789
|
disabled: submitting || !address.trim(),
|
|
2597
2790
|
style: submitStyle(submitting || !address.trim()),
|
|
2598
|
-
children:
|
|
2791
|
+
children: submitting ? "waiting for wallet\u2026" : noBrowserWallet ? "get the message to sign \u2192" : "sign in with wallet \u2192"
|
|
2599
2792
|
}
|
|
2600
2793
|
),
|
|
2601
|
-
/* @__PURE__ */ jsx(Hint, { children: "
|
|
2794
|
+
noBrowserWallet && /* @__PURE__ */ jsx(Hint, { children: "No browser wallet found here. You can sign with any wallet and paste." })
|
|
2602
2795
|
] });
|
|
2603
2796
|
}
|
|
2604
2797
|
function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
2605
|
-
const
|
|
2606
|
-
const [
|
|
2607
|
-
const [
|
|
2608
|
-
const [
|
|
2609
|
-
const [
|
|
2610
|
-
const [
|
|
2611
|
-
const [
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
|
|
2616
|
-
setEmailError("enter a valid email");
|
|
2617
|
-
return;
|
|
2618
|
-
}
|
|
2619
|
-
setEmailError(null);
|
|
2620
|
-
setSubmitting(true);
|
|
2798
|
+
const uid = React4.useId();
|
|
2799
|
+
const [stage, setStage] = React4.useState("enter");
|
|
2800
|
+
const [email, setEmail] = React4.useState("");
|
|
2801
|
+
const [emailError, setEmailError] = React4.useState(null);
|
|
2802
|
+
const [code, setCode] = React4.useState("");
|
|
2803
|
+
const [codeError, setCodeError] = React4.useState(null);
|
|
2804
|
+
const [token, setToken] = React4.useState(null);
|
|
2805
|
+
const [submitting, setSubmitting] = React4.useState(false);
|
|
2806
|
+
const [resent, setResent] = React4.useState(false);
|
|
2807
|
+
async function sendCode(address) {
|
|
2621
2808
|
try {
|
|
2622
2809
|
const res = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
|
|
2623
2810
|
method: "POST",
|
|
2624
2811
|
credentials: "include",
|
|
2625
2812
|
headers: { "Content-Type": "application/json" },
|
|
2626
|
-
body: JSON.stringify({ email:
|
|
2813
|
+
body: JSON.stringify({ email: address })
|
|
2627
2814
|
});
|
|
2628
2815
|
const json = await res.json();
|
|
2629
|
-
if (!res.ok || !json.token)
|
|
2630
|
-
throw new Error(json.reason ?? `start failed (${res.status})`);
|
|
2631
|
-
}
|
|
2816
|
+
if (!res.ok || !json.token) return humanSigninError(json.reason ?? "server_error", "email");
|
|
2632
2817
|
setToken(json.token);
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2818
|
+
return null;
|
|
2819
|
+
} catch {
|
|
2820
|
+
return NETWORK_ERROR;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
async function start(e) {
|
|
2824
|
+
e.preventDefault();
|
|
2825
|
+
const trimmed = email.trim().toLowerCase();
|
|
2826
|
+
if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
|
|
2827
|
+
setEmailError(humanSigninError("invalid_email", "email"));
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
setEmailError(null);
|
|
2831
|
+
setSubmitting(true);
|
|
2832
|
+
const err = await sendCode(trimmed);
|
|
2833
|
+
setSubmitting(false);
|
|
2834
|
+
if (err) {
|
|
2835
|
+
setEmailError(err);
|
|
2836
|
+
return;
|
|
2639
2837
|
}
|
|
2838
|
+
setEmail(trimmed);
|
|
2839
|
+
setStage("code");
|
|
2840
|
+
}
|
|
2841
|
+
async function resend() {
|
|
2842
|
+
setCodeError(null);
|
|
2843
|
+
setResent(false);
|
|
2844
|
+
setSubmitting(true);
|
|
2845
|
+
const err = await sendCode(email);
|
|
2846
|
+
setSubmitting(false);
|
|
2847
|
+
setCode("");
|
|
2848
|
+
if (err) setCodeError(err);
|
|
2849
|
+
else setResent(true);
|
|
2640
2850
|
}
|
|
2641
2851
|
async function verify(e) {
|
|
2642
2852
|
e.preventDefault();
|
|
2643
2853
|
if (!token) return;
|
|
2644
2854
|
if (code.length !== 6) {
|
|
2645
|
-
setCodeError("6 digits");
|
|
2855
|
+
setCodeError("The code is 6 digits.");
|
|
2646
2856
|
return;
|
|
2647
2857
|
}
|
|
2648
2858
|
setCodeError(null);
|
|
@@ -2663,23 +2873,24 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
|
2663
2873
|
const json = await res.json();
|
|
2664
2874
|
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
2665
2875
|
throw new Error(
|
|
2666
|
-
"reason" in json && json.reason || "error" in json && json.error ||
|
|
2876
|
+
"reason" in json && json.reason || "error" in json && json.error || "server_error"
|
|
2667
2877
|
);
|
|
2668
2878
|
}
|
|
2669
2879
|
onSuccess(json.account, json.token);
|
|
2670
2880
|
} catch (err) {
|
|
2671
|
-
setCodeError(err
|
|
2881
|
+
setCodeError(failureMessage(err, "email", "submit"));
|
|
2672
2882
|
} finally {
|
|
2673
2883
|
setSubmitting(false);
|
|
2674
2884
|
}
|
|
2675
2885
|
}
|
|
2676
2886
|
if (stage === "enter") {
|
|
2677
2887
|
return /* @__PURE__ */ jsxs("form", { onSubmit: start, "data-oc-signin-email": "", children: [
|
|
2678
|
-
/* @__PURE__ */ jsx(FlowHeader, {
|
|
2679
|
-
/* @__PURE__ */ jsx(Label, { children: "email" }),
|
|
2888
|
+
/* @__PURE__ */ jsx(FlowHeader, { children: "We email you a 6-digit code. No password, and no wallet needed to start. Link a Bitcoin address any time \u2014 that is the identity you hold yourself." }),
|
|
2889
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-email`, children: "email" }),
|
|
2680
2890
|
/* @__PURE__ */ jsx(
|
|
2681
2891
|
"input",
|
|
2682
2892
|
{
|
|
2893
|
+
id: `${uid}-email`,
|
|
2683
2894
|
type: "email",
|
|
2684
2895
|
autoComplete: "email",
|
|
2685
2896
|
value: email,
|
|
@@ -2694,20 +2905,20 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
|
2694
2905
|
}
|
|
2695
2906
|
),
|
|
2696
2907
|
emailError && /* @__PURE__ */ jsx(ErrorLine2, { children: emailError }),
|
|
2697
|
-
/* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" })
|
|
2698
|
-
/* @__PURE__ */ jsx(Hint, { children: "Codes come from the auth host, expire in 10 minutes, and work once." })
|
|
2908
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" })
|
|
2699
2909
|
] });
|
|
2700
2910
|
}
|
|
2701
2911
|
return /* @__PURE__ */ jsxs("form", { onSubmit: verify, "data-oc-signin-email-verify": "", children: [
|
|
2702
|
-
/* @__PURE__ */ jsxs(FlowHeader, {
|
|
2703
|
-
"Sent a 6-digit code to ",
|
|
2912
|
+
/* @__PURE__ */ jsxs(FlowHeader, { children: [
|
|
2913
|
+
resent ? "Sent a new code to " : "Sent a 6-digit code to ",
|
|
2704
2914
|
/* @__PURE__ */ jsx("span", { style: { color: "var(--foreground, #fafafa)" }, children: email }),
|
|
2705
|
-
".
|
|
2915
|
+
". It expires in 10 minutes."
|
|
2706
2916
|
] }),
|
|
2707
|
-
/* @__PURE__ */ jsx(Label, { children: "one-time code" }),
|
|
2917
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-code`, children: "one-time code" }),
|
|
2708
2918
|
/* @__PURE__ */ jsx(
|
|
2709
2919
|
"input",
|
|
2710
2920
|
{
|
|
2921
|
+
id: `${uid}-code`,
|
|
2711
2922
|
type: "text",
|
|
2712
2923
|
inputMode: "numeric",
|
|
2713
2924
|
pattern: "\\d{6}",
|
|
@@ -2735,31 +2946,32 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
|
2735
2946
|
children: submitting ? "verifying\u2026" : "verify \xB7 sign me in \u2192"
|
|
2736
2947
|
}
|
|
2737
2948
|
),
|
|
2738
|
-
/* @__PURE__ */
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2949
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2950
|
+
/* @__PURE__ */ jsx(
|
|
2951
|
+
"button",
|
|
2952
|
+
{
|
|
2953
|
+
type: "button",
|
|
2954
|
+
onClick: () => void resend(),
|
|
2955
|
+
disabled: submitting,
|
|
2956
|
+
style: linkButtonStyle,
|
|
2957
|
+
children: "send a new code"
|
|
2958
|
+
}
|
|
2959
|
+
),
|
|
2960
|
+
/* @__PURE__ */ jsx(
|
|
2961
|
+
"button",
|
|
2962
|
+
{
|
|
2963
|
+
type: "button",
|
|
2964
|
+
onClick: () => {
|
|
2965
|
+
setStage("enter");
|
|
2966
|
+
setCode("");
|
|
2967
|
+
setToken(null);
|
|
2968
|
+
setResent(false);
|
|
2969
|
+
},
|
|
2970
|
+
style: linkButtonStyle,
|
|
2971
|
+
children: "use a different email"
|
|
2972
|
+
}
|
|
2973
|
+
)
|
|
2974
|
+
] })
|
|
2763
2975
|
] });
|
|
2764
2976
|
}
|
|
2765
2977
|
function FlowHeader({
|
|
@@ -2767,7 +2979,7 @@ function FlowHeader({
|
|
|
2767
2979
|
children
|
|
2768
2980
|
}) {
|
|
2769
2981
|
return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
|
|
2770
|
-
/* @__PURE__ */ jsx(
|
|
2982
|
+
label && /* @__PURE__ */ jsx(
|
|
2771
2983
|
"div",
|
|
2772
2984
|
{
|
|
2773
2985
|
style: {
|
|
@@ -2795,10 +3007,30 @@ function FlowHeader({
|
|
|
2795
3007
|
)
|
|
2796
3008
|
] });
|
|
2797
3009
|
}
|
|
2798
|
-
|
|
3010
|
+
var linkButtonStyle = {
|
|
3011
|
+
display: "inline-flex",
|
|
3012
|
+
alignItems: "center",
|
|
3013
|
+
minHeight: 44,
|
|
3014
|
+
marginTop: 4,
|
|
3015
|
+
marginRight: 16,
|
|
3016
|
+
background: "transparent",
|
|
3017
|
+
border: "none",
|
|
3018
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
3019
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
3020
|
+
fontSize: 11,
|
|
3021
|
+
letterSpacing: "0.12em",
|
|
3022
|
+
textTransform: "uppercase",
|
|
3023
|
+
cursor: "pointer",
|
|
3024
|
+
padding: 0
|
|
3025
|
+
};
|
|
3026
|
+
function Label({
|
|
3027
|
+
htmlFor,
|
|
3028
|
+
children
|
|
3029
|
+
}) {
|
|
2799
3030
|
return /* @__PURE__ */ jsx(
|
|
2800
3031
|
"label",
|
|
2801
3032
|
{
|
|
3033
|
+
htmlFor,
|
|
2802
3034
|
style: {
|
|
2803
3035
|
display: "block",
|
|
2804
3036
|
color: "var(--foreground, #fafafa)",
|
|
@@ -2828,7 +3060,9 @@ var inputStyle2 = {
|
|
|
2828
3060
|
};
|
|
2829
3061
|
var linkAlsoStyle = {
|
|
2830
3062
|
display: "flex",
|
|
2831
|
-
alignItems: "
|
|
3063
|
+
alignItems: "center",
|
|
3064
|
+
// The whole row is the checkbox's hit area; the 13px box alone was not.
|
|
3065
|
+
minHeight: 44,
|
|
2832
3066
|
gap: 8,
|
|
2833
3067
|
marginTop: 16,
|
|
2834
3068
|
paddingTop: 14,
|
|
@@ -2884,8 +3118,7 @@ function Hint({ children }) {
|
|
|
2884
3118
|
color: "var(--muted-foreground, #a1a1aa)",
|
|
2885
3119
|
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
2886
3120
|
fontSize: 10.5,
|
|
2887
|
-
lineHeight: 1.55
|
|
2888
|
-
opacity: 0.7
|
|
3121
|
+
lineHeight: 1.55
|
|
2889
3122
|
},
|
|
2890
3123
|
children: [
|
|
2891
3124
|
"> ",
|
|
@@ -2904,13 +3137,13 @@ function isAbortError(err) {
|
|
|
2904
3137
|
}
|
|
2905
3138
|
function useWebAuthnRegister(opts) {
|
|
2906
3139
|
const authOrigin = resolveAuthOrigin(opts);
|
|
2907
|
-
const [status, setStatus] =
|
|
2908
|
-
const [error, setError] =
|
|
2909
|
-
const reset =
|
|
3140
|
+
const [status, setStatus] = React4.useState("idle");
|
|
3141
|
+
const [error, setError] = React4.useState(null);
|
|
3142
|
+
const reset = React4.useCallback(() => {
|
|
2910
3143
|
setStatus("idle");
|
|
2911
3144
|
setError(null);
|
|
2912
3145
|
}, []);
|
|
2913
|
-
const register =
|
|
3146
|
+
const register = React4.useCallback(
|
|
2914
3147
|
async (args) => {
|
|
2915
3148
|
setError(null);
|
|
2916
3149
|
setStatus("requesting-options");
|
|
@@ -2972,10 +3205,10 @@ function useWebAuthnRegister(opts) {
|
|
|
2972
3205
|
function useWebAuthnList(opts) {
|
|
2973
3206
|
const authOrigin = resolveAuthOrigin(opts);
|
|
2974
3207
|
const { status: sessionStatus } = useOcSession();
|
|
2975
|
-
const [credentials, setCredentials] =
|
|
2976
|
-
const [status, setStatus] =
|
|
2977
|
-
const [error, setError] =
|
|
2978
|
-
const refetch =
|
|
3208
|
+
const [credentials, setCredentials] = React4.useState([]);
|
|
3209
|
+
const [status, setStatus] = React4.useState("loading");
|
|
3210
|
+
const [error, setError] = React4.useState(null);
|
|
3211
|
+
const refetch = React4.useCallback(async () => {
|
|
2979
3212
|
setError(null);
|
|
2980
3213
|
if (sessionStatus !== "authenticated") {
|
|
2981
3214
|
setCredentials([]);
|
|
@@ -3003,10 +3236,10 @@ function useWebAuthnList(opts) {
|
|
|
3003
3236
|
setStatus("error");
|
|
3004
3237
|
}
|
|
3005
3238
|
}, [authOrigin, sessionStatus]);
|
|
3006
|
-
|
|
3239
|
+
React4.useEffect(() => {
|
|
3007
3240
|
void refetch();
|
|
3008
3241
|
}, [refetch]);
|
|
3009
|
-
const rename =
|
|
3242
|
+
const rename = React4.useCallback(
|
|
3010
3243
|
async (id, label) => {
|
|
3011
3244
|
try {
|
|
3012
3245
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -3031,7 +3264,7 @@ function useWebAuthnList(opts) {
|
|
|
3031
3264
|
},
|
|
3032
3265
|
[authOrigin, refetch]
|
|
3033
3266
|
);
|
|
3034
|
-
const remove =
|
|
3267
|
+
const remove = React4.useCallback(
|
|
3035
3268
|
async (id) => {
|
|
3036
3269
|
try {
|
|
3037
3270
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -3059,13 +3292,13 @@ function useWebAuthnList(opts) {
|
|
|
3059
3292
|
function useStepUpAuth(opts) {
|
|
3060
3293
|
const authOrigin = resolveAuthOrigin(opts);
|
|
3061
3294
|
const { refresh } = useOcSession();
|
|
3062
|
-
const [status, setStatus] =
|
|
3063
|
-
const [error, setError] =
|
|
3064
|
-
const reset =
|
|
3295
|
+
const [status, setStatus] = React4.useState("idle");
|
|
3296
|
+
const [error, setError] = React4.useState(null);
|
|
3297
|
+
const reset = React4.useCallback(() => {
|
|
3065
3298
|
setStatus("idle");
|
|
3066
3299
|
setError(null);
|
|
3067
3300
|
}, []);
|
|
3068
|
-
const stepUp =
|
|
3301
|
+
const stepUp = React4.useCallback(
|
|
3069
3302
|
async (args) => {
|
|
3070
3303
|
setError(null);
|
|
3071
3304
|
setStatus("requesting-options");
|