@orangecheck/auth-client 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +511 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +511 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -39,18 +39,20 @@ declare function useOptionalOcSession(): OcSessionState | null;
|
|
|
39
39
|
|
|
40
40
|
interface OcAccountChipProps {
|
|
41
41
|
dashboardUrl?: string;
|
|
42
|
+
signInUrl?: string;
|
|
42
43
|
signInLabel?: string;
|
|
43
44
|
className?: string;
|
|
44
45
|
triggerClassName?: string;
|
|
45
46
|
popoverClassName?: string;
|
|
46
47
|
menuItemClassName?: string;
|
|
47
48
|
}
|
|
48
|
-
declare function OcAccountChip({ dashboardUrl, signInLabel, className, triggerClassName, popoverClassName, menuItemClassName, }: OcAccountChipProps): React.ReactElement | null;
|
|
49
|
+
declare function OcAccountChip({ dashboardUrl, signInUrl: signInUrlOverride, signInLabel, className, triggerClassName, popoverClassName, menuItemClassName, }: OcAccountChipProps): React.ReactElement | null;
|
|
49
50
|
interface OcSignInButtonProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
50
51
|
label?: string;
|
|
51
52
|
eager?: boolean;
|
|
53
|
+
signInUrl?: string;
|
|
52
54
|
}
|
|
53
|
-
declare function OcSignInButton({ label, eager, className, ...rest }: OcSignInButtonProps): React.ReactElement | null;
|
|
55
|
+
declare function OcSignInButton({ label, eager, className, signInUrl: signInUrlOverride, ...rest }: OcSignInButtonProps): React.ReactElement | null;
|
|
54
56
|
interface OcAccountPillProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
55
57
|
dashboardUrl?: string;
|
|
56
58
|
render?: (account: {
|
|
@@ -92,4 +94,18 @@ interface UseOcAddressSuggestionReturn {
|
|
|
92
94
|
declare function useOcAddressSuggestion(options: UseOcAddressSuggestionOptions): UseOcAddressSuggestionReturn;
|
|
93
95
|
declare const OcAddressInput: React.ForwardRefExoticComponent<OcAddressInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
interface OcSignInProps {
|
|
98
|
+
audience: string;
|
|
99
|
+
returnTo?: string;
|
|
100
|
+
onSuccess?: (account: OcAccount) => void;
|
|
101
|
+
authOrigin?: string;
|
|
102
|
+
initialPath?: 'wallet' | 'email';
|
|
103
|
+
paths?: {
|
|
104
|
+
wallet?: boolean;
|
|
105
|
+
email?: boolean;
|
|
106
|
+
};
|
|
107
|
+
className?: string;
|
|
108
|
+
}
|
|
109
|
+
declare function OcSignIn({ audience, returnTo, onSuccess, authOrigin, initialPath, paths, className, }: OcSignInProps): React.ReactElement;
|
|
110
|
+
|
|
111
|
+
export { DEFAULT_CONFIG, type OcAccount, OcAccountChip, type OcAccountChipProps, OcAccountPill, type OcAccountPillProps, OcAddressInput, type OcAddressInputProps, type OcAuthConfig, OcSessionProvider, type OcSessionState, type OcSessionStatus, OcSignIn, OcSignInButton, type OcSignInButtonProps, type OcSignInProps, type UseOcAddressSuggestionOptions, type UseOcAddressSuggestionReturn, buildSignInUrl, useOcAddressSuggestion, useOcSession, useOptionalOcSession };
|
package/dist/index.d.ts
CHANGED
|
@@ -39,18 +39,20 @@ declare function useOptionalOcSession(): OcSessionState | null;
|
|
|
39
39
|
|
|
40
40
|
interface OcAccountChipProps {
|
|
41
41
|
dashboardUrl?: string;
|
|
42
|
+
signInUrl?: string;
|
|
42
43
|
signInLabel?: string;
|
|
43
44
|
className?: string;
|
|
44
45
|
triggerClassName?: string;
|
|
45
46
|
popoverClassName?: string;
|
|
46
47
|
menuItemClassName?: string;
|
|
47
48
|
}
|
|
48
|
-
declare function OcAccountChip({ dashboardUrl, signInLabel, className, triggerClassName, popoverClassName, menuItemClassName, }: OcAccountChipProps): React.ReactElement | null;
|
|
49
|
+
declare function OcAccountChip({ dashboardUrl, signInUrl: signInUrlOverride, signInLabel, className, triggerClassName, popoverClassName, menuItemClassName, }: OcAccountChipProps): React.ReactElement | null;
|
|
49
50
|
interface OcSignInButtonProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
50
51
|
label?: string;
|
|
51
52
|
eager?: boolean;
|
|
53
|
+
signInUrl?: string;
|
|
52
54
|
}
|
|
53
|
-
declare function OcSignInButton({ label, eager, className, ...rest }: OcSignInButtonProps): React.ReactElement | null;
|
|
55
|
+
declare function OcSignInButton({ label, eager, className, signInUrl: signInUrlOverride, ...rest }: OcSignInButtonProps): React.ReactElement | null;
|
|
54
56
|
interface OcAccountPillProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
55
57
|
dashboardUrl?: string;
|
|
56
58
|
render?: (account: {
|
|
@@ -92,4 +94,18 @@ interface UseOcAddressSuggestionReturn {
|
|
|
92
94
|
declare function useOcAddressSuggestion(options: UseOcAddressSuggestionOptions): UseOcAddressSuggestionReturn;
|
|
93
95
|
declare const OcAddressInput: React.ForwardRefExoticComponent<OcAddressInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
interface OcSignInProps {
|
|
98
|
+
audience: string;
|
|
99
|
+
returnTo?: string;
|
|
100
|
+
onSuccess?: (account: OcAccount) => void;
|
|
101
|
+
authOrigin?: string;
|
|
102
|
+
initialPath?: 'wallet' | 'email';
|
|
103
|
+
paths?: {
|
|
104
|
+
wallet?: boolean;
|
|
105
|
+
email?: boolean;
|
|
106
|
+
};
|
|
107
|
+
className?: string;
|
|
108
|
+
}
|
|
109
|
+
declare function OcSignIn({ audience, returnTo, onSuccess, authOrigin, initialPath, paths, className, }: OcSignInProps): React.ReactElement;
|
|
110
|
+
|
|
111
|
+
export { DEFAULT_CONFIG, type OcAccount, OcAccountChip, type OcAccountChipProps, OcAccountPill, type OcAccountPillProps, OcAddressInput, type OcAddressInputProps, type OcAuthConfig, OcSessionProvider, type OcSessionState, type OcSessionStatus, OcSignIn, OcSignInButton, type OcSignInButtonProps, type OcSignInProps, type UseOcAddressSuggestionOptions, type UseOcAddressSuggestionReturn, buildSignInUrl, useOcAddressSuggestion, useOcSession, useOptionalOcSession };
|
package/dist/index.js
CHANGED
|
@@ -154,13 +154,15 @@ function useUniqueId(prefix) {
|
|
|
154
154
|
}
|
|
155
155
|
function OcAccountChip({
|
|
156
156
|
dashboardUrl = "https://ochk.io/dashboard",
|
|
157
|
+
signInUrl: signInUrlOverride,
|
|
157
158
|
signInLabel = "sign in",
|
|
158
159
|
className,
|
|
159
160
|
triggerClassName,
|
|
160
161
|
popoverClassName,
|
|
161
162
|
menuItemClassName
|
|
162
163
|
}) {
|
|
163
|
-
const { status, account, signInUrl, signOut } = useOcSession();
|
|
164
|
+
const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
|
|
165
|
+
const signInUrl = signInUrlOverride ?? sessionSignInUrl;
|
|
164
166
|
const [open, setOpen] = React2__namespace.useState(false);
|
|
165
167
|
const wrapRef = React2__namespace.useRef(null);
|
|
166
168
|
React2__namespace.useEffect(() => {
|
|
@@ -417,16 +419,18 @@ function OcSignInButton({
|
|
|
417
419
|
label = "sign in with bitcoin",
|
|
418
420
|
eager = false,
|
|
419
421
|
className,
|
|
422
|
+
signInUrl: signInUrlOverride,
|
|
420
423
|
...rest
|
|
421
424
|
}) {
|
|
422
|
-
const { status, signInUrl } = useOcSession();
|
|
425
|
+
const { status, signInUrl: sessionSignInUrl } = useOcSession();
|
|
426
|
+
const href = signInUrlOverride ?? sessionSignInUrl;
|
|
423
427
|
if (status === "authenticated") return null;
|
|
424
428
|
if (!eager && status === "loading") return null;
|
|
425
429
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
426
430
|
"a",
|
|
427
431
|
{
|
|
428
432
|
...rest,
|
|
429
|
-
href
|
|
433
|
+
href,
|
|
430
434
|
className,
|
|
431
435
|
"data-oc-sign-in-button": "",
|
|
432
436
|
children: label
|
|
@@ -764,12 +768,516 @@ var OcAddressInput = React2__namespace.forwardRef(
|
|
|
764
768
|
);
|
|
765
769
|
}
|
|
766
770
|
);
|
|
771
|
+
function safeReturnTo(input) {
|
|
772
|
+
const candidate = input ?? "/";
|
|
773
|
+
if (typeof candidate !== "string") return "/";
|
|
774
|
+
if (!candidate.startsWith("/") || candidate.startsWith("//")) return "/";
|
|
775
|
+
return candidate;
|
|
776
|
+
}
|
|
777
|
+
function hardNavigate(target) {
|
|
778
|
+
if (typeof window === "undefined") return;
|
|
779
|
+
window.location.assign(target);
|
|
780
|
+
}
|
|
781
|
+
function OcSignIn({
|
|
782
|
+
audience,
|
|
783
|
+
returnTo,
|
|
784
|
+
onSuccess,
|
|
785
|
+
authOrigin = "https://ochk.io",
|
|
786
|
+
initialPath = "wallet",
|
|
787
|
+
paths,
|
|
788
|
+
className
|
|
789
|
+
}) {
|
|
790
|
+
const walletEnabled = paths?.wallet ?? true;
|
|
791
|
+
const emailEnabled = paths?.email ?? true;
|
|
792
|
+
const safeReturn = safeReturnTo(returnTo);
|
|
793
|
+
const [path, setPath] = React2__namespace.useState(initialPath);
|
|
794
|
+
const handleSuccess = React2__namespace.useCallback(
|
|
795
|
+
(account) => {
|
|
796
|
+
if (onSuccess) {
|
|
797
|
+
onSuccess(account);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
hardNavigate(safeReturn);
|
|
801
|
+
},
|
|
802
|
+
[onSuccess, safeReturn]
|
|
803
|
+
);
|
|
804
|
+
if (!walletEnabled && !emailEnabled) {
|
|
805
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
806
|
+
"div",
|
|
807
|
+
{
|
|
808
|
+
className,
|
|
809
|
+
"data-oc-signin": "",
|
|
810
|
+
style: {
|
|
811
|
+
padding: "1rem",
|
|
812
|
+
border: "1px solid var(--border, #27272a)",
|
|
813
|
+
color: "var(--foreground, #fafafa)",
|
|
814
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
815
|
+
fontSize: 12
|
|
816
|
+
},
|
|
817
|
+
children: [
|
|
818
|
+
"no signin paths enabled \u2014 set ",
|
|
819
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "paths.wallet" }),
|
|
820
|
+
" or",
|
|
821
|
+
" ",
|
|
822
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "paths.email" }),
|
|
823
|
+
" to ",
|
|
824
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "true" })
|
|
825
|
+
]
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const showWallet = walletEnabled && (path === "wallet" || !emailEnabled);
|
|
830
|
+
const showEmail = emailEnabled && (path === "email" || !walletEnabled);
|
|
831
|
+
const bothEnabled = walletEnabled && emailEnabled;
|
|
832
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, "data-oc-signin": "", children: [
|
|
833
|
+
bothEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
834
|
+
"div",
|
|
835
|
+
{
|
|
836
|
+
role: "tablist",
|
|
837
|
+
"aria-label": "signin method",
|
|
838
|
+
"data-oc-signin-tabs": "",
|
|
839
|
+
style: {
|
|
840
|
+
display: "flex",
|
|
841
|
+
gap: 8,
|
|
842
|
+
marginBottom: 16,
|
|
843
|
+
borderBottom: "1px solid var(--border, #27272a)"
|
|
844
|
+
},
|
|
845
|
+
children: [
|
|
846
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
847
|
+
SigninTab,
|
|
848
|
+
{
|
|
849
|
+
active: path === "wallet",
|
|
850
|
+
onClick: () => setPath("wallet"),
|
|
851
|
+
children: "bitcoin wallet"
|
|
852
|
+
}
|
|
853
|
+
),
|
|
854
|
+
/* @__PURE__ */ jsxRuntime.jsx(SigninTab, { active: path === "email", onClick: () => setPath("email"), children: "email + otp" })
|
|
855
|
+
]
|
|
856
|
+
}
|
|
857
|
+
),
|
|
858
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-oc-signin-panel": "", role: "tabpanel", children: [
|
|
859
|
+
showWallet && /* @__PURE__ */ jsxRuntime.jsx(
|
|
860
|
+
WalletFlow,
|
|
861
|
+
{
|
|
862
|
+
authOrigin,
|
|
863
|
+
audience,
|
|
864
|
+
onSuccess: handleSuccess
|
|
865
|
+
}
|
|
866
|
+
),
|
|
867
|
+
showEmail && /* @__PURE__ */ jsxRuntime.jsx(EmailFlow, { authOrigin, onSuccess: handleSuccess })
|
|
868
|
+
] })
|
|
869
|
+
] });
|
|
870
|
+
}
|
|
871
|
+
function SigninTab({
|
|
872
|
+
active,
|
|
873
|
+
onClick,
|
|
874
|
+
children
|
|
875
|
+
}) {
|
|
876
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
877
|
+
"button",
|
|
878
|
+
{
|
|
879
|
+
type: "button",
|
|
880
|
+
role: "tab",
|
|
881
|
+
"aria-selected": active,
|
|
882
|
+
onClick,
|
|
883
|
+
"data-oc-signin-tab": active ? "active" : "inactive",
|
|
884
|
+
style: {
|
|
885
|
+
padding: "0.6rem 0.875rem",
|
|
886
|
+
background: "transparent",
|
|
887
|
+
border: "none",
|
|
888
|
+
borderBottom: active ? "2px solid var(--primary, #f97316)" : "2px solid transparent",
|
|
889
|
+
color: active ? "var(--foreground, #fafafa)" : "var(--muted-foreground, #a1a1aa)",
|
|
890
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
891
|
+
fontSize: 11,
|
|
892
|
+
letterSpacing: "0.12em",
|
|
893
|
+
textTransform: "uppercase",
|
|
894
|
+
cursor: "pointer",
|
|
895
|
+
marginBottom: -1
|
|
896
|
+
},
|
|
897
|
+
children
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
function WalletFlow({ authOrigin, audience, onSuccess }) {
|
|
902
|
+
const [address, setAddress] = React2__namespace.useState("");
|
|
903
|
+
const [error, setError] = React2__namespace.useState(null);
|
|
904
|
+
const [submitting, setSubmitting] = React2__namespace.useState(false);
|
|
905
|
+
const [stage, setStage] = React2__namespace.useState("enter");
|
|
906
|
+
async function signIn(e) {
|
|
907
|
+
e.preventDefault();
|
|
908
|
+
const addr = address.trim();
|
|
909
|
+
if (!addr) {
|
|
910
|
+
setError("paste a Bitcoin address");
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
setError(null);
|
|
914
|
+
setSubmitting(true);
|
|
915
|
+
setStage("signing");
|
|
916
|
+
try {
|
|
917
|
+
const challengeRes = await fetch(
|
|
918
|
+
`${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
|
|
919
|
+
);
|
|
920
|
+
const challenge = await challengeRes.json();
|
|
921
|
+
if (!challengeRes.ok || !challenge.message || !challenge.nonce) {
|
|
922
|
+
throw new Error(challenge.error ?? "challenge_failed");
|
|
923
|
+
}
|
|
924
|
+
let adapter;
|
|
925
|
+
try {
|
|
926
|
+
const moduleId = "@orangecheck/wallet-adapter";
|
|
927
|
+
adapter = await Function("m", "return import(m)")(
|
|
928
|
+
moduleId
|
|
929
|
+
);
|
|
930
|
+
} catch {
|
|
931
|
+
throw new Error(
|
|
932
|
+
"@orangecheck/wallet-adapter not installed \xB7 add it to your consumer site"
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
const wallets = adapter.detectWallets().filter((w) => w.detected && w.id !== "manual");
|
|
936
|
+
if (wallets.length === 0) {
|
|
937
|
+
throw new Error("no BIP-322 wallet extension detected \xB7 install one and refresh");
|
|
938
|
+
}
|
|
939
|
+
const wallet = wallets[0];
|
|
940
|
+
const signer = adapter.getSigner(wallet.id, { address: addr });
|
|
941
|
+
const signature = await signer(challenge.message);
|
|
942
|
+
const res = await fetch(`${authOrigin}/api/auth/signin`, {
|
|
943
|
+
method: "POST",
|
|
944
|
+
credentials: "include",
|
|
945
|
+
headers: { "Content-Type": "application/json" },
|
|
946
|
+
body: JSON.stringify({
|
|
947
|
+
message: challenge.message,
|
|
948
|
+
signature,
|
|
949
|
+
scheme: "bip322",
|
|
950
|
+
expectedNonce: challenge.nonce,
|
|
951
|
+
expectedAudience: audience,
|
|
952
|
+
expectedPurpose: "login"
|
|
953
|
+
})
|
|
954
|
+
});
|
|
955
|
+
const json = await res.json();
|
|
956
|
+
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
"reason" in json && json.reason || "error" in json && json.error || `verify_failed_${res.status}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
onSuccess(json.account);
|
|
962
|
+
} catch (err) {
|
|
963
|
+
const msg = err instanceof Error ? err.message : "sign-in failed";
|
|
964
|
+
setError(msg);
|
|
965
|
+
setSubmitting(false);
|
|
966
|
+
setStage("enter");
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: signIn, "data-oc-signin-wallet": "", children: [
|
|
970
|
+
/* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { label: "\xA7 bitcoin wallet", children: "Sign in with any BIP-322-capable Bitcoin wallet (Sparrow, Xverse, Leather, UniSat, Alby, OKX, Phantom). Paste your address, click sign \u2014 your wallet extension prompts for a one-time signature on a short challenge. The address becomes your OC identity. Private key material never leaves your wallet." }),
|
|
971
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { children: "bitcoin address" }),
|
|
972
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
973
|
+
"input",
|
|
974
|
+
{
|
|
975
|
+
type: "text",
|
|
976
|
+
value: address,
|
|
977
|
+
onChange: (e) => {
|
|
978
|
+
setAddress(e.target.value);
|
|
979
|
+
if (error) setError(null);
|
|
980
|
+
},
|
|
981
|
+
placeholder: "bc1q\u2026 \xB7 3\u2026 \xB7 1\u2026",
|
|
982
|
+
autoComplete: "off",
|
|
983
|
+
autoCapitalize: "none",
|
|
984
|
+
spellCheck: false,
|
|
985
|
+
required: true,
|
|
986
|
+
"aria-invalid": error ? true : void 0,
|
|
987
|
+
style: inputStyle
|
|
988
|
+
}
|
|
989
|
+
),
|
|
990
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: error }),
|
|
991
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
992
|
+
"button",
|
|
993
|
+
{
|
|
994
|
+
type: "submit",
|
|
995
|
+
disabled: submitting || !address.trim(),
|
|
996
|
+
style: submitStyle(submitting || !address.trim()),
|
|
997
|
+
children: stage === "signing" && submitting ? "waiting for wallet\u2026" : "sign challenge \xB7 sign me in \u2192"
|
|
998
|
+
}
|
|
999
|
+
),
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "Detection picks the first installed BIP-322-capable extension. Address is the one your wallet will sign for." })
|
|
1001
|
+
] });
|
|
1002
|
+
}
|
|
1003
|
+
function EmailFlow({ authOrigin, onSuccess }) {
|
|
1004
|
+
const [stage, setStage] = React2__namespace.useState("enter");
|
|
1005
|
+
const [email, setEmail] = React2__namespace.useState("");
|
|
1006
|
+
const [emailError, setEmailError] = React2__namespace.useState(null);
|
|
1007
|
+
const [code, setCode] = React2__namespace.useState("");
|
|
1008
|
+
const [codeError, setCodeError] = React2__namespace.useState(null);
|
|
1009
|
+
const [token, setToken] = React2__namespace.useState(null);
|
|
1010
|
+
const [submitting, setSubmitting] = React2__namespace.useState(false);
|
|
1011
|
+
async function start(e) {
|
|
1012
|
+
e.preventDefault();
|
|
1013
|
+
const trimmed = email.trim().toLowerCase();
|
|
1014
|
+
if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
|
|
1015
|
+
setEmailError("enter a valid email");
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
setEmailError(null);
|
|
1019
|
+
setSubmitting(true);
|
|
1020
|
+
try {
|
|
1021
|
+
const res = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
|
|
1022
|
+
method: "POST",
|
|
1023
|
+
credentials: "include",
|
|
1024
|
+
headers: { "Content-Type": "application/json" },
|
|
1025
|
+
body: JSON.stringify({ email: trimmed })
|
|
1026
|
+
});
|
|
1027
|
+
const json = await res.json();
|
|
1028
|
+
if (!res.ok || !json.token) {
|
|
1029
|
+
throw new Error(json.reason ?? `start failed (${res.status})`);
|
|
1030
|
+
}
|
|
1031
|
+
setToken(json.token);
|
|
1032
|
+
setEmail(trimmed);
|
|
1033
|
+
setStage("code");
|
|
1034
|
+
} catch (err) {
|
|
1035
|
+
setEmailError(err instanceof Error ? err.message : "failed to send code");
|
|
1036
|
+
} finally {
|
|
1037
|
+
setSubmitting(false);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
async function verify(e) {
|
|
1041
|
+
e.preventDefault();
|
|
1042
|
+
if (!token) return;
|
|
1043
|
+
if (code.length !== 6) {
|
|
1044
|
+
setCodeError("6 digits");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
setCodeError(null);
|
|
1048
|
+
setSubmitting(true);
|
|
1049
|
+
try {
|
|
1050
|
+
const res = await fetch(`${authOrigin}/api/auth/email-otp/verify`, {
|
|
1051
|
+
method: "POST",
|
|
1052
|
+
credentials: "include",
|
|
1053
|
+
headers: { "Content-Type": "application/json" },
|
|
1054
|
+
body: JSON.stringify({ email, code, token })
|
|
1055
|
+
});
|
|
1056
|
+
const json = await res.json();
|
|
1057
|
+
if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
"reason" in json && json.reason || "error" in json && json.error || `verify failed (${res.status})`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
onSuccess(json.account);
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
setCodeError(err instanceof Error ? err.message : "verify failed");
|
|
1065
|
+
} finally {
|
|
1066
|
+
setSubmitting(false);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (stage === "enter") {
|
|
1070
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: start, "data-oc-signin-email": "", children: [
|
|
1071
|
+
/* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { label: "\xA7 email + otp", children: "We email you a 6-digit code. No password. The federation provisions a custodial wallet for you in your browser. Best path if you don't have a Bitcoin wallet yet \u2014 graduate to self-custody anytime by adding your BTC address from your account dashboard." }),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { children: "email" }),
|
|
1073
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1074
|
+
"input",
|
|
1075
|
+
{
|
|
1076
|
+
type: "email",
|
|
1077
|
+
autoComplete: "email",
|
|
1078
|
+
value: email,
|
|
1079
|
+
onChange: (e) => {
|
|
1080
|
+
setEmail(e.target.value);
|
|
1081
|
+
if (emailError) setEmailError(null);
|
|
1082
|
+
},
|
|
1083
|
+
placeholder: "you@example.com",
|
|
1084
|
+
required: true,
|
|
1085
|
+
"aria-invalid": emailError ? true : void 0,
|
|
1086
|
+
style: inputStyle
|
|
1087
|
+
}
|
|
1088
|
+
),
|
|
1089
|
+
emailError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: emailError }),
|
|
1090
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" }),
|
|
1091
|
+
/* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "OTP delivered by the auth host. Codes expire in 10 minutes and are single-use. Rate-limited 5 starts/hour/IP." })
|
|
1092
|
+
] });
|
|
1093
|
+
}
|
|
1094
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: verify, "data-oc-signin-email-verify": "", children: [
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsxs(FlowHeader, { label: "\xA7 enter the code", children: [
|
|
1096
|
+
"Sent a 6-digit code to ",
|
|
1097
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--foreground, #fafafa)" }, children: email }),
|
|
1098
|
+
". Code expires in 10 minutes."
|
|
1099
|
+
] }),
|
|
1100
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { children: "one-time code" }),
|
|
1101
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1102
|
+
"input",
|
|
1103
|
+
{
|
|
1104
|
+
type: "text",
|
|
1105
|
+
inputMode: "numeric",
|
|
1106
|
+
pattern: "\\d{6}",
|
|
1107
|
+
maxLength: 6,
|
|
1108
|
+
autoComplete: "one-time-code",
|
|
1109
|
+
value: code,
|
|
1110
|
+
onChange: (e) => {
|
|
1111
|
+
setCode(e.target.value.replace(/\D/g, "").slice(0, 6));
|
|
1112
|
+
if (codeError) setCodeError(null);
|
|
1113
|
+
},
|
|
1114
|
+
placeholder: "6 digits",
|
|
1115
|
+
required: true,
|
|
1116
|
+
autoFocus: true,
|
|
1117
|
+
"aria-invalid": codeError ? true : void 0,
|
|
1118
|
+
style: { ...inputStyle, letterSpacing: "0.4em", fontSize: 16 }
|
|
1119
|
+
}
|
|
1120
|
+
),
|
|
1121
|
+
codeError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: codeError }),
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1123
|
+
"button",
|
|
1124
|
+
{
|
|
1125
|
+
type: "submit",
|
|
1126
|
+
disabled: submitting || code.length !== 6,
|
|
1127
|
+
style: submitStyle(submitting || code.length !== 6),
|
|
1128
|
+
children: submitting ? "verifying\u2026" : "verify \xB7 sign me in \u2192"
|
|
1129
|
+
}
|
|
1130
|
+
),
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
+
"button",
|
|
1133
|
+
{
|
|
1134
|
+
type: "button",
|
|
1135
|
+
onClick: () => {
|
|
1136
|
+
setStage("enter");
|
|
1137
|
+
setCode("");
|
|
1138
|
+
setToken(null);
|
|
1139
|
+
},
|
|
1140
|
+
style: {
|
|
1141
|
+
marginTop: 12,
|
|
1142
|
+
background: "transparent",
|
|
1143
|
+
border: "none",
|
|
1144
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
1145
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1146
|
+
fontSize: 11,
|
|
1147
|
+
letterSpacing: "0.12em",
|
|
1148
|
+
textTransform: "uppercase",
|
|
1149
|
+
cursor: "pointer",
|
|
1150
|
+
padding: 0
|
|
1151
|
+
},
|
|
1152
|
+
children: "use a different email"
|
|
1153
|
+
}
|
|
1154
|
+
),
|
|
1155
|
+
/* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "On verify, oc_session is set on Domain=.ochk.io family-wide." })
|
|
1156
|
+
] });
|
|
1157
|
+
}
|
|
1158
|
+
function FlowHeader({
|
|
1159
|
+
label,
|
|
1160
|
+
children
|
|
1161
|
+
}) {
|
|
1162
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 16 }, children: [
|
|
1163
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1164
|
+
"div",
|
|
1165
|
+
{
|
|
1166
|
+
style: {
|
|
1167
|
+
color: "var(--primary, #f97316)",
|
|
1168
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1169
|
+
fontSize: 10,
|
|
1170
|
+
letterSpacing: "0.18em",
|
|
1171
|
+
textTransform: "uppercase",
|
|
1172
|
+
marginBottom: 6
|
|
1173
|
+
},
|
|
1174
|
+
children: label
|
|
1175
|
+
}
|
|
1176
|
+
),
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1178
|
+
"p",
|
|
1179
|
+
{
|
|
1180
|
+
style: {
|
|
1181
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
1182
|
+
fontSize: 13,
|
|
1183
|
+
lineHeight: 1.55,
|
|
1184
|
+
margin: 0
|
|
1185
|
+
},
|
|
1186
|
+
children
|
|
1187
|
+
}
|
|
1188
|
+
)
|
|
1189
|
+
] });
|
|
1190
|
+
}
|
|
1191
|
+
function Label({ children }) {
|
|
1192
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1193
|
+
"label",
|
|
1194
|
+
{
|
|
1195
|
+
style: {
|
|
1196
|
+
display: "block",
|
|
1197
|
+
color: "var(--foreground, #fafafa)",
|
|
1198
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1199
|
+
fontSize: 11,
|
|
1200
|
+
letterSpacing: "0.12em",
|
|
1201
|
+
textTransform: "uppercase",
|
|
1202
|
+
marginBottom: 6
|
|
1203
|
+
},
|
|
1204
|
+
children
|
|
1205
|
+
}
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
var inputStyle = {
|
|
1209
|
+
width: "100%",
|
|
1210
|
+
padding: "0.55rem 0.75rem",
|
|
1211
|
+
background: "var(--background, #0a0a0a)",
|
|
1212
|
+
color: "var(--foreground, #fafafa)",
|
|
1213
|
+
border: "1px solid var(--input, #27272a)",
|
|
1214
|
+
borderRadius: 6,
|
|
1215
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1216
|
+
fontSize: 13,
|
|
1217
|
+
outline: "none"
|
|
1218
|
+
};
|
|
1219
|
+
function submitStyle(disabled) {
|
|
1220
|
+
return {
|
|
1221
|
+
marginTop: 12,
|
|
1222
|
+
padding: "0.6rem 1rem",
|
|
1223
|
+
background: "var(--primary, #f97316)",
|
|
1224
|
+
color: "var(--primary-foreground, #0a0a0a)",
|
|
1225
|
+
border: "none",
|
|
1226
|
+
borderRadius: 6,
|
|
1227
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1228
|
+
fontSize: 11,
|
|
1229
|
+
letterSpacing: "0.14em",
|
|
1230
|
+
textTransform: "uppercase",
|
|
1231
|
+
fontWeight: 600,
|
|
1232
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1233
|
+
opacity: disabled ? 0.5 : 1,
|
|
1234
|
+
width: "100%"
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function ErrorLine({ children }) {
|
|
1238
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1239
|
+
"p",
|
|
1240
|
+
{
|
|
1241
|
+
role: "alert",
|
|
1242
|
+
style: {
|
|
1243
|
+
marginTop: 6,
|
|
1244
|
+
marginBottom: 0,
|
|
1245
|
+
color: "var(--destructive, #ef4444)",
|
|
1246
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1247
|
+
fontSize: 11,
|
|
1248
|
+
letterSpacing: "0.04em"
|
|
1249
|
+
},
|
|
1250
|
+
children
|
|
1251
|
+
}
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
function Hint({ children }) {
|
|
1255
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1256
|
+
"p",
|
|
1257
|
+
{
|
|
1258
|
+
style: {
|
|
1259
|
+
marginTop: 12,
|
|
1260
|
+
marginBottom: 0,
|
|
1261
|
+
color: "var(--muted-foreground, #a1a1aa)",
|
|
1262
|
+
fontFamily: "ui-monospace, SFMono-Regular, monospace",
|
|
1263
|
+
fontSize: 10.5,
|
|
1264
|
+
lineHeight: 1.55,
|
|
1265
|
+
opacity: 0.7
|
|
1266
|
+
},
|
|
1267
|
+
children: [
|
|
1268
|
+
"> ",
|
|
1269
|
+
children
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
767
1274
|
|
|
768
1275
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
769
1276
|
exports.OcAccountChip = OcAccountChip;
|
|
770
1277
|
exports.OcAccountPill = OcAccountPill;
|
|
771
1278
|
exports.OcAddressInput = OcAddressInput;
|
|
772
1279
|
exports.OcSessionProvider = OcSessionProvider;
|
|
1280
|
+
exports.OcSignIn = OcSignIn;
|
|
773
1281
|
exports.OcSignInButton = OcSignInButton;
|
|
774
1282
|
exports.buildSignInUrl = buildSignInUrl;
|
|
775
1283
|
exports.useOcAddressSuggestion = useOcAddressSuggestion;
|