Files
lib-auth/dist/react/index.js

435 lines
16 KiB
JavaScript

// react/AuthGuard.tsx
import { useEffect, useState } from "react";
// react/chakra-compat.tsx
import {
Alert as ChakraAlert,
Box as ChakraBox,
Button as ChakraButton,
Center as ChakraCenter,
HStack as ChakraHStack,
Icon as ChakraIcon,
Input as ChakraInput,
Spinner as ChakraSpinner,
Stack as ChakraStack,
Text as ChakraText
} from "@chakra-ui/react";
import { jsx, jsxs } from "react/jsx-runtime";
function normalizeSpacingProps(props) {
const next = { ...props };
if (next.spacing !== void 0 && next.gap === void 0) {
next.gap = next.spacing;
}
delete next.spacing;
return next;
}
function normalizeInteractiveProps(props) {
const next = normalizeSpacingProps(props);
if (next.isDisabled !== void 0 && next.disabled === void 0) {
next.disabled = next.isDisabled;
}
if (next.isLoading !== void 0 && next.loading === void 0) {
next.loading = next.isLoading;
}
delete next.isDisabled;
delete next.isLoading;
return next;
}
var Center = ChakraCenter;
var Icon = ChakraIcon;
var Input = ChakraInput;
var Spinner = ChakraSpinner;
function Stack(props) {
return /* @__PURE__ */ jsx(ChakraStack, { ...normalizeSpacingProps(props) });
}
function HStack(props) {
return /* @__PURE__ */ jsx(ChakraHStack, { ...normalizeSpacingProps(props) });
}
function Text(props) {
const next = { ...props };
if (next.noOfLines !== void 0 && next.lineClamp === void 0) {
next.lineClamp = next.noOfLines;
}
delete next.noOfLines;
return /* @__PURE__ */ jsx(ChakraText, { ...next });
}
function Button(props) {
const { leftIcon, rightIcon, children, ...rest } = normalizeInteractiveProps(props);
return /* @__PURE__ */ jsx(ChakraButton, { ...rest, children: /* @__PURE__ */ jsxs(ChakraHStack, { gap: 2, children: [
leftIcon ?? null,
/* @__PURE__ */ jsx("span", { children }),
rightIcon ?? null
] }) });
}
function Alert({ children, status = "info", ...props }) {
return /* @__PURE__ */ jsx(ChakraAlert.Root, { status, ...props, children });
}
function AlertIcon() {
return /* @__PURE__ */ jsx(ChakraAlert.Indicator, {});
}
function AlertDescription({ children, ...props }) {
return /* @__PURE__ */ jsx(ChakraAlert.Description, { ...props, children });
}
function FormControl({ children, ...props }) {
return /* @__PURE__ */ jsx(ChakraStack, { gap: 2, ...normalizeSpacingProps(props), children });
}
function FormLabel(props) {
return /* @__PURE__ */ jsx(ChakraBox, { as: "label", fontWeight: "medium", ...props });
}
// react/AuthGuard.tsx
import { Navigate } from "react-router";
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
function AuthGuard({
children,
fetchCurrentUser,
redirectTo = "/login",
loadingFallback,
authenticatedWrapper
}) {
const [state, setState] = useState({
loading: true,
authenticated: false
});
useEffect(() => {
let cancelled = false;
fetchCurrentUser().then(() => {
if (!cancelled) {
setState({ loading: false, authenticated: true });
}
}).catch(() => {
if (!cancelled) {
setState({ loading: false, authenticated: false });
}
});
return () => {
cancelled = true;
};
}, [fetchCurrentUser]);
if (state.loading) {
return /* @__PURE__ */ jsx2(Fragment, { children: loadingFallback ?? /* @__PURE__ */ jsx2(Center, { h: "var(--app-height)", children: /* @__PURE__ */ jsx2(Spinner, { size: "xl" }) }) });
}
if (!state.authenticated) {
return /* @__PURE__ */ jsx2(Navigate, { to: redirectTo, replace: true });
}
return /* @__PURE__ */ jsx2(Fragment, { children: authenticatedWrapper ? authenticatedWrapper(children) : children });
}
// react/client.ts
async function readJsonError(response, fallback) {
const payload = await response.json().catch(() => null);
return new Error(payload?.error ?? fallback);
}
function createAuthClient(options) {
const fetchImpl = options.fetchImpl ?? fetch;
const authUrl = options.authUrl ?? options.apiUrl;
const credentials = options.credentials ?? "include";
function resolveDefaultOAuthCallbackUrl() {
const configured = options.defaultOAuthCallbackUrl;
if (typeof configured === "function") {
return configured();
}
if (typeof configured === "string" && configured.trim().length > 0) {
return configured;
}
return `${window.location.origin}/chat`;
}
async function request(path, init) {
return fetchImpl(options.apiUrl(path), {
...init,
credentials,
headers: {
"Content-Type": "application/json",
...init?.headers ?? {}
}
});
}
return {
async getProviders() {
const response = await request("/api/auth/providers");
if (!response.ok) {
throw await readJsonError(response, "providers_unavailable");
}
return await response.json();
},
async getCurrentUser() {
const response = await request("/api/me");
if (!response.ok) {
throw await readJsonError(response, "Unauthorized");
}
const payload = await response.json();
return payload.user;
},
async register(input) {
const response = await request("/api/auth/register", {
method: "POST",
body: JSON.stringify(input)
});
if (!response.ok) {
throw await readJsonError(response, "Registration failed");
}
},
async login(input) {
const response = await request("/api/auth/login", {
method: "POST",
body: JSON.stringify(input)
});
if (!response.ok) {
throw await readJsonError(response, "Sign in failed");
}
},
async requestPasswordReset(email) {
const response = await request("/api/auth/password-reset/request", {
method: "POST",
body: JSON.stringify({ email })
});
if (!response.ok) {
throw await readJsonError(response, "Password reset request failed");
}
},
async validatePasswordResetToken(token) {
const response = await request(`/api/auth/password-reset/validate?token=${encodeURIComponent(token)}`, {
headers: {}
});
const payload = await response.json().catch(() => null);
if (!response.ok || !payload?.email || payload.mode !== "reset" && payload.mode !== "create") {
throw new Error(payload?.error ?? "Invalid reset link");
}
return {
email: payload.email,
mode: payload.mode
};
},
async confirmPasswordReset(input) {
const response = await request("/api/auth/password-reset/confirm", {
method: "POST",
body: JSON.stringify(input)
});
if (!response.ok) {
throw await readJsonError(response, "Invalid reset link");
}
},
async logout() {
const response = await request("/api/auth/logout", {
method: "POST"
});
if (!response.ok) {
throw await readJsonError(response, "Logout failed");
}
},
async startOAuthSignIn(provider, callbackUrl = resolveDefaultOAuthCallbackUrl()) {
const response = await fetchImpl(authUrl("/auth/csrf"), {
credentials
});
if (!response.ok) {
throw await readJsonError(response, "Sign in failed");
}
const payload = await response.json();
if (!payload.csrfToken) {
throw new Error("Sign in failed");
}
const form = document.createElement("form");
form.method = "POST";
form.action = authUrl(`/auth/signin/${provider}`);
form.style.display = "none";
const csrfInput = document.createElement("input");
csrfInput.type = "hidden";
csrfInput.name = "csrfToken";
csrfInput.value = payload.csrfToken;
form.appendChild(csrfInput);
const callbackInput = document.createElement("input");
callbackInput.type = "hidden";
callbackInput.name = "callbackUrl";
callbackInput.value = callbackUrl;
form.appendChild(callbackInput);
document.body.appendChild(form);
form.submit();
}
};
}
// react/LoginForm.tsx
import { FcGoogle } from "react-icons/fc";
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
function LoginForm({
mode,
texts,
onSubmit,
onModeToggle,
loading = false,
oauthLoadingProvider = null,
providers,
onOAuthSignIn,
errorMessage,
successMessage,
footer,
forgotPasswordLink,
emailPlaceholder = "you@example.com",
namePlaceholder = "Jane Doe"
}) {
const registerMode = mode === "register";
function handleSubmit(event) {
event.preventDefault();
const form = new FormData(event.currentTarget);
void onSubmit({
name: String(form.get("name") ?? ""),
email: String(form.get("email") ?? ""),
password: String(form.get("password") ?? ""),
passwordConfirm: String(form.get("passwordConfirm") ?? "")
});
}
return /* @__PURE__ */ jsxs2(Stack, { spacing: 5, children: [
errorMessage ? /* @__PURE__ */ jsxs2(Alert, { status: "error", borderRadius: "md", children: [
/* @__PURE__ */ jsx3(AlertIcon, {}),
/* @__PURE__ */ jsx3(AlertDescription, { children: errorMessage })
] }) : null,
successMessage ? /* @__PURE__ */ jsxs2(Alert, { status: "success", borderRadius: "md", children: [
/* @__PURE__ */ jsx3(AlertIcon, {}),
/* @__PURE__ */ jsx3(AlertDescription, { children: successMessage })
] }) : null,
/* @__PURE__ */ jsx3("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs2(Stack, { spacing: 4, children: [
registerMode ? /* @__PURE__ */ jsxs2(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx3(FormLabel, { children: texts.nameLabel }),
/* @__PURE__ */ jsx3(Input, { name: "name", placeholder: namePlaceholder })
] }) : null,
/* @__PURE__ */ jsxs2(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx3(FormLabel, { children: texts.emailLabel }),
/* @__PURE__ */ jsx3(Input, { name: "email", type: "email", placeholder: emailPlaceholder })
] }),
/* @__PURE__ */ jsxs2(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx3(FormLabel, { children: texts.passwordLabel }),
/* @__PURE__ */ jsx3(Input, { name: "password", type: "password", minLength: 8 })
] }),
!registerMode && forgotPasswordLink ? /* @__PURE__ */ jsx3(Stack, { align: "flex-end", children: forgotPasswordLink }) : null,
registerMode ? /* @__PURE__ */ jsxs2(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx3(FormLabel, { children: texts.passwordConfirmLabel }),
/* @__PURE__ */ jsx3(Input, { name: "passwordConfirm", type: "password", minLength: 8 })
] }) : null,
/* @__PURE__ */ jsx3(Button, { type: "submit", isLoading: loading, children: registerMode ? texts.submitRegisterLabel : texts.submitSignInLabel })
] }) }),
registerMode || !onOAuthSignIn || !providers?.google && !providers?.slack ? null : /* @__PURE__ */ jsxs2(HStack, { children: [
providers.google ? /* @__PURE__ */ jsx3(
Button,
{
flex: 1,
bg: "gray.200",
color: "gray.900",
borderRadius: "full",
justifyContent: "flex-start",
h: "56px",
px: 3,
fontSize: { base: "md", md: "lg" },
fontWeight: "semibold",
iconSpacing: 4,
leftIcon: /* @__PURE__ */ jsx3(Center, { boxSize: "40px", bg: "white", borderRadius: "full", boxShadow: "sm", children: /* @__PURE__ */ jsx3(Icon, { as: FcGoogle, boxSize: 6 }) }),
_hover: { bg: "gray.300" },
_active: { bg: "gray.300" },
isLoading: oauthLoadingProvider === "google",
onClick: () => void onOAuthSignIn("google"),
children: texts.googleLabel
}
) : null,
providers.slack ? /* @__PURE__ */ jsx3(
Button,
{
flex: 1,
variant: "outline",
isLoading: oauthLoadingProvider === "slack",
onClick: () => void onOAuthSignIn("slack"),
children: texts.slackLabel
}
) : null
] }),
/* @__PURE__ */ jsx3(Button, { variant: "ghost", onClick: onModeToggle, children: registerMode ? texts.toggleToSignInLabel : texts.toggleToRegisterLabel }),
footer ?? null
] });
}
// react/PasswordResetForms.tsx
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
function PasswordResetRequestForm({
texts,
helperText,
loading = false,
requestSent = false,
onSubmit,
emailPlaceholder = "you@example.com"
}) {
function handleSubmit(event) {
event.preventDefault();
const form = new FormData(event.currentTarget);
void onSubmit({
email: String(form.get("email") ?? "")
});
}
return /* @__PURE__ */ jsxs3(Stack, { spacing: 5, children: [
requestSent ? /* @__PURE__ */ jsxs3(Alert, { status: "success", borderRadius: "md", children: [
/* @__PURE__ */ jsx4(AlertIcon, {}),
/* @__PURE__ */ jsx4(AlertDescription, { children: texts.requestSentMessage })
] }) : null,
/* @__PURE__ */ jsx4("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs3(Stack, { spacing: 4, children: [
/* @__PURE__ */ jsxs3(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx4(FormLabel, { children: texts.emailLabel }),
/* @__PURE__ */ jsx4(Input, { name: "email", type: "email", placeholder: emailPlaceholder })
] }),
/* @__PURE__ */ jsx4(Text, { fontSize: "sm", color: "gray.600", children: helperText }),
/* @__PURE__ */ jsx4(Button, { type: "submit", isLoading: loading, children: texts.submitLabel })
] }) })
] });
}
function PasswordResetConfirmForm({
texts,
tokenState,
loading = false,
completedMode = null,
onSubmit
}) {
function handleSubmit(event) {
event.preventDefault();
const form = new FormData(event.currentTarget);
void onSubmit({
password: String(form.get("password") ?? ""),
passwordConfirm: String(form.get("passwordConfirm") ?? "")
});
}
if (tokenState.status === "loading") {
return /* @__PURE__ */ jsxs3(Stack, { align: "center", py: 6, spacing: 3, children: [
/* @__PURE__ */ jsx4(Spinner, {}),
/* @__PURE__ */ jsx4(Text, { color: "gray.600", children: texts.loadingLabel })
] });
}
if (tokenState.status === "invalid") {
return /* @__PURE__ */ jsxs3(Alert, { status: "error", borderRadius: "md", children: [
/* @__PURE__ */ jsx4(AlertIcon, {}),
/* @__PURE__ */ jsx4(AlertDescription, { children: tokenState.error || texts.invalidLinkLabel })
] });
}
if (completedMode !== null) {
return /* @__PURE__ */ jsxs3(Alert, { status: "success", borderRadius: "md", children: [
/* @__PURE__ */ jsx4(AlertIcon, {}),
/* @__PURE__ */ jsx4(AlertDescription, { children: completedMode === "create" ? texts.createSuccessLabel : texts.resetSuccessLabel })
] });
}
return /* @__PURE__ */ jsxs3(Stack, { spacing: 4, children: [
/* @__PURE__ */ jsx4(Text, { fontSize: "sm", color: "gray.600", children: tokenState.email }),
/* @__PURE__ */ jsx4("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs3(Stack, { spacing: 4, children: [
/* @__PURE__ */ jsxs3(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx4(FormLabel, { children: texts.passwordLabel }),
/* @__PURE__ */ jsx4(Input, { name: "password", type: "password", minLength: 8 })
] }),
/* @__PURE__ */ jsxs3(FormControl, { isRequired: true, children: [
/* @__PURE__ */ jsx4(FormLabel, { children: texts.passwordConfirmLabel }),
/* @__PURE__ */ jsx4(Input, { name: "passwordConfirm", type: "password", minLength: 8 })
] }),
/* @__PURE__ */ jsx4(Button, { type: "submit", isLoading: loading, children: tokenState.mode === "create" ? texts.createSubmitLabel : texts.resetSubmitLabel })
] }) })
] });
}
export {
AuthGuard,
LoginForm,
PasswordResetConfirmForm,
PasswordResetRequestForm,
createAuthClient
};
//# sourceMappingURL=index.js.map