@orangecheck/auth-client 2.5.1 → 2.7.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 +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +1083 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1082 -72
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -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,977 @@ 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
|
+
hasBtc && resp.did_oc && /* @__PURE__ */ jsx(OcIdentityBond, { didOc: resp.did_oc, btc: btcRows.find((i) => !!i.value).value })
|
|
872
|
+
] });
|
|
873
|
+
}
|
|
874
|
+
function IdentityGroup({
|
|
875
|
+
kind,
|
|
876
|
+
title,
|
|
877
|
+
rows,
|
|
878
|
+
authOrigin,
|
|
879
|
+
onChange,
|
|
880
|
+
addCta,
|
|
881
|
+
onAdd
|
|
882
|
+
}) {
|
|
883
|
+
if (rows.length === 0) {
|
|
884
|
+
return /* @__PURE__ */ jsxs("div", { style: rowStyle, children: [
|
|
885
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
886
|
+
/* @__PURE__ */ jsxs("div", { style: rowLabelStyle, children: [
|
|
887
|
+
"\xA7 ",
|
|
888
|
+
title
|
|
889
|
+
] }),
|
|
890
|
+
/* @__PURE__ */ jsx("div", { style: { ...bodyStyle, marginTop: 4 }, children: "none linked" })
|
|
891
|
+
] }),
|
|
892
|
+
addCta && /* @__PURE__ */ jsxs("button", { type: "button", onClick: onAdd, style: primaryChipStyle, children: [
|
|
893
|
+
"+ ",
|
|
894
|
+
addCta
|
|
895
|
+
] })
|
|
896
|
+
] });
|
|
897
|
+
}
|
|
898
|
+
return /* @__PURE__ */ jsx(Fragment, { children: rows.map((r) => /* @__PURE__ */ jsx(
|
|
899
|
+
IdentityRow,
|
|
900
|
+
{
|
|
901
|
+
row: r,
|
|
902
|
+
kind,
|
|
903
|
+
authOrigin,
|
|
904
|
+
onChange
|
|
905
|
+
},
|
|
906
|
+
r.id
|
|
907
|
+
)) });
|
|
908
|
+
}
|
|
909
|
+
function IdentityRow({
|
|
910
|
+
row,
|
|
911
|
+
kind,
|
|
912
|
+
authOrigin,
|
|
913
|
+
onChange
|
|
914
|
+
}) {
|
|
915
|
+
const [shown, setShown] = React3.useState(false);
|
|
916
|
+
const [confirming, setConfirming] = React3.useState(false);
|
|
917
|
+
const [busy, setBusy] = React3.useState(false);
|
|
918
|
+
const [err, setErr] = React3.useState(null);
|
|
919
|
+
const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
|
|
920
|
+
const display = shown && row.value ? row.value : masked;
|
|
921
|
+
async function unlink() {
|
|
922
|
+
if (row.is_primary) return;
|
|
923
|
+
setBusy(true);
|
|
924
|
+
setErr(null);
|
|
925
|
+
try {
|
|
926
|
+
const r = await fetch(
|
|
927
|
+
`${authOrigin}/api/auth/identities?id=${encodeURIComponent(row.id)}`,
|
|
928
|
+
{ method: "DELETE", credentials: "include" }
|
|
929
|
+
);
|
|
930
|
+
if (!r.ok) {
|
|
931
|
+
const j = await r.json().catch(() => ({}));
|
|
932
|
+
throw new Error(j.reason ?? `http_${r.status}`);
|
|
933
|
+
}
|
|
934
|
+
onChange();
|
|
935
|
+
} catch (e) {
|
|
936
|
+
setErr(e instanceof Error ? e.message : "unlink failed");
|
|
937
|
+
setBusy(false);
|
|
938
|
+
setConfirming(false);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return /* @__PURE__ */ jsxs("div", { style: rowStyle, "data-oc-li-row": kind, children: [
|
|
942
|
+
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
|
|
943
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "4px 12px" }, children: [
|
|
944
|
+
/* @__PURE__ */ jsxs("span", { style: rowLabelStyle, children: [
|
|
945
|
+
"\xA7 ",
|
|
946
|
+
kind === "email" ? "email" : "bitcoin address"
|
|
947
|
+
] }),
|
|
948
|
+
row.is_primary && /* @__PURE__ */ jsx("span", { style: { ...rowLabelStyle, color: V.primary }, children: "primary \xB7 signup-time" })
|
|
949
|
+
] }),
|
|
950
|
+
/* @__PURE__ */ jsx(
|
|
951
|
+
"div",
|
|
952
|
+
{
|
|
953
|
+
style: {
|
|
954
|
+
marginTop: 4,
|
|
955
|
+
color: V.foreground,
|
|
956
|
+
fontFamily: MONO,
|
|
957
|
+
fontSize: 12,
|
|
958
|
+
wordBreak: "break-all"
|
|
959
|
+
},
|
|
960
|
+
children: display
|
|
961
|
+
}
|
|
962
|
+
),
|
|
963
|
+
row.verified_at && /* @__PURE__ */ jsxs("div", { style: { ...hintStyle, marginTop: 4 }, children: [
|
|
964
|
+
"verified \xB7 ",
|
|
965
|
+
new Date(row.verified_at).toUTCString()
|
|
966
|
+
] }),
|
|
967
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err })
|
|
968
|
+
] }),
|
|
969
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexShrink: 0, gap: 8, flexWrap: "wrap" }, children: [
|
|
970
|
+
row.value && /* @__PURE__ */ jsx(
|
|
971
|
+
"button",
|
|
972
|
+
{
|
|
973
|
+
type: "button",
|
|
974
|
+
onClick: () => setShown((s) => !s),
|
|
975
|
+
style: ghostBtnStyle(false),
|
|
976
|
+
children: shown ? "hide" : "show"
|
|
977
|
+
}
|
|
978
|
+
),
|
|
979
|
+
!row.is_primary && !confirming && /* @__PURE__ */ jsx(
|
|
980
|
+
"button",
|
|
981
|
+
{
|
|
982
|
+
type: "button",
|
|
983
|
+
onClick: () => setConfirming(true),
|
|
984
|
+
style: dangerBtnStyle(false),
|
|
985
|
+
children: "unlink"
|
|
986
|
+
}
|
|
987
|
+
),
|
|
988
|
+
!row.is_primary && confirming && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
989
|
+
/* @__PURE__ */ jsx(
|
|
990
|
+
"button",
|
|
991
|
+
{
|
|
992
|
+
type: "button",
|
|
993
|
+
onClick: () => void unlink(),
|
|
994
|
+
disabled: busy,
|
|
995
|
+
style: dangerBtnStyle(busy),
|
|
996
|
+
children: busy ? "unlinking\u2026" : "confirm unlink"
|
|
997
|
+
}
|
|
998
|
+
),
|
|
999
|
+
/* @__PURE__ */ jsx(
|
|
1000
|
+
"button",
|
|
1001
|
+
{
|
|
1002
|
+
type: "button",
|
|
1003
|
+
onClick: () => setConfirming(false),
|
|
1004
|
+
disabled: busy,
|
|
1005
|
+
style: ghostBtnStyle(busy),
|
|
1006
|
+
children: "cancel"
|
|
1007
|
+
}
|
|
1008
|
+
)
|
|
1009
|
+
] })
|
|
1010
|
+
] })
|
|
1011
|
+
] });
|
|
1012
|
+
}
|
|
1013
|
+
function ConsolidationPanel({ entries }) {
|
|
1014
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("success"), "data-oc-li-consolidated": "", children: [
|
|
1015
|
+
/* @__PURE__ */ jsxs(SectionLabel, { tone: "success", children: [
|
|
1016
|
+
"\xA7 consolidated accounts \xB7 ",
|
|
1017
|
+
entries.length
|
|
1018
|
+
] }),
|
|
1019
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1020
|
+
entries.length === 1 ? "A previous OC account" : "Previous OC accounts",
|
|
1021
|
+
" you signed up with, folded into this one under dual proof of control. Historical activity is unioned into this account automatically;",
|
|
1022
|
+
" ",
|
|
1023
|
+
entries.length === 1 ? "it" : "they",
|
|
1024
|
+
" can no longer sign in."
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: "12px 0 0", padding: 0 }, children: entries.map((e) => /* @__PURE__ */ jsxs(
|
|
1027
|
+
"li",
|
|
1028
|
+
{
|
|
1029
|
+
style: {
|
|
1030
|
+
display: "flex",
|
|
1031
|
+
flexWrap: "wrap",
|
|
1032
|
+
gap: 8,
|
|
1033
|
+
padding: "8px 0",
|
|
1034
|
+
borderTop: `1px solid ${V.border}`,
|
|
1035
|
+
fontFamily: MONO,
|
|
1036
|
+
fontSize: 11
|
|
1037
|
+
},
|
|
1038
|
+
children: [
|
|
1039
|
+
/* @__PURE__ */ jsx("code", { style: { color: V.foreground, wordBreak: "break-all" }, children: e.did_oc }),
|
|
1040
|
+
/* @__PURE__ */ jsxs("span", { style: { ...hintStyle, marginLeft: "auto" }, children: [
|
|
1041
|
+
"created ",
|
|
1042
|
+
new Date(e.created_at).toLocaleDateString()
|
|
1043
|
+
] })
|
|
1044
|
+
]
|
|
1045
|
+
},
|
|
1046
|
+
e.did_oc
|
|
1047
|
+
)) })
|
|
1048
|
+
] });
|
|
1049
|
+
}
|
|
1050
|
+
function EmailLinkForm({
|
|
1051
|
+
authOrigin,
|
|
1052
|
+
onDone,
|
|
1053
|
+
onCancel
|
|
1054
|
+
}) {
|
|
1055
|
+
const [step, setStep] = React3.useState("email");
|
|
1056
|
+
const [email, setEmail] = React3.useState("");
|
|
1057
|
+
const [code, setCode] = React3.useState("");
|
|
1058
|
+
const [token, setToken] = React3.useState("");
|
|
1059
|
+
const [busy, setBusy] = React3.useState(false);
|
|
1060
|
+
const [err, setErr] = React3.useState(null);
|
|
1061
|
+
const [transfer, setTransfer] = React3.useState(false);
|
|
1062
|
+
async function start() {
|
|
1063
|
+
setBusy(true);
|
|
1064
|
+
setErr(null);
|
|
1065
|
+
try {
|
|
1066
|
+
const r = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
|
|
1067
|
+
method: "POST",
|
|
1068
|
+
credentials: "include",
|
|
1069
|
+
headers: { "Content-Type": "application/json" },
|
|
1070
|
+
body: JSON.stringify({ email: email.trim().toLowerCase() })
|
|
1071
|
+
});
|
|
1072
|
+
const j = await r.json();
|
|
1073
|
+
if (!r.ok || !j.token) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1074
|
+
setToken(j.token);
|
|
1075
|
+
setStep("code");
|
|
1076
|
+
} catch (e) {
|
|
1077
|
+
setErr(e instanceof Error ? e.message : "could not send code");
|
|
1078
|
+
} finally {
|
|
1079
|
+
setBusy(false);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async function verify(confirmTransfer) {
|
|
1083
|
+
setBusy(true);
|
|
1084
|
+
setErr(null);
|
|
1085
|
+
try {
|
|
1086
|
+
const r = await fetch(`${authOrigin}/api/auth/link/email`, {
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
credentials: "include",
|
|
1089
|
+
headers: { "Content-Type": "application/json" },
|
|
1090
|
+
body: JSON.stringify({
|
|
1091
|
+
email: email.trim().toLowerCase(),
|
|
1092
|
+
code: code.trim(),
|
|
1093
|
+
token,
|
|
1094
|
+
...confirmTransfer ? { confirm_transfer: true } : {}
|
|
1095
|
+
})
|
|
1096
|
+
});
|
|
1097
|
+
const j = await r.json();
|
|
1098
|
+
if (r.status === 409 && j.reason === "email_linked_elsewhere") {
|
|
1099
|
+
setTransfer(true);
|
|
1100
|
+
setBusy(false);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1104
|
+
onDone();
|
|
1105
|
+
} catch (e) {
|
|
1106
|
+
setErr(e instanceof Error ? e.message : "link failed");
|
|
1107
|
+
setBusy(false);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("accent"), "data-oc-li-form": "email", children: [
|
|
1111
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 link an email" }),
|
|
1112
|
+
step === "email" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1113
|
+
/* @__PURE__ */ jsx(
|
|
1114
|
+
"input",
|
|
1115
|
+
{
|
|
1116
|
+
type: "email",
|
|
1117
|
+
autoComplete: "email",
|
|
1118
|
+
value: email,
|
|
1119
|
+
onChange: (e) => setEmail(e.target.value),
|
|
1120
|
+
placeholder: "you@example.com",
|
|
1121
|
+
style: inputStyle
|
|
1122
|
+
}
|
|
1123
|
+
),
|
|
1124
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1125
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1126
|
+
/* @__PURE__ */ jsx(
|
|
1127
|
+
"button",
|
|
1128
|
+
{
|
|
1129
|
+
type: "button",
|
|
1130
|
+
onClick: () => void start(),
|
|
1131
|
+
disabled: busy || email.trim().length < 5,
|
|
1132
|
+
style: primaryBtnStyle(busy || email.trim().length < 5),
|
|
1133
|
+
children: busy ? "sending\u2026" : "send code"
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, style: ghostBtnStyle(busy), children: "cancel" })
|
|
1137
|
+
] })
|
|
1138
|
+
] }) : transfer ? /* @__PURE__ */ jsx(
|
|
1139
|
+
TransferConfirm,
|
|
1140
|
+
{
|
|
1141
|
+
what: "email",
|
|
1142
|
+
busy,
|
|
1143
|
+
onConfirm: () => void verify(true),
|
|
1144
|
+
onCancel,
|
|
1145
|
+
err
|
|
1146
|
+
}
|
|
1147
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1148
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1149
|
+
"Enter the 6-digit code sent to ",
|
|
1150
|
+
email,
|
|
1151
|
+
"."
|
|
1152
|
+
] }),
|
|
1153
|
+
/* @__PURE__ */ jsx(
|
|
1154
|
+
"input",
|
|
1155
|
+
{
|
|
1156
|
+
type: "text",
|
|
1157
|
+
inputMode: "numeric",
|
|
1158
|
+
autoComplete: "one-time-code",
|
|
1159
|
+
maxLength: 6,
|
|
1160
|
+
value: code,
|
|
1161
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
1162
|
+
placeholder: "123456",
|
|
1163
|
+
style: { ...inputStyle, letterSpacing: "0.3em" }
|
|
1164
|
+
}
|
|
1165
|
+
),
|
|
1166
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1167
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1168
|
+
/* @__PURE__ */ jsx(
|
|
1169
|
+
"button",
|
|
1170
|
+
{
|
|
1171
|
+
type: "button",
|
|
1172
|
+
onClick: () => void verify(false),
|
|
1173
|
+
disabled: busy || code.length !== 6,
|
|
1174
|
+
style: primaryBtnStyle(busy || code.length !== 6),
|
|
1175
|
+
children: busy ? "linking\u2026" : "verify + link"
|
|
1176
|
+
}
|
|
1177
|
+
),
|
|
1178
|
+
/* @__PURE__ */ jsx(
|
|
1179
|
+
"button",
|
|
1180
|
+
{
|
|
1181
|
+
type: "button",
|
|
1182
|
+
onClick: () => setStep("email"),
|
|
1183
|
+
disabled: busy,
|
|
1184
|
+
style: ghostBtnStyle(busy),
|
|
1185
|
+
children: "back"
|
|
1186
|
+
}
|
|
1187
|
+
)
|
|
1188
|
+
] })
|
|
1189
|
+
] })
|
|
1190
|
+
] });
|
|
1191
|
+
}
|
|
1192
|
+
function BtcLinkForm({
|
|
1193
|
+
authOrigin,
|
|
1194
|
+
didOc,
|
|
1195
|
+
onDone,
|
|
1196
|
+
onCancel
|
|
1197
|
+
}) {
|
|
1198
|
+
const [step, setStep] = React3.useState("request");
|
|
1199
|
+
const [addr, setAddr] = React3.useState("");
|
|
1200
|
+
const [challenge, setChallenge] = React3.useState("");
|
|
1201
|
+
const [busy, setBusy] = React3.useState(false);
|
|
1202
|
+
const [err, setErr] = React3.useState(null);
|
|
1203
|
+
const [pendingSig, setPendingSig] = React3.useState(null);
|
|
1204
|
+
async function getChallenge() {
|
|
1205
|
+
if (!didOc) {
|
|
1206
|
+
setErr("session not loaded yet \xB7 refresh and retry");
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
setBusy(true);
|
|
1210
|
+
setErr(null);
|
|
1211
|
+
try {
|
|
1212
|
+
const r = await fetch(
|
|
1213
|
+
`${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&purpose=link&audience=${encodeURIComponent(didOc)}`
|
|
1214
|
+
);
|
|
1215
|
+
const j = await r.json();
|
|
1216
|
+
if (!r.ok || !j.message) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1217
|
+
setChallenge(j.message);
|
|
1218
|
+
setStep("sign");
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
setErr(e instanceof Error ? e.message : "challenge failed");
|
|
1221
|
+
} finally {
|
|
1222
|
+
setBusy(false);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
async function postLink(sig, confirmTransfer) {
|
|
1226
|
+
setBusy(true);
|
|
1227
|
+
setErr(null);
|
|
1228
|
+
try {
|
|
1229
|
+
const r = await fetch(`${authOrigin}/api/auth/link/btc`, {
|
|
1230
|
+
method: "POST",
|
|
1231
|
+
credentials: "include",
|
|
1232
|
+
headers: { "Content-Type": "application/json" },
|
|
1233
|
+
body: JSON.stringify({
|
|
1234
|
+
message: challenge,
|
|
1235
|
+
signature: sig,
|
|
1236
|
+
...confirmTransfer ? { confirm_transfer: true } : {}
|
|
1237
|
+
})
|
|
1238
|
+
});
|
|
1239
|
+
const j = await r.json();
|
|
1240
|
+
if (r.status === 409 && j.reason === "address_linked_elsewhere") {
|
|
1241
|
+
setPendingSig(sig);
|
|
1242
|
+
setBusy(false);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);
|
|
1246
|
+
onDone();
|
|
1247
|
+
} catch (e) {
|
|
1248
|
+
setErr(e instanceof Error ? e.message : "link failed");
|
|
1249
|
+
setBusy(false);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
return /* @__PURE__ */ jsxs("div", { style: panelStyle("accent"), "data-oc-li-form": "btc", children: [
|
|
1253
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 link a Bitcoin address" }),
|
|
1254
|
+
step === "request" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1255
|
+
/* @__PURE__ */ jsx("p", { style: bodyStyle, children: "Enter a Bitcoin address you control. We generate a challenge and your wallet signs it (BIP-322)." }),
|
|
1256
|
+
/* @__PURE__ */ jsx(
|
|
1257
|
+
"input",
|
|
1258
|
+
{
|
|
1259
|
+
type: "text",
|
|
1260
|
+
value: addr,
|
|
1261
|
+
onChange: (e) => setAddr(e.target.value.trim()),
|
|
1262
|
+
placeholder: "bc1q\u2026",
|
|
1263
|
+
autoCapitalize: "none",
|
|
1264
|
+
spellCheck: false,
|
|
1265
|
+
style: { ...inputStyle, fontFamily: MONO }
|
|
1266
|
+
}
|
|
1267
|
+
),
|
|
1268
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1269
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1270
|
+
/* @__PURE__ */ jsx(
|
|
1271
|
+
"button",
|
|
1272
|
+
{
|
|
1273
|
+
type: "button",
|
|
1274
|
+
onClick: () => void getChallenge(),
|
|
1275
|
+
disabled: busy || addr.length < 14,
|
|
1276
|
+
style: primaryBtnStyle(busy || addr.length < 14),
|
|
1277
|
+
children: busy ? "requesting\u2026" : "get challenge"
|
|
1278
|
+
}
|
|
1279
|
+
),
|
|
1280
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, style: ghostBtnStyle(busy), children: "cancel" })
|
|
1281
|
+
] })
|
|
1282
|
+
] }) : pendingSig ? /* @__PURE__ */ jsx(
|
|
1283
|
+
TransferConfirm,
|
|
1284
|
+
{
|
|
1285
|
+
what: "bitcoin address",
|
|
1286
|
+
busy,
|
|
1287
|
+
onConfirm: () => void postLink(pendingSig, true),
|
|
1288
|
+
onCancel,
|
|
1289
|
+
err
|
|
1290
|
+
}
|
|
1291
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1292
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1293
|
+
"Sign the challenge with the wallet that controls",
|
|
1294
|
+
" ",
|
|
1295
|
+
/* @__PURE__ */ jsx("span", { style: { fontFamily: MONO, wordBreak: "break-all" }, children: addr }),
|
|
1296
|
+
"."
|
|
1297
|
+
] }),
|
|
1298
|
+
/* @__PURE__ */ jsx(
|
|
1299
|
+
LazyWalletButton,
|
|
1300
|
+
{
|
|
1301
|
+
address: addr,
|
|
1302
|
+
message: challenge,
|
|
1303
|
+
showManual: true,
|
|
1304
|
+
layout: "list",
|
|
1305
|
+
heading: null,
|
|
1306
|
+
onSigned: (sig) => void postLink(sig, false),
|
|
1307
|
+
onError: (e) => setErr(e?.message ?? "wallet rejected")
|
|
1308
|
+
}
|
|
1309
|
+
),
|
|
1310
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1311
|
+
/* @__PURE__ */ jsx(FormButtons, { children: /* @__PURE__ */ jsx(
|
|
1312
|
+
"button",
|
|
1313
|
+
{
|
|
1314
|
+
type: "button",
|
|
1315
|
+
onClick: () => {
|
|
1316
|
+
setStep("request");
|
|
1317
|
+
setErr(null);
|
|
1318
|
+
},
|
|
1319
|
+
disabled: busy,
|
|
1320
|
+
style: ghostBtnStyle(busy),
|
|
1321
|
+
children: "back"
|
|
1322
|
+
}
|
|
1323
|
+
) })
|
|
1324
|
+
] })
|
|
1325
|
+
] });
|
|
1326
|
+
}
|
|
1327
|
+
function TransferConfirm({
|
|
1328
|
+
what,
|
|
1329
|
+
busy,
|
|
1330
|
+
onConfirm,
|
|
1331
|
+
onCancel,
|
|
1332
|
+
err
|
|
1333
|
+
}) {
|
|
1334
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1335
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1336
|
+
"This ",
|
|
1337
|
+
what,
|
|
1338
|
+
" 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?"
|
|
1339
|
+
] }),
|
|
1340
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
|
|
1341
|
+
/* @__PURE__ */ jsxs(FormButtons, { children: [
|
|
1342
|
+
/* @__PURE__ */ jsx(
|
|
1343
|
+
"button",
|
|
1344
|
+
{
|
|
1345
|
+
type: "button",
|
|
1346
|
+
onClick: onConfirm,
|
|
1347
|
+
disabled: busy,
|
|
1348
|
+
style: dangerBtnStyle(busy),
|
|
1349
|
+
children: busy ? "transferring\u2026" : "transfer link here"
|
|
1350
|
+
}
|
|
1351
|
+
),
|
|
1352
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, disabled: busy, style: ghostBtnStyle(busy), children: "cancel" })
|
|
1353
|
+
] })
|
|
1354
|
+
] });
|
|
1355
|
+
}
|
|
1356
|
+
function OcIdentityBond({
|
|
1357
|
+
didOc,
|
|
1358
|
+
btc,
|
|
1359
|
+
className
|
|
1360
|
+
}) {
|
|
1361
|
+
const [stage, setStage] = React3.useState("idle");
|
|
1362
|
+
const [message, setMessage] = React3.useState("");
|
|
1363
|
+
const [err, setErr] = React3.useState(null);
|
|
1364
|
+
const [result, setResult] = React3.useState(null);
|
|
1365
|
+
function nip07() {
|
|
1366
|
+
if (typeof window === "undefined") return null;
|
|
1367
|
+
const w = window;
|
|
1368
|
+
return w.nostr ?? null;
|
|
1369
|
+
}
|
|
1370
|
+
async function begin() {
|
|
1371
|
+
setErr(null);
|
|
1372
|
+
const nostr = nip07();
|
|
1373
|
+
if (!nostr) {
|
|
1374
|
+
setErr(
|
|
1375
|
+
"no Nostr signer found \xB7 install a NIP-07 extension (Alby, nos2x) to counter-sign the bond"
|
|
1376
|
+
);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
try {
|
|
1380
|
+
const sdk = await loadSdk();
|
|
1381
|
+
const pubkeyHex = (await nostr.getPublicKey()).toLowerCase();
|
|
1382
|
+
const npub = sdk.xOnlyHexToNpub(pubkeyHex);
|
|
1383
|
+
const msg = sdk.buildBindingMessage({
|
|
1384
|
+
principal: didOc,
|
|
1385
|
+
btc,
|
|
1386
|
+
nostr: npub,
|
|
1387
|
+
nonce: randomNonceHex(),
|
|
1388
|
+
issued_at: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
1389
|
+
});
|
|
1390
|
+
setMessage(msg);
|
|
1391
|
+
setStage("sign-btc");
|
|
1392
|
+
} catch (e) {
|
|
1393
|
+
setErr(e instanceof Error ? e.message : "could not start the bond ceremony");
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
async function afterBtcSign(btcSignature) {
|
|
1397
|
+
setErr(null);
|
|
1398
|
+
setStage("sign-nostr");
|
|
1399
|
+
try {
|
|
1400
|
+
const nostr = nip07();
|
|
1401
|
+
if (!nostr) throw new Error("Nostr extension went away \xB7 retry");
|
|
1402
|
+
const sdk = await loadSdk();
|
|
1403
|
+
const pubkeyHex = (await nostr.getPublicKey()).toLowerCase();
|
|
1404
|
+
const template = sdk.buildBindingEventTemplate({
|
|
1405
|
+
message,
|
|
1406
|
+
btcSignature,
|
|
1407
|
+
nostrPubkeyHex: pubkeyHex,
|
|
1408
|
+
btc
|
|
1409
|
+
});
|
|
1410
|
+
const signedEvent = await nostr.signEvent(template);
|
|
1411
|
+
const envelope = sdk.assembleBindingEnvelope({
|
|
1412
|
+
message,
|
|
1413
|
+
btcSignature,
|
|
1414
|
+
btcScheme: "bip322",
|
|
1415
|
+
nostrEvent: signedEvent
|
|
1416
|
+
});
|
|
1417
|
+
const check = sdk.verifyBinding(envelope);
|
|
1418
|
+
if (!check.valid) {
|
|
1419
|
+
throw new Error(`assembled bond failed local verification (${check.status})`);
|
|
1420
|
+
}
|
|
1421
|
+
setStage("publishing");
|
|
1422
|
+
const { publishEvent } = await loadNostrCore();
|
|
1423
|
+
const results = await publishEvent(signedEvent);
|
|
1424
|
+
const ok = results.filter((r) => r.ok).length;
|
|
1425
|
+
if (ok === 0) {
|
|
1426
|
+
throw new Error("no relay accepted the bond \xB7 check your connection and retry");
|
|
1427
|
+
}
|
|
1428
|
+
setResult({
|
|
1429
|
+
bindingId: check.valid ? check.binding_id : sdk.bindingId(message),
|
|
1430
|
+
relays: ok,
|
|
1431
|
+
relaysTotal: results.length
|
|
1432
|
+
});
|
|
1433
|
+
setStage("done");
|
|
1434
|
+
} catch (e) {
|
|
1435
|
+
setErr(e instanceof Error ? e.message : "bond publish failed");
|
|
1436
|
+
setStage("sign-btc");
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return /* @__PURE__ */ jsxs("div", { className, style: panelStyle("accent"), "data-oc-identity-bond": "", children: [
|
|
1440
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 identity bond \xB7 publish to nostr" }),
|
|
1441
|
+
stage === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1442
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1443
|
+
"Publish a ",
|
|
1444
|
+
/* @__PURE__ */ jsx("strong", { children: "Binding Attestation" }),
|
|
1445
|
+
" \u2014 a Bitcoin-signed, Nostr-signed proof that this address and your Nostr key are one identity. It lives on Nostr, verifies with zero trust in OrangeCheck, and means your identity survives even if OC's database does not. Two signatures: your Bitcoin wallet, then your Nostr extension."
|
|
1446
|
+
] }),
|
|
1447
|
+
/* @__PURE__ */ jsx(FormButtons, { children: /* @__PURE__ */ jsx(
|
|
1448
|
+
"button",
|
|
1449
|
+
{
|
|
1450
|
+
type: "button",
|
|
1451
|
+
onClick: () => void begin(),
|
|
1452
|
+
style: primaryBtnStyle(false),
|
|
1453
|
+
children: "create & publish bond"
|
|
1454
|
+
}
|
|
1455
|
+
) })
|
|
1456
|
+
] }),
|
|
1457
|
+
stage === "sign-btc" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1458
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1459
|
+
"Step 1 of 2 \u2014 sign the binding message with the Bitcoin wallet that controls ",
|
|
1460
|
+
/* @__PURE__ */ jsx("span", { style: { fontFamily: MONO, wordBreak: "break-all" }, children: btc }),
|
|
1461
|
+
"."
|
|
1462
|
+
] }),
|
|
1463
|
+
/* @__PURE__ */ jsx(
|
|
1464
|
+
LazyWalletButton,
|
|
1465
|
+
{
|
|
1466
|
+
address: btc,
|
|
1467
|
+
message,
|
|
1468
|
+
showManual: true,
|
|
1469
|
+
layout: "list",
|
|
1470
|
+
heading: null,
|
|
1471
|
+
onSigned: (sig) => void afterBtcSign(sig),
|
|
1472
|
+
onError: (e) => setErr(e?.message ?? "wallet rejected the signature")
|
|
1473
|
+
}
|
|
1474
|
+
)
|
|
1475
|
+
] }),
|
|
1476
|
+
(stage === "sign-nostr" || stage === "publishing") && /* @__PURE__ */ jsx("p", { style: bodyStyle, children: stage === "sign-nostr" ? "Step 2 of 2 \u2014 approve the signature in your Nostr extension\u2026" : "Publishing the bond to Nostr\u2026" }),
|
|
1477
|
+
stage === "done" && result && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1478
|
+
/* @__PURE__ */ jsx(SectionLabel, { tone: "success", children: "\xA7 bond published" }),
|
|
1479
|
+
/* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
|
|
1480
|
+
"Your identity bond is live on ",
|
|
1481
|
+
result.relays,
|
|
1482
|
+
"/",
|
|
1483
|
+
result.relaysTotal,
|
|
1484
|
+
" ",
|
|
1485
|
+
"relay",
|
|
1486
|
+
result.relays === 1 ? "" : "s",
|
|
1487
|
+
". Anyone can now verify the BTC \u21C4 Nostr binding offline."
|
|
1488
|
+
] }),
|
|
1489
|
+
/* @__PURE__ */ jsxs(
|
|
1490
|
+
"div",
|
|
1491
|
+
{
|
|
1492
|
+
style: {
|
|
1493
|
+
fontFamily: MONO,
|
|
1494
|
+
fontSize: 11,
|
|
1495
|
+
color: V.muted,
|
|
1496
|
+
wordBreak: "break-all"
|
|
1497
|
+
},
|
|
1498
|
+
children: [
|
|
1499
|
+
"binding_id: ",
|
|
1500
|
+
result.bindingId
|
|
1501
|
+
]
|
|
1502
|
+
}
|
|
1503
|
+
)
|
|
1504
|
+
] }),
|
|
1505
|
+
err && /* @__PURE__ */ jsx(ErrorLine, { children: err })
|
|
1506
|
+
] });
|
|
1507
|
+
}
|
|
1508
|
+
function randomNonceHex() {
|
|
1509
|
+
const b = new Uint8Array(16);
|
|
1510
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
1511
|
+
crypto.getRandomValues(b);
|
|
1512
|
+
} else {
|
|
1513
|
+
for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
1514
|
+
}
|
|
1515
|
+
let out = "";
|
|
1516
|
+
for (const x of b) out += x.toString(16).padStart(2, "0");
|
|
1517
|
+
return out;
|
|
1518
|
+
}
|
|
1519
|
+
async function loadSdk() {
|
|
1520
|
+
try {
|
|
1521
|
+
return await import('@orangecheck/sdk');
|
|
1522
|
+
} catch {
|
|
1523
|
+
throw new Error("@orangecheck/sdk not installed \xB7 add it to your site to publish bonds");
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async function loadNostrCore() {
|
|
1527
|
+
try {
|
|
1528
|
+
return await import('@orangecheck/nostr-core');
|
|
1529
|
+
} catch {
|
|
1530
|
+
throw new Error(
|
|
1531
|
+
"@orangecheck/nostr-core not installed \xB7 add it to your site to publish bonds"
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function LazyWalletButton(props) {
|
|
1536
|
+
const [Comp, setComp] = React3.useState(null);
|
|
1537
|
+
const [failed, setFailed] = React3.useState(false);
|
|
1538
|
+
React3.useEffect(() => {
|
|
1539
|
+
let alive = true;
|
|
1540
|
+
import('@orangecheck/wallet-adapter/react').then((m) => {
|
|
1541
|
+
if (alive) {
|
|
1542
|
+
setComp(
|
|
1543
|
+
() => m.OcWalletButton
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
}).catch(() => {
|
|
1547
|
+
if (alive) setFailed(true);
|
|
1548
|
+
});
|
|
1549
|
+
return () => {
|
|
1550
|
+
alive = false;
|
|
1551
|
+
};
|
|
1552
|
+
}, []);
|
|
1553
|
+
if (failed) {
|
|
1554
|
+
return /* @__PURE__ */ jsx(ErrorLine, { children: "@orangecheck/wallet-adapter not installed \xB7 add it to your site to link a wallet" });
|
|
1555
|
+
}
|
|
1556
|
+
if (!Comp) return /* @__PURE__ */ jsxs("span", { style: hintStyle, children: [
|
|
1557
|
+
"> ",
|
|
1558
|
+
"loading wallet options\u2026"
|
|
1559
|
+
] });
|
|
1560
|
+
return /* @__PURE__ */ jsx(Comp, { ...props });
|
|
1561
|
+
}
|
|
1562
|
+
function mask(value, kind) {
|
|
1563
|
+
if (kind === "email") {
|
|
1564
|
+
const [user, host] = value.split("@");
|
|
1565
|
+
if (!user || !host) return "\u2022\u2022\u2022\u2022";
|
|
1566
|
+
const m = user.length <= 2 ? "\u2022".repeat(user.length) : user[0] + "\u2022".repeat(user.length - 2) + user.slice(-1);
|
|
1567
|
+
return `${m}@${host}`;
|
|
1568
|
+
}
|
|
1569
|
+
if (value.length <= 12) return "\u2022".repeat(value.length);
|
|
1570
|
+
return `${value.slice(0, 6)}\u2026${value.slice(-4)}`;
|
|
1571
|
+
}
|
|
1572
|
+
function SectionLabel({
|
|
1573
|
+
children,
|
|
1574
|
+
tone = "primary"
|
|
1575
|
+
}) {
|
|
1576
|
+
const color = tone === "success" ? V.success : tone === "warn" ? V.warn : V.primary;
|
|
1577
|
+
return /* @__PURE__ */ jsx(
|
|
1578
|
+
"div",
|
|
1579
|
+
{
|
|
1580
|
+
style: {
|
|
1581
|
+
color,
|
|
1582
|
+
fontFamily: MONO,
|
|
1583
|
+
fontSize: 10,
|
|
1584
|
+
letterSpacing: "0.18em",
|
|
1585
|
+
textTransform: "uppercase",
|
|
1586
|
+
marginBottom: 8
|
|
1587
|
+
},
|
|
1588
|
+
children
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
function ErrorLine({ children }) {
|
|
1593
|
+
return /* @__PURE__ */ jsx(
|
|
1594
|
+
"p",
|
|
1595
|
+
{
|
|
1596
|
+
role: "alert",
|
|
1597
|
+
style: {
|
|
1598
|
+
margin: "8px 0 0",
|
|
1599
|
+
color: V.destructive,
|
|
1600
|
+
fontFamily: MONO,
|
|
1601
|
+
fontSize: 11
|
|
1602
|
+
},
|
|
1603
|
+
children
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
function FormButtons({ children }) {
|
|
1608
|
+
return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }, children });
|
|
1609
|
+
}
|
|
1610
|
+
var MONO = "ui-monospace, SFMono-Regular, monospace";
|
|
1611
|
+
var V = {
|
|
1612
|
+
primary: "var(--primary, #f97316)",
|
|
1613
|
+
foreground: "var(--foreground, #fafafa)",
|
|
1614
|
+
muted: "var(--muted-foreground, #a1a1aa)",
|
|
1615
|
+
background: "var(--background, #0a0a0a)",
|
|
1616
|
+
card: "var(--card, #111113)",
|
|
1617
|
+
border: "var(--border, #27272a)",
|
|
1618
|
+
destructive: "var(--destructive, #ef4444)",
|
|
1619
|
+
success: "var(--success, #22c55e)",
|
|
1620
|
+
warn: "var(--warning, #eab308)"
|
|
1621
|
+
};
|
|
1622
|
+
function panelStyle(tone = "default") {
|
|
1623
|
+
const base = {
|
|
1624
|
+
background: V.card,
|
|
1625
|
+
border: `1px solid ${V.border}`,
|
|
1626
|
+
padding: "1.25rem"
|
|
1627
|
+
};
|
|
1628
|
+
if (tone === "plain") return { ...base, padding: 0 };
|
|
1629
|
+
if (tone === "accent") return { ...base, borderColor: V.primary, background: "var(--card, #111113)" };
|
|
1630
|
+
if (tone === "success") return { ...base, borderColor: V.success };
|
|
1631
|
+
if (tone === "warn") return { ...base, borderColor: V.warn };
|
|
1632
|
+
return base;
|
|
1633
|
+
}
|
|
1634
|
+
var bodyStyle = {
|
|
1635
|
+
color: V.muted,
|
|
1636
|
+
fontSize: 13,
|
|
1637
|
+
lineHeight: 1.55,
|
|
1638
|
+
margin: "0 0 12px"
|
|
1639
|
+
};
|
|
1640
|
+
var hintStyle = {
|
|
1641
|
+
color: V.muted,
|
|
1642
|
+
fontFamily: MONO,
|
|
1643
|
+
fontSize: 10.5,
|
|
1644
|
+
opacity: 0.8
|
|
1645
|
+
};
|
|
1646
|
+
var codeStyle = {
|
|
1647
|
+
fontFamily: MONO,
|
|
1648
|
+
color: V.foreground
|
|
1649
|
+
};
|
|
1650
|
+
var rowStyle = {
|
|
1651
|
+
display: "flex",
|
|
1652
|
+
flexWrap: "wrap",
|
|
1653
|
+
alignItems: "center",
|
|
1654
|
+
justifyContent: "space-between",
|
|
1655
|
+
gap: 12,
|
|
1656
|
+
padding: "1rem"
|
|
1657
|
+
};
|
|
1658
|
+
var rowLabelStyle = {
|
|
1659
|
+
color: V.foreground,
|
|
1660
|
+
fontFamily: MONO,
|
|
1661
|
+
fontSize: 10.5,
|
|
1662
|
+
letterSpacing: "0.12em",
|
|
1663
|
+
textTransform: "uppercase",
|
|
1664
|
+
opacity: 0.8
|
|
1665
|
+
};
|
|
1666
|
+
var inputStyle = {
|
|
1667
|
+
width: "100%",
|
|
1668
|
+
padding: "0.55rem 0.75rem",
|
|
1669
|
+
background: V.background,
|
|
1670
|
+
color: V.foreground,
|
|
1671
|
+
border: `1px solid ${V.border}`,
|
|
1672
|
+
borderRadius: 6,
|
|
1673
|
+
fontFamily: MONO,
|
|
1674
|
+
// 16px — never lower · suppresses iOS Safari focus auto-zoom.
|
|
1675
|
+
fontSize: 16,
|
|
1676
|
+
outline: "none",
|
|
1677
|
+
boxSizing: "border-box"
|
|
1678
|
+
};
|
|
1679
|
+
function btnBase(disabled) {
|
|
1680
|
+
return {
|
|
1681
|
+
padding: "0.5rem 0.875rem",
|
|
1682
|
+
borderRadius: 6,
|
|
1683
|
+
fontFamily: MONO,
|
|
1684
|
+
fontSize: 10.5,
|
|
1685
|
+
letterSpacing: "0.12em",
|
|
1686
|
+
textTransform: "uppercase",
|
|
1687
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1688
|
+
opacity: disabled ? 0.5 : 1
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function primaryBtnStyle(disabled) {
|
|
1692
|
+
return {
|
|
1693
|
+
...btnBase(disabled),
|
|
1694
|
+
background: V.primary,
|
|
1695
|
+
color: "var(--primary-foreground, #0a0a0a)",
|
|
1696
|
+
border: `1px solid ${V.primary}`,
|
|
1697
|
+
fontWeight: 600
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
function ghostBtnStyle(disabled) {
|
|
1701
|
+
return {
|
|
1702
|
+
...btnBase(disabled),
|
|
1703
|
+
background: V.background,
|
|
1704
|
+
color: V.foreground,
|
|
1705
|
+
border: `1px solid ${V.border}`
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
function dangerBtnStyle(disabled) {
|
|
1709
|
+
return {
|
|
1710
|
+
...btnBase(disabled),
|
|
1711
|
+
background: "transparent",
|
|
1712
|
+
color: V.destructive,
|
|
1713
|
+
border: `1px solid ${V.destructive}`
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
var primaryChipStyle = {
|
|
1717
|
+
...btnBase(false),
|
|
1718
|
+
background: "transparent",
|
|
1719
|
+
color: V.primary,
|
|
1720
|
+
border: `1px solid ${V.primary}`
|
|
1721
|
+
};
|
|
751
1722
|
function safeReturnTo(input) {
|
|
752
1723
|
const candidate = input ?? "/";
|
|
753
1724
|
if (typeof candidate !== "string") return "/";
|
|
@@ -763,6 +1734,7 @@ function OcSignIn({
|
|
|
763
1734
|
returnTo,
|
|
764
1735
|
onSuccess,
|
|
765
1736
|
resolveReturnTo,
|
|
1737
|
+
linkPrompt,
|
|
766
1738
|
authOrigin = "https://ochk.io",
|
|
767
1739
|
initialPath = "wallet",
|
|
768
1740
|
paths,
|
|
@@ -771,24 +1743,34 @@ function OcSignIn({
|
|
|
771
1743
|
const walletEnabled = paths?.wallet ?? true;
|
|
772
1744
|
const emailEnabled = paths?.email ?? true;
|
|
773
1745
|
const safeReturn = safeReturnTo(returnTo);
|
|
774
|
-
const [path, setPath] =
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
onSuccess(account, token);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
1746
|
+
const [path, setPath] = React3.useState(initialPath);
|
|
1747
|
+
const [signedIn, setSignedIn] = React3.useState(null);
|
|
1748
|
+
const navigate = React3.useCallback(
|
|
1749
|
+
async (account) => {
|
|
781
1750
|
if (resolveReturnTo) {
|
|
782
1751
|
try {
|
|
783
|
-
|
|
784
|
-
hardNavigate(safeReturnTo(target));
|
|
1752
|
+
hardNavigate(safeReturnTo(await resolveReturnTo(account)));
|
|
785
1753
|
return;
|
|
786
1754
|
} catch {
|
|
787
1755
|
}
|
|
788
1756
|
}
|
|
789
1757
|
hardNavigate(safeReturn);
|
|
790
1758
|
},
|
|
791
|
-
[
|
|
1759
|
+
[resolveReturnTo, safeReturn]
|
|
1760
|
+
);
|
|
1761
|
+
const handleSuccess = React3.useCallback(
|
|
1762
|
+
async (account, token) => {
|
|
1763
|
+
if (onSuccess) {
|
|
1764
|
+
onSuccess(account, token);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (linkPrompt) {
|
|
1768
|
+
setSignedIn(account);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
await navigate(account);
|
|
1772
|
+
},
|
|
1773
|
+
[onSuccess, linkPrompt, navigate]
|
|
792
1774
|
);
|
|
793
1775
|
if (!walletEnabled && !emailEnabled) {
|
|
794
1776
|
return /* @__PURE__ */ jsxs(
|
|
@@ -815,6 +1797,15 @@ function OcSignIn({
|
|
|
815
1797
|
}
|
|
816
1798
|
);
|
|
817
1799
|
}
|
|
1800
|
+
if (signedIn) {
|
|
1801
|
+
return /* @__PURE__ */ jsx("div", { className, "data-oc-signin": "", children: /* @__PURE__ */ jsx(
|
|
1802
|
+
LinkPromptStep,
|
|
1803
|
+
{
|
|
1804
|
+
authOrigin,
|
|
1805
|
+
onContinue: () => void navigate(signedIn)
|
|
1806
|
+
}
|
|
1807
|
+
) });
|
|
1808
|
+
}
|
|
818
1809
|
const showWallet = walletEnabled && (path === "wallet" || !emailEnabled);
|
|
819
1810
|
const showEmail = emailEnabled && (path === "email" || !walletEnabled);
|
|
820
1811
|
const bothEnabled = walletEnabled && emailEnabled;
|
|
@@ -857,6 +1848,25 @@ function OcSignIn({
|
|
|
857
1848
|
] })
|
|
858
1849
|
] });
|
|
859
1850
|
}
|
|
1851
|
+
function LinkPromptStep({
|
|
1852
|
+
authOrigin,
|
|
1853
|
+
onContinue
|
|
1854
|
+
}) {
|
|
1855
|
+
return /* @__PURE__ */ jsxs("div", { "data-oc-signin-linkprompt": "", children: [
|
|
1856
|
+
/* @__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." }),
|
|
1857
|
+
/* @__PURE__ */ jsx(OcLinkedIdentities, { authOrigin }),
|
|
1858
|
+
/* @__PURE__ */ jsx(
|
|
1859
|
+
"button",
|
|
1860
|
+
{
|
|
1861
|
+
type: "button",
|
|
1862
|
+
onClick: onContinue,
|
|
1863
|
+
style: submitStyle(false),
|
|
1864
|
+
"data-oc-signin-continue": "",
|
|
1865
|
+
children: "continue \u2192"
|
|
1866
|
+
}
|
|
1867
|
+
)
|
|
1868
|
+
] });
|
|
1869
|
+
}
|
|
860
1870
|
function SigninTab({
|
|
861
1871
|
active,
|
|
862
1872
|
onClick,
|
|
@@ -888,10 +1898,10 @@ function SigninTab({
|
|
|
888
1898
|
);
|
|
889
1899
|
}
|
|
890
1900
|
function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
891
|
-
const [address, setAddress] =
|
|
892
|
-
const [error, setError] =
|
|
893
|
-
const [submitting, setSubmitting] =
|
|
894
|
-
const [stage, setStage] =
|
|
1901
|
+
const [address, setAddress] = React3.useState("");
|
|
1902
|
+
const [error, setError] = React3.useState(null);
|
|
1903
|
+
const [submitting, setSubmitting] = React3.useState(false);
|
|
1904
|
+
const [stage, setStage] = React3.useState("enter");
|
|
895
1905
|
async function signIn(e) {
|
|
896
1906
|
e.preventDefault();
|
|
897
1907
|
const addr = address.trim();
|
|
@@ -970,10 +1980,10 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
|
970
1980
|
spellCheck: false,
|
|
971
1981
|
required: true,
|
|
972
1982
|
"aria-invalid": error ? true : void 0,
|
|
973
|
-
style:
|
|
1983
|
+
style: inputStyle2
|
|
974
1984
|
}
|
|
975
1985
|
),
|
|
976
|
-
error && /* @__PURE__ */ jsx(
|
|
1986
|
+
error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
|
|
977
1987
|
/* @__PURE__ */ jsx(
|
|
978
1988
|
"button",
|
|
979
1989
|
{
|
|
@@ -987,13 +1997,13 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
|
987
1997
|
] });
|
|
988
1998
|
}
|
|
989
1999
|
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] =
|
|
2000
|
+
const [stage, setStage] = React3.useState("enter");
|
|
2001
|
+
const [email, setEmail] = React3.useState("");
|
|
2002
|
+
const [emailError, setEmailError] = React3.useState(null);
|
|
2003
|
+
const [code, setCode] = React3.useState("");
|
|
2004
|
+
const [codeError, setCodeError] = React3.useState(null);
|
|
2005
|
+
const [token, setToken] = React3.useState(null);
|
|
2006
|
+
const [submitting, setSubmitting] = React3.useState(false);
|
|
997
2007
|
async function start(e) {
|
|
998
2008
|
e.preventDefault();
|
|
999
2009
|
const trimmed = email.trim().toLowerCase();
|
|
@@ -1069,10 +2079,10 @@ function EmailFlow({ authOrigin, onSuccess }) {
|
|
|
1069
2079
|
placeholder: "you@example.com",
|
|
1070
2080
|
required: true,
|
|
1071
2081
|
"aria-invalid": emailError ? true : void 0,
|
|
1072
|
-
style:
|
|
2082
|
+
style: inputStyle2
|
|
1073
2083
|
}
|
|
1074
2084
|
),
|
|
1075
|
-
emailError && /* @__PURE__ */ jsx(
|
|
2085
|
+
emailError && /* @__PURE__ */ jsx(ErrorLine2, { children: emailError }),
|
|
1076
2086
|
/* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" }),
|
|
1077
2087
|
/* @__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
2088
|
] });
|
|
@@ -1101,10 +2111,10 @@ function EmailFlow({ authOrigin, onSuccess }) {
|
|
|
1101
2111
|
required: true,
|
|
1102
2112
|
autoFocus: true,
|
|
1103
2113
|
"aria-invalid": codeError ? true : void 0,
|
|
1104
|
-
style: { ...
|
|
2114
|
+
style: { ...inputStyle2, letterSpacing: "0.4em", fontSize: 16 }
|
|
1105
2115
|
}
|
|
1106
2116
|
),
|
|
1107
|
-
codeError && /* @__PURE__ */ jsx(
|
|
2117
|
+
codeError && /* @__PURE__ */ jsx(ErrorLine2, { children: codeError }),
|
|
1108
2118
|
/* @__PURE__ */ jsx(
|
|
1109
2119
|
"button",
|
|
1110
2120
|
{
|
|
@@ -1191,7 +2201,7 @@ function Label({ children }) {
|
|
|
1191
2201
|
}
|
|
1192
2202
|
);
|
|
1193
2203
|
}
|
|
1194
|
-
var
|
|
2204
|
+
var inputStyle2 = {
|
|
1195
2205
|
width: "100%",
|
|
1196
2206
|
padding: "0.55rem 0.75rem",
|
|
1197
2207
|
background: "var(--background, #0a0a0a)",
|
|
@@ -1223,7 +2233,7 @@ function submitStyle(disabled) {
|
|
|
1223
2233
|
width: "100%"
|
|
1224
2234
|
};
|
|
1225
2235
|
}
|
|
1226
|
-
function
|
|
2236
|
+
function ErrorLine2({ children }) {
|
|
1227
2237
|
return /* @__PURE__ */ jsx(
|
|
1228
2238
|
"p",
|
|
1229
2239
|
{
|
|
@@ -1270,13 +2280,13 @@ function isAbortError(err) {
|
|
|
1270
2280
|
}
|
|
1271
2281
|
function useWebAuthnRegister(opts) {
|
|
1272
2282
|
const authOrigin = resolveAuthOrigin(opts);
|
|
1273
|
-
const [status, setStatus] =
|
|
1274
|
-
const [error, setError] =
|
|
1275
|
-
const reset =
|
|
2283
|
+
const [status, setStatus] = React3.useState("idle");
|
|
2284
|
+
const [error, setError] = React3.useState(null);
|
|
2285
|
+
const reset = React3.useCallback(() => {
|
|
1276
2286
|
setStatus("idle");
|
|
1277
2287
|
setError(null);
|
|
1278
2288
|
}, []);
|
|
1279
|
-
const register =
|
|
2289
|
+
const register = React3.useCallback(
|
|
1280
2290
|
async (args) => {
|
|
1281
2291
|
setError(null);
|
|
1282
2292
|
setStatus("requesting-options");
|
|
@@ -1338,10 +2348,10 @@ function useWebAuthnRegister(opts) {
|
|
|
1338
2348
|
function useWebAuthnList(opts) {
|
|
1339
2349
|
const authOrigin = resolveAuthOrigin(opts);
|
|
1340
2350
|
const { status: sessionStatus } = useOcSession();
|
|
1341
|
-
const [credentials, setCredentials] =
|
|
1342
|
-
const [status, setStatus] =
|
|
1343
|
-
const [error, setError] =
|
|
1344
|
-
const refetch =
|
|
2351
|
+
const [credentials, setCredentials] = React3.useState([]);
|
|
2352
|
+
const [status, setStatus] = React3.useState("loading");
|
|
2353
|
+
const [error, setError] = React3.useState(null);
|
|
2354
|
+
const refetch = React3.useCallback(async () => {
|
|
1345
2355
|
setError(null);
|
|
1346
2356
|
if (sessionStatus !== "authenticated") {
|
|
1347
2357
|
setCredentials([]);
|
|
@@ -1369,10 +2379,10 @@ function useWebAuthnList(opts) {
|
|
|
1369
2379
|
setStatus("error");
|
|
1370
2380
|
}
|
|
1371
2381
|
}, [authOrigin, sessionStatus]);
|
|
1372
|
-
|
|
2382
|
+
React3.useEffect(() => {
|
|
1373
2383
|
void refetch();
|
|
1374
2384
|
}, [refetch]);
|
|
1375
|
-
const rename =
|
|
2385
|
+
const rename = React3.useCallback(
|
|
1376
2386
|
async (id, label) => {
|
|
1377
2387
|
try {
|
|
1378
2388
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -1397,7 +2407,7 @@ function useWebAuthnList(opts) {
|
|
|
1397
2407
|
},
|
|
1398
2408
|
[authOrigin, refetch]
|
|
1399
2409
|
);
|
|
1400
|
-
const remove =
|
|
2410
|
+
const remove = React3.useCallback(
|
|
1401
2411
|
async (id) => {
|
|
1402
2412
|
try {
|
|
1403
2413
|
const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
|
|
@@ -1425,13 +2435,13 @@ function useWebAuthnList(opts) {
|
|
|
1425
2435
|
function useStepUpAuth(opts) {
|
|
1426
2436
|
const authOrigin = resolveAuthOrigin(opts);
|
|
1427
2437
|
const { refresh } = useOcSession();
|
|
1428
|
-
const [status, setStatus] =
|
|
1429
|
-
const [error, setError] =
|
|
1430
|
-
const reset =
|
|
2438
|
+
const [status, setStatus] = React3.useState("idle");
|
|
2439
|
+
const [error, setError] = React3.useState(null);
|
|
2440
|
+
const reset = React3.useCallback(() => {
|
|
1431
2441
|
setStatus("idle");
|
|
1432
2442
|
setError(null);
|
|
1433
2443
|
}, []);
|
|
1434
|
-
const stepUp =
|
|
2444
|
+
const stepUp = React3.useCallback(
|
|
1435
2445
|
async (args) => {
|
|
1436
2446
|
setError(null);
|
|
1437
2447
|
setStatus("requesting-options");
|
|
@@ -1516,6 +2526,6 @@ function handleSudoRequired(body, args = {}) {
|
|
|
1516
2526
|
return false;
|
|
1517
2527
|
}
|
|
1518
2528
|
|
|
1519
|
-
export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcSessionProvider, OcSignIn, OcSignInButton, buildSignInUrl, handleSudoRequired, redirectToSudo, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister };
|
|
2529
|
+
export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcIdentityBond, OcLinkedIdentities, OcSessionProvider, OcSignIn, OcSignInButton, buildSignInUrl, handleSudoRequired, redirectToSudo, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister };
|
|
1520
2530
|
//# sourceMappingURL=index.mjs.map
|
|
1521
2531
|
//# sourceMappingURL=index.mjs.map
|