@orangecheck/auth-client 2.5.1 → 2.6.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 +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +902 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +902 -72
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
1
|
+
import * as React3 from 'react';
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
|
|
4
4
|
|
|
5
5
|
// src/provider.tsx
|
|
@@ -21,7 +21,7 @@ function buildSignInUrl(cfg, returnTo) {
|
|
|
21
21
|
u.searchParams.set("return_to", returnTo);
|
|
22
22
|
return u.toString();
|
|
23
23
|
}
|
|
24
|
-
var SessionContext =
|
|
24
|
+
var SessionContext = React3.createContext(null);
|
|
25
25
|
function normalizeAccount(raw) {
|
|
26
26
|
if (!raw) return null;
|
|
27
27
|
const didOc = raw.did_oc ?? raw.didOc;
|
|
@@ -44,11 +44,11 @@ function OcSessionProvider({
|
|
|
44
44
|
config,
|
|
45
45
|
defaultReturnTo
|
|
46
46
|
}) {
|
|
47
|
-
const cfg =
|
|
48
|
-
const [account, setAccount] =
|
|
49
|
-
const [status, setStatus] =
|
|
50
|
-
const [error, setError] =
|
|
51
|
-
const refresh =
|
|
47
|
+
const cfg = React3.useMemo(() => resolveConfig(config), [config]);
|
|
48
|
+
const [account, setAccount] = React3.useState(null);
|
|
49
|
+
const [status, setStatus] = React3.useState("loading");
|
|
50
|
+
const [error, setError] = React3.useState(null);
|
|
51
|
+
const refresh = React3.useCallback(async () => {
|
|
52
52
|
if (typeof window === "undefined") return;
|
|
53
53
|
try {
|
|
54
54
|
const res = await fetch(cfg.mePath, {
|
|
@@ -77,10 +77,10 @@ function OcSessionProvider({
|
|
|
77
77
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
78
78
|
}
|
|
79
79
|
}, [cfg.mePath]);
|
|
80
|
-
|
|
80
|
+
React3.useEffect(() => {
|
|
81
81
|
void refresh();
|
|
82
82
|
}, [refresh]);
|
|
83
|
-
const signOut =
|
|
83
|
+
const signOut = React3.useCallback(async () => {
|
|
84
84
|
try {
|
|
85
85
|
await fetch(`${cfg.authOrigin}${cfg.logoutPath}`, {
|
|
86
86
|
method: "POST",
|
|
@@ -91,7 +91,7 @@ function OcSessionProvider({
|
|
|
91
91
|
setAccount(null);
|
|
92
92
|
setStatus("anonymous");
|
|
93
93
|
}, [cfg.authOrigin, cfg.logoutPath]);
|
|
94
|
-
const value =
|
|
94
|
+
const value = React3.useMemo(() => {
|
|
95
95
|
const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
96
96
|
return {
|
|
97
97
|
status,
|
|
@@ -105,7 +105,7 @@ function OcSessionProvider({
|
|
|
105
105
|
return /* @__PURE__ */ jsx(SessionContext.Provider, { value, children });
|
|
106
106
|
}
|
|
107
107
|
function useOcSession() {
|
|
108
|
-
const ctx =
|
|
108
|
+
const ctx = React3.useContext(SessionContext);
|
|
109
109
|
if (!ctx) {
|
|
110
110
|
throw new Error(
|
|
111
111
|
"[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
|
|
@@ -114,7 +114,7 @@ function useOcSession() {
|
|
|
114
114
|
return ctx;
|
|
115
115
|
}
|
|
116
116
|
function useOptionalOcSession() {
|
|
117
|
-
return
|
|
117
|
+
return React3.useContext(SessionContext);
|
|
118
118
|
}
|
|
119
119
|
function shortenAddress(addr) {
|
|
120
120
|
if (addr.length <= 12) return addr;
|
|
@@ -129,7 +129,7 @@ function isPrefixOf(value, target) {
|
|
|
129
129
|
}
|
|
130
130
|
var listboxIdCounter = 0;
|
|
131
131
|
function useUniqueId(prefix) {
|
|
132
|
-
const [id] =
|
|
132
|
+
const [id] = React3.useState(() => `${prefix}-${++listboxIdCounter}`);
|
|
133
133
|
return id;
|
|
134
134
|
}
|
|
135
135
|
function OcAccountChip({
|
|
@@ -143,9 +143,9 @@ function OcAccountChip({
|
|
|
143
143
|
}) {
|
|
144
144
|
const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
|
|
145
145
|
const signInUrl = signInUrlOverride ?? sessionSignInUrl;
|
|
146
|
-
const [open, setOpen] =
|
|
147
|
-
const wrapRef =
|
|
148
|
-
|
|
146
|
+
const [open, setOpen] = React3.useState(false);
|
|
147
|
+
const wrapRef = React3.useRef(null);
|
|
148
|
+
React3.useEffect(() => {
|
|
149
149
|
if (!open) return;
|
|
150
150
|
function handleClickOutside(e) {
|
|
151
151
|
if (wrapRef.current && !wrapRef.current.contains(e.target)) {
|
|
@@ -468,9 +468,9 @@ function useOcAddressSuggestion(options) {
|
|
|
468
468
|
} = options;
|
|
469
469
|
const { status, account } = useOcSession();
|
|
470
470
|
const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
|
|
471
|
-
const [open, setOpen] =
|
|
472
|
-
const [highlighted, setHighlighted] =
|
|
473
|
-
const blurTimer =
|
|
471
|
+
const [open, setOpen] = React3.useState(false);
|
|
472
|
+
const [highlighted, setHighlighted] = React3.useState(false);
|
|
473
|
+
const blurTimer = React3.useRef(null);
|
|
474
474
|
const listboxId = useUniqueId("oc-addr-listbox");
|
|
475
475
|
const optionId = `${listboxId}-opt`;
|
|
476
476
|
const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
|
|
@@ -482,7 +482,7 @@ function useOcAddressSuggestion(options) {
|
|
|
482
482
|
setOpen(false);
|
|
483
483
|
setHighlighted(false);
|
|
484
484
|
}
|
|
485
|
-
|
|
485
|
+
React3.useEffect(() => {
|
|
486
486
|
return () => {
|
|
487
487
|
if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
|
|
488
488
|
};
|
|
@@ -585,7 +585,7 @@ function useOcAddressSuggestion(options) {
|
|
|
585
585
|
) : null;
|
|
586
586
|
return { inputProps, popover };
|
|
587
587
|
}
|
|
588
|
-
var OcAddressInput =
|
|
588
|
+
var OcAddressInput = React3.forwardRef(
|
|
589
589
|
function OcAddressInput2({
|
|
590
590
|
value,
|
|
591
591
|
onValueChange,
|
|
@@ -600,15 +600,15 @@ var OcAddressInput = React4.forwardRef(
|
|
|
600
600
|
}, forwardedRef) {
|
|
601
601
|
const { status, account } = useOcSession();
|
|
602
602
|
const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
|
|
603
|
-
const [open, setOpen] =
|
|
604
|
-
const [highlighted, setHighlighted] =
|
|
605
|
-
const innerRef =
|
|
603
|
+
const [open, setOpen] = React3.useState(false);
|
|
604
|
+
const [highlighted, setHighlighted] = React3.useState(false);
|
|
605
|
+
const innerRef = React3.useRef(null);
|
|
606
606
|
const setRef = (node) => {
|
|
607
607
|
innerRef.current = node;
|
|
608
608
|
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
609
609
|
else if (forwardedRef) forwardedRef.current = node;
|
|
610
610
|
};
|
|
611
|
-
const blurTimer =
|
|
611
|
+
const blurTimer = React3.useRef(null);
|
|
612
612
|
const listboxId = useUniqueId("oc-addr-listbox");
|
|
613
613
|
const optionId = `${listboxId}-opt`;
|
|
614
614
|
const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
|
|
@@ -655,7 +655,7 @@ var OcAddressInput = React4.forwardRef(
|
|
|
655
655
|
}
|
|
656
656
|
onKeyDown?.(e);
|
|
657
657
|
}
|
|
658
|
-
|
|
658
|
+
React3.useEffect(() => {
|
|
659
659
|
return () => {
|
|
660
660
|
if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
|
|
661
661
|
};
|
|
@@ -748,6 +748,797 @@ var OcAddressInput = React4.forwardRef(
|
|
|
748
748
|
);
|
|
749
749
|
}
|
|
750
750
|
);
|
|
751
|
+
var DEFAULT_AUTH_ORIGIN = "https://ochk.io";
|
|
752
|
+
function OcLinkedIdentities({
|
|
753
|
+
authOrigin = DEFAULT_AUTH_ORIGIN,
|
|
754
|
+
className,
|
|
755
|
+
onChange
|
|
756
|
+
}) {
|
|
757
|
+
const [resp, setResp] = React3.useState(null);
|
|
758
|
+
const [error, setError] = React3.useState(null);
|
|
759
|
+
const [showEmailLink, setShowEmailLink] = React3.useState(false);
|
|
760
|
+
const [showBtcLink, setShowBtcLink] = React3.useState(false);
|
|
761
|
+
const refresh = React3.useCallback(async () => {
|
|
762
|
+
setError(null);
|
|
763
|
+
try {
|
|
764
|
+
const r = await fetch(`${authOrigin}/api/auth/identities`, {
|
|
765
|
+
credentials: "include"
|
|
766
|
+
});
|
|
767
|
+
if (!r.ok) {
|
|
768
|
+
if (r.status === 401) {
|
|
769
|
+
setError("not signed in");
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
throw new Error(`http_${r.status}`);
|
|
773
|
+
}
|
|
774
|
+
setResp(await r.json());
|
|
775
|
+
} catch (e) {
|
|
776
|
+
setError(e instanceof Error ? e.message : "load failed");
|
|
777
|
+
}
|
|
778
|
+
}, [authOrigin]);
|
|
779
|
+
React3.useEffect(() => {
|
|
780
|
+
void refresh();
|
|
781
|
+
}, [refresh]);
|
|
782
|
+
const afterChange = React3.useCallback(() => {
|
|
783
|
+
setShowEmailLink(false);
|
|
784
|
+
setShowBtcLink(false);
|
|
785
|
+
void refresh();
|
|
786
|
+
onChange?.();
|
|
787
|
+
}, [refresh, onChange]);
|
|
788
|
+
if (error) {
|
|
789
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-oc-li": "", style: panelStyle("warn"), role: "alert", children: [
|
|
790
|
+
/* @__PURE__ */ jsx(SectionLabel, { tone: "warn", children: "\xA7 identities \xB7 load deferred" }),
|
|
791
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
792
|
+
"Couldn't reach the auth host (",
|
|
793
|
+
error,
|
|
794
|
+
"). Linking is temporarily unavailable; your signed-in identity still works as normal."
|
|
795
|
+
] }),
|
|
796
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void refresh(), style: ghostBtnStyle(false), children: "retry" })
|
|
797
|
+
] });
|
|
798
|
+
}
|
|
799
|
+
if (!resp) {
|
|
800
|
+
return /* @__PURE__ */ jsx("div", { className, "data-oc-li": "", style: panelStyle(), children: /* @__PURE__ */ jsxs("span", { style: hintStyle, children: [
|
|
801
|
+
"> ",
|
|
802
|
+
"loading\u2026"
|
|
803
|
+
] }) });
|
|
804
|
+
}
|
|
805
|
+
const linked = resp.linked ?? [];
|
|
806
|
+
const emailRows = linked.filter((i) => i.kind === "email");
|
|
807
|
+
const btcRows = linked.filter((i) => i.kind === "btc");
|
|
808
|
+
const hasEmail = emailRows.some((i) => i.value);
|
|
809
|
+
const hasBtc = btcRows.some((i) => i.value);
|
|
810
|
+
const mergedFrom = resp.merged_from ?? [];
|
|
811
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-oc-li": "", style: { display: "grid", gap: 16 }, children: [
|
|
812
|
+
/* @__PURE__ */ jsxs("div", { style: panelStyle(), children: [
|
|
813
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 what this is" }),
|
|
814
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
815
|
+
"Your public identity is the opaque ",
|
|
816
|
+
/* @__PURE__ */ jsx("code", { style: codeStyle, children: "did:oc:" }),
|
|
817
|
+
" 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."
|
|
818
|
+
] })
|
|
819
|
+
] }),
|
|
820
|
+
mergedFrom.length > 0 && /* @__PURE__ */ jsx(ConsolidationPanel, { entries: mergedFrom }),
|
|
821
|
+
/* @__PURE__ */ jsxs("div", { style: panelStyle("plain"), "data-oc-li-rows": "", children: [
|
|
822
|
+
/* @__PURE__ */ jsx(
|
|
823
|
+
IdentityGroup,
|
|
824
|
+
{
|
|
825
|
+
kind: "email",
|
|
826
|
+
title: "email",
|
|
827
|
+
rows: emailRows,
|
|
828
|
+
authOrigin,
|
|
829
|
+
onChange: afterChange,
|
|
830
|
+
addCta: hasEmail ? null : "link an email",
|
|
831
|
+
onAdd: () => {
|
|
832
|
+
setShowEmailLink((v) => !v);
|
|
833
|
+
setShowBtcLink(false);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
),
|
|
837
|
+
/* @__PURE__ */ jsx("div", { style: { borderTop: `1px solid ${V.border}` } }),
|
|
838
|
+
/* @__PURE__ */ jsx(
|
|
839
|
+
IdentityGroup,
|
|
840
|
+
{
|
|
841
|
+
kind: "btc",
|
|
842
|
+
title: "bitcoin address",
|
|
843
|
+
rows: btcRows,
|
|
844
|
+
authOrigin,
|
|
845
|
+
onChange: afterChange,
|
|
846
|
+
addCta: hasBtc ? null : "link a Bitcoin address",
|
|
847
|
+
onAdd: () => {
|
|
848
|
+
setShowBtcLink((v) => !v);
|
|
849
|
+
setShowEmailLink(false);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
)
|
|
853
|
+
] }),
|
|
854
|
+
showEmailLink && /* @__PURE__ */ jsx(
|
|
855
|
+
EmailLinkForm,
|
|
856
|
+
{
|
|
857
|
+
authOrigin,
|
|
858
|
+
onDone: afterChange,
|
|
859
|
+
onCancel: () => setShowEmailLink(false)
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
showBtcLink && /* @__PURE__ */ jsx(
|
|
863
|
+
BtcLinkForm,
|
|
864
|
+
{
|
|
865
|
+
authOrigin,
|
|
866
|
+
didOc: resp.did_oc ?? null,
|
|
867
|
+
onDone: afterChange,
|
|
868
|
+
onCancel: () => setShowBtcLink(false)
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
] });
|
|
872
|
+
}
|
|
873
|
+
function IdentityGroup({
|
|
874
|
+
kind,
|
|
875
|
+
title,
|
|
876
|
+
rows,
|
|
877
|
+
authOrigin,
|
|
878
|
+
onChange,
|
|
879
|
+
addCta,
|
|
880
|
+
onAdd
|
|
881
|
+
}) {
|
|
882
|
+
if (rows.length === 0) {
|
|
883
|
+
return /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
|
|
884
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
885
|
+
/* @__PURE__ */ jsxs("div", { style: rowLabelStyle, children: [
|
|
886
|
+
"\xA7 ",
|
|
887
|
+
title
|
|
888
|
+
] }),
|
|
889
|
+
/* @__PURE__ */ jsx("div", { style: { ...bodyStyle, marginTop: 4 }, children: "none linked" })
|
|
890
|
+
] }),
|
|
891
|
+
addCta && /* @__PURE__ */ jsxs("button", { type: "button", onClick: onAdd, style: primaryChipStyle, children: [
|
|
892
|
+
"+ ",
|
|
893
|
+
addCta
|
|
894
|
+
] })
|
|
895
|
+
] });
|
|
896
|
+
}
|
|
897
|
+
return /* @__PURE__ */ jsx(Fragment, { children: rows.map((r) => /* @__PURE__ */ jsx(
|
|
898
|
+
IdentityRow,
|
|
899
|
+
{
|
|
900
|
+
row: r,
|
|
901
|
+
kind,
|
|
902
|
+
authOrigin,
|
|
903
|
+
onChange
|
|
904
|
+
},
|
|
905
|
+
r.id
|
|
906
|
+
)) });
|
|
907
|
+
}
|
|
908
|
+
function IdentityRow({
|
|
909
|
+
row,
|
|
910
|
+
kind,
|
|
911
|
+
authOrigin,
|
|
912
|
+
onChange
|
|
913
|
+
}) {
|
|
914
|
+
const [shown, setShown] = React3.useState(false);
|
|
915
|
+
const [confirming, setConfirming] = React3.useState(false);
|
|
916
|
+
const [busy, setBusy] = React3.useState(false);
|
|
917
|
+
const [err, setErr] = React3.useState(null);
|
|
918
|
+
const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
|
|
919
|
+
const display = shown && row.value ? row.value : masked;
|
|
920
|
+
async function unlink() {
|
|
921
|
+
if (row.is_primary) return;
|
|
922
|
+
setBusy(true);
|
|
923
|
+
setErr(null);
|
|
924
|
+
try {
|
|
925
|
+
const r = await fetch(
|
|
926
|
+
`${authOrigin}/api/auth/identities?id=${encodeURIComponent(row.id)}`,
|
|
927
|
+
{ method: "DELETE", credentials: "include" }
|
|
928
|
+
);
|
|
929
|
+
if (!r.ok) {
|
|
930
|
+
const j = await r.json().catch(() => ({}));
|
|
931
|
+
throw new Error(j.reason ?? `http_${r.status}`);
|
|
932
|
+
}
|
|
933
|
+
onChange();
|
|
934
|
+
} catch (e) {
|
|
935
|
+
setErr(e instanceof Error ? e.message : "unlink failed");
|
|
936
|
+
setBusy(false);
|
|
937
|
+
setConfirming(false);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return /* @__PURE__ */ jsxs("div", { style: rowStyle, "data-oc-li-row": kind, children: [
|
|
941
|
+
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
|
|
942
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "4px 12px" }, children: [
|
|
943
|
+
/* @__PURE__ */ jsxs("span", { style: rowLabelStyle, children: [
|
|
944
|
+
"\xA7 ",
|
|
945
|
+
kind === "email" ? "email" : "bitcoin address"
|
|
946
|
+
] }),
|
|
947
|
+
row.is_primary && /* @__PURE__ */ jsx("span", { style: { ...rowLabelStyle, color: V.primary }, children: "primary \xB7 signup-time" })
|
|
948
|
+
] }),
|
|
949
|
+
/* @__PURE__ */ jsx(
|
|
950
|
+
"div",
|
|
951
|
+
{
|
|
952
|
+
style: {
|
|
953
|
+
marginTop: 4,
|
|
954
|
+
color: V.foreground,
|
|
955
|
+
fontFamily: MONO,
|
|
956
|
+
fontSize: 12,
|
|
957
|
+
wordBreak: "break-all"
|
|
958
|
+
},
|
|
959
|
+
children: display
|
|
960
|
+
}
|
|
961
|
+
),
|
|
962
|
+
row.verified_at && /* @__PURE__ */ jsxs("div", { style: { ...hintStyle, marginTop: 4 }, children: [
|
|
963
|
+
"verified \xB7 ",
|
|
964
|
+
new Date(row.verified_at).toUTCString()
|
|
965
|
+
] }),
|
|
966
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err })
|
|
967
|
+
] }),
|
|
968
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexShrink: 0, gap: 8, flexWrap: "wrap" }, children: [
|
|
969
|
+
row.value && /* @__PURE__ */ jsx(
|
|
970
|
+
"button",
|
|
971
|
+
{
|
|
972
|
+
type: "button",
|
|
973
|
+
onClick: () => setShown((s) => !s),
|
|
974
|
+
style: ghostBtnStyle(false),
|
|
975
|
+
children: shown ? "hide" : "show"
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
!row.is_primary && !confirming && /* @__PURE__ */ jsx(
|
|
979
|
+
"button",
|
|
980
|
+
{
|
|
981
|
+
type: "button",
|
|
982
|
+
onClick: () => setConfirming(true),
|
|
983
|
+
style: dangerBtnStyle(false),
|
|
984
|
+
children: "unlink"
|
|
985
|
+
}
|
|
986
|
+
),
|
|
987
|
+
!row.is_primary && confirming && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
988
|
+
/* @__PURE__ */ jsx(
|
|
989
|
+
"button",
|
|
990
|
+
{
|
|
991
|
+
type: "button",
|
|
992
|
+
onClick: () => void unlink(),
|
|
993
|
+
disabled: busy,
|
|
994
|
+
style: dangerBtnStyle(busy),
|
|
995
|
+
children: busy ? "unlinking\u2026" : "confirm unlink"
|
|
996
|
+
}
|
|
997
|
+
),
|
|
998
|
+
/* @__PURE__ */ jsx(
|
|
999
|
+
"button",
|
|
1000
|
+
{
|
|
1001
|
+
type: "button",
|
|
1002
|
+
onClick: () => setConfirming(false),
|
|
1003
|
+
disabled: busy,
|
|
1004
|
+
style: ghostBtnStyle(busy),
|
|
1005
|
+
children: "cancel"
|
|
1006
|
+
}
|
|
1007
|
+
)
|
|
1008
|
+
] })
|
|
1009
|
+
] })
|
|
1010
|
+
] });
|
|
1011
|
+
}
|
|
1012
|
+
function ConsolidationPanel({ entries }) {
|
|
1013
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("success"), "data-oc-li-consolidated": "", children: [
|
|
1014
|
+
/* @__PURE__ */ jsxs(SectionLabel, { tone: "success", children: [
|
|
1015
|
+
"\xA7 consolidated accounts \xB7 ",
|
|
1016
|
+
entries.length
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1019
|
+
entries.length === 1 ? "A previous OC account" : "Previous OC accounts",
|
|
1020
|
+
" you signed up with, folded into this one under dual proof of control. Historical activity is unioned into this account automatically;",
|
|
1021
|
+
" ",
|
|
1022
|
+
entries.length === 1 ? "it" : "they",
|
|
1023
|
+
" can no longer sign in."
|
|
1024
|
+
] }),
|
|
1025
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: "12px 0 0", padding: 0 }, children: entries.map((e) => /* @__PURE__ */ jsxs(
|
|
1026
|
+
"li",
|
|
1027
|
+
{
|
|
1028
|
+
style: {
|
|
1029
|
+
display: "flex",
|
|
1030
|
+
flexWrap: "wrap",
|
|
1031
|
+
gap: 8,
|
|
1032
|
+
padding: "8px 0",
|
|
1033
|
+
borderTop: `1px solid ${V.border}`,
|
|
1034
|
+
fontFamily: MONO,
|
|
1035
|
+
fontSize: 11
|
|
1036
|
+
},
|
|
1037
|
+
children: [
|
|
1038
|
+
/* @__PURE__ */ jsx("code", { style: { color: V.foreground, wordBreak: "break-all" }, children: e.did_oc }),
|
|
1039
|
+
/* @__PURE__ */ jsxs("span", { style: { ...hintStyle, marginLeft: "auto" }, children: [
|
|
1040
|
+
"created ",
|
|
1041
|
+
new Date(e.created_at).toLocaleDateString()
|
|
1042
|
+
] })
|
|
1043
|
+
]
|
|
1044
|
+
},
|
|
1045
|
+
e.did_oc
|
|
1046
|
+
)) })
|
|
1047
|
+
] });
|
|
1048
|
+
}
|
|
1049
|
+
function EmailLinkForm({
|
|
1050
|
+
authOrigin,
|
|
1051
|
+
onDone,
|
|
1052
|
+
onCancel
|
|
1053
|
+
}) {
|
|
1054
|
+
const [step, setStep] = React3.useState("email");
|
|
1055
|
+
const [email, setEmail] = React3.useState("");
|
|
1056
|
+
const [code, setCode] = React3.useState("");
|
|
1057
|
+
const [token, setToken] = React3.useState("");
|
|
1058
|
+
const [busy, setBusy] = React3.useState(false);
|
|
1059
|
+
const [err, setErr] = React3.useState(null);
|
|
1060
|
+
const [transfer, setTransfer] = React3.useState(false);
|
|
1061
|
+
async function start() {
|
|
1062
|
+
setBusy(true);
|
|
1063
|
+
setErr(null);
|
|
1064
|
+
try {
|
|
1065
|
+
const r = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
|
|
1066
|
+
method: "POST",
|
|
1067
|
+
credentials: "include",
|
|
1068
|
+
headers: { "Content-Type": "application/json" },
|
|
1069
|
+
body: JSON.stringify({ email: email.trim().toLowerCase() })
|
|
1070
|
+
});
|
|
1071
|
+
const j = await r.json();
|
|
1072
|
+
if (!r.ok || !j.token) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1073
|
+
setToken(j.token);
|
|
1074
|
+
setStep("code");
|
|
1075
|
+
} catch (e) {
|
|
1076
|
+
setErr(e instanceof Error ? e.message : "could not send code");
|
|
1077
|
+
} finally {
|
|
1078
|
+
setBusy(false);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async function verify(confirmTransfer) {
|
|
1082
|
+
setBusy(true);
|
|
1083
|
+
setErr(null);
|
|
1084
|
+
try {
|
|
1085
|
+
const r = await fetch(`${authOrigin}/api/auth/link/email`, {
|
|
1086
|
+
method: "POST",
|
|
1087
|
+
credentials: "include",
|
|
1088
|
+
headers: { "Content-Type": "application/json" },
|
|
1089
|
+
body: JSON.stringify({
|
|
1090
|
+
email: email.trim().toLowerCase(),
|
|
1091
|
+
code: code.trim(),
|
|
1092
|
+
token,
|
|
1093
|
+
...confirmTransfer ? { confirm_transfer: true } : {}
|
|
1094
|
+
})
|
|
1095
|
+
});
|
|
1096
|
+
const j = await r.json();
|
|
1097
|
+
if (r.status === 409 && j.reason === "email_linked_elsewhere") {
|
|
1098
|
+
setTransfer(true);
|
|
1099
|
+
setBusy(false);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1103
|
+
onDone();
|
|
1104
|
+
} catch (e) {
|
|
1105
|
+
setErr(e instanceof Error ? e.message : "link failed");
|
|
1106
|
+
setBusy(false);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("accent"), "data-oc-li-form": "email", children: [
|
|
1110
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 link an email" }),
|
|
1111
|
+
step === "email" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1112
|
+
/* @__PURE__ */ jsx(
|
|
1113
|
+
"input",
|
|
1114
|
+
{
|
|
1115
|
+
type: "email",
|
|
1116
|
+
autoComplete: "email",
|
|
1117
|
+
value: email,
|
|
1118
|
+
onChange: (e) => setEmail(e.target.value),
|
|
1119
|
+
placeholder: "you@example.com",
|
|
1120
|
+
style: inputStyle
|
|
1121
|
+
}
|
|
1122
|
+
),
|
|
1123
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1124
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1125
|
+
/* @__PURE__ */ jsx(
|
|
1126
|
+
"button",
|
|
1127
|
+
{
|
|
1128
|
+
type: "button",
|
|
1129
|
+
onClick: () => void start(),
|
|
1130
|
+
disabled: busy || email.trim().length < 5,
|
|
1131
|
+
style: primaryBtnStyle(busy || email.trim().length < 5),
|
|
1132
|
+
children: busy ? "sending\u2026" : "send code"
|
|
1133
|
+
}
|
|
1134
|
+
),
|
|
1135
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, style: ghostBtnStyle(busy), children: "cancel" })
|
|
1136
|
+
] })
|
|
1137
|
+
] }) : transfer ? /* @__PURE__ */ jsx(
|
|
1138
|
+
TransferConfirm,
|
|
1139
|
+
{
|
|
1140
|
+
what: "email",
|
|
1141
|
+
busy,
|
|
1142
|
+
onConfirm: () => void verify(true),
|
|
1143
|
+
onCancel,
|
|
1144
|
+
err
|
|
1145
|
+
}
|
|
1146
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1147
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1148
|
+
"Enter the 6-digit code sent to ",
|
|
1149
|
+
email,
|
|
1150
|
+
"."
|
|
1151
|
+
] }),
|
|
1152
|
+
/* @__PURE__ */ jsx(
|
|
1153
|
+
"input",
|
|
1154
|
+
{
|
|
1155
|
+
type: "text",
|
|
1156
|
+
inputMode: "numeric",
|
|
1157
|
+
autoComplete: "one-time-code",
|
|
1158
|
+
maxLength: 6,
|
|
1159
|
+
value: code,
|
|
1160
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
1161
|
+
placeholder: "123456",
|
|
1162
|
+
style: { ...inputStyle, letterSpacing: "0.3em" }
|
|
1163
|
+
}
|
|
1164
|
+
),
|
|
1165
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1166
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1167
|
+
/* @__PURE__ */ jsx(
|
|
1168
|
+
"button",
|
|
1169
|
+
{
|
|
1170
|
+
type: "button",
|
|
1171
|
+
onClick: () => void verify(false),
|
|
1172
|
+
disabled: busy || code.length !== 6,
|
|
1173
|
+
style: primaryBtnStyle(busy || code.length !== 6),
|
|
1174
|
+
children: busy ? "linking\u2026" : "verify + link"
|
|
1175
|
+
}
|
|
1176
|
+
),
|
|
1177
|
+
/* @__PURE__ */ jsx(
|
|
1178
|
+
"button",
|
|
1179
|
+
{
|
|
1180
|
+
type: "button",
|
|
1181
|
+
onClick: () => setStep("email"),
|
|
1182
|
+
disabled: busy,
|
|
1183
|
+
style: ghostBtnStyle(busy),
|
|
1184
|
+
children: "back"
|
|
1185
|
+
}
|
|
1186
|
+
)
|
|
1187
|
+
] })
|
|
1188
|
+
] })
|
|
1189
|
+
] });
|
|
1190
|
+
}
|
|
1191
|
+
function BtcLinkForm({
|
|
1192
|
+
authOrigin,
|
|
1193
|
+
didOc,
|
|
1194
|
+
onDone,
|
|
1195
|
+
onCancel
|
|
1196
|
+
}) {
|
|
1197
|
+
const [step, setStep] = React3.useState("request");
|
|
1198
|
+
const [addr, setAddr] = React3.useState("");
|
|
1199
|
+
const [challenge, setChallenge] = React3.useState("");
|
|
1200
|
+
const [busy, setBusy] = React3.useState(false);
|
|
1201
|
+
const [err, setErr] = React3.useState(null);
|
|
1202
|
+
const [pendingSig, setPendingSig] = React3.useState(null);
|
|
1203
|
+
async function getChallenge() {
|
|
1204
|
+
if (!didOc) {
|
|
1205
|
+
setErr("session not loaded yet \xB7 refresh and retry");
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
setBusy(true);
|
|
1209
|
+
setErr(null);
|
|
1210
|
+
try {
|
|
1211
|
+
const r = await fetch(
|
|
1212
|
+
`${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&purpose=link&audience=${encodeURIComponent(didOc)}`
|
|
1213
|
+
);
|
|
1214
|
+
const j = await r.json();
|
|
1215
|
+
if (!r.ok || !j.message) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1216
|
+
setChallenge(j.message);
|
|
1217
|
+
setStep("sign");
|
|
1218
|
+
} catch (e) {
|
|
1219
|
+
setErr(e instanceof Error ? e.message : "challenge failed");
|
|
1220
|
+
} finally {
|
|
1221
|
+
setBusy(false);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
async function postLink(sig, confirmTransfer) {
|
|
1225
|
+
setBusy(true);
|
|
1226
|
+
setErr(null);
|
|
1227
|
+
try {
|
|
1228
|
+
const r = await fetch(`${authOrigin}/api/auth/link/btc`, {
|
|
1229
|
+
method: "POST",
|
|
1230
|
+
credentials: "include",
|
|
1231
|
+
headers: { "Content-Type": "application/json" },
|
|
1232
|
+
body: JSON.stringify({
|
|
1233
|
+
message: challenge,
|
|
1234
|
+
signature: sig,
|
|
1235
|
+
...confirmTransfer ? { confirm_transfer: true } : {}
|
|
1236
|
+
})
|
|
1237
|
+
});
|
|
1238
|
+
const j = await r.json();
|
|
1239
|
+
if (r.status === 409 && j.reason === "address_linked_elsewhere") {
|
|
1240
|
+
setPendingSig(sig);
|
|
1241
|
+
setBusy(false);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1245
|
+
onDone();
|
|
1246
|
+
} catch (e) {
|
|
1247
|
+
setErr(e instanceof Error ? e.message : "link failed");
|
|
1248
|
+
setBusy(false);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("accent"), "data-oc-li-form": "btc", children: [
|
|
1252
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 link a Bitcoin address" }),
|
|
1253
|
+
step === "request" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1254
|
+
/* @__PURE__ */ jsx("p", { style: bodyStyle, children: "Enter a Bitcoin address you control. We generate a challenge and your wallet signs it (BIP-322)." }),
|
|
1255
|
+
/* @__PURE__ */ jsx(
|
|
1256
|
+
"input",
|
|
1257
|
+
{
|
|
1258
|
+
type: "text",
|
|
1259
|
+
value: addr,
|
|
1260
|
+
onChange: (e) => setAddr(e.target.value.trim()),
|
|
1261
|
+
placeholder: "bc1q\u2026",
|
|
1262
|
+
autoCapitalize: "none",
|
|
1263
|
+
spellCheck: false,
|
|
1264
|
+
style: { ...inputStyle, fontFamily: MONO }
|
|
1265
|
+
}
|
|
1266
|
+
),
|
|
1267
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1268
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1269
|
+
/* @__PURE__ */ jsx(
|
|
1270
|
+
"button",
|
|
1271
|
+
{
|
|
1272
|
+
type: "button",
|
|
1273
|
+
onClick: () => void getChallenge(),
|
|
1274
|
+
disabled: busy || addr.length < 14,
|
|
1275
|
+
style: primaryBtnStyle(busy || addr.length < 14),
|
|
1276
|
+
children: busy ? "requesting\u2026" : "get challenge"
|
|
1277
|
+
}
|
|
1278
|
+
),
|
|
1279
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, style: ghostBtnStyle(busy), children: "cancel" })
|
|
1280
|
+
] })
|
|
1281
|
+
] }) : pendingSig ? /* @__PURE__ */ jsx(
|
|
1282
|
+
TransferConfirm,
|
|
1283
|
+
{
|
|
1284
|
+
what: "bitcoin address",
|
|
1285
|
+
busy,
|
|
1286
|
+
onConfirm: () => void postLink(pendingSig, true),
|
|
1287
|
+
onCancel,
|
|
1288
|
+
err
|
|
1289
|
+
}
|
|
1290
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1291
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1292
|
+
"Sign the challenge with the wallet that controls",
|
|
1293
|
+
" ",
|
|
1294
|
+
/* @__PURE__ */ jsx("span", { style: { fontFamily: MONO, wordBreak: "break-all" }, children: addr }),
|
|
1295
|
+
"."
|
|
1296
|
+
] }),
|
|
1297
|
+
/* @__PURE__ */ jsx(
|
|
1298
|
+
LazyWalletButton,
|
|
1299
|
+
{
|
|
1300
|
+
address: addr,
|
|
1301
|
+
message: challenge,
|
|
1302
|
+
showManual: true,
|
|
1303
|
+
layout: "list",
|
|
1304
|
+
heading: null,
|
|
1305
|
+
onSigned: (sig) => void postLink(sig, false),
|
|
1306
|
+
onError: (e) => setErr(e?.message ?? "wallet rejected")
|
|
1307
|
+
}
|
|
1308
|
+
),
|
|
1309
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1310
|
+
/* @__PURE__ */ jsx(FormButtons, { children: /* @__PURE__ */ jsx(
|
|
1311
|
+
"button",
|
|
1312
|
+
{
|
|
1313
|
+
type: "button",
|
|
1314
|
+
onClick: () => {
|
|
1315
|
+
setStep("request");
|
|
1316
|
+
setErr(null);
|
|
1317
|
+
},
|
|
1318
|
+
disabled: busy,
|
|
1319
|
+
style: ghostBtnStyle(busy),
|
|
1320
|
+
children: "back"
|
|
1321
|
+
}
|
|
1322
|
+
) })
|
|
1323
|
+
] })
|
|
1324
|
+
] });
|
|
1325
|
+
}
|
|
1326
|
+
function TransferConfirm({
|
|
1327
|
+
what,
|
|
1328
|
+
busy,
|
|
1329
|
+
onConfirm,
|
|
1330
|
+
onCancel,
|
|
1331
|
+
err
|
|
1332
|
+
}) {
|
|
1333
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1334
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1335
|
+
"This ",
|
|
1336
|
+
what,
|
|
1337
|
+
" is already linked to another OrangeCheck account. You've just proven control of it \u2014 you can transfer the link to this account under dual proof of control. The other account loses this sign-in path; its history stays verifiable. Continue?"
|
|
1338
|
+
] }),
|
|
1339
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1340
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1341
|
+
/* @__PURE__ */ jsx(
|
|
1342
|
+
"button",
|
|
1343
|
+
{
|
|
1344
|
+
type: "button",
|
|
1345
|
+
onClick: onConfirm,
|
|
1346
|
+
disabled: busy,
|
|
1347
|
+
style: dangerBtnStyle(busy),
|
|
1348
|
+
children: busy ? "transferring\u2026" : "transfer link here"
|
|
1349
|
+
}
|
|
1350
|
+
),
|
|
1351
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, disabled: busy, style: ghostBtnStyle(busy), children: "cancel" })
|
|
1352
|
+
] })
|
|
1353
|
+
] });
|
|
1354
|
+
}
|
|
1355
|
+
function LazyWalletButton(props) {
|
|
1356
|
+
const [Comp, setComp] = React3.useState(null);
|
|
1357
|
+
const [failed, setFailed] = React3.useState(false);
|
|
1358
|
+
React3.useEffect(() => {
|
|
1359
|
+
let alive = true;
|
|
1360
|
+
import('@orangecheck/wallet-adapter/react').then((m) => {
|
|
1361
|
+
if (alive) {
|
|
1362
|
+
setComp(
|
|
1363
|
+
() => m.OcWalletButton
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
}).catch(() => {
|
|
1367
|
+
if (alive) setFailed(true);
|
|
1368
|
+
});
|
|
1369
|
+
return () => {
|
|
1370
|
+
alive = false;
|
|
1371
|
+
};
|
|
1372
|
+
}, []);
|
|
1373
|
+
if (failed) {
|
|
1374
|
+
return /* @__PURE__ */ jsx(ErrorLine, { children: "@orangecheck/wallet-adapter not installed \xB7 add it to your site to link a wallet" });
|
|
1375
|
+
}
|
|
1376
|
+
if (!Comp) return /* @__PURE__ */ jsxs("span", { style: hintStyle, children: [
|
|
1377
|
+
"> ",
|
|
1378
|
+
"loading wallet options\u2026"
|
|
1379
|
+
] });
|
|
1380
|
+
return /* @__PURE__ */ jsx(Comp, { ...props });
|
|
1381
|
+
}
|
|
1382
|
+
function mask(value, kind) {
|
|
1383
|
+
if (kind === "email") {
|
|
1384
|
+
const [user, host] = value.split("@");
|
|
1385
|
+
if (!user || !host) return "\u2022\u2022\u2022\u2022";
|
|
1386
|
+
const m = user.length <= 2 ? "\u2022".repeat(user.length) : user[0] + "\u2022".repeat(user.length - 2) + user.slice(-1);
|
|
1387
|
+
return `${m}@${host}`;
|
|
1388
|
+
}
|
|
1389
|
+
if (value.length <= 12) return "\u2022".repeat(value.length);
|
|
1390
|
+
return `${value.slice(0, 6)}\u2026${value.slice(-4)}`;
|
|
1391
|
+
}
|
|
1392
|
+
function SectionLabel({
|
|
1393
|
+
children,
|
|
1394
|
+
tone = "primary"
|
|
1395
|
+
}) {
|
|
1396
|
+
const color = tone === "success" ? V.success : tone === "warn" ? V.warn : V.primary;
|
|
1397
|
+
return /* @__PURE__ */ jsx(
|
|
1398
|
+
"div",
|
|
1399
|
+
{
|
|
1400
|
+
style: {
|
|
1401
|
+
color,
|
|
1402
|
+
fontFamily: MONO,
|
|
1403
|
+
fontSize: 10,
|
|
1404
|
+
letterSpacing: "0.18em",
|
|
1405
|
+
textTransform: "uppercase",
|
|
1406
|
+
marginBottom: 8
|
|
1407
|
+
},
|
|
1408
|
+
children
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
function ErrorLine({ children }) {
|
|
1413
|
+
return /* @__PURE__ */ jsx(
|
|
1414
|
+
"p",
|
|
1415
|
+
{
|
|
1416
|
+
role: "alert",
|
|
1417
|
+
style: {
|
|
1418
|
+
margin: "8px 0 0",
|
|
1419
|
+
color: V.destructive,
|
|
1420
|
+
fontFamily: MONO,
|
|
1421
|
+
fontSize: 11
|
|
1422
|
+
},
|
|
1423
|
+
children
|
|
1424
|
+
}
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
function FormButtons({ children }) {
|
|
1428
|
+
return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }, children });
|
|
1429
|
+
}
|
|
1430
|
+
var MONO = "ui-monospace, SFMono-Regular, monospace";
|
|
1431
|
+
var V = {
|
|
1432
|
+
primary: "var(--primary, #f97316)",
|
|
1433
|
+
foreground: "var(--foreground, #fafafa)",
|
|
1434
|
+
muted: "var(--muted-foreground, #a1a1aa)",
|
|
1435
|
+
background: "var(--background, #0a0a0a)",
|
|
1436
|
+
card: "var(--card, #111113)",
|
|
1437
|
+
border: "var(--border, #27272a)",
|
|
1438
|
+
destructive: "var(--destructive, #ef4444)",
|
|
1439
|
+
success: "var(--success, #22c55e)",
|
|
1440
|
+
warn: "var(--warning, #eab308)"
|
|
1441
|
+
};
|
|
1442
|
+
function panelStyle(tone = "default") {
|
|
1443
|
+
const base = {
|
|
1444
|
+
background: V.card,
|
|
1445
|
+
border: `1px solid ${V.border}`,
|
|
1446
|
+
padding: "1.25rem"
|
|
1447
|
+
};
|
|
1448
|
+
if (tone === "plain") return { ...base, padding: 0 };
|
|
1449
|
+
if (tone === "accent") return { ...base, borderColor: V.primary, background: "var(--card, #111113)" };
|
|
1450
|
+
if (tone === "success") return { ...base, borderColor: V.success };
|
|
1451
|
+
if (tone === "warn") return { ...base, borderColor: V.warn };
|
|
1452
|
+
return base;
|
|
1453
|
+
}
|
|
1454
|
+
var bodyStyle = {
|
|
1455
|
+
color: V.muted,
|
|
1456
|
+
fontSize: 13,
|
|
1457
|
+
lineHeight: 1.55,
|
|
1458
|
+
margin: "0 0 12px"
|
|
1459
|
+
};
|
|
1460
|
+
var hintStyle = {
|
|
1461
|
+
color: V.muted,
|
|
1462
|
+
fontFamily: MONO,
|
|
1463
|
+
fontSize: 10.5,
|
|
1464
|
+
opacity: 0.8
|
|
1465
|
+
};
|
|
1466
|
+
var codeStyle = {
|
|
1467
|
+
fontFamily: MONO,
|
|
1468
|
+
color: V.foreground
|
|
1469
|
+
};
|
|
1470
|
+
var rowStyle = {
|
|
1471
|
+
display: "flex",
|
|
1472
|
+
flexWrap: "wrap",
|
|
1473
|
+
alignItems: "center",
|
|
1474
|
+
justifyContent: "space-between",
|
|
1475
|
+
gap: 12,
|
|
1476
|
+
padding: "1rem"
|
|
1477
|
+
};
|
|
1478
|
+
var rowLabelStyle = {
|
|
1479
|
+
color: V.foreground,
|
|
1480
|
+
fontFamily: MONO,
|
|
1481
|
+
fontSize: 10.5,
|
|
1482
|
+
letterSpacing: "0.12em",
|
|
1483
|
+
textTransform: "uppercase",
|
|
1484
|
+
opacity: 0.8
|
|
1485
|
+
};
|
|
1486
|
+
var inputStyle = {
|
|
1487
|
+
width: "100%",
|
|
1488
|
+
padding: "0.55rem 0.75rem",
|
|
1489
|
+
background: V.background,
|
|
1490
|
+
color: V.foreground,
|
|
1491
|
+
border: `1px solid ${V.border}`,
|
|
1492
|
+
borderRadius: 6,
|
|
1493
|
+
fontFamily: MONO,
|
|
1494
|
+
// 16px — never lower · suppresses iOS Safari focus auto-zoom.
|
|
1495
|
+
fontSize: 16,
|
|
1496
|
+
outline: "none",
|
|
1497
|
+
boxSizing: "border-box"
|
|
1498
|
+
};
|
|
1499
|
+
function btnBase(disabled) {
|
|
1500
|
+
return {
|
|
1501
|
+
padding: "0.5rem 0.875rem",
|
|
1502
|
+
borderRadius: 6,
|
|
1503
|
+
fontFamily: MONO,
|
|
1504
|
+
fontSize: 10.5,
|
|
1505
|
+
letterSpacing: "0.12em",
|
|
1506
|
+
textTransform: "uppercase",
|
|
1507
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1508
|
+
opacity: disabled ? 0.5 : 1
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
function primaryBtnStyle(disabled) {
|
|
1512
|
+
return {
|
|
1513
|
+
...btnBase(disabled),
|
|
1514
|
+
background: V.primary,
|
|
1515
|
+
color: "var(--primary-foreground, #0a0a0a)",
|
|
1516
|
+
border: `1px solid ${V.primary}`,
|
|
1517
|
+
fontWeight: 600
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function ghostBtnStyle(disabled) {
|
|
1521
|
+
return {
|
|
1522
|
+
...btnBase(disabled),
|
|
1523
|
+
background: V.background,
|
|
1524
|
+
color: V.foreground,
|
|
1525
|
+
border: `1px solid ${V.border}`
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
function dangerBtnStyle(disabled) {
|
|
1529
|
+
return {
|
|
1530
|
+
...btnBase(disabled),
|
|
1531
|
+
background: "transparent",
|
|
1532
|
+
color: V.destructive,
|
|
1533
|
+
border: `1px solid ${V.destructive}`
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
var primaryChipStyle = {
|
|
1537
|
+
...btnBase(false),
|
|
1538
|
+
background: "transparent",
|
|
1539
|
+
color: V.primary,
|
|
1540
|
+
border: `1px solid ${V.primary}`
|
|
1541
|
+
};
|
|
751
1542
|
function safeReturnTo(input) {
|
|
752
1543
|
const candidate = input ?? "/";
|
|
753
1544
|
if (typeof candidate !== "string") return "/";
|
|
@@ -763,6 +1554,7 @@ function OcSignIn({
|
|
|
763
1554
|
returnTo,
|
|
764
1555
|
onSuccess,
|
|
765
1556
|
resolveReturnTo,
|
|
1557
|
+
linkPrompt,
|
|
766
1558
|
authOrigin = "https://ochk.io",
|
|
767
1559
|
initialPath = "wallet",
|
|
768
1560
|
paths,
|
|
@@ -771,24 +1563,34 @@ function OcSignIn({
|
|
|
771
1563
|
const walletEnabled = paths?.wallet ?? true;
|
|
772
1564
|
const emailEnabled = paths?.email ?? true;
|
|
773
1565
|
const safeReturn = safeReturnTo(returnTo);
|
|
774
|
-
const [path, setPath] =
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
onSuccess(account, token);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
1566
|
+
const [path, setPath] = React3.useState(initialPath);
|
|
1567
|
+
const [signedIn, setSignedIn] = React3.useState(null);
|
|
1568
|
+
const navigate = React3.useCallback(
|
|
1569
|
+
async (account) => {
|
|
781
1570
|
if (resolveReturnTo) {
|
|
782
1571
|
try {
|
|
783
|
-
|
|
784
|
-
hardNavigate(safeReturnTo(target));
|
|
1572
|
+
hardNavigate(safeReturnTo(await resolveReturnTo(account)));
|
|
785
1573
|
return;
|
|
786
1574
|
} catch {
|
|
787
1575
|
}
|
|
788
1576
|
}
|
|
789
1577
|
hardNavigate(safeReturn);
|
|
790
1578
|
},
|
|
791
|
-
[
|
|
1579
|
+
[resolveReturnTo, safeReturn]
|
|
1580
|
+
);
|
|
1581
|
+
const handleSuccess = React3.useCallback(
|
|
1582
|
+
async (account, token) => {
|
|
1583
|
+
if (onSuccess) {
|
|
1584
|
+
onSuccess(account, token);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (linkPrompt) {
|
|
1588
|
+
setSignedIn(account);
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
await navigate(account);
|
|
1592
|
+
},
|
|
1593
|
+
[onSuccess, linkPrompt, navigate]
|
|
792
1594
|
);
|
|
793
1595
|
if (!walletEnabled && !emailEnabled) {
|
|
794
1596
|
return /* @__PURE__ */ jsxs(
|
|
@@ -815,6 +1617,15 @@ function OcSignIn({
|
|
|
815
1617
|
}
|
|
816
1618
|
);
|
|
817
1619
|
}
|
|
1620
|
+
if (signedIn) {
|
|
1621
|
+
return /* @__PURE__ */ jsx("div", { className, "data-oc-signin": "", children: /* @__PURE__ */ jsx(
|
|
1622
|
+
LinkPromptStep,
|
|
1623
|
+
{
|
|
1624
|
+
authOrigin,
|
|
1625
|
+
onContinue: () => void navigate(signedIn)
|
|
1626
|
+
}
|
|
1627
|
+
) });
|
|
1628
|
+
}
|
|
818
1629
|
const showWallet = walletEnabled && (path === "wallet" || !emailEnabled);
|
|
819
1630
|
const showEmail = emailEnabled && (path === "email" || !walletEnabled);
|
|
820
1631
|
const bothEnabled = walletEnabled && emailEnabled;
|
|
@@ -857,6 +1668,25 @@ function OcSignIn({
|
|
|
857
1668
|
] })
|
|
858
1669
|
] });
|
|
859
1670
|
}
|
|
1671
|
+
function LinkPromptStep({
|
|
1672
|
+
authOrigin,
|
|
1673
|
+
onContinue
|
|
1674
|
+
}) {
|
|
1675
|
+
return /* @__PURE__ */ jsxs("div", { "data-oc-signin-linkprompt": "", children: [
|
|
1676
|
+
/* @__PURE__ */ jsx(FlowHeader, { label: "\xA7 signed in \xB7 add a backup", children: "You're in. Linking a second way to sign in \u2014 your email or your Bitcoin wallet \u2014 is also your recovery path: lose one, the other still gets you in. Optional; skip with continue." }),
|
|
1677
|
+
/* @__PURE__ */ jsx(OcLinkedIdentities, { authOrigin }),
|
|
1678
|
+
/* @__PURE__ */ jsx(
|
|
1679
|
+
"button",
|
|
1680
|
+
{
|
|
1681
|
+
type: "button",
|
|
1682
|
+
onClick: onContinue,
|
|
1683
|
+
style: submitStyle(false),
|
|
1684
|
+
"data-oc-signin-continue": "",
|
|
1685
|
+
children: "continue \u2192"
|
|
1686
|
+
}
|
|
1687
|
+
)
|
|
1688
|
+
] });
|
|
1689
|
+
}
|
|
860
1690
|
function SigninTab({
|
|
861
1691
|
active,
|
|
862
1692
|
onClick,
|
|
@@ -888,10 +1718,10 @@ function SigninTab({
|
|
|
888
1718
|
);
|
|
889
1719
|
}
|
|
890
1720
|
function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
891
|
-
const [address, setAddress] =
|
|
892
|
-
const [error, setError] =
|
|
893
|
-
const [submitting, setSubmitting] =
|
|
894
|
-
const [stage, setStage] =
|
|
1721
|
+
const [address, setAddress] = React3.useState("");
|
|
1722
|
+
const [error, setError] = React3.useState(null);
|
|
1723
|
+
const [submitting, setSubmitting] = React3.useState(false);
|
|
1724
|
+
const [stage, setStage] = React3.useState("enter");
|
|
895
1725
|
async function signIn(e) {
|
|
896
1726
|
e.preventDefault();
|
|
897
1727
|
const addr = address.trim();
|
|
@@ -970,10 +1800,10 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
|
970
1800
|
spellCheck: false,
|
|
971
1801
|
required: true,
|
|
972
1802
|
"aria-invalid": error ? true : void 0,
|
|
973
|
-
style:
|
|
1803
|
+
style: inputStyle2
|
|
974
1804
|
}
|
|
975
1805
|
),
|
|
976
|
-
error && /* @__PURE__ */ jsx(
|
|
1806
|
+
error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
|
|
977
1807
|
/* @__PURE__ */ jsx(
|
|
978
1808
|
"button",
|
|
979
1809
|
{
|
|
@@ -987,13 +1817,13 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
|
987
1817
|
] });
|
|
988
1818
|
}
|
|
989
1819
|
function EmailFlow({ authOrigin, onSuccess }) {
|
|
990
|
-
const [stage, setStage] =
|
|
991
|
-
const [email, setEmail] =
|
|
992
|
-
const [emailError, setEmailError] =
|
|
993
|
-
const [code, setCode] =
|
|
994
|
-
const [codeError, setCodeError] =
|
|
995
|
-
const [token, setToken] =
|
|
996
|
-
const [submitting, setSubmitting] =
|
|
1820
|
+
const [stage, setStage] = React3.useState("enter");
|
|
1821
|
+
const [email, setEmail] = React3.useState("");
|
|
1822
|
+
const [emailError, setEmailError] = React3.useState(null);
|
|
1823
|
+
const [code, setCode] = React3.useState("");
|
|
1824
|
+
const [codeError, setCodeError] = React3.useState(null);
|
|
1825
|
+
const [token, setToken] = React3.useState(null);
|
|
1826
|
+
const [submitting, setSubmitting] = React3.useState(false);
|
|
997
1827
|
async function start(e) {
|
|
998
1828
|
e.preventDefault();
|
|
999
1829
|
const trimmed = email.trim().toLowerCase();
|
|
@@ -1069,10 +1899,10 @@ function EmailFlow({ authOrigin, onSuccess }) {
|
|
|
1069
1899
|
placeholder: "you@example.com",
|
|
1070
1900
|
required: true,
|
|
1071
1901
|
"aria-invalid": emailError ? true : void 0,
|
|
1072
|
-
style:
|
|
1902
|
+
style: inputStyle2
|
|
1073
1903
|
}
|
|
1074
1904
|
),
|
|
1075
|
-
emailError && /* @__PURE__ */ jsx(
|
|
1905
|
+
emailError && /* @__PURE__ */ jsx(ErrorLine2, { children: emailError }),
|
|
1076
1906
|
/* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" }),
|
|
1077
1907
|
/* @__PURE__ */ jsx(Hint, { children: "OTP delivered by the auth host. Codes expire in 10 minutes and are single-use. Rate-limited 5 starts/hour/IP." })
|
|
1078
1908
|
] });
|
|
@@ -1101,10 +1931,10 @@ function EmailFlow({ authOrigin, onSuccess }) {
|
|
|
1101
1931
|
required: true,
|
|
1102
1932
|
autoFocus: true,
|
|
1103
1933
|
"aria-invalid": codeError ? true : void 0,
|
|
1104
|
-
style: { ...
|
|
1934
|
+
style: { ...inputStyle2, letterSpacing: "0.4em", fontSize: 16 }
|
|
1105
1935
|
}
|
|
1106
1936
|
),
|
|
1107
|
-
codeError && /* @__PURE__ */ jsx(
|
|
1937
|
+
codeError && /* @__PURE__ */ jsx(ErrorLine2, { children: codeError }),
|
|
1108
1938
|
/* @__PURE__ */ jsx(
|
|
1109
1939
|
"button",
|
|
1110
1940
|
{
|
|
@@ -1191,7 +2021,7 @@ function Label({ children }) {
|
|
|
1191
2021
|
}
|
|
1192
2022
|
);
|
|
1193
2023
|
}
|
|
1194
|
-
var
|
|
2024
|
+
var inputStyle2 = {
|
|
1195
2025
|
width: "100%",
|
|
1196
2026
|
padding: "0.55rem 0.75rem",
|
|
1197
2027
|
background: "var(--background, #0a0a0a)",
|
|
@@ -1223,7 +2053,7 @@ function submitStyle(disabled) {
|
|
|
1223
2053
|
width: "100%"
|
|
1224
2054
|
};
|
|
1225
2055
|
}
|
|
1226
|
-
function
|
|
2056
|
+
function ErrorLine2({ children }) {
|
|
1227
2057
|
return /* @__PURE__ */ jsx(
|
|
1228
2058
|
"p",
|
|
1229
2059
|
{
|
|
@@ -1270,13 +2100,13 @@ function isAbortError(err) {
|
|
|
1270
2100
|
}
|
|
1271
2101
|
function useWebAuthnRegister(opts) {
|
|
1272
2102
|
const authOrigin = resolveAuthOrigin(opts);
|
|
1273
|
-
const [status, setStatus] =
|
|
1274
|
-
const [error, setError] =
|
|
1275
|
-
const reset =
|
|
2103
|
+
const [status, setStatus] = React3.useState("idle");
|
|
2104
|
+
const [error, setError] = React3.useState(null);
|
|
2105
|
+
const reset = React3.useCallback(() => {
|
|
1276
2106
|
setStatus("idle");
|
|
1277
2107
|
setError(null);
|
|
1278
2108
|
}, []);
|
|
1279
|
-
const register =
|
|
2109
|
+
const register = React3.useCallback(
|
|
1280
2110
|
async (args) => {
|
|
1281
2111
|
setError(null);
|
|
1282
2112
|
setStatus("requesting-options");
|
|
@@ -1338,10 +2168,10 @@ function useWebAuthnRegister(opts) {
|
|
|
1338
2168
|
function useWebAuthnList(opts) {
|
|
1339
2169
|
const authOrigin = resolveAuthOrigin(opts);
|
|
1340
2170
|
const { status: sessionStatus } = useOcSession();
|
|
1341
|
-
const [credentials, setCredentials] =
|
|
1342
|
-
const [status, setStatus] =
|
|
1343
|
-
const [error, setError] =
|
|
1344
|
-
const refetch =
|
|
2171
|
+
const [credentials, setCredentials] = React3.useState([]);
|
|
2172
|
+
const [status, setStatus] = React3.useState("loading");
|
|
2173
|
+
const [error, setError] = React3.useState(null);
|
|
2174
|
+
const refetch = React3.useCallback(async () => {
|
|
1345
2175
|
setError(null);
|
|
1346
2176
|
if (sessionStatus !== "authenticated") {
|
|
1347
2177
|
setCredentials([]);
|
|
@@ -1369,10 +2199,10 @@ function useWebAuthnList(opts) {
|
|
|
1369
2199
|
setStatus("error");
|
|
1370
2200
|
}
|
|
1371
2201
|
}, [authOrigin, sessionStatus]);
|
|
1372
|
-
|
|
2202
|
+
React3.useEffect(() => {
|
|
1373
2203
|
void refetch();
|
|
1374
2204
|
}, [refetch]);
|
|
1375
|
-
const rename =
|
|
2205
|
+
const rename = React3.useCallback(
|
|
1376
2206
|
async (id, label) => {
|
|
1377
2207
|
try {
|
|
1378
2208
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -1397,7 +2227,7 @@ function useWebAuthnList(opts) {
|
|
|
1397
2227
|
},
|
|
1398
2228
|
[authOrigin, refetch]
|
|
1399
2229
|
);
|
|
1400
|
-
const remove =
|
|
2230
|
+
const remove = React3.useCallback(
|
|
1401
2231
|
async (id) => {
|
|
1402
2232
|
try {
|
|
1403
2233
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -1425,13 +2255,13 @@ function useWebAuthnList(opts) {
|
|
|
1425
2255
|
function useStepUpAuth(opts) {
|
|
1426
2256
|
const authOrigin = resolveAuthOrigin(opts);
|
|
1427
2257
|
const { refresh } = useOcSession();
|
|
1428
|
-
const [status, setStatus] =
|
|
1429
|
-
const [error, setError] =
|
|
1430
|
-
const reset =
|
|
2258
|
+
const [status, setStatus] = React3.useState("idle");
|
|
2259
|
+
const [error, setError] = React3.useState(null);
|
|
2260
|
+
const reset = React3.useCallback(() => {
|
|
1431
2261
|
setStatus("idle");
|
|
1432
2262
|
setError(null);
|
|
1433
2263
|
}, []);
|
|
1434
|
-
const stepUp =
|
|
2264
|
+
const stepUp = React3.useCallback(
|
|
1435
2265
|
async (args) => {
|
|
1436
2266
|
setError(null);
|
|
1437
2267
|
setStatus("requesting-options");
|
|
@@ -1516,6 +2346,6 @@ function handleSudoRequired(body, args = {}) {
|
|
|
1516
2346
|
return false;
|
|
1517
2347
|
}
|
|
1518
2348
|
|
|
1519
|
-
export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcSessionProvider, OcSignIn, OcSignInButton, buildSignInUrl, handleSudoRequired, redirectToSudo, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister };
|
|
2349
|
+
export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcLinkedIdentities, OcSessionProvider, OcSignIn, OcSignInButton, buildSignInUrl, handleSudoRequired, redirectToSudo, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister };
|
|
1520
2350
|
//# sourceMappingURL=index.mjs.map
|
|
1521
2351
|
//# sourceMappingURL=index.mjs.map
|