@lalternative/auth 0.5.0 → 0.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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/use-session.ts","../src/components/auth-field.tsx","../src/components/auth-submit.tsx","../src/components/login-form.tsx","../src/components/social-buttons.tsx","../src/components/register-form.tsx","../src/components/verify-email-form.tsx","../src/components/forgot-password-form.tsx","../src/components/reset-password-form.tsx","../src/components/auth-layout.tsx","../src/components/invitation-notice.tsx"],"sourcesContent":["import type { PlatformAuthClient } from \"../client\"\n\n/**\n * Returns a useSession hook bound to the given auth client.\n * Usage: const { data: session, isPending } = useSession(authClient)\n */\nexport function useSession(authClient: PlatformAuthClient) {\n return authClient.useSession()\n}\n\n/**\n * Returns a logout function bound to the given auth client.\n * Usage: const logout = useLogout(authClient)\n */\nexport function useLogout(authClient: PlatformAuthClient) {\n const signOut = async () => {\n await authClient.signOut()\n }\n return signOut\n}\n","import { useId, useState, type InputHTMLAttributes, type ReactNode } from \"react\"\n\ntype NativeProps = Omit<InputHTMLAttributes<HTMLInputElement>, \"id\" | \"className\">\n\nexport interface AuthFieldProps extends NativeProps {\n label: string\n /** Rendered on the right of the label row — typically a \"forgot password\" link */\n hint?: ReactNode\n /** Persistent helper text under the field, tied to it for screen readers */\n description?: string\n invalid?: boolean\n}\n\n/**\n * A single credential field.\n *\n * The label is a real <label>, always visible, never a placeholder standing in\n * for one: a placeholder disappears the moment someone types, which is exactly\n * when a person double-checking a long password needs to know what the box is.\n *\n * Touch targets are 52px tall on mobile and 44px from sm up. Below ~48px,\n * thumbs miss; on a pointer device the same height reads as oversized, so the\n * two are not one compromise value.\n *\n * The focus ring is a ring rather than an outline so it follows the rounded\n * corners exactly, and the border darkens at the same time — the border is what\n * survives forced-colors mode, where the ring is dropped.\n */\nexport function AuthField({\n label,\n hint,\n description,\n invalid = false,\n ...props\n}: AuthFieldProps) {\n const id = useId()\n const descriptionId = description ? `${id}-description` : undefined\n const [revealed, setRevealed] = useState(false)\n\n const isPassword = props.type === \"password\"\n const type = isPassword && revealed ? \"text\" : props.type\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-baseline justify-between gap-3\">\n <label\n htmlFor={id}\n className=\"text-sm font-medium leading-none text-foreground\"\n >\n {label}\n </label>\n {hint}\n </div>\n\n <div className=\"relative\">\n <input\n {...props}\n id={id}\n type={type}\n aria-invalid={invalid || undefined}\n aria-describedby={descriptionId}\n className={[\n \"h-[52px] w-full rounded-xl border bg-background px-4 text-base\",\n \"sm:h-11 sm:text-sm\",\n isPassword ? \"pr-12\" : \"\",\n \"text-foreground placeholder:text-muted-foreground/70\",\n \"transition-[border-color,box-shadow] duration-150 ease-out\",\n \"focus-visible:outline-none focus-visible:ring-[3px]\",\n invalid\n ? \"border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20\"\n : \"border-input focus-visible:border-ring focus-visible:ring-ring/15\",\n \"disabled:cursor-not-allowed disabled:opacity-60\",\n ]\n .filter(Boolean)\n .join(\" \")}\n />\n\n {isPassword && (\n <button\n type=\"button\"\n onClick={() => setRevealed((v) => !v)}\n disabled={props.disabled}\n aria-pressed={revealed}\n aria-label={\n revealed ? \"Masquer le mot de passe\" : \"Afficher le mot de passe\"\n }\n className=\"absolute inset-y-0 right-0 flex w-12 items-center justify-center rounded-r-xl text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-60\"\n >\n <EyeIcon open={revealed} />\n </button>\n )}\n </div>\n\n {description && (\n <p id={descriptionId} className=\"text-xs text-muted-foreground\">\n {description}\n </p>\n )}\n </div>\n )\n}\n\n// Inline rather than from lucide-react: the package would gain a dependency\n// consumers already have at a different version, for two paths.\nfunction EyeIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0Z\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n {!open && <path d=\"m3 3 18 18\" />}\n </svg>\n )\n}\n","import type { ReactNode } from \"react\"\n\ninterface AuthSubmitProps {\n pending?: boolean\n disabled?: boolean\n pendingLabel: string\n children: ReactNode\n}\n\n/**\n * The primary action of an auth screen.\n *\n * The label swaps to its pending form in place, with the spinner absolutely\n * positioned: a spinner inserted into the flow would widen the row and shift\n * the text sideways at the exact moment the person is watching it.\n *\n * The press feedback is a 1px translate rather than a scale — scaling a\n * full-width button visibly blurs its text mid-transform.\n */\nexport function AuthSubmit({\n pending = false,\n disabled = false,\n pendingLabel,\n children,\n}: AuthSubmitProps) {\n return (\n <button\n type=\"submit\"\n disabled={disabled || pending}\n aria-busy={pending || undefined}\n className={[\n \"relative flex h-[52px] w-full items-center justify-center rounded-xl sm:h-11\",\n \"bg-primary text-base font-medium text-primary-foreground sm:text-sm\",\n \"shadow-sm transition-[background-color,transform,box-shadow] duration-150 ease-out\",\n \"hover:bg-primary/90 active:translate-y-px\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-55\",\n ].join(\" \")}\n >\n {pending && (\n <span\n aria-hidden=\"true\"\n className=\"absolute left-4 size-4 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70 motion-reduce:animate-none\"\n />\n )}\n {pending ? pendingLabel : children}\n </button>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { LoginFormLabels, LoginFormProps } from \"../types\"\nimport { AuthField } from \"./auth-field\"\nimport { AuthSubmit } from \"./auth-submit\"\nimport { SocialButtons } from \"./social-buttons\"\n\nconst DEFAULTS: Required<LoginFormLabels> = {\n title: \"Connexion\",\n subtitle: \"Content de te revoir. Entre tes identifiants pour continuer.\",\n emailPlaceholder: \"Adresse e-mail\",\n passwordPlaceholder: \"Mot de passe\",\n forgotPassword: \"Mot de passe oublié ?\",\n submit: \"Se connecter\",\n submitPending: \"Connexion…\",\n noAccount: \"Pas encore de compte ?\",\n register: \"Créer un compte\",\n emailRequired: \"Renseigne ton adresse e-mail\",\n passwordRequired: \"Renseigne ton mot de passe\",\n invalidCredentials: \"Adresse e-mail ou mot de passe incorrect\",\n // accountLinking is disabled in createPlatformAuth, so a social sign-in on an\n // address already registered with a password is refused with this code.\n // Without a message the button reads as broken rather than as a rejection.\n accountNotLinked:\n \"Cette adresse est déjà associée à un mot de passe. Connecte-toi avec ton mot de passe.\",\n socialCancelled: \"Connexion annulée.\",\n socialFailed: \"La connexion a échoué. Réessaie.\",\n}\n\nexport function LoginForm({\n onSuccess,\n registerUrl = \"/register\",\n forgotPasswordUrl = \"/forgot-password\",\n socialCallbackUrl = \"/\",\n socialProviders = [],\n coreTokenUrl = \"/api/auth/core-token\",\n labels,\n authClient,\n}: LoginFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [email, setEmail] = useState(\"\")\n const [password, setPassword] = useState(\"\")\n const [error, setError] = useState<string | undefined>()\n const [isPending, setIsPending] = useState(false)\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!email.trim()) {\n setError(t.emailRequired)\n return\n }\n if (!password) {\n setError(t.passwordRequired)\n return\n }\n setError(undefined)\n setIsPending(true)\n try {\n const res = await authClient.signIn.email({\n email: email.trim(),\n password,\n })\n if (res?.error) {\n setError(res.error.message ?? t.invalidCredentials)\n return\n }\n // The better-auth session cookie alone does not authenticate a Go core:\n // it verifies the EdDSA JWT minted here against the issuer's JWKS.\n // Skipped when the app sets the token itself, or has no core at all.\n if (coreTokenUrl) {\n await fetch(coreTokenUrl, { credentials: \"include\" })\n }\n onSuccess?.()\n } catch (err) {\n setError(err instanceof Error ? err.message : t.invalidCredentials)\n } finally {\n setIsPending(false)\n }\n }\n\n const handleSocial = async (provider: \"google\" | \"github\") => {\n setError(undefined)\n try {\n await authClient.signIn.social({\n provider,\n callbackURL: socialCallbackUrl,\n })\n } catch (err) {\n const code = err instanceof Error ? err.message : \"\"\n setError(\n code === \"account_not_linked\"\n ? t.accountNotLinked\n : code === \"access_denied\"\n ? t.socialCancelled\n : t.socialFailed,\n )\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <header className=\"space-y-1.5\">\n <h1 className=\"text-[26px] font-semibold leading-tight tracking-[-0.02em] text-foreground sm:text-2xl\">\n {t.title}\n </h1>\n <p className=\"text-balance text-sm leading-relaxed text-muted-foreground\">\n {t.subtitle}\n </p>\n </header>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-xl border border-destructive/25 bg-destructive/10 px-3.5 py-3 text-sm leading-snug text-destructive\"\n >\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-5\" noValidate>\n <AuthField\n label={t.emailPlaceholder}\n type=\"email\"\n inputMode=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"email\"\n autoCapitalize=\"none\"\n spellCheck={false}\n invalid={!!error}\n />\n\n <AuthField\n label={t.passwordPlaceholder}\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"current-password\"\n invalid={!!error}\n hint={\n <a\n href={forgotPasswordUrl}\n className=\"rounded text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.forgotPassword}\n </a>\n }\n />\n\n <AuthSubmit\n pending={isPending}\n disabled={!email.trim() || !password}\n pendingLabel={t.submitPending}\n >\n {t.submit}\n </AuthSubmit>\n </form>\n\n <SocialButtons\n providers={socialProviders}\n onSelect={handleSocial}\n disabled={isPending}\n />\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t.noAccount}{\" \"}\n <a\n href={registerUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.register}\n </a>\n </p>\n </div>\n )\n}\n","const LABELS: Record<string, string> = {\n google: \"Continuer avec Google\",\n github: \"Continuer avec GitHub\",\n}\n\ninterface SocialButtonsProps {\n providers: Array<\"google\" | \"github\">\n onSelect: (provider: \"google\" | \"github\") => void | Promise<void>\n disabled?: boolean\n /** Divider text between the password form and the providers */\n separator?: string\n /** Per-provider button copy, merged over the French defaults */\n labels?: Partial<Record<\"google\" | \"github\", string>>\n}\n\nexport function SocialButtons({\n providers,\n onSelect,\n disabled = false,\n separator = \"ou\",\n labels,\n}: SocialButtonsProps) {\n if (providers.length === 0) return null\n\n const copy = { ...LABELS, ...labels }\n\n return (\n <div className=\"space-y-4\">\n {/* The rule is two flex segments rather than a line behind an opaque\n label: an opaque background only hides the rule when it matches the\n surface behind it, which breaks the moment this sits on a card. */}\n <div aria-hidden=\"true\" className=\"flex items-center gap-3\">\n <span className=\"h-px flex-1 bg-border\" />\n <span className=\"text-[11px] uppercase tracking-[0.14em] text-muted-foreground\">\n {separator}\n </span>\n <span className=\"h-px flex-1 bg-border\" />\n </div>\n\n <div className=\"space-y-2.5\">\n {providers.map((provider) => (\n <button\n key={provider}\n type=\"button\"\n onClick={() => onSelect(provider)}\n disabled={disabled}\n className={[\n \"flex h-[52px] w-full items-center justify-center gap-2.5 rounded-xl sm:h-11\",\n \"border border-input bg-background text-base font-medium text-foreground sm:text-sm\",\n \"transition-[background-color,border-color,transform] duration-150 ease-out\",\n \"hover:border-foreground/20 hover:bg-accent active:translate-y-px\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-55\",\n ].join(\" \")}\n >\n <ProviderMark provider={provider} />\n {copy[provider] ?? provider}\n </button>\n ))}\n </div>\n </div>\n )\n}\n\n// Brand marks are inlined: they must keep their own colors (Google's mark is\n// unusable in monochrome) and adding an icon dependency to this package would\n// duplicate one every consumer already ships.\nfunction ProviderMark({ provider }: { provider: \"google\" | \"github\" }) {\n if (provider === \"google\") {\n return (\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path\n fill=\"#4285F4\"\n d=\"M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62Z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.02-3.7H.96v2.33A9 9 0 0 0 9 18Z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M3.98 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.02-2.33Z\"\n />\n <path\n fill=\"#EA4335\"\n d=\"M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.46.9 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.02 2.33C4.68 5.16 6.66 3.58 9 3.58Z\"\n />\n </svg>\n )\n }\n return (\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.4 7.4 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8 8 0 0 0 8 0Z\" />\n </svg>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { RegisterFormLabels, RegisterFormProps } from \"../types\"\nimport { AuthField } from \"./auth-field\"\nimport { AuthSubmit } from \"./auth-submit\"\nimport { SocialButtons } from \"./social-buttons\"\n\nconst MIN_PASSWORD_LENGTH = 8\n\nconst DEFAULTS: Required<RegisterFormLabels> = {\n title: \"Créer un compte\",\n subtitle: \"Nous t'enverrons un code pour confirmer ton adresse e-mail.\",\n namePlaceholder: \"Nom complet\",\n emailPlaceholder: \"Adresse e-mail\",\n passwordPlaceholder: \"Mot de passe\",\n passwordHint: `Au moins ${MIN_PASSWORD_LENGTH} caractères.`,\n submit: \"Créer mon compte\",\n submitPending: \"Création…\",\n haveAccount: \"Tu as déjà un compte ?\",\n login: \"Se connecter\",\n nameRequired: \"Renseigne ton nom\",\n emailRequired: \"Renseigne ton adresse e-mail\",\n passwordTooShort: `Le mot de passe doit faire au moins ${MIN_PASSWORD_LENGTH} caractères`,\n signUpFailed: \"La création du compte a échoué\",\n}\n\nexport function RegisterForm({\n onSuccess,\n loginUrl = \"/login\",\n legal,\n socialCallbackUrl = \"/\",\n socialProviders = [],\n labels,\n authClient,\n}: RegisterFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [name, setName] = useState(\"\")\n const [email, setEmail] = useState(\"\")\n const [password, setPassword] = useState(\"\")\n const [error, setError] = useState<string | undefined>()\n const [isPending, setIsPending] = useState(false)\n\n const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!name.trim()) {\n setError(t.nameRequired)\n return\n }\n if (!email.trim()) {\n setError(t.emailRequired)\n return\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n setError(t.passwordTooShort)\n return\n }\n setError(undefined)\n setIsPending(true)\n try {\n const res = await authClient.signUp.email({\n name: name.trim(),\n email: email.trim(),\n password,\n })\n if (res?.error) {\n setError(res.error.message ?? t.signUpFailed)\n return\n }\n // createPlatformAuth sets requireEmailVerification, so sign-up leaves the\n // account unverified and without a session: the caller routes to the OTP\n // step rather than into the app.\n onSuccess?.(email.trim())\n } catch (err) {\n setError(err instanceof Error ? err.message : t.signUpFailed)\n } finally {\n setIsPending(false)\n }\n }\n\n const handleSocial = async (provider: \"google\" | \"github\") => {\n setError(undefined)\n try {\n await authClient.signIn.social({\n provider,\n callbackURL: socialCallbackUrl,\n })\n } catch (err) {\n setError(err instanceof Error ? err.message : t.signUpFailed)\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <header className=\"space-y-1.5\">\n <h1 className=\"text-[26px] font-semibold leading-tight tracking-[-0.02em] text-foreground sm:text-2xl\">\n {t.title}\n </h1>\n <p className=\"text-balance text-sm leading-relaxed text-muted-foreground\">\n {t.subtitle}\n </p>\n </header>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-xl border border-destructive/25 bg-destructive/10 px-3.5 py-3 text-sm leading-snug text-destructive\"\n >\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-5\" noValidate>\n <AuthField\n label={t.namePlaceholder}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"name\"\n />\n\n <AuthField\n label={t.emailPlaceholder}\n type=\"email\"\n inputMode=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"email\"\n autoCapitalize=\"none\"\n spellCheck={false}\n />\n\n <AuthField\n label={t.passwordPlaceholder}\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"new-password\"\n description={t.passwordHint}\n // Only once they have typed something: flagging an untouched field\n // red would scold someone for not having started yet.\n invalid={tooShort}\n />\n\n <AuthSubmit pending={isPending} pendingLabel={t.submitPending}>\n {t.submit}\n </AuthSubmit>\n\n {legal && (\n <p className=\"text-center text-xs leading-relaxed text-muted-foreground\">\n {legal}\n </p>\n )}\n </form>\n\n <SocialButtons\n providers={socialProviders}\n onSelect={handleSocial}\n disabled={isPending}\n />\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t.haveAccount}{\" \"}\n <a\n href={loginUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.login}\n </a>\n </p>\n </div>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { VerifyEmailFormProps } from \"../types\"\n\nexport function VerifyEmailForm({\n email,\n onSuccess,\n authClient,\n}: VerifyEmailFormProps) {\n const [otp, setOtp] = useState(\"\")\n const [isVerifying, setIsVerifying] = useState(false)\n const [isResending, setIsResending] = useState(false)\n const [resendMessage, setResendMessage] = useState<string | undefined>()\n const [error, setError] = useState<string | undefined>()\n\n const handleVerify = async (e: FormEvent) => {\n e.preventDefault()\n if (!otp.trim() || otp.length < 6) {\n setError(\"Please enter the 6-digit code\")\n return\n }\n setError(undefined)\n setIsVerifying(true)\n try {\n const res = await authClient.emailOtp.verifyEmail({ email, otp })\n console.log(\"[verify-email] response:\", JSON.stringify(res?.data), \"error:\", JSON.stringify(res?.error))\n if (res?.error) {\n setError(res.error.message ?? \"Invalid code. Please try again.\")\n return\n }\n onSuccess?.()\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Invalid code. Please try again.\")\n } finally {\n setIsVerifying(false)\n }\n }\n\n const handleResend = async () => {\n if (!email) {\n setError(\"Email address is not available. Please register again.\")\n return\n }\n setError(undefined)\n setResendMessage(undefined)\n setIsResending(true)\n try {\n await authClient.emailOtp.sendVerificationOtp({\n email,\n type: \"email-verification\",\n })\n setResendMessage(\"A new code has been sent to your inbox.\")\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to resend code.\")\n } finally {\n setIsResending(false)\n }\n }\n\n return (\n <div className=\"space-y-8\">\n <div>\n <h1 className=\"text-2xl font-bold tracking-tight\">\n Verify your email\n </h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {email ? (\n <>\n Enter the 6-digit code sent to{\" \"}\n <span className=\"font-medium text-foreground\">{email}</span>\n </>\n ) : (\n \"Enter the 6-digit code sent to your email\"\n )}\n </p>\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {error}\n </div>\n )}\n\n {resendMessage && (\n <div className=\"rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700\">\n {resendMessage}\n </div>\n )}\n\n <form onSubmit={handleVerify} className=\"space-y-4\" noValidate>\n <input\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={6}\n value={otp}\n onChange={(e) => setOtp(e.target.value.replace(/\\D/g, \"\"))}\n placeholder=\"000000\"\n required\n disabled={isVerifying}\n autoComplete=\"one-time-code\"\n className=\"flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-center text-lg tracking-[0.4em] font-mono ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n <button\n type=\"submit\"\n disabled={isVerifying || otp.length < 6}\n className=\"inline-flex h-11 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\"\n >\n {isVerifying ? \"Verifying...\" : \"Verify email\"}\n </button>\n\n <button\n type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n className=\"inline-flex h-11 w-full items-center justify-center rounded-md border border-input bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n >\n {isResending ? \"Sending...\" : \"Resend code\"}\n </button>\n </form>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n Already verified?{\" \"}\n <a\n href=\"/login\"\n className=\"font-medium text-foreground underline underline-offset-4 hover:text-foreground/80\"\n >\n Sign in\n </a>\n </p>\n </div>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { ForgotPasswordFormProps } from \"../types\"\n\nexport function ForgotPasswordForm({\n onSuccess,\n loginUrl = \"/login\",\n authClient,\n}: ForgotPasswordFormProps) {\n const [email, setEmail] = useState(\"\")\n const [error, setError] = useState<string | undefined>()\n const [isPending, setIsPending] = useState(false)\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!email.trim()) {\n setError(\"Please enter your email address\")\n return\n }\n setError(undefined)\n setIsPending(true)\n try {\n const res = await authClient.emailOtp.sendVerificationOtp({\n email,\n type: \"forget-password\",\n })\n if (res?.error) {\n setError(res.error.message ?? \"Failed to send reset code\")\n return\n }\n onSuccess?.(email)\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to send reset code\")\n } finally {\n setIsPending(false)\n }\n }\n\n return (\n <div className=\"space-y-8\">\n <div>\n <h1 className=\"text-2xl font-bold tracking-tight\">\n Forgot your password?\n </h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n Enter your email address and we'll send you a code to reset your\n password.\n </p>\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-4\" noValidate>\n <input\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"Email address\"\n required\n disabled={isPending}\n autoComplete=\"email\"\n className=\"flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n <button\n type=\"submit\"\n disabled={isPending || !email.trim()}\n className=\"inline-flex h-11 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\"\n >\n {isPending ? \"Sending...\" : \"Send reset code\"}\n </button>\n </form>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n Remember your password?{\" \"}\n <a\n href={loginUrl}\n className=\"font-medium text-foreground underline underline-offset-4 hover:text-foreground/80\"\n >\n Sign in\n </a>\n </p>\n </div>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { ResetPasswordFormProps } from \"../types\"\n\nexport function ResetPasswordForm({\n email,\n onSuccess,\n loginUrl = \"/login\",\n authClient,\n}: ResetPasswordFormProps) {\n const [otp, setOtp] = useState(\"\")\n const [password, setPassword] = useState(\"\")\n const [confirmPassword, setConfirmPassword] = useState(\"\")\n const [error, setError] = useState<string | undefined>()\n const [isResetting, setIsResetting] = useState(false)\n const [isResending, setIsResending] = useState(false)\n const [resendMessage, setResendMessage] = useState<string | undefined>()\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!otp.trim() || otp.length < 6) {\n setError(\"Please enter the 6-digit code\")\n return\n }\n if (password.length < 8) {\n setError(\"Password must be at least 8 characters\")\n return\n }\n if (password !== confirmPassword) {\n setError(\"Passwords do not match\")\n return\n }\n setError(undefined)\n setIsResetting(true)\n try {\n const res = await fetch(\"/api/auth/email-otp/reset-password\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, otp, password }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => null)\n setError(body?.message ?? \"Failed to reset password\")\n return\n }\n onSuccess?.()\n } catch (err) {\n setError(\n err instanceof Error ? err.message : \"Failed to reset password\",\n )\n } finally {\n setIsResetting(false)\n }\n }\n\n const handleResend = async () => {\n setError(undefined)\n setResendMessage(undefined)\n setIsResending(true)\n try {\n await authClient.emailOtp.sendVerificationOtp({\n email,\n type: \"forget-password\",\n })\n setResendMessage(\"A new code has been sent to your inbox.\")\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to resend code.\")\n } finally {\n setIsResending(false)\n }\n }\n\n return (\n <div className=\"space-y-8\">\n <div>\n <h1 className=\"text-2xl font-bold tracking-tight\">\n Reset your password\n </h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n Enter the 6-digit code sent to{\" \"}\n <span className=\"font-medium text-foreground\">{email}</span> and your\n new password.\n </p>\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {error}\n </div>\n )}\n\n {resendMessage && (\n <div className=\"rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700\">\n {resendMessage}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-4\" noValidate>\n <input\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={6}\n value={otp}\n onChange={(e) => setOtp(e.target.value.replace(/\\D/g, \"\"))}\n placeholder=\"000000\"\n required\n disabled={isResetting}\n autoComplete=\"one-time-code\"\n className=\"flex h-12 w-full rounded-md border border-input bg-background px-3 py-2 text-center text-lg tracking-[0.4em] font-mono ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n <input\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"New password\"\n required\n disabled={isResetting}\n autoComplete=\"new-password\"\n className=\"flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n <input\n type=\"password\"\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n placeholder=\"Confirm new password\"\n required\n disabled={isResetting}\n autoComplete=\"new-password\"\n className=\"flex h-11 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n\n <button\n type=\"submit\"\n disabled={isResetting || otp.length < 6 || !password}\n className=\"inline-flex h-11 w-full items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\"\n >\n {isResetting ? \"Resetting...\" : \"Reset password\"}\n </button>\n\n <button\n type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n className=\"inline-flex h-11 w-full items-center justify-center rounded-md border border-input bg-background px-4 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n >\n {isResending ? \"Sending...\" : \"Resend code\"}\n </button>\n </form>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n Remember your password?{\" \"}\n <a\n href={loginUrl}\n className=\"font-medium text-foreground underline underline-offset-4 hover:text-foreground/80\"\n >\n Sign in\n </a>\n </p>\n </div>\n )\n}\n","import type { AuthLayoutProps } from \"../types\"\n\n/**\n * The frame every auth screen sits in.\n *\n * Mobile and desktop are two layouts, not one scaled down. On a phone the form\n * IS the page: no card, no border, edge-to-edge padding, top-aligned so the\n * fields stay above the keyboard instead of being pushed under it by vertical\n * centering. From sm up it becomes a bounded card on a tinted ground — a\n * full-width form on a 1440px display is unreadable. It stays top-aligned\n * there too: vertical centering drops a short form to the middle of a tall\n * window, well below where the eye lands.\n *\n * min-h-dvh rather than min-h-screen: on mobile browsers 100vh includes the\n * retracting URL bar, so a screen-height container overflows by its height.\n */\nexport function AuthLayout({\n logo,\n title,\n subtitle,\n children,\n footer,\n}: AuthLayoutProps) {\n return (\n <div className=\"flex min-h-dvh flex-col bg-background px-5 pb-10 pt-12 sm:items-center sm:bg-muted/40 sm:px-6 sm:py-16\">\n <div className=\"mx-auto w-full max-w-[420px]\">\n <div className=\"mb-7 text-center sm:mb-6\">\n {logo && <div className=\"mb-5 flex justify-center\">{logo}</div>}\n <p className=\"text-sm font-medium tracking-[0.02em] text-foreground\">\n {title}\n </p>\n {subtitle && (\n <p className=\"mt-1 text-balance text-sm text-muted-foreground\">\n {subtitle}\n </p>\n )}\n </div>\n\n <div className=\"sm:rounded-2xl sm:border sm:border-border/70 sm:bg-card sm:p-8 sm:shadow-lg sm:shadow-black/5\">\n {children}\n </div>\n\n {footer && (\n <div className=\"mt-6 text-center text-xs leading-relaxed text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n </div>\n )\n}\n","import type { InvitationNoticeProps } from \"../types\"\n\n/**\n * Every app is reachable at contact@ its own apex domain, so the address is\n * derived rather than configured. Sub-domains are stripped because the app is\n * routinely served from app./admin. while the mailbox lives on the apex;\n * multi-part public suffixes (.co.uk) would need a real suffix list and no app\n * using this is on one.\n */\nfunction defaultSupportEmail(): string | undefined {\n if (typeof window === \"undefined\") return undefined\n const host = window.location.hostname\n if (!host || host === \"localhost\" || /^[\\d.]+$/.test(host)) return undefined\n const apex = host.split(\".\").slice(-2).join(\".\")\n return `contact@${apex}`\n}\n\nconst REASON_MESSAGE: Record<string, string> = {\n expired: \"Cette invitation a expiré.\",\n claimed: \"Cette invitation a déjà été utilisée.\",\n unknown: \"Ce lien d'invitation n'est pas valide.\",\n}\n\n/**\n * What an invitee sees when their link does not work.\n *\n * It names the reason instead of merging the cases into one message: \"expired\"\n * tells someone their link was real and that asking for another one is worth\n * it, which \"invalid\" does not. Short TTLs make that distinction routine.\n *\n * The only way forward offered is a mailto, deliberately. A self-service\n * \"request a new invitation\" form would mean a public endpoint accepting dead\n * tokens, a queue to moderate, and a way to probe which tokens once existed —\n * for a flow where the operator already knows the person by name.\n */\nexport function InvitationNotice({\n reason = \"unknown\",\n supportEmail,\n title = \"Invitation indisponible\",\n action,\n}: InvitationNoticeProps) {\n const contact = supportEmail ?? defaultSupportEmail()\n\n return (\n <div className=\"space-y-8\">\n <div>\n <h1 className=\"text-2xl font-bold tracking-tight\">{title}</h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown}\n </p>\n </div>\n\n {contact ? (\n <p className=\"text-sm text-muted-foreground\">\n Écrivez-nous à{\" \"}\n <a\n href={`mailto:${contact}`}\n className=\"font-medium text-foreground underline underline-offset-4 hover:text-foreground/80\"\n >\n {contact}\n </a>{\" \"}\n pour en recevoir une nouvelle.\n </p>\n ) : null}\n\n {action}\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","useState","useState","jsx","jsxs","DEFAULTS","useState","useState","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/hooks/use-session.ts","../src/components/auth-field.tsx","../src/components/auth-submit.tsx","../src/components/login-form.tsx","../src/components/auth-alert.tsx","../src/components/auth-heading.tsx","../src/components/social-buttons.tsx","../src/components/register-form.tsx","../src/components/verify-email-form.tsx","../src/components/auth-otp-field.tsx","../src/components/forgot-password-form.tsx","../src/components/reset-password-form.tsx","../src/components/auth-layout.tsx","../src/components/invitation-notice.tsx"],"sourcesContent":["import type { PlatformAuthClient } from \"../client\"\n\n/**\n * Returns a useSession hook bound to the given auth client.\n * Usage: const { data: session, isPending } = useSession(authClient)\n */\nexport function useSession(authClient: PlatformAuthClient) {\n return authClient.useSession()\n}\n\n/**\n * Returns a logout function bound to the given auth client.\n * Usage: const logout = useLogout(authClient)\n */\nexport function useLogout(authClient: PlatformAuthClient) {\n const signOut = async () => {\n await authClient.signOut()\n }\n return signOut\n}\n","import { useId, useState, type InputHTMLAttributes, type ReactNode } from \"react\"\n\ntype NativeProps = Omit<InputHTMLAttributes<HTMLInputElement>, \"id\" | \"className\">\n\nexport interface AuthFieldProps extends NativeProps {\n label: string\n /** Rendered on the right of the label row — typically a \"forgot password\" link */\n hint?: ReactNode\n /** Persistent helper text under the field, tied to it for screen readers */\n description?: string\n invalid?: boolean\n}\n\n/**\n * A single credential field.\n *\n * The label is a real <label>, always visible, never a placeholder standing in\n * for one: a placeholder disappears the moment someone types, which is exactly\n * when a person double-checking a long password needs to know what the box is.\n *\n * Touch targets are 52px tall on mobile and 44px from sm up. Below ~48px,\n * thumbs miss; on a pointer device the same height reads as oversized, so the\n * two are not one compromise value.\n *\n * The focus ring is a ring rather than an outline so it follows the rounded\n * corners exactly, and the border darkens at the same time — the border is what\n * survives forced-colors mode, where the ring is dropped.\n */\nexport function AuthField({\n label,\n hint,\n description,\n invalid = false,\n ...props\n}: AuthFieldProps) {\n const id = useId()\n const descriptionId = description ? `${id}-description` : undefined\n const [revealed, setRevealed] = useState(false)\n\n const isPassword = props.type === \"password\"\n const type = isPassword && revealed ? \"text\" : props.type\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-baseline justify-between gap-3\">\n <label\n htmlFor={id}\n className=\"text-sm font-medium leading-none text-foreground\"\n >\n {label}\n </label>\n {hint}\n </div>\n\n <div className=\"relative\">\n <input\n {...props}\n id={id}\n type={type}\n aria-invalid={invalid || undefined}\n aria-describedby={descriptionId}\n className={[\n \"h-[52px] w-full rounded-xl border bg-background px-4 text-base\",\n \"sm:h-11 sm:text-sm\",\n isPassword ? \"pr-12\" : \"\",\n \"text-foreground placeholder:text-muted-foreground/70\",\n \"transition-[border-color,box-shadow] duration-150 ease-out\",\n \"focus-visible:outline-none focus-visible:ring-[3px]\",\n invalid\n ? \"border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20\"\n : \"border-input focus-visible:border-ring focus-visible:ring-ring/15\",\n \"disabled:cursor-not-allowed disabled:opacity-60\",\n ]\n .filter(Boolean)\n .join(\" \")}\n />\n\n {isPassword && (\n <button\n type=\"button\"\n onClick={() => setRevealed((v) => !v)}\n disabled={props.disabled}\n aria-pressed={revealed}\n aria-label={\n revealed ? \"Masquer le mot de passe\" : \"Afficher le mot de passe\"\n }\n className=\"absolute inset-y-0 right-0 flex w-12 items-center justify-center rounded-r-xl text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-60\"\n >\n <EyeIcon open={revealed} />\n </button>\n )}\n </div>\n\n {description && (\n <p id={descriptionId} className=\"text-xs text-muted-foreground\">\n {description}\n </p>\n )}\n </div>\n )\n}\n\n// Inline rather than from lucide-react: the package would gain a dependency\n// consumers already have at a different version, for two paths.\nfunction EyeIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0Z\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n {!open && <path d=\"m3 3 18 18\" />}\n </svg>\n )\n}\n","import type { ReactNode } from \"react\"\n\ninterface AuthSubmitProps {\n pending?: boolean\n disabled?: boolean\n pendingLabel: string\n children: ReactNode\n}\n\n/**\n * The primary action of an auth screen.\n *\n * The label swaps to its pending form in place, with the spinner absolutely\n * positioned: a spinner inserted into the flow would widen the row and shift\n * the text sideways at the exact moment the person is watching it.\n *\n * The press feedback is a 1px translate rather than a scale — scaling a\n * full-width button visibly blurs its text mid-transform.\n */\nexport function AuthSubmit({\n pending = false,\n disabled = false,\n pendingLabel,\n children,\n}: AuthSubmitProps) {\n return (\n <button\n type=\"submit\"\n disabled={disabled || pending}\n aria-busy={pending || undefined}\n className={[\n \"relative flex h-[52px] w-full items-center justify-center rounded-xl sm:h-11\",\n \"bg-primary text-base font-medium text-primary-foreground sm:text-sm\",\n \"shadow-sm transition-[background-color,transform,box-shadow] duration-150 ease-out\",\n \"hover:bg-primary/90 active:translate-y-px\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-55\",\n ].join(\" \")}\n >\n {pending && (\n <span\n aria-hidden=\"true\"\n className=\"absolute left-4 size-4 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70 motion-reduce:animate-none\"\n />\n )}\n {pending ? pendingLabel : children}\n </button>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { LoginFormLabels, LoginFormProps } from \"../types\"\nimport { AuthAlert } from \"./auth-alert\"\nimport { AuthField } from \"./auth-field\"\nimport { AuthHeading } from \"./auth-heading\"\nimport { AuthSubmit } from \"./auth-submit\"\nimport { SocialButtons } from \"./social-buttons\"\n\nconst DEFAULTS: Required<LoginFormLabels> = {\n title: \"Connexion\",\n subtitle: \"Content de te revoir. Entre tes identifiants pour continuer.\",\n emailPlaceholder: \"Adresse e-mail\",\n passwordPlaceholder: \"Mot de passe\",\n forgotPassword: \"Mot de passe oublié ?\",\n submit: \"Se connecter\",\n submitPending: \"Connexion…\",\n noAccount: \"Pas encore de compte ?\",\n register: \"Créer un compte\",\n emailRequired: \"Renseigne ton adresse e-mail\",\n passwordRequired: \"Renseigne ton mot de passe\",\n invalidCredentials: \"Adresse e-mail ou mot de passe incorrect\",\n // accountLinking is disabled in createPlatformAuth, so a social sign-in on an\n // address already registered with a password is refused with this code.\n // Without a message the button reads as broken rather than as a rejection.\n accountNotLinked:\n \"Cette adresse est déjà associée à un mot de passe. Connecte-toi avec ton mot de passe.\",\n socialCancelled: \"Connexion annulée.\",\n socialFailed: \"La connexion a échoué. Réessaie.\",\n}\n\nexport function LoginForm({\n onSuccess,\n registerUrl = \"/register\",\n forgotPasswordUrl = \"/forgot-password\",\n socialCallbackUrl = \"/\",\n socialProviders = [],\n coreTokenUrl = \"/api/auth/core-token\",\n labels,\n titleClassName,\n error: externalError,\n authClient,\n}: LoginFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [email, setEmail] = useState(\"\")\n const [password, setPassword] = useState(\"\")\n const [ownError, setOwnError] = useState<string | undefined>()\n const [isPending, setIsPending] = useState(false)\n\n // What the person just did outranks what happened before they arrived.\n const error = ownError ?? externalError\n const setError = setOwnError\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!email.trim()) {\n setError(t.emailRequired)\n return\n }\n if (!password) {\n setError(t.passwordRequired)\n return\n }\n setError(undefined)\n setIsPending(true)\n try {\n const res = await authClient.signIn.email({\n email: email.trim(),\n password,\n })\n if (res?.error) {\n setError(res.error.message ?? t.invalidCredentials)\n return\n }\n // The better-auth session cookie alone does not authenticate a Go core:\n // it verifies the EdDSA JWT minted here against the issuer's JWKS.\n // Skipped when the app sets the token itself, or has no core at all.\n if (coreTokenUrl) {\n await fetch(coreTokenUrl, { credentials: \"include\" })\n }\n onSuccess?.()\n } catch (err) {\n setError(err instanceof Error ? err.message : t.invalidCredentials)\n } finally {\n setIsPending(false)\n }\n }\n\n const handleSocial = async (provider: \"google\" | \"github\") => {\n setError(undefined)\n try {\n await authClient.signIn.social({\n provider,\n callbackURL: socialCallbackUrl,\n })\n } catch (err) {\n const code = err instanceof Error ? err.message : \"\"\n setError(\n code === \"account_not_linked\"\n ? t.accountNotLinked\n : code === \"access_denied\"\n ? t.socialCancelled\n : t.socialFailed,\n )\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <AuthHeading\n title={t.title}\n subtitle={t.subtitle}\n titleClassName={titleClassName}\n />\n\n <AuthAlert>{error}</AuthAlert>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\" noValidate>\n <AuthField\n label={t.emailPlaceholder}\n type=\"email\"\n inputMode=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"email\"\n autoCapitalize=\"none\"\n spellCheck={false}\n invalid={!!error}\n />\n\n <AuthField\n label={t.passwordPlaceholder}\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"current-password\"\n invalid={!!error}\n hint={\n <a\n href={forgotPasswordUrl}\n className=\"rounded text-xs text-muted-foreground underline-offset-4 transition-colors hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.forgotPassword}\n </a>\n }\n />\n\n <AuthSubmit\n pending={isPending}\n disabled={!email.trim() || !password}\n pendingLabel={t.submitPending}\n >\n {t.submit}\n </AuthSubmit>\n </form>\n\n <SocialButtons\n providers={socialProviders}\n onSelect={handleSocial}\n disabled={isPending}\n />\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t.noAccount}{\" \"}\n <a\n href={registerUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.register}\n </a>\n </p>\n </div>\n )\n}\n","interface AuthAlertProps {\n children?: string\n tone?: \"error\" | \"success\"\n}\n\n/**\n * Inline feedback for an auth screen.\n *\n * `role=\"alert\"` on the wrapper is not enough on its own: the node has to be\n * in the tree before the text lands in it for the live region to fire, which\n * is why the element renders empty rather than being mounted with its message.\n *\n * The success tone uses the theme's own tokens rather than a fixed green,\n * which would sit on a dark surface as an unreadable pale block.\n */\nexport function AuthAlert({ children, tone = \"error\" }: AuthAlertProps) {\n return (\n <div\n role={tone === \"error\" ? \"alert\" : \"status\"}\n aria-live={tone === \"error\" ? \"assertive\" : \"polite\"}\n className={\n children\n ? [\n \"rounded-xl border px-3.5 py-3 text-sm leading-snug\",\n tone === \"error\"\n ? \"border-destructive/25 bg-destructive/10 text-destructive\"\n : \"border-emerald-500/25 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400\",\n ].join(\" \")\n : undefined\n }\n >\n {children}\n </div>\n )\n}\n","interface AuthHeadingProps {\n title: string\n subtitle?: string\n /** Replaces the default size and weight — pass the whole look */\n titleClassName?: string\n}\n\n/**\n * The heading of an auth screen, centred above left-aligned fields. That\n * contrast of alignment is what gives the card its hierarchy without a rule.\n *\n * It lives in the form rather than in the layout so a screen carries exactly\n * one <h1>, and so the copy travels with the component that owns the step.\n */\nexport function AuthHeading({\n title,\n subtitle,\n titleClassName = \"text-2xl font-semibold tracking-tight\",\n}: AuthHeadingProps) {\n return (\n <header className=\"space-y-1.5 text-center\">\n <h1 className={`leading-tight text-foreground ${titleClassName}`}>\n {title}\n </h1>\n {subtitle && (\n <p className=\"text-balance text-sm leading-relaxed text-muted-foreground\">\n {subtitle}\n </p>\n )}\n </header>\n )\n}\n","const LABELS: Record<string, string> = {\n google: \"Continuer avec Google\",\n github: \"Continuer avec GitHub\",\n}\n\ninterface SocialButtonsProps {\n providers: Array<\"google\" | \"github\">\n onSelect: (provider: \"google\" | \"github\") => void | Promise<void>\n disabled?: boolean\n /** Divider text between the password form and the providers */\n separator?: string\n /** Per-provider button copy, merged over the French defaults */\n labels?: Partial<Record<\"google\" | \"github\", string>>\n}\n\nexport function SocialButtons({\n providers,\n onSelect,\n disabled = false,\n separator = \"ou\",\n labels,\n}: SocialButtonsProps) {\n if (providers.length === 0) return null\n\n const copy = { ...LABELS, ...labels }\n\n return (\n <div className=\"space-y-4\">\n {/* The rule is two flex segments rather than a line behind an opaque\n label: an opaque background only hides the rule when it matches the\n surface behind it, which breaks the moment this sits on a card. */}\n <div aria-hidden=\"true\" className=\"flex items-center gap-3\">\n <span className=\"h-px flex-1 bg-border\" />\n <span className=\"text-[11px] uppercase tracking-[0.14em] text-muted-foreground\">\n {separator}\n </span>\n <span className=\"h-px flex-1 bg-border\" />\n </div>\n\n {/* One per row while there is space for the wording, side by side once\n there are several: in a half-card the full label no longer fits, and\n a truncated \"Continuer avec…\" says less than the mark alone. The\n wording stays as the accessible name either way. */}\n <div\n className={\n providers.length > 1\n ? \"grid gap-2.5 sm:grid-cols-2\"\n : \"grid gap-2.5\"\n }\n >\n {providers.map((provider) => (\n <button\n key={provider}\n type=\"button\"\n onClick={() => onSelect(provider)}\n disabled={disabled}\n aria-label={copy[provider] ?? provider}\n className={[\n \"flex h-[52px] w-full items-center justify-center gap-2.5 rounded-xl sm:h-11\",\n \"border border-input bg-background text-base font-medium text-foreground sm:text-sm\",\n \"transition-[background-color,border-color,transform] duration-150 ease-out\",\n \"hover:border-foreground/20 hover:bg-accent active:translate-y-px\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-55\",\n ].join(\" \")}\n >\n <ProviderMark provider={provider} />\n <span className={providers.length > 1 ? \"sm:hidden\" : \"\"}>\n {copy[provider] ?? provider}\n </span>\n </button>\n ))}\n </div>\n </div>\n )\n}\n\n// Brand marks are inlined: they must keep their own colors (Google's mark is\n// unusable in monochrome) and adding an icon dependency to this package would\n// duplicate one every consumer already ships.\nfunction ProviderMark({ provider }: { provider: \"google\" | \"github\" }) {\n if (provider === \"google\") {\n return (\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path\n fill=\"#4285F4\"\n d=\"M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62Z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.02-3.7H.96v2.33A9 9 0 0 0 9 18Z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M3.98 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.02-2.33Z\"\n />\n <path\n fill=\"#EA4335\"\n d=\"M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.46.9 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.02 2.33C4.68 5.16 6.66 3.58 9 3.58Z\"\n />\n </svg>\n )\n }\n return (\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38l-.01-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.4 7.4 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48l-.01 2.2c0 .21.15.46.55.38A8 8 0 0 0 8 0Z\" />\n </svg>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { RegisterFormLabels, RegisterFormProps } from \"../types\"\nimport { AuthAlert } from \"./auth-alert\"\nimport { AuthField } from \"./auth-field\"\nimport { AuthHeading } from \"./auth-heading\"\nimport { AuthSubmit } from \"./auth-submit\"\nimport { SocialButtons } from \"./social-buttons\"\n\nconst MIN_PASSWORD_LENGTH = 8\n\nconst DEFAULTS: Required<RegisterFormLabels> = {\n title: \"Créer un compte\",\n subtitle: \"Nous t'enverrons un code pour confirmer ton adresse e-mail.\",\n namePlaceholder: \"Nom complet\",\n emailPlaceholder: \"Adresse e-mail\",\n passwordPlaceholder: \"Mot de passe\",\n passwordHint: `Au moins ${MIN_PASSWORD_LENGTH} caractères.`,\n submit: \"Créer mon compte\",\n submitPending: \"Création…\",\n haveAccount: \"Tu as déjà un compte ?\",\n login: \"Se connecter\",\n nameRequired: \"Renseigne ton nom\",\n emailRequired: \"Renseigne ton adresse e-mail\",\n passwordTooShort: `Le mot de passe doit faire au moins ${MIN_PASSWORD_LENGTH} caractères`,\n signUpFailed: \"La création du compte a échoué\",\n}\n\nexport function RegisterForm({\n onSuccess,\n loginUrl = \"/login\",\n legal,\n socialCallbackUrl = \"/\",\n socialProviders = [],\n labels,\n titleClassName,\n error: externalError,\n authClient,\n}: RegisterFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [name, setName] = useState(\"\")\n const [email, setEmail] = useState(\"\")\n const [password, setPassword] = useState(\"\")\n const [ownError, setOwnError] = useState<string | undefined>()\n const [isPending, setIsPending] = useState(false)\n\n // What the person just did outranks what happened before they arrived.\n const error = ownError ?? externalError\n const setError = setOwnError\n\n const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!name.trim()) {\n setError(t.nameRequired)\n return\n }\n if (!email.trim()) {\n setError(t.emailRequired)\n return\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n setError(t.passwordTooShort)\n return\n }\n setError(undefined)\n setIsPending(true)\n try {\n const res = await authClient.signUp.email({\n name: name.trim(),\n email: email.trim(),\n password,\n })\n if (res?.error) {\n setError(res.error.message ?? t.signUpFailed)\n return\n }\n // createPlatformAuth sets requireEmailVerification, so sign-up leaves the\n // account unverified and without a session: the caller routes to the OTP\n // step rather than into the app.\n onSuccess?.(email.trim())\n } catch (err) {\n setError(err instanceof Error ? err.message : t.signUpFailed)\n } finally {\n setIsPending(false)\n }\n }\n\n const handleSocial = async (provider: \"google\" | \"github\") => {\n setError(undefined)\n try {\n await authClient.signIn.social({\n provider,\n callbackURL: socialCallbackUrl,\n })\n } catch (err) {\n setError(err instanceof Error ? err.message : t.signUpFailed)\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <AuthHeading\n title={t.title}\n subtitle={t.subtitle}\n titleClassName={titleClassName}\n />\n\n <AuthAlert>{error}</AuthAlert>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\" noValidate>\n <AuthField\n label={t.namePlaceholder}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"name\"\n />\n\n <AuthField\n label={t.emailPlaceholder}\n type=\"email\"\n inputMode=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"email\"\n autoCapitalize=\"none\"\n spellCheck={false}\n />\n\n <AuthField\n label={t.passwordPlaceholder}\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"new-password\"\n description={t.passwordHint}\n // Only once they have typed something: flagging an untouched field\n // red would scold someone for not having started yet.\n invalid={tooShort}\n />\n\n <AuthSubmit pending={isPending} pendingLabel={t.submitPending}>\n {t.submit}\n </AuthSubmit>\n\n {legal && (\n <p className=\"text-center text-xs leading-relaxed text-muted-foreground\">\n {legal}\n </p>\n )}\n </form>\n\n <SocialButtons\n providers={socialProviders}\n onSelect={handleSocial}\n disabled={isPending}\n />\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t.haveAccount}{\" \"}\n <a\n href={loginUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.login}\n </a>\n </p>\n </div>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { VerifyEmailFormLabels, VerifyEmailFormProps } from \"../types\"\nimport { AuthAlert } from \"./auth-alert\"\nimport { AuthHeading } from \"./auth-heading\"\nimport { AuthOtpField, OTP_LENGTH } from \"./auth-otp-field\"\nimport { AuthSubmit } from \"./auth-submit\"\n\nconst DEFAULTS: Required<VerifyEmailFormLabels> = {\n title: \"Vérifie ton adresse\",\n subtitle: \"Entre le code à 6 chiffres envoyé à\",\n subtitleNoEmail: \"Entre le code à 6 chiffres reçu par e-mail.\",\n codePlaceholder: \"Code de vérification\",\n submit: \"Vérifier\",\n submitPending: \"Vérification…\",\n resend: \"Renvoyer le code\",\n resendPending: \"Envoi…\",\n resent: \"Un nouveau code vient de t'être envoyé.\",\n alreadyVerified: \"Adresse déjà vérifiée ?\",\n login: \"Se connecter\",\n codeRequired: \"Entre le code à 6 chiffres\",\n invalidCode: \"Code invalide. Réessaie.\",\n resendFailed: \"L'envoi du code a échoué\",\n missingEmail: \"Adresse e-mail introuvable. Recommence l'inscription.\",\n}\n\nexport function VerifyEmailForm({\n email,\n onSuccess,\n loginUrl = \"/login\",\n labels,\n titleClassName,\n authClient,\n}: VerifyEmailFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [otp, setOtp] = useState(\"\")\n const [isVerifying, setIsVerifying] = useState(false)\n const [isResending, setIsResending] = useState(false)\n const [resendMessage, setResendMessage] = useState<string | undefined>()\n const [error, setError] = useState<string | undefined>()\n\n const handleVerify = async (e: FormEvent) => {\n e.preventDefault()\n if (otp.length < OTP_LENGTH) {\n setError(t.codeRequired)\n return\n }\n setError(undefined)\n setResendMessage(undefined)\n setIsVerifying(true)\n try {\n const res = await authClient.emailOtp.verifyEmail({ email, otp })\n if (res?.error) {\n setError(res.error.message ?? t.invalidCode)\n return\n }\n onSuccess?.()\n } catch (err) {\n setError(err instanceof Error ? err.message : t.invalidCode)\n } finally {\n setIsVerifying(false)\n }\n }\n\n const handleResend = async () => {\n if (!email) {\n setError(t.missingEmail)\n return\n }\n setError(undefined)\n setResendMessage(undefined)\n setIsResending(true)\n try {\n await authClient.emailOtp.sendVerificationOtp({\n email,\n type: \"email-verification\",\n })\n setResendMessage(t.resent)\n } catch (err) {\n setError(err instanceof Error ? err.message : t.resendFailed)\n } finally {\n setIsResending(false)\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <AuthHeading\n title={t.title}\n subtitle={email ? `${t.subtitle} ${email}.` : t.subtitleNoEmail}\n titleClassName={titleClassName}\n />\n\n <AuthAlert>{error}</AuthAlert>\n <AuthAlert tone=\"success\">{resendMessage}</AuthAlert>\n\n <form onSubmit={handleVerify} className=\"space-y-5\" noValidate>\n <AuthOtpField\n id=\"verify-email-otp\"\n label={t.codePlaceholder}\n value={otp}\n onChange={setOtp}\n disabled={isVerifying}\n />\n\n <AuthSubmit\n pending={isVerifying}\n disabled={otp.length < OTP_LENGTH}\n pendingLabel={t.submitPending}\n >\n {t.submit}\n </AuthSubmit>\n </form>\n\n <div className=\"space-y-4 text-center text-sm text-muted-foreground\">\n <button\n type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-55\"\n >\n {isResending ? t.resendPending : t.resend}\n </button>\n\n <p>\n {t.alreadyVerified}{\" \"}\n <a\n href={loginUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.login}\n </a>\n </p>\n </div>\n </div>\n )\n}\n","interface AuthOtpFieldProps {\n label: string\n value: string\n onChange: (value: string) => void\n disabled?: boolean\n invalid?: boolean\n id: string\n}\n\nexport const OTP_LENGTH = 6\n\n/**\n * The 6-digit code field.\n *\n * Not an AuthField: the value is a code being read off another screen, so it\n * is spaced and monospaced to be checked digit by digit, and non-digits are\n * dropped on the way in — pasting a code from a mail client routinely carries\n * a trailing space.\n */\nexport function AuthOtpField({\n label,\n value,\n onChange,\n disabled = false,\n invalid = false,\n id,\n}: AuthOtpFieldProps) {\n return (\n <div className=\"space-y-2\">\n <label\n htmlFor={id}\n className=\"block text-sm font-medium leading-none text-foreground\"\n >\n {label}\n </label>\n <input\n id={id}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={OTP_LENGTH}\n value={value}\n onChange={(e) => onChange(e.target.value.replace(/\\D/g, \"\"))}\n required\n disabled={disabled}\n autoComplete=\"one-time-code\"\n aria-invalid={invalid || undefined}\n className={[\n \"h-[52px] w-full rounded-xl border bg-background px-4 sm:h-11\",\n \"text-center font-mono text-lg tracking-[0.4em]\",\n \"text-foreground\",\n \"transition-[border-color,box-shadow] duration-150 ease-out\",\n \"focus-visible:outline-none focus-visible:ring-[3px]\",\n invalid\n ? \"border-destructive focus-visible:border-destructive focus-visible:ring-destructive/20\"\n : \"border-input focus-visible:border-ring focus-visible:ring-ring/15\",\n \"disabled:cursor-not-allowed disabled:opacity-60\",\n ].join(\" \")}\n />\n </div>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type {\n ForgotPasswordFormLabels,\n ForgotPasswordFormProps,\n} from \"../types\"\nimport { AuthAlert } from \"./auth-alert\"\nimport { AuthField } from \"./auth-field\"\nimport { AuthHeading } from \"./auth-heading\"\nimport { AuthSubmit } from \"./auth-submit\"\n\nconst DEFAULTS: Required<ForgotPasswordFormLabels> = {\n title: \"Mot de passe oublié ?\",\n subtitle:\n \"Entre ton adresse e-mail et nous t'enverrons un code pour le réinitialiser.\",\n emailPlaceholder: \"Adresse e-mail\",\n submit: \"Envoyer le code\",\n submitPending: \"Envoi…\",\n rememberPassword: \"Tu t'en souviens finalement ?\",\n login: \"Se connecter\",\n emailRequired: \"Renseigne ton adresse e-mail\",\n sendFailed: \"L'envoi du code a échoué\",\n}\n\nexport function ForgotPasswordForm({\n onSuccess,\n loginUrl = \"/login\",\n labels,\n titleClassName,\n authClient,\n}: ForgotPasswordFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [email, setEmail] = useState(\"\")\n const [error, setError] = useState<string | undefined>()\n const [isPending, setIsPending] = useState(false)\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (!email.trim()) {\n setError(t.emailRequired)\n return\n }\n setError(undefined)\n setIsPending(true)\n try {\n const res = await authClient.emailOtp.sendVerificationOtp({\n email: email.trim(),\n type: \"forget-password\",\n })\n if (res?.error) {\n setError(res.error.message ?? t.sendFailed)\n return\n }\n onSuccess?.(email.trim())\n } catch (err) {\n setError(err instanceof Error ? err.message : t.sendFailed)\n } finally {\n setIsPending(false)\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <AuthHeading\n title={t.title}\n subtitle={t.subtitle}\n titleClassName={titleClassName}\n />\n\n <AuthAlert>{error}</AuthAlert>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\" noValidate>\n <AuthField\n label={t.emailPlaceholder}\n type=\"email\"\n inputMode=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n disabled={isPending}\n autoComplete=\"email\"\n autoCapitalize=\"none\"\n spellCheck={false}\n invalid={!!error}\n />\n\n <AuthSubmit\n pending={isPending}\n disabled={!email.trim()}\n pendingLabel={t.submitPending}\n >\n {t.submit}\n </AuthSubmit>\n </form>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t.rememberPassword}{\" \"}\n <a\n href={loginUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.login}\n </a>\n </p>\n </div>\n )\n}\n","import { useState, type FormEvent } from \"react\"\nimport type { ResetPasswordFormLabels, ResetPasswordFormProps } from \"../types\"\nimport { AuthAlert } from \"./auth-alert\"\nimport { AuthField } from \"./auth-field\"\nimport { AuthHeading } from \"./auth-heading\"\nimport { AuthOtpField, OTP_LENGTH } from \"./auth-otp-field\"\nimport { AuthSubmit } from \"./auth-submit\"\n\nconst MIN_PASSWORD_LENGTH = 8\n\nconst DEFAULTS: Required<ResetPasswordFormLabels> = {\n title: \"Nouveau mot de passe\",\n subtitle: \"Entre le code à 6 chiffres reçu par e-mail et ton nouveau mot de passe.\",\n codePlaceholder: \"Code de vérification\",\n passwordPlaceholder: \"Nouveau mot de passe\",\n passwordHint: `Au moins ${MIN_PASSWORD_LENGTH} caractères.`,\n confirmPlaceholder: \"Confirme le mot de passe\",\n submit: \"Réinitialiser\",\n submitPending: \"Réinitialisation…\",\n resend: \"Renvoyer le code\",\n resendPending: \"Envoi…\",\n resent: \"Un nouveau code vient de t'être envoyé.\",\n rememberPassword: \"Tu t'en souviens finalement ?\",\n login: \"Se connecter\",\n codeRequired: \"Entre le code à 6 chiffres\",\n passwordTooShort: `Le mot de passe doit faire au moins ${MIN_PASSWORD_LENGTH} caractères`,\n passwordMismatch: \"Les deux mots de passe ne correspondent pas\",\n resetFailed: \"La réinitialisation a échoué\",\n resendFailed: \"L'envoi du code a échoué\",\n}\n\nexport function ResetPasswordForm({\n email,\n onSuccess,\n loginUrl = \"/login\",\n labels,\n titleClassName,\n authClient,\n}: ResetPasswordFormProps) {\n const t = { ...DEFAULTS, ...labels }\n const [otp, setOtp] = useState(\"\")\n const [password, setPassword] = useState(\"\")\n const [confirmPassword, setConfirmPassword] = useState(\"\")\n const [error, setError] = useState<string | undefined>()\n const [isResetting, setIsResetting] = useState(false)\n const [isResending, setIsResending] = useState(false)\n const [resendMessage, setResendMessage] = useState<string | undefined>()\n\n const tooShort = password.length > 0 && password.length < MIN_PASSWORD_LENGTH\n const mismatch = confirmPassword.length > 0 && confirmPassword !== password\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault()\n if (otp.length < OTP_LENGTH) {\n setError(t.codeRequired)\n return\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n setError(t.passwordTooShort)\n return\n }\n if (password !== confirmPassword) {\n setError(t.passwordMismatch)\n return\n }\n setError(undefined)\n setResendMessage(undefined)\n setIsResetting(true)\n try {\n const res = await fetch(\"/api/auth/email-otp/reset-password\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, otp, password }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => null)\n setError(body?.message ?? t.resetFailed)\n return\n }\n onSuccess?.()\n } catch (err) {\n setError(err instanceof Error ? err.message : t.resetFailed)\n } finally {\n setIsResetting(false)\n }\n }\n\n const handleResend = async () => {\n setError(undefined)\n setResendMessage(undefined)\n setIsResending(true)\n try {\n await authClient.emailOtp.sendVerificationOtp({\n email,\n type: \"forget-password\",\n })\n setResendMessage(t.resent)\n } catch (err) {\n setError(err instanceof Error ? err.message : t.resendFailed)\n } finally {\n setIsResending(false)\n }\n }\n\n return (\n <div className=\"space-y-7\">\n <AuthHeading\n title={t.title}\n subtitle={t.subtitle}\n titleClassName={titleClassName}\n />\n\n <AuthAlert>{error}</AuthAlert>\n <AuthAlert tone=\"success\">{resendMessage}</AuthAlert>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\" noValidate>\n <AuthOtpField\n id=\"reset-password-otp\"\n label={t.codePlaceholder}\n value={otp}\n onChange={setOtp}\n disabled={isResetting}\n />\n\n <AuthField\n label={t.passwordPlaceholder}\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n disabled={isResetting}\n autoComplete=\"new-password\"\n description={t.passwordHint}\n invalid={tooShort}\n />\n\n <AuthField\n label={t.confirmPlaceholder}\n type=\"password\"\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n required\n disabled={isResetting}\n autoComplete=\"new-password\"\n invalid={mismatch}\n />\n\n <AuthSubmit\n pending={isResetting}\n disabled={otp.length < OTP_LENGTH || !password || !confirmPassword}\n pendingLabel={t.submitPending}\n >\n {t.submit}\n </AuthSubmit>\n </form>\n\n <div className=\"space-y-4 text-center text-sm text-muted-foreground\">\n <button\n type=\"button\"\n onClick={handleResend}\n disabled={isResending}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-55\"\n >\n {isResending ? t.resendPending : t.resend}\n </button>\n\n <p>\n {t.rememberPassword}{\" \"}\n <a\n href={loginUrl}\n className=\"rounded font-medium text-foreground underline underline-offset-4 decoration-foreground/25 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {t.login}\n </a>\n </p>\n </div>\n </div>\n )\n}\n","import type { AuthLayoutProps } from \"../types\"\n\n/**\n * The frame every auth screen sits in. It owns the ground, the card and the\n * app's own marks (logo, illustration, legal footer); the form it wraps owns\n * its title and its fields.\n *\n * Mobile and desktop are two layouts, not one scaled down. On a phone the form\n * IS the page: no card, no border, edge-to-edge padding, top-aligned so the\n * fields stay above the keyboard instead of being pushed under it by vertical\n * centering. From sm up it becomes a bounded card on a tinted ground — a\n * full-width form on a 1440px display is unreadable.\n *\n * With a `panel` the card splits in two from md up, the form on the left and\n * the illustration on the right. The panel is dropped below that width rather\n * than stacked: a decorative half-screen above a form costs a full swipe\n * before the first field. Without a `panel` the card stays the single 420px\n * column it has always been.\n *\n * min-h-dvh rather than min-h-screen: on mobile browsers 100vh includes the\n * retracting URL bar, so a screen-height container overflows by its height.\n */\nexport function AuthLayout({\n logo,\n panel,\n children,\n footer,\n}: AuthLayoutProps) {\n return (\n <div className=\"flex min-h-dvh flex-col bg-background px-5 pb-10 pt-12 sm:items-center sm:bg-muted/40 sm:px-6 sm:py-16\">\n <div\n className={`mx-auto w-full ${panel ? \"max-w-4xl\" : \"max-w-[420px]\"}`}\n >\n <div\n className={[\n \"sm:overflow-hidden sm:rounded-2xl sm:border sm:border-border/70 sm:bg-card sm:shadow-lg sm:shadow-black/5\",\n panel ? \"md:grid md:grid-cols-2\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <div className=\"sm:p-8\">\n {logo && <div className=\"mb-7 flex justify-center\">{logo}</div>}\n {children}\n </div>\n\n {panel && (\n // Decorative: it must never carry information the form does not,\n // so it is hidden from assistive tech rather than described.\n <div\n aria-hidden=\"true\"\n className=\"hidden bg-muted md:block [&>*]:h-full [&>*]:w-full [&>img]:object-cover\"\n >\n {panel}\n </div>\n )}\n </div>\n\n {footer && (\n <div className=\"mt-6 text-center text-xs leading-relaxed text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n </div>\n )\n}\n","import type { InvitationNoticeProps } from \"../types\"\n\n/**\n * Every app is reachable at contact@ its own apex domain, so the address is\n * derived rather than configured. Sub-domains are stripped because the app is\n * routinely served from app./admin. while the mailbox lives on the apex;\n * multi-part public suffixes (.co.uk) would need a real suffix list and no app\n * using this is on one.\n */\nfunction defaultSupportEmail(): string | undefined {\n if (typeof window === \"undefined\") return undefined\n const host = window.location.hostname\n if (!host || host === \"localhost\" || /^[\\d.]+$/.test(host)) return undefined\n const apex = host.split(\".\").slice(-2).join(\".\")\n return `contact@${apex}`\n}\n\nconst REASON_MESSAGE: Record<string, string> = {\n expired: \"Cette invitation a expiré.\",\n claimed: \"Cette invitation a déjà été utilisée.\",\n unknown: \"Ce lien d'invitation n'est pas valide.\",\n}\n\n/**\n * What an invitee sees when their link does not work.\n *\n * It names the reason instead of merging the cases into one message: \"expired\"\n * tells someone their link was real and that asking for another one is worth\n * it, which \"invalid\" does not. Short TTLs make that distinction routine.\n *\n * The only way forward offered is a mailto, deliberately. A self-service\n * \"request a new invitation\" form would mean a public endpoint accepting dead\n * tokens, a queue to moderate, and a way to probe which tokens once existed —\n * for a flow where the operator already knows the person by name.\n */\nexport function InvitationNotice({\n reason = \"unknown\",\n supportEmail,\n title = \"Invitation indisponible\",\n action,\n}: InvitationNoticeProps) {\n const contact = supportEmail ?? defaultSupportEmail()\n\n return (\n <div className=\"space-y-8\">\n <div>\n <h1 className=\"text-2xl font-bold tracking-tight\">{title}</h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown}\n </p>\n </div>\n\n {contact ? (\n <p className=\"text-sm text-muted-foreground\">\n Écrivez-nous à{\" \"}\n <a\n href={`mailto:${contact}`}\n className=\"font-medium text-foreground underline underline-offset-4 hover:text-foreground/80\"\n >\n {contact}\n </a>{\" \"}\n pour en recevoir une nouvelle.\n </p>\n ) : null}\n\n {action}\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","useState","jsx","jsx","jsxs","jsx","jsxs","jsx","jsxs","useState","useState","jsx","jsxs","DEFAULTS","useState","useState","jsx","jsxs","jsx","jsxs","DEFAULTS","useState","useState","jsx","jsxs","DEFAULTS","useState","useState","jsx","jsxs","MIN_PASSWORD_LENGTH","DEFAULTS","useState","jsx","jsxs","jsx","jsxs"]}
@@ -1,4 +1,4 @@
1
- import { b as InvitationFailure } from './types-BEldQPou.js';
1
+ import { b as InvitationFailure } from './types-SieiPwdd.js';
2
2
 
