@orangecheck/auth-client 2.25.3 → 2.27.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 +559 -318
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +558 -317
- 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
|
};
|
|
@@ -1158,6 +1158,15 @@ var SUDO_ACCOUNT_MISMATCH_MESSAGE = "Switch this tab to that account first \u201
|
|
|
1158
1158
|
function isSudoAccountMismatch(body) {
|
|
1159
1159
|
return body?.reason === "sudo_account_mismatch";
|
|
1160
1160
|
}
|
|
1161
|
+
|
|
1162
|
+
// src/appearance.ts
|
|
1163
|
+
var SIGNIN_FONT = "var(--oc-signin-font, ui-monospace, SFMono-Regular, monospace)";
|
|
1164
|
+
var SIGNIN_CASE = "var(--oc-signin-case, uppercase)";
|
|
1165
|
+
var signinTracking = (em) => `var(--oc-signin-tracking, ${em})`;
|
|
1166
|
+
var signinSize = (px) => `calc(${px}px * var(--oc-signin-scale, 1))`;
|
|
1167
|
+
var signinRadius = (px) => `var(--oc-signin-radius, ${px}px)`;
|
|
1168
|
+
var SIGNIN_BUTTON_RADIUS = "var(--oc-signin-button-radius, 6px)";
|
|
1169
|
+
var SIGNIN_STRONG = "var(--oc-signin-strong, 600)";
|
|
1161
1170
|
var DEFAULT_AUTH_ORIGIN = "https://ochk.io";
|
|
1162
1171
|
async function fetchOcLinkedIdentities(opts) {
|
|
1163
1172
|
const authOrigin = opts?.authOrigin ?? DEFAULT_AUTH_ORIGIN;
|
|
@@ -1173,13 +1182,14 @@ async function fetchOcLinkedIdentities(opts) {
|
|
|
1173
1182
|
function OcLinkedIdentities({
|
|
1174
1183
|
authOrigin = DEFAULT_AUTH_ORIGIN,
|
|
1175
1184
|
className,
|
|
1176
|
-
onChange
|
|
1185
|
+
onChange,
|
|
1186
|
+
attestationLink = true
|
|
1177
1187
|
}) {
|
|
1178
|
-
const [resp, setResp] =
|
|
1179
|
-
const [error, setError] =
|
|
1180
|
-
const [showEmailLink, setShowEmailLink] =
|
|
1181
|
-
const [showBtcLink, setShowBtcLink] =
|
|
1182
|
-
const refresh =
|
|
1188
|
+
const [resp, setResp] = React4.useState(null);
|
|
1189
|
+
const [error, setError] = React4.useState(null);
|
|
1190
|
+
const [showEmailLink, setShowEmailLink] = React4.useState(false);
|
|
1191
|
+
const [showBtcLink, setShowBtcLink] = React4.useState(false);
|
|
1192
|
+
const refresh = React4.useCallback(async () => {
|
|
1183
1193
|
setError(null);
|
|
1184
1194
|
try {
|
|
1185
1195
|
const r = await fetch(`${authOrigin}/api/auth/identities`, {
|
|
@@ -1198,10 +1208,10 @@ function OcLinkedIdentities({
|
|
|
1198
1208
|
setError(e instanceof Error ? e.message : "load failed");
|
|
1199
1209
|
}
|
|
1200
1210
|
}, [authOrigin]);
|
|
1201
|
-
|
|
1211
|
+
React4.useEffect(() => {
|
|
1202
1212
|
void refresh();
|
|
1203
1213
|
}, [refresh]);
|
|
1204
|
-
const afterChange =
|
|
1214
|
+
const afterChange = React4.useCallback(() => {
|
|
1205
1215
|
setShowEmailLink(false);
|
|
1206
1216
|
setShowBtcLink(false);
|
|
1207
1217
|
void refresh();
|
|
@@ -1233,11 +1243,7 @@ function OcLinkedIdentities({
|
|
|
1233
1243
|
return /* @__PURE__ */ jsxs("div", { className, "data-oc-li": "", style: { display: "grid", gap: 16 }, children: [
|
|
1234
1244
|
/* @__PURE__ */ jsxs("div", { style: panelStyle(), children: [
|
|
1235
1245
|
/* @__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
|
-
] })
|
|
1246
|
+
/* @__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
1247
|
] }),
|
|
1242
1248
|
mergedFrom.length > 0 && /* @__PURE__ */ jsx(ConsolidationPanel, { entries: mergedFrom }),
|
|
1243
1249
|
/* @__PURE__ */ jsxs("div", { style: panelStyle("plain"), "data-oc-li-rows": "", children: [
|
|
@@ -1290,7 +1296,7 @@ function OcLinkedIdentities({
|
|
|
1290
1296
|
onCancel: () => setShowBtcLink(false)
|
|
1291
1297
|
}
|
|
1292
1298
|
),
|
|
1293
|
-
hasBtc && /* @__PURE__ */ jsx(AttestationLink, {})
|
|
1299
|
+
hasBtc && attestationLink && /* @__PURE__ */ jsx(AttestationLink, {})
|
|
1294
1300
|
] });
|
|
1295
1301
|
}
|
|
1296
1302
|
function IdentityGroup({
|
|
@@ -1334,10 +1340,10 @@ function IdentityRow({
|
|
|
1334
1340
|
authOrigin,
|
|
1335
1341
|
onChange
|
|
1336
1342
|
}) {
|
|
1337
|
-
const [shown, setShown] =
|
|
1338
|
-
const [confirming, setConfirming] =
|
|
1339
|
-
const [busy, setBusy] =
|
|
1340
|
-
const [err, setErr] =
|
|
1343
|
+
const [shown, setShown] = React4.useState(false);
|
|
1344
|
+
const [confirming, setConfirming] = React4.useState(false);
|
|
1345
|
+
const [busy, setBusy] = React4.useState(false);
|
|
1346
|
+
const [err, setErr] = React4.useState(null);
|
|
1341
1347
|
const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
|
|
1342
1348
|
const display = shown && row.value ? row.value : masked;
|
|
1343
1349
|
async function unlink() {
|
|
@@ -1375,8 +1381,8 @@ function IdentityRow({
|
|
|
1375
1381
|
style: {
|
|
1376
1382
|
marginTop: 4,
|
|
1377
1383
|
color: V.foreground,
|
|
1378
|
-
fontFamily:
|
|
1379
|
-
fontSize: 12,
|
|
1384
|
+
fontFamily: SIGNIN_FONT,
|
|
1385
|
+
fontSize: signinSize(12),
|
|
1380
1386
|
wordBreak: "break-all"
|
|
1381
1387
|
},
|
|
1382
1388
|
children: display
|
|
@@ -1454,8 +1460,8 @@ function ConsolidationPanel({ entries }) {
|
|
|
1454
1460
|
gap: 8,
|
|
1455
1461
|
padding: "8px 0",
|
|
1456
1462
|
borderTop: `1px solid ${V.border}`,
|
|
1457
|
-
fontFamily:
|
|
1458
|
-
fontSize: 11
|
|
1463
|
+
fontFamily: SIGNIN_FONT,
|
|
1464
|
+
fontSize: signinSize(11)
|
|
1459
1465
|
},
|
|
1460
1466
|
children: [
|
|
1461
1467
|
/* @__PURE__ */ jsx("code", { style: { color: V.foreground, wordBreak: "break-all" }, children: e.did_oc }),
|
|
@@ -1536,13 +1542,13 @@ function EmailLinkForm({
|
|
|
1536
1542
|
onDone,
|
|
1537
1543
|
onCancel
|
|
1538
1544
|
}) {
|
|
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] =
|
|
1545
|
+
const [step, setStep] = React4.useState("email");
|
|
1546
|
+
const [email, setEmail] = React4.useState("");
|
|
1547
|
+
const [code, setCode] = React4.useState("");
|
|
1548
|
+
const [token, setToken] = React4.useState("");
|
|
1549
|
+
const [busy, setBusy] = React4.useState(false);
|
|
1550
|
+
const [err, setErr] = React4.useState(null);
|
|
1551
|
+
const [transfer, setTransfer] = React4.useState(false);
|
|
1546
1552
|
async function start() {
|
|
1547
1553
|
setBusy(true);
|
|
1548
1554
|
setErr(null);
|
|
@@ -1685,12 +1691,12 @@ function BtcLinkForm({
|
|
|
1685
1691
|
onDone,
|
|
1686
1692
|
onCancel
|
|
1687
1693
|
}) {
|
|
1688
|
-
const [step, setStep] =
|
|
1689
|
-
const [addr, setAddr] =
|
|
1690
|
-
const [challenge, setChallenge] =
|
|
1691
|
-
const [busy, setBusy] =
|
|
1692
|
-
const [err, setErr] =
|
|
1693
|
-
const [pendingSig, setPendingSig] =
|
|
1694
|
+
const [step, setStep] = React4.useState("request");
|
|
1695
|
+
const [addr, setAddr] = React4.useState("");
|
|
1696
|
+
const [challenge, setChallenge] = React4.useState("");
|
|
1697
|
+
const [busy, setBusy] = React4.useState(false);
|
|
1698
|
+
const [err, setErr] = React4.useState(null);
|
|
1699
|
+
const [pendingSig, setPendingSig] = React4.useState(null);
|
|
1694
1700
|
async function getChallenge() {
|
|
1695
1701
|
if (!didOc) {
|
|
1696
1702
|
setErr("session not loaded yet \xB7 refresh and retry");
|
|
@@ -1758,7 +1764,7 @@ function BtcLinkForm({
|
|
|
1758
1764
|
placeholder: "bc1q\u2026",
|
|
1759
1765
|
autoCapitalize: "none",
|
|
1760
1766
|
spellCheck: false,
|
|
1761
|
-
style: { ...inputStyle, fontFamily:
|
|
1767
|
+
style: { ...inputStyle, fontFamily: SIGNIN_FONT }
|
|
1762
1768
|
}
|
|
1763
1769
|
),
|
|
1764
1770
|
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
@@ -1788,7 +1794,7 @@ function BtcLinkForm({
|
|
|
1788
1794
|
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1789
1795
|
"Sign the challenge with the wallet that controls",
|
|
1790
1796
|
" ",
|
|
1791
|
-
/* @__PURE__ */ jsx("span", { style: { fontFamily:
|
|
1797
|
+
/* @__PURE__ */ jsx("span", { style: { fontFamily: SIGNIN_FONT, wordBreak: "break-all" }, children: addr }),
|
|
1792
1798
|
"."
|
|
1793
1799
|
] }),
|
|
1794
1800
|
/* @__PURE__ */ jsx(
|
|
@@ -1850,9 +1856,9 @@ function TransferConfirm({
|
|
|
1850
1856
|
] });
|
|
1851
1857
|
}
|
|
1852
1858
|
function LazyWalletButton(props) {
|
|
1853
|
-
const [Comp, setComp] =
|
|
1854
|
-
const [failed, setFailed] =
|
|
1855
|
-
|
|
1859
|
+
const [Comp, setComp] = React4.useState(null);
|
|
1860
|
+
const [failed, setFailed] = React4.useState(false);
|
|
1861
|
+
React4.useEffect(() => {
|
|
1856
1862
|
let alive = true;
|
|
1857
1863
|
import('@orangecheck/wallet-adapter/react').then((m) => {
|
|
1858
1864
|
if (alive) {
|
|
@@ -1896,10 +1902,10 @@ function SectionLabel({
|
|
|
1896
1902
|
{
|
|
1897
1903
|
style: {
|
|
1898
1904
|
color,
|
|
1899
|
-
fontFamily:
|
|
1900
|
-
fontSize: 10,
|
|
1901
|
-
letterSpacing: "0.18em",
|
|
1902
|
-
textTransform:
|
|
1905
|
+
fontFamily: SIGNIN_FONT,
|
|
1906
|
+
fontSize: signinSize(10),
|
|
1907
|
+
letterSpacing: signinTracking("0.18em"),
|
|
1908
|
+
textTransform: SIGNIN_CASE,
|
|
1903
1909
|
marginBottom: 8
|
|
1904
1910
|
},
|
|
1905
1911
|
children
|
|
@@ -1914,8 +1920,8 @@ function ErrorLine({ children }) {
|
|
|
1914
1920
|
style: {
|
|
1915
1921
|
margin: "8px 0 0",
|
|
1916
1922
|
color: V.destructive,
|
|
1917
|
-
fontFamily:
|
|
1918
|
-
fontSize: 11
|
|
1923
|
+
fontFamily: SIGNIN_FONT,
|
|
1924
|
+
fontSize: signinSize(11)
|
|
1919
1925
|
},
|
|
1920
1926
|
children
|
|
1921
1927
|
}
|
|
@@ -1924,7 +1930,6 @@ function ErrorLine({ children }) {
|
|
|
1924
1930
|
function FormButtons({ children }) {
|
|
1925
1931
|
return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }, children });
|
|
1926
1932
|
}
|
|
1927
|
-
var MONO = "ui-monospace, SFMono-Regular, monospace";
|
|
1928
1933
|
var V = {
|
|
1929
1934
|
primary: "var(--primary, #f97316)",
|
|
1930
1935
|
foreground: "var(--foreground, #fafafa)",
|
|
@@ -1956,14 +1961,10 @@ var bodyStyle = {
|
|
|
1956
1961
|
};
|
|
1957
1962
|
var hintStyle = {
|
|
1958
1963
|
color: V.muted,
|
|
1959
|
-
fontFamily:
|
|
1960
|
-
fontSize: 10.5,
|
|
1964
|
+
fontFamily: SIGNIN_FONT,
|
|
1965
|
+
fontSize: signinSize(10.5),
|
|
1961
1966
|
opacity: 0.8
|
|
1962
1967
|
};
|
|
1963
|
-
var codeStyle = {
|
|
1964
|
-
fontFamily: MONO,
|
|
1965
|
-
color: V.foreground
|
|
1966
|
-
};
|
|
1967
1968
|
var rowStyle = {
|
|
1968
1969
|
display: "flex",
|
|
1969
1970
|
flexWrap: "wrap",
|
|
@@ -1974,10 +1975,10 @@ var rowStyle = {
|
|
|
1974
1975
|
};
|
|
1975
1976
|
var rowLabelStyle = {
|
|
1976
1977
|
color: V.foreground,
|
|
1977
|
-
fontFamily:
|
|
1978
|
-
fontSize: 10.5,
|
|
1979
|
-
letterSpacing: "0.12em",
|
|
1980
|
-
textTransform:
|
|
1978
|
+
fontFamily: SIGNIN_FONT,
|
|
1979
|
+
fontSize: signinSize(10.5),
|
|
1980
|
+
letterSpacing: signinTracking("0.12em"),
|
|
1981
|
+
textTransform: SIGNIN_CASE,
|
|
1981
1982
|
opacity: 0.8
|
|
1982
1983
|
};
|
|
1983
1984
|
var inputStyle = {
|
|
@@ -1986,8 +1987,8 @@ var inputStyle = {
|
|
|
1986
1987
|
background: V.background,
|
|
1987
1988
|
color: V.foreground,
|
|
1988
1989
|
border: `1px solid ${V.border}`,
|
|
1989
|
-
borderRadius: 6,
|
|
1990
|
-
fontFamily:
|
|
1990
|
+
borderRadius: signinRadius(6),
|
|
1991
|
+
fontFamily: SIGNIN_FONT,
|
|
1991
1992
|
// 16px — never lower · suppresses iOS Safari focus auto-zoom.
|
|
1992
1993
|
fontSize: 16,
|
|
1993
1994
|
outline: "none",
|
|
@@ -1996,11 +1997,11 @@ var inputStyle = {
|
|
|
1996
1997
|
function btnBase(disabled) {
|
|
1997
1998
|
return {
|
|
1998
1999
|
padding: "0.5rem 0.875rem",
|
|
1999
|
-
borderRadius:
|
|
2000
|
-
fontFamily:
|
|
2001
|
-
fontSize: 10.5,
|
|
2002
|
-
letterSpacing: "0.12em",
|
|
2003
|
-
textTransform:
|
|
2000
|
+
borderRadius: SIGNIN_BUTTON_RADIUS,
|
|
2001
|
+
fontFamily: SIGNIN_FONT,
|
|
2002
|
+
fontSize: signinSize(10.5),
|
|
2003
|
+
letterSpacing: signinTracking("0.12em"),
|
|
2004
|
+
textTransform: SIGNIN_CASE,
|
|
2004
2005
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
2005
2006
|
opacity: disabled ? 0.5 : 1
|
|
2006
2007
|
};
|
|
@@ -2070,6 +2071,50 @@ function hardNavigate(target) {
|
|
|
2070
2071
|
if (typeof window === "undefined") return;
|
|
2071
2072
|
window.location.assign(target);
|
|
2072
2073
|
}
|
|
2074
|
+
var SIGNIN_ERRORS = {
|
|
2075
|
+
bad_request: "That doesn't look like a Bitcoin address.",
|
|
2076
|
+
invalid_challenge: "That signature doesn't match this address. Sign with the wallet that holds it.",
|
|
2077
|
+
sig_invalid: "That signature doesn't match this address. Sign with the wallet that holds it.",
|
|
2078
|
+
sig_unsupported_scheme: "We can't check this wallet's signature format yet. Try another wallet, or use email.",
|
|
2079
|
+
expired: "That request timed out. Try again for a fresh one.",
|
|
2080
|
+
not_yet_valid: "That request is not valid yet. Check your device clock, then try again.",
|
|
2081
|
+
nonce_mismatch: "That request didn't match. Try again for a fresh one.",
|
|
2082
|
+
audience_mismatch: "That request didn't match this site. Reload the page and try again.",
|
|
2083
|
+
purpose_mismatch: "That request didn't match. Try again for a fresh one.",
|
|
2084
|
+
malformed: "That signature couldn't be read. Copy it again, whole.",
|
|
2085
|
+
cross_site_blocked: "Your browser blocked this request. Reload the page and try again.",
|
|
2086
|
+
rate_limited: "Too many tries. Wait a minute, then try again.",
|
|
2087
|
+
invalid_email: "That email address doesn't look right.",
|
|
2088
|
+
mailer_unavailable: "We couldn't send the email right now. Try again in a minute.",
|
|
2089
|
+
mailer_error: "We couldn't send the email right now. Try again in a minute.",
|
|
2090
|
+
wrong_code: "That code isn't right. Check the newest email from us.",
|
|
2091
|
+
replay: "That code was already used. Send a new one.",
|
|
2092
|
+
invalid_token: "That code is no longer valid. Send a new one.",
|
|
2093
|
+
email_mismatch: "That code was sent to a different address. Send a new one."
|
|
2094
|
+
};
|
|
2095
|
+
var EMAIL_ERRORS = {
|
|
2096
|
+
expired: "That code expired. Send a new one."
|
|
2097
|
+
};
|
|
2098
|
+
var SUBMIT_ERRORS = {
|
|
2099
|
+
bad_request: "That signature couldn't be read. Copy it again, whole."
|
|
2100
|
+
};
|
|
2101
|
+
var NETWORK_ERROR = "We couldn't reach the server. Check your connection and try again.";
|
|
2102
|
+
function humanSigninError(reason, path, step = "challenge") {
|
|
2103
|
+
if (!/^[a-z0-9_]+$/.test(reason)) return reason;
|
|
2104
|
+
if (path === "wallet" && step === "submit" && SUBMIT_ERRORS[reason]) return SUBMIT_ERRORS[reason];
|
|
2105
|
+
if (path === "email" && EMAIL_ERRORS[reason]) return EMAIL_ERRORS[reason];
|
|
2106
|
+
return SIGNIN_ERRORS[reason] ?? (path === "email" ? "That didn't work. Try again, or send a new code." : "That didn't work. Try again.");
|
|
2107
|
+
}
|
|
2108
|
+
function failureMessage(err, path, step) {
|
|
2109
|
+
if (err instanceof TypeError || err instanceof SyntaxError) return NETWORK_ERROR;
|
|
2110
|
+
return humanSigninError(err instanceof Error ? err.message : "failed", path, step);
|
|
2111
|
+
}
|
|
2112
|
+
var BECH32_ADDRESS = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{8,87}$/i;
|
|
2113
|
+
var BASE58_ADDRESS = /^[123mn][1-9A-HJ-NP-Za-km-z]{25,34}$/;
|
|
2114
|
+
function looksLikeBitcoinAddress(input) {
|
|
2115
|
+
const s = input.trim();
|
|
2116
|
+
return BECH32_ADDRESS.test(s) || BASE58_ADDRESS.test(s);
|
|
2117
|
+
}
|
|
2073
2118
|
function OcSignIn({
|
|
2074
2119
|
audience,
|
|
2075
2120
|
returnTo,
|
|
@@ -2085,10 +2130,10 @@ function OcSignIn({
|
|
|
2085
2130
|
}) {
|
|
2086
2131
|
const walletEnabled = paths?.wallet ?? true;
|
|
2087
2132
|
const emailEnabled = paths?.email ?? true;
|
|
2088
|
-
const [resolvedReturn, setResolvedReturn] =
|
|
2133
|
+
const [resolvedReturn, setResolvedReturn] = React4.useState(
|
|
2089
2134
|
() => familyReturnTarget(returnTo) ?? safeReturnTo(returnTo)
|
|
2090
2135
|
);
|
|
2091
|
-
|
|
2136
|
+
React4.useEffect(() => {
|
|
2092
2137
|
const fromProp = familyReturnTarget(returnTo);
|
|
2093
2138
|
if (fromProp) {
|
|
2094
2139
|
setResolvedReturn(fromProp);
|
|
@@ -2099,8 +2144,8 @@ function OcSignIn({
|
|
|
2099
2144
|
const fromQuery = familyReturnTarget(q.get("return_to")) ?? familyReturnTarget(q.get("next"));
|
|
2100
2145
|
setResolvedReturn(fromQuery ?? "/");
|
|
2101
2146
|
}, [returnTo]);
|
|
2102
|
-
const [addMode, setAddMode] =
|
|
2103
|
-
|
|
2147
|
+
const [addMode, setAddMode] = React4.useState(Boolean(addProp));
|
|
2148
|
+
React4.useEffect(() => {
|
|
2104
2149
|
if (typeof window === "undefined") return;
|
|
2105
2150
|
if (addProp !== void 0) {
|
|
2106
2151
|
setAddMode(Boolean(addProp));
|
|
@@ -2108,19 +2153,19 @@ function OcSignIn({
|
|
|
2108
2153
|
}
|
|
2109
2154
|
setAddMode(new URLSearchParams(window.location.search).get("add") === "1");
|
|
2110
2155
|
}, [addProp]);
|
|
2111
|
-
const [path, setPath] =
|
|
2156
|
+
const [path, setPath] = React4.useState(
|
|
2112
2157
|
initialPath ?? (providersFirst ? "email" : "wallet")
|
|
2113
2158
|
);
|
|
2114
|
-
const [signedIn, setSignedIn] =
|
|
2115
|
-
const [linkAlso, setLinkAlso] =
|
|
2116
|
-
const [oauthError, setOauthError] =
|
|
2117
|
-
|
|
2159
|
+
const [signedIn, setSignedIn] = React4.useState(null);
|
|
2160
|
+
const [linkAlso, setLinkAlso] = React4.useState(false);
|
|
2161
|
+
const [oauthError, setOauthError] = React4.useState(false);
|
|
2162
|
+
React4.useEffect(() => {
|
|
2118
2163
|
if (typeof window === "undefined") return;
|
|
2119
2164
|
if (new URLSearchParams(window.location.search).has("oauth_error")) {
|
|
2120
2165
|
setOauthError(true);
|
|
2121
2166
|
}
|
|
2122
2167
|
}, []);
|
|
2123
|
-
const navigate =
|
|
2168
|
+
const navigate = React4.useCallback(
|
|
2124
2169
|
async (account) => {
|
|
2125
2170
|
if (resolveReturnTo) {
|
|
2126
2171
|
try {
|
|
@@ -2134,7 +2179,7 @@ function OcSignIn({
|
|
|
2134
2179
|
},
|
|
2135
2180
|
[resolveReturnTo, resolvedReturn]
|
|
2136
2181
|
);
|
|
2137
|
-
const handleSuccess =
|
|
2182
|
+
const handleSuccess = React4.useCallback(
|
|
2138
2183
|
async (account, token, via) => {
|
|
2139
2184
|
const proceed = () => {
|
|
2140
2185
|
clearTabSession();
|
|
@@ -2176,8 +2221,8 @@ function OcSignIn({
|
|
|
2176
2221
|
padding: "1rem",
|
|
2177
2222
|
border: "1px solid var(--border, #27272a)",
|
|
2178
2223
|
color: "var(--foreground, #fafafa)",
|
|
2179
|
-
fontFamily:
|
|
2180
|
-
fontSize: 12
|
|
2224
|
+
fontFamily: SIGNIN_FONT,
|
|
2225
|
+
fontSize: signinSize(12)
|
|
2181
2226
|
},
|
|
2182
2227
|
children: [
|
|
2183
2228
|
"no signin paths enabled \u2014 set ",
|
|
@@ -2215,12 +2260,12 @@ function OcSignIn({
|
|
|
2215
2260
|
marginBottom: 14,
|
|
2216
2261
|
padding: "0.6rem 0.75rem",
|
|
2217
2262
|
border: "1px solid var(--destructive, #ef4444)",
|
|
2218
|
-
borderRadius: 6,
|
|
2263
|
+
borderRadius: signinRadius(6),
|
|
2219
2264
|
color: "var(--destructive, #ef4444)",
|
|
2220
|
-
fontFamily:
|
|
2221
|
-
fontSize: 11
|
|
2265
|
+
fontFamily: SIGNIN_FONT,
|
|
2266
|
+
fontSize: signinSize(11)
|
|
2222
2267
|
},
|
|
2223
|
-
children: "That sign-in didn't
|
|
2268
|
+
children: "That sign-in didn't finish. Try again, or use another way below."
|
|
2224
2269
|
}
|
|
2225
2270
|
),
|
|
2226
2271
|
addMode && /* @__PURE__ */ jsxs(
|
|
@@ -2232,11 +2277,11 @@ function OcSignIn({
|
|
|
2232
2277
|
padding: "0.6rem 0.75rem",
|
|
2233
2278
|
border: "1px solid var(--primary, #f97316)",
|
|
2234
2279
|
borderLeftWidth: 3,
|
|
2235
|
-
borderRadius: 4,
|
|
2280
|
+
borderRadius: signinRadius(4),
|
|
2236
2281
|
background: "color-mix(in srgb, var(--primary, #f97316) 8%, transparent)",
|
|
2237
2282
|
color: "var(--foreground, #fafafa)",
|
|
2238
|
-
fontFamily:
|
|
2239
|
-
fontSize: 11,
|
|
2283
|
+
fontFamily: SIGNIN_FONT,
|
|
2284
|
+
fontSize: signinSize(11),
|
|
2240
2285
|
lineHeight: 1.6
|
|
2241
2286
|
},
|
|
2242
2287
|
children: [
|
|
@@ -2245,9 +2290,9 @@ function OcSignIn({
|
|
|
2245
2290
|
{
|
|
2246
2291
|
style: {
|
|
2247
2292
|
color: "var(--primary, #f97316)",
|
|
2248
|
-
letterSpacing: "0.12em",
|
|
2249
|
-
textTransform:
|
|
2250
|
-
fontSize: 10,
|
|
2293
|
+
letterSpacing: signinTracking("0.12em"),
|
|
2294
|
+
textTransform: SIGNIN_CASE,
|
|
2295
|
+
fontSize: signinSize(10),
|
|
2251
2296
|
display: "block",
|
|
2252
2297
|
marginBottom: 3
|
|
2253
2298
|
},
|
|
@@ -2285,7 +2330,7 @@ function OcSignIn({
|
|
|
2285
2330
|
{
|
|
2286
2331
|
active: path === "email",
|
|
2287
2332
|
onClick: () => setPath("email"),
|
|
2288
|
-
children: "email
|
|
2333
|
+
children: "email"
|
|
2289
2334
|
}
|
|
2290
2335
|
),
|
|
2291
2336
|
/* @__PURE__ */ jsx(
|
|
@@ -2293,7 +2338,7 @@ function OcSignIn({
|
|
|
2293
2338
|
{
|
|
2294
2339
|
active: path === "wallet",
|
|
2295
2340
|
onClick: () => setPath("wallet"),
|
|
2296
|
-
children: "bitcoin
|
|
2341
|
+
children: "bitcoin wallet"
|
|
2297
2342
|
}
|
|
2298
2343
|
)
|
|
2299
2344
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -2310,7 +2355,7 @@ function OcSignIn({
|
|
|
2310
2355
|
{
|
|
2311
2356
|
active: path === "email",
|
|
2312
2357
|
onClick: () => setPath("email"),
|
|
2313
|
-
children: "email
|
|
2358
|
+
children: "email"
|
|
2314
2359
|
}
|
|
2315
2360
|
)
|
|
2316
2361
|
] })
|
|
@@ -2343,7 +2388,7 @@ function OcSignIn({
|
|
|
2343
2388
|
type: "checkbox",
|
|
2344
2389
|
checked: linkAlso,
|
|
2345
2390
|
onChange: (e) => setLinkAlso(e.target.checked),
|
|
2346
|
-
style: {
|
|
2391
|
+
style: { margin: 0, accentColor: "var(--primary, #f97316)" }
|
|
2347
2392
|
}
|
|
2348
2393
|
),
|
|
2349
2394
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -2351,7 +2396,7 @@ function OcSignIn({
|
|
|
2351
2396
|
" ",
|
|
2352
2397
|
activeMethod === "email" ? "a Bitcoin wallet" : "an email",
|
|
2353
2398
|
" ",
|
|
2354
|
-
/* @__PURE__ */ jsx("span", {
|
|
2399
|
+
/* @__PURE__ */ jsx("span", { children: "\u2014 optional, one more signature." })
|
|
2355
2400
|
] })
|
|
2356
2401
|
] })
|
|
2357
2402
|
] });
|
|
@@ -2370,15 +2415,16 @@ function SigninTab({
|
|
|
2370
2415
|
onClick,
|
|
2371
2416
|
"data-oc-signin-tab": active ? "active" : "inactive",
|
|
2372
2417
|
style: {
|
|
2418
|
+
minHeight: 44,
|
|
2373
2419
|
padding: "0.6rem 0.875rem",
|
|
2374
2420
|
background: "transparent",
|
|
2375
2421
|
border: "none",
|
|
2376
2422
|
borderBottom: active ? "2px solid var(--primary, #f97316)" : "2px solid transparent",
|
|
2377
2423
|
color: active ? "var(--foreground, #fafafa)" : "var(--muted-foreground, #a1a1aa)",
|
|
2378
|
-
fontFamily:
|
|
2379
|
-
fontSize: 11,
|
|
2380
|
-
letterSpacing: "0.12em",
|
|
2381
|
-
textTransform:
|
|
2424
|
+
fontFamily: SIGNIN_FONT,
|
|
2425
|
+
fontSize: signinSize(11),
|
|
2426
|
+
letterSpacing: signinTracking("0.12em"),
|
|
2427
|
+
textTransform: SIGNIN_CASE,
|
|
2382
2428
|
cursor: "pointer",
|
|
2383
2429
|
marginBottom: -1
|
|
2384
2430
|
},
|
|
@@ -2409,20 +2455,21 @@ function ProviderSignIn({
|
|
|
2409
2455
|
add,
|
|
2410
2456
|
first = false
|
|
2411
2457
|
}) {
|
|
2412
|
-
const [providers, setProviders] =
|
|
2413
|
-
const [origin, setOrigin] =
|
|
2414
|
-
|
|
2458
|
+
const [providers, setProviders] = React4.useState(null);
|
|
2459
|
+
const [origin, setOrigin] = React4.useState("");
|
|
2460
|
+
React4.useEffect(() => {
|
|
2415
2461
|
setOrigin(window.location.origin);
|
|
2416
2462
|
let cancelled = false;
|
|
2417
2463
|
fetch(`${authOrigin}/api/auth/providers`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((body) => {
|
|
2418
|
-
if (!cancelled
|
|
2464
|
+
if (!cancelled) setProviders(body?.providers ?? []);
|
|
2419
2465
|
}).catch(() => {
|
|
2466
|
+
if (!cancelled) setProviders([]);
|
|
2420
2467
|
});
|
|
2421
2468
|
return () => {
|
|
2422
2469
|
cancelled = true;
|
|
2423
2470
|
};
|
|
2424
2471
|
}, [authOrigin]);
|
|
2425
|
-
if (providers.length === 0) return null;
|
|
2472
|
+
if (providers !== null && providers.length === 0) return null;
|
|
2426
2473
|
const providerReturnTo = returnTo.startsWith("/") ? origin ? `${origin}${returnTo}` : returnTo : returnTo;
|
|
2427
2474
|
const line = { flex: 1, height: 1, background: "var(--border, #27272a)" };
|
|
2428
2475
|
const BLOCK_GAP = 16;
|
|
@@ -2439,10 +2486,10 @@ function ProviderSignIn({
|
|
|
2439
2486
|
// marginTop owns the gap above, so the divider only pads below.
|
|
2440
2487
|
margin: first ? `${BLOCK_GAP}px 0` : `0 0 ${BLOCK_GAP}px`,
|
|
2441
2488
|
color: "var(--muted-foreground, #a1a1aa)",
|
|
2442
|
-
fontFamily:
|
|
2443
|
-
fontSize: 10,
|
|
2444
|
-
letterSpacing: "0.16em",
|
|
2445
|
-
textTransform:
|
|
2489
|
+
fontFamily: SIGNIN_FONT,
|
|
2490
|
+
fontSize: signinSize(10),
|
|
2491
|
+
letterSpacing: signinTracking("0.16em"),
|
|
2492
|
+
textTransform: SIGNIN_CASE
|
|
2446
2493
|
},
|
|
2447
2494
|
children: [
|
|
2448
2495
|
/* @__PURE__ */ jsx("span", { style: line }),
|
|
@@ -2451,6 +2498,31 @@ function ProviderSignIn({
|
|
|
2451
2498
|
]
|
|
2452
2499
|
}
|
|
2453
2500
|
);
|
|
2501
|
+
const buttonStyle = (i) => ({
|
|
2502
|
+
display: "flex",
|
|
2503
|
+
alignItems: "center",
|
|
2504
|
+
justifyContent: "center",
|
|
2505
|
+
gap: 10,
|
|
2506
|
+
boxSizing: "border-box",
|
|
2507
|
+
width: "100%",
|
|
2508
|
+
minHeight: 44,
|
|
2509
|
+
marginTop: i === 0 ? 0 : BUTTON_GAP,
|
|
2510
|
+
padding: "0.6rem 0.875rem",
|
|
2511
|
+
border: "1px solid var(--border, #27272a)",
|
|
2512
|
+
borderRadius: SIGNIN_BUTTON_RADIUS,
|
|
2513
|
+
background: "transparent",
|
|
2514
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
2515
|
+
fontFamily: SIGNIN_FONT,
|
|
2516
|
+
fontSize: signinSize(12),
|
|
2517
|
+
textDecoration: "none"
|
|
2518
|
+
});
|
|
2519
|
+
if (providers === null) {
|
|
2520
|
+
if (!first) return null;
|
|
2521
|
+
return /* @__PURE__ */ jsxs("div", { "data-oc-signin-providers": "", "aria-hidden": "true", children: [
|
|
2522
|
+
[0, 1].map((i) => /* @__PURE__ */ jsx("div", { style: { ...buttonStyle(i), visibility: "hidden" }, children: "\xA0" }, i)),
|
|
2523
|
+
divider
|
|
2524
|
+
] });
|
|
2525
|
+
}
|
|
2454
2526
|
const buttons = providers.map((p, i) => /* @__PURE__ */ jsxs(
|
|
2455
2527
|
"a",
|
|
2456
2528
|
{
|
|
@@ -2458,23 +2530,7 @@ function ProviderSignIn({
|
|
|
2458
2530
|
providerReturnTo
|
|
2459
2531
|
)}${add ? "&add=1" : ""}`,
|
|
2460
2532
|
"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
|
-
},
|
|
2533
|
+
style: buttonStyle(i),
|
|
2478
2534
|
children: [
|
|
2479
2535
|
/* @__PURE__ */ jsx(ProviderIcon, { id: p.id }),
|
|
2480
2536
|
/* @__PURE__ */ jsx("span", { children: p.label })
|
|
@@ -2497,82 +2553,214 @@ function ProviderSignIn({
|
|
|
2497
2553
|
}
|
|
2498
2554
|
);
|
|
2499
2555
|
}
|
|
2556
|
+
async function loadAdapter() {
|
|
2557
|
+
return await import('@orangecheck/wallet-adapter');
|
|
2558
|
+
}
|
|
2559
|
+
async function detectBrowserWallets() {
|
|
2560
|
+
return (await loadAdapter()).detectWallets().filter((w) => w.detected && w.id !== "manual").map((w) => ({ id: w.id, name: w.name }));
|
|
2561
|
+
}
|
|
2500
2562
|
function WalletFlow({ authOrigin, audience, add, onSuccess }) {
|
|
2501
|
-
const
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2563
|
+
const uid = React4.useId();
|
|
2564
|
+
const ids = {
|
|
2565
|
+
address: `${uid}-address`,
|
|
2566
|
+
wallet: `${uid}-wallet`,
|
|
2567
|
+
message: `${uid}-message`,
|
|
2568
|
+
signature: `${uid}-signature`
|
|
2569
|
+
};
|
|
2570
|
+
const [address, setAddress] = React4.useState("");
|
|
2571
|
+
const [error, setError] = React4.useState(null);
|
|
2572
|
+
const [submitting, setSubmitting] = React4.useState(false);
|
|
2573
|
+
const [wallets, setWallets] = React4.useState(null);
|
|
2574
|
+
const [walletId, setWalletId] = React4.useState(null);
|
|
2575
|
+
const [manual, setManual] = React4.useState(null);
|
|
2576
|
+
const [pasted, setPasted] = React4.useState("");
|
|
2577
|
+
const [copied, setCopied] = React4.useState("idle");
|
|
2578
|
+
React4.useEffect(() => {
|
|
2579
|
+
let cancelled = false;
|
|
2580
|
+
detectBrowserWallets().then((found) => {
|
|
2581
|
+
if (cancelled) return;
|
|
2582
|
+
setWallets(found);
|
|
2583
|
+
setWalletId(found[0]?.id ?? null);
|
|
2584
|
+
}).catch(() => {
|
|
2585
|
+
if (!cancelled) setWallets([]);
|
|
2586
|
+
});
|
|
2587
|
+
return () => {
|
|
2588
|
+
cancelled = true;
|
|
2589
|
+
};
|
|
2590
|
+
}, []);
|
|
2591
|
+
async function fetchChallenge(addr) {
|
|
2592
|
+
const res = await fetch(
|
|
2593
|
+
`${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
|
|
2594
|
+
);
|
|
2595
|
+
const json = await res.json();
|
|
2596
|
+
if (!res.ok || !json.message || !json.nonce) {
|
|
2597
|
+
throw new Error(json.error ?? "challenge_failed");
|
|
2598
|
+
}
|
|
2599
|
+
return { message: json.message, nonce: json.nonce };
|
|
2600
|
+
}
|
|
2601
|
+
async function submitSignature(message, nonce, signature, scheme) {
|
|
2602
|
+
const res = await fetch(`${authOrigin}/api/auth/signin`, {
|
|
2603
|
+
method: "POST",
|
|
2604
|
+
credentials: "include",
|
|
2605
|
+
headers: { "Content-Type": "application/json" },
|
|
2606
|
+
body: JSON.stringify({
|
|
2607
|
+
message,
|
|
2608
|
+
signature,
|
|
2609
|
+
...scheme ? { scheme } : {},
|
|
2610
|
+
expectedNonce: nonce,
|
|
2611
|
+
expectedAudience: audience,
|
|
2612
|
+
expectedPurpose: "login",
|
|
2613
|
+
// Multi-account add-mode · the auth host preserves the current
|
|
2614
|
+
// roster_id when set, instead of minting a fresh one.
|
|
2615
|
+
...add ? { add: true } : {}
|
|
2616
|
+
})
|
|
2617
|
+
});
|
|
2618
|
+
const json = await res.json();
|
|
2619
|
+
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
2620
|
+
throw new Error(
|
|
2621
|
+
"reason" in json && json.reason || "error" in json && json.error || "verify_failed"
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
onSuccess(json.account, json.token);
|
|
2625
|
+
}
|
|
2505
2626
|
async function signIn(e) {
|
|
2506
2627
|
e.preventDefault();
|
|
2507
2628
|
const addr = address.trim();
|
|
2508
2629
|
if (!addr) {
|
|
2509
|
-
setError("
|
|
2630
|
+
setError("Paste your Bitcoin address.");
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
if (!looksLikeBitcoinAddress(addr)) {
|
|
2634
|
+
setError(humanSigninError("bad_request", "wallet"));
|
|
2510
2635
|
return;
|
|
2511
2636
|
}
|
|
2512
2637
|
setError(null);
|
|
2513
2638
|
setSubmitting(true);
|
|
2514
|
-
|
|
2639
|
+
let step = "challenge";
|
|
2515
2640
|
try {
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2641
|
+
let id = walletId;
|
|
2642
|
+
if (wallets === null) {
|
|
2643
|
+
const found = await detectBrowserWallets().catch(() => []);
|
|
2644
|
+
setWallets(found);
|
|
2645
|
+
id = found[0]?.id ?? null;
|
|
2646
|
+
setWalletId(id);
|
|
2522
2647
|
}
|
|
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");
|
|
2648
|
+
const challenge = await fetchChallenge(addr);
|
|
2649
|
+
if (!id) {
|
|
2650
|
+
setManual(challenge);
|
|
2651
|
+
setSubmitting(false);
|
|
2652
|
+
return;
|
|
2534
2653
|
}
|
|
2535
|
-
const
|
|
2536
|
-
const signer = adapter.getSigner(wallet.id, { address: addr });
|
|
2654
|
+
const signer = (await loadAdapter()).getSigner(id, { address: addr });
|
|
2537
2655
|
const signature = await signer(challenge.message);
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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);
|
|
2656
|
+
step = "submit";
|
|
2657
|
+
await submitSignature(challenge.message, challenge.nonce, signature, "bip322");
|
|
2658
|
+
} catch (err) {
|
|
2659
|
+
setError(failureMessage(err, "wallet", step));
|
|
2660
|
+
setSubmitting(false);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
async function signInManual(e) {
|
|
2664
|
+
e.preventDefault();
|
|
2665
|
+
if (!manual) return;
|
|
2666
|
+
const sig = pasted.trim();
|
|
2667
|
+
if (!sig) {
|
|
2668
|
+
setError("Paste the signature your wallet gave you.");
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
setError(null);
|
|
2672
|
+
setSubmitting(true);
|
|
2673
|
+
try {
|
|
2674
|
+
await submitSignature(manual.message, manual.nonce, sig, void 0);
|
|
2563
2675
|
} catch (err) {
|
|
2564
|
-
|
|
2565
|
-
setError(msg);
|
|
2676
|
+
setError(failureMessage(err, "wallet", "submit"));
|
|
2566
2677
|
setSubmitting(false);
|
|
2567
|
-
setStage("enter");
|
|
2568
2678
|
}
|
|
2569
2679
|
}
|
|
2680
|
+
async function copyMessage() {
|
|
2681
|
+
if (!manual) return;
|
|
2682
|
+
try {
|
|
2683
|
+
await navigator.clipboard.writeText(manual.message);
|
|
2684
|
+
setCopied("copied");
|
|
2685
|
+
} catch {
|
|
2686
|
+
setCopied("failed");
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
if (manual) {
|
|
2690
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: signInManual, "data-oc-signin-wallet-manual": "", children: [
|
|
2691
|
+
/* @__PURE__ */ jsxs(FlowHeader, { children: [
|
|
2692
|
+
"In your wallet, use \u201Csign message\u201D (BIP-322) with ",
|
|
2693
|
+
address.trim(),
|
|
2694
|
+
", sign the text below, and paste the signature back here. Nothing is spent."
|
|
2695
|
+
] }),
|
|
2696
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.message, children: "message to sign" }),
|
|
2697
|
+
/* @__PURE__ */ jsx(
|
|
2698
|
+
"textarea",
|
|
2699
|
+
{
|
|
2700
|
+
id: ids.message,
|
|
2701
|
+
readOnly: true,
|
|
2702
|
+
value: manual.message,
|
|
2703
|
+
rows: 5,
|
|
2704
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
2705
|
+
style: { ...inputStyle2, resize: "vertical" }
|
|
2706
|
+
}
|
|
2707
|
+
),
|
|
2708
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void copyMessage(), style: linkButtonStyle, children: copied === "copied" ? "copied" : "copy message" }),
|
|
2709
|
+
copied === "failed" && /* @__PURE__ */ jsx(Hint, { children: "Couldn't copy here. Select the text above and copy it." }),
|
|
2710
|
+
/* @__PURE__ */ jsxs("div", { style: { marginTop: 14 }, children: [
|
|
2711
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.signature, children: "signature" }),
|
|
2712
|
+
/* @__PURE__ */ jsx(
|
|
2713
|
+
"textarea",
|
|
2714
|
+
{
|
|
2715
|
+
id: ids.signature,
|
|
2716
|
+
value: pasted,
|
|
2717
|
+
onChange: (e) => {
|
|
2718
|
+
setPasted(e.target.value);
|
|
2719
|
+
if (error) setError(null);
|
|
2720
|
+
},
|
|
2721
|
+
rows: 3,
|
|
2722
|
+
placeholder: "paste the signature",
|
|
2723
|
+
spellCheck: false,
|
|
2724
|
+
autoFocus: true,
|
|
2725
|
+
"aria-invalid": error ? true : void 0,
|
|
2726
|
+
style: { ...inputStyle2, resize: "vertical" }
|
|
2727
|
+
}
|
|
2728
|
+
)
|
|
2729
|
+
] }),
|
|
2730
|
+
error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
|
|
2731
|
+
/* @__PURE__ */ jsx(
|
|
2732
|
+
"button",
|
|
2733
|
+
{
|
|
2734
|
+
type: "submit",
|
|
2735
|
+
disabled: submitting || !pasted.trim(),
|
|
2736
|
+
style: submitStyle(submitting || !pasted.trim()),
|
|
2737
|
+
children: submitting ? "checking\u2026" : "sign me in \u2192"
|
|
2738
|
+
}
|
|
2739
|
+
),
|
|
2740
|
+
/* @__PURE__ */ jsx(
|
|
2741
|
+
"button",
|
|
2742
|
+
{
|
|
2743
|
+
type: "button",
|
|
2744
|
+
onClick: () => {
|
|
2745
|
+
setManual(null);
|
|
2746
|
+
setPasted("");
|
|
2747
|
+
setError(null);
|
|
2748
|
+
setCopied("idle");
|
|
2749
|
+
},
|
|
2750
|
+
style: linkButtonStyle,
|
|
2751
|
+
children: "use a different address"
|
|
2752
|
+
}
|
|
2753
|
+
)
|
|
2754
|
+
] });
|
|
2755
|
+
}
|
|
2756
|
+
const noBrowserWallet = wallets !== null && wallets.length === 0;
|
|
2570
2757
|
return /* @__PURE__ */ jsxs("form", { onSubmit: signIn, "data-oc-signin-wallet": "", children: [
|
|
2571
|
-
/* @__PURE__ */ jsx(FlowHeader, {
|
|
2572
|
-
/* @__PURE__ */ jsx(Label, { children: "bitcoin address" }),
|
|
2758
|
+
/* @__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." }),
|
|
2759
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.address, children: "bitcoin address" }),
|
|
2573
2760
|
/* @__PURE__ */ jsx(
|
|
2574
2761
|
"input",
|
|
2575
2762
|
{
|
|
2763
|
+
id: ids.address,
|
|
2576
2764
|
type: "text",
|
|
2577
2765
|
value: address,
|
|
2578
2766
|
onChange: (e) => {
|
|
@@ -2588,6 +2776,19 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
|
|
|
2588
2776
|
style: inputStyle2
|
|
2589
2777
|
}
|
|
2590
2778
|
),
|
|
2779
|
+
wallets && wallets.length > 1 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
|
|
2780
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: ids.wallet, children: "sign with" }),
|
|
2781
|
+
/* @__PURE__ */ jsx(
|
|
2782
|
+
"select",
|
|
2783
|
+
{
|
|
2784
|
+
id: ids.wallet,
|
|
2785
|
+
value: walletId ?? "",
|
|
2786
|
+
onChange: (e) => setWalletId(e.target.value),
|
|
2787
|
+
style: inputStyle2,
|
|
2788
|
+
children: wallets.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.name }, w.id))
|
|
2789
|
+
}
|
|
2790
|
+
)
|
|
2791
|
+
] }),
|
|
2591
2792
|
error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
|
|
2592
2793
|
/* @__PURE__ */ jsx(
|
|
2593
2794
|
"button",
|
|
@@ -2595,54 +2796,71 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
|
|
|
2595
2796
|
type: "submit",
|
|
2596
2797
|
disabled: submitting || !address.trim(),
|
|
2597
2798
|
style: submitStyle(submitting || !address.trim()),
|
|
2598
|
-
children:
|
|
2799
|
+
children: submitting ? "waiting for wallet\u2026" : noBrowserWallet ? "get the message to sign \u2192" : "sign in with wallet \u2192"
|
|
2599
2800
|
}
|
|
2600
2801
|
),
|
|
2601
|
-
/* @__PURE__ */ jsx(Hint, { children: "
|
|
2802
|
+
noBrowserWallet && /* @__PURE__ */ jsx(Hint, { children: "No browser wallet found here. You can sign with any wallet and paste." })
|
|
2602
2803
|
] });
|
|
2603
2804
|
}
|
|
2604
2805
|
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);
|
|
2806
|
+
const uid = React4.useId();
|
|
2807
|
+
const [stage, setStage] = React4.useState("enter");
|
|
2808
|
+
const [email, setEmail] = React4.useState("");
|
|
2809
|
+
const [emailError, setEmailError] = React4.useState(null);
|
|
2810
|
+
const [code, setCode] = React4.useState("");
|
|
2811
|
+
const [codeError, setCodeError] = React4.useState(null);
|
|
2812
|
+
const [token, setToken] = React4.useState(null);
|
|
2813
|
+
const [submitting, setSubmitting] = React4.useState(false);
|
|
2814
|
+
const [resent, setResent] = React4.useState(false);
|
|
2815
|
+
async function sendCode(address) {
|
|
2621
2816
|
try {
|
|
2622
2817
|
const res = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
|
|
2623
2818
|
method: "POST",
|
|
2624
2819
|
credentials: "include",
|
|
2625
2820
|
headers: { "Content-Type": "application/json" },
|
|
2626
|
-
body: JSON.stringify({ email:
|
|
2821
|
+
body: JSON.stringify({ email: address })
|
|
2627
2822
|
});
|
|
2628
2823
|
const json = await res.json();
|
|
2629
|
-
if (!res.ok || !json.token)
|
|
2630
|
-
throw new Error(json.reason ?? `start failed (${res.status})`);
|
|
2631
|
-
}
|
|
2824
|
+
if (!res.ok || !json.token) return humanSigninError(json.reason ?? "server_error", "email");
|
|
2632
2825
|
setToken(json.token);
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2826
|
+
return null;
|
|
2827
|
+
} catch {
|
|
2828
|
+
return NETWORK_ERROR;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
async function start(e) {
|
|
2832
|
+
e.preventDefault();
|
|
2833
|
+
const trimmed = email.trim().toLowerCase();
|
|
2834
|
+
if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
|
|
2835
|
+
setEmailError(humanSigninError("invalid_email", "email"));
|
|
2836
|
+
return;
|
|
2639
2837
|
}
|
|
2838
|
+
setEmailError(null);
|
|
2839
|
+
setSubmitting(true);
|
|
2840
|
+
const err = await sendCode(trimmed);
|
|
2841
|
+
setSubmitting(false);
|
|
2842
|
+
if (err) {
|
|
2843
|
+
setEmailError(err);
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
setEmail(trimmed);
|
|
2847
|
+
setStage("code");
|
|
2848
|
+
}
|
|
2849
|
+
async function resend() {
|
|
2850
|
+
setCodeError(null);
|
|
2851
|
+
setResent(false);
|
|
2852
|
+
setSubmitting(true);
|
|
2853
|
+
const err = await sendCode(email);
|
|
2854
|
+
setSubmitting(false);
|
|
2855
|
+
setCode("");
|
|
2856
|
+
if (err) setCodeError(err);
|
|
2857
|
+
else setResent(true);
|
|
2640
2858
|
}
|
|
2641
2859
|
async function verify(e) {
|
|
2642
2860
|
e.preventDefault();
|
|
2643
2861
|
if (!token) return;
|
|
2644
2862
|
if (code.length !== 6) {
|
|
2645
|
-
setCodeError("6 digits");
|
|
2863
|
+
setCodeError("The code is 6 digits.");
|
|
2646
2864
|
return;
|
|
2647
2865
|
}
|
|
2648
2866
|
setCodeError(null);
|
|
@@ -2663,23 +2881,24 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
|
2663
2881
|
const json = await res.json();
|
|
2664
2882
|
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
2665
2883
|
throw new Error(
|
|
2666
|
-
"reason" in json && json.reason || "error" in json && json.error ||
|
|
2884
|
+
"reason" in json && json.reason || "error" in json && json.error || "server_error"
|
|
2667
2885
|
);
|
|
2668
2886
|
}
|
|
2669
2887
|
onSuccess(json.account, json.token);
|
|
2670
2888
|
} catch (err) {
|
|
2671
|
-
setCodeError(err
|
|
2889
|
+
setCodeError(failureMessage(err, "email", "submit"));
|
|
2672
2890
|
} finally {
|
|
2673
2891
|
setSubmitting(false);
|
|
2674
2892
|
}
|
|
2675
2893
|
}
|
|
2676
2894
|
if (stage === "enter") {
|
|
2677
2895
|
return /* @__PURE__ */ jsxs("form", { onSubmit: start, "data-oc-signin-email": "", children: [
|
|
2678
|
-
/* @__PURE__ */ jsx(FlowHeader, {
|
|
2679
|
-
/* @__PURE__ */ jsx(Label, { children: "email" }),
|
|
2896
|
+
/* @__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." }),
|
|
2897
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-email`, children: "email" }),
|
|
2680
2898
|
/* @__PURE__ */ jsx(
|
|
2681
2899
|
"input",
|
|
2682
2900
|
{
|
|
2901
|
+
id: `${uid}-email`,
|
|
2683
2902
|
type: "email",
|
|
2684
2903
|
autoComplete: "email",
|
|
2685
2904
|
value: email,
|
|
@@ -2694,20 +2913,20 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
|
2694
2913
|
}
|
|
2695
2914
|
),
|
|
2696
2915
|
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." })
|
|
2916
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" })
|
|
2699
2917
|
] });
|
|
2700
2918
|
}
|
|
2701
2919
|
return /* @__PURE__ */ jsxs("form", { onSubmit: verify, "data-oc-signin-email-verify": "", children: [
|
|
2702
|
-
/* @__PURE__ */ jsxs(FlowHeader, {
|
|
2703
|
-
"Sent a 6-digit code to ",
|
|
2920
|
+
/* @__PURE__ */ jsxs(FlowHeader, { children: [
|
|
2921
|
+
resent ? "Sent a new code to " : "Sent a 6-digit code to ",
|
|
2704
2922
|
/* @__PURE__ */ jsx("span", { style: { color: "var(--foreground, #fafafa)" }, children: email }),
|
|
2705
|
-
".
|
|
2923
|
+
". It expires in 10 minutes."
|
|
2706
2924
|
] }),
|
|
2707
|
-
/* @__PURE__ */ jsx(Label, { children: "one-time code" }),
|
|
2925
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-code`, children: "one-time code" }),
|
|
2708
2926
|
/* @__PURE__ */ jsx(
|
|
2709
2927
|
"input",
|
|
2710
2928
|
{
|
|
2929
|
+
id: `${uid}-code`,
|
|
2711
2930
|
type: "text",
|
|
2712
2931
|
inputMode: "numeric",
|
|
2713
2932
|
pattern: "\\d{6}",
|
|
@@ -2735,31 +2954,32 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
|
|
|
2735
2954
|
children: submitting ? "verifying\u2026" : "verify \xB7 sign me in \u2192"
|
|
2736
2955
|
}
|
|
2737
2956
|
),
|
|
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
|
-
|
|
2957
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2958
|
+
/* @__PURE__ */ jsx(
|
|
2959
|
+
"button",
|
|
2960
|
+
{
|
|
2961
|
+
type: "button",
|
|
2962
|
+
onClick: () => void resend(),
|
|
2963
|
+
disabled: submitting,
|
|
2964
|
+
style: linkButtonStyle,
|
|
2965
|
+
children: "send a new code"
|
|
2966
|
+
}
|
|
2967
|
+
),
|
|
2968
|
+
/* @__PURE__ */ jsx(
|
|
2969
|
+
"button",
|
|
2970
|
+
{
|
|
2971
|
+
type: "button",
|
|
2972
|
+
onClick: () => {
|
|
2973
|
+
setStage("enter");
|
|
2974
|
+
setCode("");
|
|
2975
|
+
setToken(null);
|
|
2976
|
+
setResent(false);
|
|
2977
|
+
},
|
|
2978
|
+
style: linkButtonStyle,
|
|
2979
|
+
children: "use a different email"
|
|
2980
|
+
}
|
|
2981
|
+
)
|
|
2982
|
+
] })
|
|
2763
2983
|
] });
|
|
2764
2984
|
}
|
|
2765
2985
|
function FlowHeader({
|
|
@@ -2767,15 +2987,15 @@ function FlowHeader({
|
|
|
2767
2987
|
children
|
|
2768
2988
|
}) {
|
|
2769
2989
|
return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
|
|
2770
|
-
/* @__PURE__ */ jsx(
|
|
2990
|
+
label && /* @__PURE__ */ jsx(
|
|
2771
2991
|
"div",
|
|
2772
2992
|
{
|
|
2773
2993
|
style: {
|
|
2774
2994
|
color: "var(--primary, #f97316)",
|
|
2775
|
-
fontFamily:
|
|
2776
|
-
fontSize: 10,
|
|
2777
|
-
letterSpacing: "0.18em",
|
|
2778
|
-
textTransform:
|
|
2995
|
+
fontFamily: SIGNIN_FONT,
|
|
2996
|
+
fontSize: signinSize(10),
|
|
2997
|
+
letterSpacing: signinTracking("0.18em"),
|
|
2998
|
+
textTransform: SIGNIN_CASE,
|
|
2779
2999
|
marginBottom: 6
|
|
2780
3000
|
},
|
|
2781
3001
|
children: label
|
|
@@ -2795,17 +3015,37 @@ function FlowHeader({
|
|
|
2795
3015
|
)
|
|
2796
3016
|
] });
|
|
2797
3017
|
}
|
|
2798
|
-
|
|
3018
|
+
var linkButtonStyle = {
|
|
3019
|
+
display: "inline-flex",
|
|
3020
|
+
alignItems: "center",
|
|
3021
|
+
minHeight: 44,
|
|
3022
|
+
marginTop: 4,
|
|
3023
|
+
marginRight: 16,
|
|
3024
|
+
background: "transparent",
|
|
3025
|
+
border: "none",
|
|
3026
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
3027
|
+
fontFamily: SIGNIN_FONT,
|
|
3028
|
+
fontSize: signinSize(11),
|
|
3029
|
+
letterSpacing: signinTracking("0.12em"),
|
|
3030
|
+
textTransform: SIGNIN_CASE,
|
|
3031
|
+
cursor: "pointer",
|
|
3032
|
+
padding: 0
|
|
3033
|
+
};
|
|
3034
|
+
function Label({
|
|
3035
|
+
htmlFor,
|
|
3036
|
+
children
|
|
3037
|
+
}) {
|
|
2799
3038
|
return /* @__PURE__ */ jsx(
|
|
2800
3039
|
"label",
|
|
2801
3040
|
{
|
|
3041
|
+
htmlFor,
|
|
2802
3042
|
style: {
|
|
2803
3043
|
display: "block",
|
|
2804
3044
|
color: "var(--foreground, #fafafa)",
|
|
2805
|
-
fontFamily:
|
|
2806
|
-
fontSize: 11,
|
|
2807
|
-
letterSpacing: "0.12em",
|
|
2808
|
-
textTransform:
|
|
3045
|
+
fontFamily: SIGNIN_FONT,
|
|
3046
|
+
fontSize: signinSize(11),
|
|
3047
|
+
letterSpacing: signinTracking("0.12em"),
|
|
3048
|
+
textTransform: SIGNIN_CASE,
|
|
2809
3049
|
marginBottom: 6
|
|
2810
3050
|
},
|
|
2811
3051
|
children
|
|
@@ -2818,8 +3058,8 @@ var inputStyle2 = {
|
|
|
2818
3058
|
background: "var(--background, #0a0a0a)",
|
|
2819
3059
|
color: "var(--foreground, #fafafa)",
|
|
2820
3060
|
border: "1px solid var(--input, #27272a)",
|
|
2821
|
-
borderRadius: 6,
|
|
2822
|
-
fontFamily:
|
|
3061
|
+
borderRadius: signinRadius(6),
|
|
3062
|
+
fontFamily: SIGNIN_FONT,
|
|
2823
3063
|
// 16px — never below it. A sub-16px font-size makes iOS Safari
|
|
2824
3064
|
// auto-zoom the viewport on focus; the family rule is >=16px on
|
|
2825
3065
|
// every form field. Desktop reads fine at 16 too.
|
|
@@ -2828,14 +3068,16 @@ var inputStyle2 = {
|
|
|
2828
3068
|
};
|
|
2829
3069
|
var linkAlsoStyle = {
|
|
2830
3070
|
display: "flex",
|
|
2831
|
-
alignItems: "
|
|
3071
|
+
alignItems: "center",
|
|
3072
|
+
// The whole row is the checkbox's hit area; the 13px box alone was not.
|
|
3073
|
+
minHeight: 44,
|
|
2832
3074
|
gap: 8,
|
|
2833
3075
|
marginTop: 16,
|
|
2834
3076
|
paddingTop: 14,
|
|
2835
3077
|
borderTop: "1px solid var(--border, #27272a)",
|
|
2836
3078
|
color: "var(--muted-foreground, #a1a1aa)",
|
|
2837
|
-
fontFamily:
|
|
2838
|
-
fontSize: 12,
|
|
3079
|
+
fontFamily: SIGNIN_FONT,
|
|
3080
|
+
fontSize: signinSize(12),
|
|
2839
3081
|
lineHeight: 1.5,
|
|
2840
3082
|
cursor: "pointer"
|
|
2841
3083
|
};
|
|
@@ -2846,12 +3088,12 @@ function submitStyle(disabled) {
|
|
|
2846
3088
|
background: "var(--primary, #f97316)",
|
|
2847
3089
|
color: "var(--primary-foreground, #0a0a0a)",
|
|
2848
3090
|
border: "none",
|
|
2849
|
-
borderRadius:
|
|
2850
|
-
fontFamily:
|
|
2851
|
-
fontSize: 11,
|
|
2852
|
-
letterSpacing: "0.14em",
|
|
2853
|
-
textTransform:
|
|
2854
|
-
fontWeight:
|
|
3091
|
+
borderRadius: SIGNIN_BUTTON_RADIUS,
|
|
3092
|
+
fontFamily: SIGNIN_FONT,
|
|
3093
|
+
fontSize: signinSize(11),
|
|
3094
|
+
letterSpacing: signinTracking("0.14em"),
|
|
3095
|
+
textTransform: SIGNIN_CASE,
|
|
3096
|
+
fontWeight: SIGNIN_STRONG,
|
|
2855
3097
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
2856
3098
|
opacity: disabled ? 0.5 : 1,
|
|
2857
3099
|
width: "100%"
|
|
@@ -2866,8 +3108,8 @@ function ErrorLine2({ children }) {
|
|
|
2866
3108
|
marginTop: 6,
|
|
2867
3109
|
marginBottom: 0,
|
|
2868
3110
|
color: "var(--destructive, #ef4444)",
|
|
2869
|
-
fontFamily:
|
|
2870
|
-
fontSize: 11,
|
|
3111
|
+
fontFamily: SIGNIN_FONT,
|
|
3112
|
+
fontSize: signinSize(11),
|
|
2871
3113
|
letterSpacing: "0.04em"
|
|
2872
3114
|
},
|
|
2873
3115
|
children
|
|
@@ -2882,10 +3124,9 @@ function Hint({ children }) {
|
|
|
2882
3124
|
marginTop: 12,
|
|
2883
3125
|
marginBottom: 0,
|
|
2884
3126
|
color: "var(--muted-foreground, #a1a1aa)",
|
|
2885
|
-
fontFamily:
|
|
3127
|
+
fontFamily: SIGNIN_FONT,
|
|
2886
3128
|
fontSize: 10.5,
|
|
2887
|
-
lineHeight: 1.55
|
|
2888
|
-
opacity: 0.7
|
|
3129
|
+
lineHeight: 1.55
|
|
2889
3130
|
},
|
|
2890
3131
|
children: [
|
|
2891
3132
|
"> ",
|
|
@@ -2904,13 +3145,13 @@ function isAbortError(err) {
|
|
|
2904
3145
|
}
|
|
2905
3146
|
function useWebAuthnRegister(opts) {
|
|
2906
3147
|
const authOrigin = resolveAuthOrigin(opts);
|
|
2907
|
-
const [status, setStatus] =
|
|
2908
|
-
const [error, setError] =
|
|
2909
|
-
const reset =
|
|
3148
|
+
const [status, setStatus] = React4.useState("idle");
|
|
3149
|
+
const [error, setError] = React4.useState(null);
|
|
3150
|
+
const reset = React4.useCallback(() => {
|
|
2910
3151
|
setStatus("idle");
|
|
2911
3152
|
setError(null);
|
|
2912
3153
|
}, []);
|
|
2913
|
-
const register =
|
|
3154
|
+
const register = React4.useCallback(
|
|
2914
3155
|
async (args) => {
|
|
2915
3156
|
setError(null);
|
|
2916
3157
|
setStatus("requesting-options");
|
|
@@ -2972,10 +3213,10 @@ function useWebAuthnRegister(opts) {
|
|
|
2972
3213
|
function useWebAuthnList(opts) {
|
|
2973
3214
|
const authOrigin = resolveAuthOrigin(opts);
|
|
2974
3215
|
const { status: sessionStatus } = useOcSession();
|
|
2975
|
-
const [credentials, setCredentials] =
|
|
2976
|
-
const [status, setStatus] =
|
|
2977
|
-
const [error, setError] =
|
|
2978
|
-
const refetch =
|
|
3216
|
+
const [credentials, setCredentials] = React4.useState([]);
|
|
3217
|
+
const [status, setStatus] = React4.useState("loading");
|
|
3218
|
+
const [error, setError] = React4.useState(null);
|
|
3219
|
+
const refetch = React4.useCallback(async () => {
|
|
2979
3220
|
setError(null);
|
|
2980
3221
|
if (sessionStatus !== "authenticated") {
|
|
2981
3222
|
setCredentials([]);
|
|
@@ -3003,10 +3244,10 @@ function useWebAuthnList(opts) {
|
|
|
3003
3244
|
setStatus("error");
|
|
3004
3245
|
}
|
|
3005
3246
|
}, [authOrigin, sessionStatus]);
|
|
3006
|
-
|
|
3247
|
+
React4.useEffect(() => {
|
|
3007
3248
|
void refetch();
|
|
3008
3249
|
}, [refetch]);
|
|
3009
|
-
const rename =
|
|
3250
|
+
const rename = React4.useCallback(
|
|
3010
3251
|
async (id, label) => {
|
|
3011
3252
|
try {
|
|
3012
3253
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -3031,7 +3272,7 @@ function useWebAuthnList(opts) {
|
|
|
3031
3272
|
},
|
|
3032
3273
|
[authOrigin, refetch]
|
|
3033
3274
|
);
|
|
3034
|
-
const remove =
|
|
3275
|
+
const remove = React4.useCallback(
|
|
3035
3276
|
async (id) => {
|
|
3036
3277
|
try {
|
|
3037
3278
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -3059,13 +3300,13 @@ function useWebAuthnList(opts) {
|
|
|
3059
3300
|
function useStepUpAuth(opts) {
|
|
3060
3301
|
const authOrigin = resolveAuthOrigin(opts);
|
|
3061
3302
|
const { refresh } = useOcSession();
|
|
3062
|
-
const [status, setStatus] =
|
|
3063
|
-
const [error, setError] =
|
|
3064
|
-
const reset =
|
|
3303
|
+
const [status, setStatus] = React4.useState("idle");
|
|
3304
|
+
const [error, setError] = React4.useState(null);
|
|
3305
|
+
const reset = React4.useCallback(() => {
|
|
3065
3306
|
setStatus("idle");
|
|
3066
3307
|
setError(null);
|
|
3067
3308
|
}, []);
|
|
3068
|
-
const stepUp =
|
|
3309
|
+
const stepUp = React4.useCallback(
|
|
3069
3310
|
async (args) => {
|
|
3070
3311
|
setError(null);
|
|
3071
3312
|
setStatus("requesting-options");
|