3
3
  /**
4
4
  * What became of a claim, in terms the invitee can be told.
@@ -47,17 +47,56 @@ declare function claimInvitation({ endpoint, token, externalUserId, apiKey, extr
47
47
  *
48
48
  * The token lives on the page the invitee landed on (/register?invite=…), never
49
49
  * on the auth endpoints themselves, so it has to be recovered from the request
50
- * that completes the sign-up. Two sources, because no single one covers every
51
- * flow: the URL, for the OAuth callback reached through a redirect whose query
52
- * string the app controls; and the Referer, for the password and OTP flows,
53
- * which are XHR calls issued BY that page and therefore carry it.
50
+ * that completes the sign-up. Three sources, in order of trust:
54
51
  *
55
- * Reading it per-request keeps the claim stateless: nothing associates a
56
- * browser with a pending invitation, and a request with no token claims
57
- * nothing. A malformed Referer is ignored rather than thrown on it is
58
- * attacker-controlled input on a best-effort path.
52
+ * - the URL, for a callback reached through a redirect whose query string
53
+ * the app controls;
54
+ * - the cookie held since the link was opened, which is the only one that
55
+ * survives an OAuth round trip or an OTP screen that never carried the
56
+ * token (see holdInviteTokenCookie);
57
+ * - the Referer, for the password flow, whose XHR is issued BY the page
58
+ * holding it.
59
+ *
60
+ * The Referer stays last and stays supported: it covers a caller that never
61
+ * held the cookie. It is attacker-controlled input on a best-effort path, so a
62
+ * malformed one is ignored rather than thrown on.
59
63
  */
60
64
  declare function inviteTokenFrom(request: Request, param?: string): string | null;
65
+ /**
66
+ * Pins the token onto the browser the first time a request carries it, so the
67
+ * rest of the sign-up can find it.
68
+ *
69
+ * Called on every auth request, not only the ones completing a sign-up: the
70
+ * token is legible on the FIRST call of a flow (the page holding it issues that
71
+ * XHR, so the Referer still has it) and gone by the last (verified from a
72
+ * screen that never held it, or returned from Google). Waiting for the moment
73
+ * the account exists is waiting one request too long.
74
+ *
75
+ * Returns null when there is nothing to pin — no token in the request, or one
76
+ * already held — so a caller can skip the Set-Cookie entirely.
77
+ */
78
+ declare function pinInviteToken(request: Request, param?: string): string | null;
79
+ /**
80
+ * Holds the token from the moment the link is opened until the account exists.
81
+ *
82
+ * The URL and the Referer each cover only part of the ground: the OTP flow
83
+ * verifies from a screen that never carried the token, and an OAuth sign-up
84
+ * comes back from Google with no Referer of ours at all. Both lose it, and the
85
+ * invitee lands on the default tier with the offer still pending.
86
+ *
87
+ * SameSite=Lax rather than Strict: the return from Google is a cross-site
88
+ * top-level navigation, which Strict would refuse — the one case this exists
89
+ * for. HttpOnly because the page has no reason to read it, and short-lived
90
+ * because signing up takes minutes: a single-use invitation has no business
91
+ * sitting in a browser for longer.
92
+ */
93
+ declare function holdInviteTokenCookie(token: string, maxAgeSeconds?: number): string;
94
+ /**
95
+ * Clears the held token. Sent once the claim has been attempted: the token is
96
+ * single-use, so keeping it would only replay a call that can no longer
97
+ * succeed.
98
+ */
99
+ declare function releaseInviteTokenCookie(): string;
61
100
  /**
62
101
  * Whether this request is the one that just created a usable account — the
63
102
  * moment to provision, claim an invitation, or greet someone. Matching on the
@@ -79,4 +118,4 @@ declare function invitationOutcomeCookie(outcome: ClaimOutcome, name?: string):
79
118
  */
80
119
  declare function isInvitationFailure(outcome: ClaimOutcome): outcome is InvitationFailure;
81
120
 
82
- export { type ClaimOutcome as C, type ClaimInvitationOptions as a, completesSignup as b, claimInvitation as c, invitationOutcomeCookie as d, inviteTokenFrom as e, isInvitationFailure as i };
121
+ export { type ClaimOutcome as C, type ClaimInvitationOptions as a, completesSignup as b, claimInvitation as c, invitationOutcomeCookie as d, inviteTokenFrom as e, holdInviteTokenCookie as h, isInvitationFailure as i, pinInviteToken as p, releaseInviteTokenCookie as r };
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Auth, BetterAuthOptions } from 'better-auth';
2
- import { d as PlatformAuthConfig } from './types-BEldQPou.js';
3
- export { h as PlatformSession, i as PlatformSessionData, j as PlatformUser } from './types-BEldQPou.js';
4
- export { a as ClaimInvitationOptions, C as ClaimOutcome, c as claimInvitation, b as completesSignup, d as invitationOutcomeCookie, e as inviteTokenFrom, i as isInvitationFailure } from './invitation-BpowqA66.js';
2
+ import { d as PlatformAuthConfig } from './types-SieiPwdd.js';
3
+ export { h as PlatformSession, i as PlatformSessionData, j as PlatformUser } from './types-SieiPwdd.js';
4
+ export { a as ClaimInvitationOptions, C as ClaimOutcome, c as claimInvitation, b as completesSignup, h as holdInviteTokenCookie, d as invitationOutcomeCookie, e as inviteTokenFrom, i as isInvitationFailure, p as pinInviteToken, r as releaseInviteTokenCookie } from './invitation-BUqM_BF8.js';
5
5
 
6
6
  /**
7
7
  * Creates a Better Auth instance with platform defaults.
package/dist/server.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  claimInvitation,
3
3
  completesSignup,
4
+ holdInviteTokenCookie,
4
5
  invitationOutcomeCookie,
5
6
  inviteTokenFrom,
6
- isInvitationFailure
7
- } from "./chunk-73BHM4PZ.js";
7
+ isInvitationFailure,
8
+ pinInviteToken,
9
+ releaseInviteTokenCookie
10
+ } from "./chunk-6URDBMQY.js";
8
11
 
9
12
  // src/server.ts
10
13
  import { betterAuth, APIError } from "better-auth";
@@ -130,8 +133,11 @@ export {
130
133
  claimInvitation,
131
134
  completesSignup,
132
135
  createPlatformAuth,
136
+ holdInviteTokenCookie,
133
137
  invitationOutcomeCookie,
134
138
  inviteTokenFrom,
135
- isInvitationFailure
139
+ isInvitationFailure,
140
+ pinInviteToken,
141
+ releaseInviteTokenCookie
136
142
  };
137
143
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin } from \"better-auth/plugins\"\nimport type { PlatformAuthConfig, PlatformAuthMailerType } from \"./types\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n } = config\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n account: {\n accountLinking: {\n enabled: false,\n },\n },\n // Passed through as given. The platform defines none of its own today, so\n // there is nothing to merge; should it ever add one, this becomes a merge\n // rather than a hand-off, or an app silently switches a platform hook off.\n ...(databaseHooks ? { databaseHooks } : {}),\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n admin(),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n ...(google\n ? {\n google: {\n clientId: google.clientId,\n clientSecret: google.clientSecret,\n },\n }\n : {}),\n ...(github\n ? {\n github: {\n clientId: github.clientId,\n clientSecret: github.clientSecret,\n },\n }\n : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n"],"mappings":";;;;;;;;;AAAA,SAAS,YAAY,gBAAmD;AACxE,SAAS,UAAU,aAAa;AAGhC,IAAM,yBAAiD;AAAA,EACrD,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,WAAW;AACb;AAEA,SAAS,sBAAsB,KAAqB;AAClD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKW,GAAG;AAAA;AAAA;AAAA;AAAA;AAKvB;AAMO,SAAS,mBACd,QACyB;AACzB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,CAAC;AAAA,IACX;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,WAAW,EAAE,GAAG,wBAAwB,GAAG,cAAc;AAC/D,QAAM,cAAc,kBAAkB;AAOtC,SAAO,WAAW;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,MAChB,SAAS;AAAA,MACT,0BAA0B;AAAA,IAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,SAAS;AAAA,MACP,gBAAgB;AAAA,QACd,SAAS;AAAA,MACX;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAIA,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;AAAA,IACzC,OAAO;AAAA;AAAA,MAEL,QAAQ,OAAO,QAAa;AAC1B,YAAI,CAAC,SAAU;AACf,YAAI,IAAI,SAAS,iBAAkB;AACnC,cAAM,OAAO,IAAI;AACjB,cAAM,QAAQ,MAAM;AACpB,cAAM,cAAc,MAAM;AAC1B,YAAI,SAAS,eAAe,WAAW;AACrC,gBAAM,KAAK,MAAM,UAAU,OAAO,WAAW;AAC7C,cAAI,GAAI;AAAA,QACV;AACA,cAAM,IAAI,SAAS,aAAa;AAAA,UAC9B,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA,QACP,MAAM,oBAAoB,EAAE,OAAO,KAAK,KAAK,GAAG;AAC9C,gBAAM,UAAU,SAAS,IAAI,IACzB,GAAG,SAAS,IAAI,CAAC,MAAM,OAAO,KAC9B,QAAQ,OAAO;AACnB,gBAAM,OAAO,YAAY,KAAK,IAA8B;AAE5D,cAAI,QAAQ;AACV,kBAAM,OAAO;AAAA,cACX,IAAI;AAAA,cACJ;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AACD;AAAA,UACF;AAEA,kBAAQ;AAAA,YACN,iEAA4D,KAAK,KAAK,IAAI,MAAM,GAAG;AAAA,UACrF;AAAA,QACF;AAAA,QACA,WAAW;AAAA,QACX,WAAW;AAAA,QACX,kCAAkC;AAAA,MACpC,CAAC;AAAA,MACD,MAAM;AAAA,MACN,GAAG;AAAA;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,MACf,GAAI,SACA;AAAA,QACE,QAAQ;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,cAAc,OAAO;AAAA,QACvB;AAAA,MACF,IACA,CAAC;AAAA,MACL,GAAI,SACA;AAAA,QACE,QAAQ;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,cAAc,OAAO;AAAA,QACvB;AAAA,MACF,IACA,CAAC;AAAA,IACP;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { betterAuth, APIError, type Auth, type BetterAuthOptions } from \"better-auth\"\nimport { emailOTP, admin } from \"better-auth/plugins\"\nimport type { PlatformAuthConfig, PlatformAuthMailerType } from \"./types\"\n\nconst DEFAULT_EMAIL_SUBJECTS: Record<string, string> = {\n \"email-verification\": \"Verify your account\",\n \"forget-password\": \"Reset your password\",\n \"sign-in\": \"Your sign-in code\",\n}\n\nfunction defaultRenderOtpEmail(otp: string): string {\n return `\n <div style=\"font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px\">\n <h2 style=\"font-size:20px;font-weight:600;margin-bottom:16px\">Your verification code</h2>\n <p style=\"color:#555;margin-bottom:24px\">Use the code below to continue. It expires in 5 minutes.</p>\n <div style=\"background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700\">\n ${otp}\n </div>\n <p style=\"color:#999;font-size:12px;margin-top:24px\">If you didn't request this, you can safely ignore this email.</p>\n </div>\n `\n}\n\n/**\n * Creates a Better Auth instance with platform defaults.\n * Each app calls this with its own config (DB, secret, providers, plugins).\n */\nexport function createPlatformAuth(\n config: PlatformAuthConfig,\n): Auth<BetterAuthOptions> {\n const {\n database,\n baseURL,\n secret,\n appName,\n mailer,\n google,\n github,\n plugins = [],\n databaseHooks,\n betaMode = false,\n isInvited,\n emailSubjects,\n renderOtpEmail,\n } = config\n\n const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects }\n const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail\n\n // The concrete instance type (with email-otp/admin plugins) is widened to\n // the base Auth type so the published .d.ts stays portable (inferring the\n // full plugin type triggers TS2742 — it can't be named without a zod ref).\n // The admin() plugin's user.role field is re-exposed via module augmentation\n // below, so consumers (e.g. transcript-web me.ts) still see session.user.role.\n return betterAuth({\n database,\n baseURL,\n secret,\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n },\n // Never auto-merge a social identity into an existing account by matching\n // email. Better Auth links by default (email-verified providers are trusted),\n // so signing in with Google/GitHub on an email already registered would fold\n // that identity into the existing account. We keep each sign-in method its\n // own account: a social login on a taken email is refused, not linked.\n account: {\n accountLinking: {\n enabled: false,\n },\n },\n // Passed through as given. The platform defines none of its own today, so\n // there is nothing to merge; should it ever add one, this becomes a merge\n // rather than a hand-off, or an app silently switches a platform hook off.\n ...(databaseHooks ? { databaseHooks } : {}),\n hooks: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n before: async (ctx: any) => {\n if (!betaMode) return\n if (ctx.path !== \"/sign-up/email\") return\n const body = ctx.body as { email?: string; inviteToken?: string } | undefined\n const email = body?.email\n const inviteToken = body?.inviteToken\n if (email && inviteToken && isInvited) {\n const ok = await isInvited(email, inviteToken)\n if (ok) return\n }\n throw new APIError(\"FORBIDDEN\", {\n message: \"Registration is invite-only during the private beta.\",\n })\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n const subject = subjects[type]\n ? `${subjects[type]} - ${appName}`\n : `Your ${appName} code`\n const html = renderEmail(otp, type as PlatformAuthMailerType)\n\n if (mailer) {\n await mailer({\n to: email,\n subject,\n html,\n type: type as PlatformAuthMailerType,\n otp,\n })\n return\n }\n\n console.warn(\n `[EMAIL] No mailer configured — logging OTP to stdout for ${email} (${type}): ${otp}`,\n )\n },\n otpLength: 6,\n expiresIn: 300,\n overrideDefaultEmailVerification: true,\n }),\n admin(),\n ...plugins, // app-specific plugins (e.g. tanstackStartCookies)\n ],\n socialProviders: {\n ...(google\n ? {\n google: {\n clientId: google.clientId,\n clientSecret: google.clientSecret,\n },\n }\n : {}),\n ...(github\n ? {\n github: {\n clientId: github.clientId,\n clientSecret: github.clientSecret,\n },\n }\n : {}),\n },\n }) as unknown as Auth<BetterAuthOptions>\n}\n\nexport type PlatformAuth = ReturnType<typeof createPlatformAuth>\n\n// Re-export the session contract from /server so consumers that import the\n// auth factory can type api.getSession() without a second import path.\nexport type {\n PlatformUser,\n PlatformSession,\n PlatformSessionData,\n} from \"./types\"\n\n// Invitation claiming runs on the auth callback, where the session is\n// established — the one place every sign-up flow passes through.\nexport {\n claimInvitation,\n completesSignup,\n holdInviteTokenCookie,\n invitationOutcomeCookie,\n inviteTokenFrom,\n isInvitationFailure,\n pinInviteToken,\n releaseInviteTokenCookie,\n} from \"./invitation\"\nexport type { ClaimOutcome, ClaimInvitationOptions } from \"./invitation\"\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,YAAY,gBAAmD;AACxE,SAAS,UAAU,aAAa;AAGhC,IAAM,yBAAiD;AAAA,EACrD,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,WAAW;AACb;AAEA,SAAS,sBAAsB,KAAqB;AAClD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKW,GAAG;AAAA;AAAA;AAAA;AAAA;AAKvB;AAMO,SAAS,mBACd,QACyB;AACzB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,CAAC;AAAA,IACX;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,WAAW,EAAE,GAAG,wBAAwB,GAAG,cAAc;AAC/D,QAAM,cAAc,kBAAkB;AAOtC,SAAO,WAAW;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,MAChB,SAAS;AAAA,MACT,0BAA0B;AAAA,IAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,SAAS;AAAA,MACP,gBAAgB;AAAA,QACd,SAAS;AAAA,MACX;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAIA,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;AAAA,IACzC,OAAO;AAAA;AAAA,MAEL,QAAQ,OAAO,QAAa;AAC1B,YAAI,CAAC,SAAU;AACf,YAAI,IAAI,SAAS,iBAAkB;AACnC,cAAM,OAAO,IAAI;AACjB,cAAM,QAAQ,MAAM;AACpB,cAAM,cAAc,MAAM;AAC1B,YAAI,SAAS,eAAe,WAAW;AACrC,gBAAM,KAAK,MAAM,UAAU,OAAO,WAAW;AAC7C,cAAI,GAAI;AAAA,QACV;AACA,cAAM,IAAI,SAAS,aAAa;AAAA,UAC9B,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA,QACP,MAAM,oBAAoB,EAAE,OAAO,KAAK,KAAK,GAAG;AAC9C,gBAAM,UAAU,SAAS,IAAI,IACzB,GAAG,SAAS,IAAI,CAAC,MAAM,OAAO,KAC9B,QAAQ,OAAO;AACnB,gBAAM,OAAO,YAAY,KAAK,IAA8B;AAE5D,cAAI,QAAQ;AACV,kBAAM,OAAO;AAAA,cACX,IAAI;AAAA,cACJ;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AACD;AAAA,UACF;AAEA,kBAAQ;AAAA,YACN,iEAA4D,KAAK,KAAK,IAAI,MAAM,GAAG;AAAA,UACrF;AAAA,QACF;AAAA,QACA,WAAW;AAAA,QACX,WAAW;AAAA,QACX,kCAAkC;AAAA,MACpC,CAAC;AAAA,MACD,MAAM;AAAA,MACN,GAAG;AAAA;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,MACf,GAAI,SACA;AAAA,QACE,QAAQ;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,cAAc,OAAO;AAAA,QACvB;AAAA,MACF,IACA,CAAC;AAAA,MACL,GAAI,SACA;AAAA,QACE,QAAQ;AAAA,UACN,UAAU,OAAO;AAAA,UACjB,cAAc,OAAO;AAAA,QACvB;AAAA,MACF,IACA,CAAC;AAAA,IACP;AAAA,EACF,CAAC;AACH;","names":[]}