@lalternative/auth 0.3.0 → 0.4.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/README.md +23 -3
- package/package.json +1 -1
- package/dist/client.d.ts +0 -15
- package/dist/client.js +0 -13
- package/dist/client.js.map +0 -1
- package/dist/index.d.ts +0 -69
- package/dist/index.js +0 -440
- package/dist/index.js.map +0 -1
- package/dist/server.d.ts +0 -12
- package/dist/server.js +0 -119
- package/dist/server.js.map +0 -1
- package/dist/types-DKEKNQkL.d.ts +0 -159
package/README.md
CHANGED
|
@@ -45,17 +45,37 @@ that held the token.
|
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
47
|
// server — auth callback (e.g. routes/api/auth/$.ts)
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
claimInvitation,
|
|
50
|
+
completesSignup,
|
|
51
|
+
invitationOutcomeCookie,
|
|
52
|
+
inviteTokenFrom,
|
|
53
|
+
} from "@lalternative/auth/server"
|
|
54
|
+
|
|
55
|
+
const response = await auth.handler(request)
|
|
56
|
+
if (!response.ok || !completesSignup(new URL(request.url).pathname)) return response
|
|
57
|
+
|
|
58
|
+
// Read the session back from the Set-Cookie the handler just issued, so this
|
|
59
|
+
// works for every flow without parsing each response body shape.
|
|
60
|
+
const setCookie = response.headers.get("set-cookie")
|
|
61
|
+
const session = setCookie
|
|
62
|
+
? await auth.api
|
|
63
|
+
.getSession({ headers: new Headers({ cookie: setCookie }) })
|
|
64
|
+
.catch(() => null)
|
|
65
|
+
: null
|
|
49
66
|
|
|
50
67
|
const token = inviteTokenFrom(request)
|
|
51
|
-
if (token && user) {
|
|
68
|
+
if (token && session?.user) {
|
|
52
69
|
// Best-effort: a sign-in must never fail because a claim did not go through.
|
|
53
70
|
const outcome = await claimInvitation({
|
|
54
71
|
endpoint: `${process.env.LUNGOR_API_URL}/invitations/claim`,
|
|
55
72
|
apiKey: process.env.LUNGOR_APP_API_KEY,
|
|
56
73
|
token,
|
|
57
|
-
externalUserId: user.id,
|
|
74
|
+
externalUserId: session.user.id,
|
|
58
75
|
})
|
|
76
|
+
if (outcome !== "granted") {
|
|
77
|
+
response.headers.append("set-cookie", invitationOutcomeCookie(outcome))
|
|
78
|
+
}
|
|
59
79
|
}
|
|
60
80
|
```
|
|
61
81
|
|
package/package.json
CHANGED
package/dist/client.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import * as better_auth_react from 'better-auth/react';
|
|
2
|
-
import { P as PlatformAuthClientConfig } from './types-DKEKNQkL.js';
|
|
3
|
-
import 'better-auth';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a Better Auth client for React usage.
|
|
7
|
-
* Provides useSession() hook and other React-integrated methods.
|
|
8
|
-
*/
|
|
9
|
-
declare function createPlatformAuthClient(config?: PlatformAuthClientConfig): better_auth_react.ReactAuthClient<{
|
|
10
|
-
baseURL: string;
|
|
11
|
-
plugins: any[];
|
|
12
|
-
}>;
|
|
13
|
-
type PlatformAuthClient = ReturnType<typeof createPlatformAuthClient>;
|
|
14
|
-
|
|
15
|
-
export { type PlatformAuthClient, createPlatformAuthClient };
|
package/dist/client.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// src/client.ts
|
|
2
|
-
import { createAuthClient } from "better-auth/react";
|
|
3
|
-
import { emailOTPClient, adminClient } from "better-auth/client/plugins";
|
|
4
|
-
function createPlatformAuthClient(config) {
|
|
5
|
-
return createAuthClient({
|
|
6
|
-
baseURL: config?.baseURL ?? (typeof window !== "undefined" ? window.location.origin : "http://localhost:3000"),
|
|
7
|
-
plugins: [emailOTPClient(), adminClient(), ...config?.plugins ?? []]
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
export {
|
|
11
|
-
createPlatformAuthClient
|
|
12
|
-
};
|
|
13
|
-
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createAuthClient } from \"better-auth/react\"\nimport { emailOTPClient, adminClient } from \"better-auth/client/plugins\"\nimport type { PlatformAuthClientConfig } from \"./types\"\n\n/**\n * Creates a Better Auth client for React usage.\n * Provides useSession() hook and other React-integrated methods.\n */\nexport function createPlatformAuthClient(config?: PlatformAuthClientConfig) {\n return createAuthClient({\n baseURL:\n config?.baseURL ??\n (typeof window !== \"undefined\"\n ? window.location.origin\n : \"http://localhost:3000\"),\n plugins: [emailOTPClient(), adminClient(), ...(config?.plugins ?? [])],\n })\n}\n\nexport type PlatformAuthClient = ReturnType<typeof createPlatformAuthClient>\n"],"mappings":";AAAA,SAAS,wBAAwB;AACjC,SAAS,gBAAgB,mBAAmB;AAOrC,SAAS,yBAAyB,QAAmC;AAC1E,SAAO,iBAAiB;AAAA,IACtB,SACE,QAAQ,YACP,OAAO,WAAW,cACf,OAAO,SAAS,SAChB;AAAA,IACN,SAAS,CAAC,eAAe,GAAG,YAAY,GAAG,GAAI,QAAQ,WAAW,CAAC,CAAE;AAAA,EACvE,CAAC;AACH;","names":[]}
|
package/dist/index.d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { V as VerifyEmailFormProps, F as ForgotPasswordFormProps, R as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps } from './types-DKEKNQkL.js';
|
|
2
|
-
export { a as InvitationFailure, P as PlatformAuthClientConfig, b as PlatformAuthConfig, c as PlatformAuthMailer, d as PlatformAuthMailerArgs, e as PlatformAuthMailerType, f as PlatformSession, g as PlatformSessionData, h as PlatformUser } from './types-DKEKNQkL.js';
|
|
3
|
-
import * as better_auth_react from 'better-auth/react';
|
|
4
|
-
import * as better_auth from 'better-auth';
|
|
5
|
-
import { PlatformAuthClient } from './client.js';
|
|
6
|
-
import * as react from 'react';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Returns a useSession hook bound to the given auth client.
|
|
10
|
-
* Usage: const { data: session, isPending } = useSession(authClient)
|
|
11
|
-
*/
|
|
12
|
-
declare function useSession(authClient: PlatformAuthClient): {
|
|
13
|
-
data: {
|
|
14
|
-
user: better_auth.StripEmptyObjects<{
|
|
15
|
-
id: string;
|
|
16
|
-
createdAt: Date;
|
|
17
|
-
updatedAt: Date;
|
|
18
|
-
email: string;
|
|
19
|
-
emailVerified: boolean;
|
|
20
|
-
name: string;
|
|
21
|
-
image?: string | null | undefined;
|
|
22
|
-
}>;
|
|
23
|
-
session: better_auth.StripEmptyObjects<{
|
|
24
|
-
id: string;
|
|
25
|
-
createdAt: Date;
|
|
26
|
-
updatedAt: Date;
|
|
27
|
-
userId: string;
|
|
28
|
-
expiresAt: Date;
|
|
29
|
-
token: string;
|
|
30
|
-
ipAddress?: string | null | undefined;
|
|
31
|
-
userAgent?: string | null | undefined;
|
|
32
|
-
}>;
|
|
33
|
-
} | null;
|
|
34
|
-
isPending: boolean;
|
|
35
|
-
isRefetching: boolean;
|
|
36
|
-
error: better_auth_react.BetterFetchError | null;
|
|
37
|
-
refetch: (queryParams?: {
|
|
38
|
-
query?: better_auth.SessionQueryParams;
|
|
39
|
-
} | undefined) => Promise<void>;
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Returns a logout function bound to the given auth client.
|
|
43
|
-
* Usage: const logout = useLogout(authClient)
|
|
44
|
-
*/
|
|
45
|
-
declare function useLogout(authClient: PlatformAuthClient): () => Promise<void>;
|
|
46
|
-
|
|
47
|
-
declare function VerifyEmailForm({ email, onSuccess, authClient, }: VerifyEmailFormProps): react.JSX.Element;
|
|
48
|
-
|
|
49
|
-
declare function ForgotPasswordForm({ onSuccess, loginUrl, authClient, }: ForgotPasswordFormProps): react.JSX.Element;
|
|
50
|
-
|
|
51
|
-
declare function ResetPasswordForm({ email, onSuccess, loginUrl, authClient, }: ResetPasswordFormProps): react.JSX.Element;
|
|
52
|
-
|
|
53
|
-
declare function AuthLayout({ logo, title, subtitle, children, footer, }: AuthLayoutProps): react.JSX.Element;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* What an invitee sees when their link does not work.
|
|
57
|
-
*
|
|
58
|
-
* It names the reason instead of merging the cases into one message: "expired"
|
|
59
|
-
* tells someone their link was real and that asking for another one is worth
|
|
60
|
-
* it, which "invalid" does not. Short TTLs make that distinction routine.
|
|
61
|
-
*
|
|
62
|
-
* The only way forward offered is a mailto, deliberately. A self-service
|
|
63
|
-
* "request a new invitation" form would mean a public endpoint accepting dead
|
|
64
|
-
* tokens, a queue to moderate, and a way to probe which tokens once existed —
|
|
65
|
-
* for a flow where the operator already knows the person by name.
|
|
66
|
-
*/
|
|
67
|
-
declare function InvitationNotice({ reason, supportEmail, title, action, }: InvitationNoticeProps): react.JSX.Element;
|
|
68
|
-
|
|
69
|
-
export { AuthLayout, AuthLayoutProps, ForgotPasswordForm, ForgotPasswordFormProps, InvitationNotice, InvitationNoticeProps, ResetPasswordForm, ResetPasswordFormProps, VerifyEmailForm, VerifyEmailFormProps, useLogout, useSession };
|
package/dist/index.js
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
// src/hooks/use-session.ts
|
|
2
|
-
function useSession(authClient) {
|
|
3
|
-
return authClient.useSession();
|
|
4
|
-
}
|
|
5
|
-
function useLogout(authClient) {
|
|
6
|
-
const signOut = async () => {
|
|
7
|
-
await authClient.signOut();
|
|
8
|
-
};
|
|
9
|
-
return signOut;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// src/components/verify-email-form.tsx
|
|
13
|
-
import { useState } from "react";
|
|
14
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
function VerifyEmailForm({
|
|
16
|
-
email,
|
|
17
|
-
onSuccess,
|
|
18
|
-
authClient
|
|
19
|
-
}) {
|
|
20
|
-
const [otp, setOtp] = useState("");
|
|
21
|
-
const [isVerifying, setIsVerifying] = useState(false);
|
|
22
|
-
const [isResending, setIsResending] = useState(false);
|
|
23
|
-
const [resendMessage, setResendMessage] = useState();
|
|
24
|
-
const [error, setError] = useState();
|
|
25
|
-
const handleVerify = async (e) => {
|
|
26
|
-
e.preventDefault();
|
|
27
|
-
if (!otp.trim() || otp.length < 6) {
|
|
28
|
-
setError("Please enter the 6-digit code");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
setError(void 0);
|
|
32
|
-
setIsVerifying(true);
|
|
33
|
-
try {
|
|
34
|
-
const res = await authClient.emailOtp.verifyEmail({ email, otp });
|
|
35
|
-
console.log("[verify-email] response:", JSON.stringify(res?.data), "error:", JSON.stringify(res?.error));
|
|
36
|
-
if (res?.error) {
|
|
37
|
-
setError(res.error.message ?? "Invalid code. Please try again.");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
onSuccess?.();
|
|
41
|
-
} catch (err) {
|
|
42
|
-
setError(err instanceof Error ? err.message : "Invalid code. Please try again.");
|
|
43
|
-
} finally {
|
|
44
|
-
setIsVerifying(false);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const handleResend = async () => {
|
|
48
|
-
if (!email) {
|
|
49
|
-
setError("Email address is not available. Please register again.");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
setError(void 0);
|
|
53
|
-
setResendMessage(void 0);
|
|
54
|
-
setIsResending(true);
|
|
55
|
-
try {
|
|
56
|
-
await authClient.emailOtp.sendVerificationOtp({
|
|
57
|
-
email,
|
|
58
|
-
type: "email-verification"
|
|
59
|
-
});
|
|
60
|
-
setResendMessage("A new code has been sent to your inbox.");
|
|
61
|
-
} catch (err) {
|
|
62
|
-
setError(err instanceof Error ? err.message : "Failed to resend code.");
|
|
63
|
-
} finally {
|
|
64
|
-
setIsResending(false);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
68
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
69
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Verify your email" }),
|
|
70
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: email ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
71
|
-
"Enter the 6-digit code sent to",
|
|
72
|
-
" ",
|
|
73
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: email })
|
|
74
|
-
] }) : "Enter the 6-digit code sent to your email" })
|
|
75
|
-
] }),
|
|
76
|
-
error && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
|
|
77
|
-
resendMessage && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700", children: resendMessage }),
|
|
78
|
-
/* @__PURE__ */ jsxs("form", { onSubmit: handleVerify, className: "space-y-4", noValidate: true, children: [
|
|
79
|
-
/* @__PURE__ */ jsx(
|
|
80
|
-
"input",
|
|
81
|
-
{
|
|
82
|
-
type: "text",
|
|
83
|
-
inputMode: "numeric",
|
|
84
|
-
pattern: "[0-9]*",
|
|
85
|
-
maxLength: 6,
|
|
86
|
-
value: otp,
|
|
87
|
-
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
88
|
-
placeholder: "000000",
|
|
89
|
-
required: true,
|
|
90
|
-
disabled: isVerifying,
|
|
91
|
-
autoComplete: "one-time-code",
|
|
92
|
-
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"
|
|
93
|
-
}
|
|
94
|
-
),
|
|
95
|
-
/* @__PURE__ */ jsx(
|
|
96
|
-
"button",
|
|
97
|
-
{
|
|
98
|
-
type: "submit",
|
|
99
|
-
disabled: isVerifying || otp.length < 6,
|
|
100
|
-
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",
|
|
101
|
-
children: isVerifying ? "Verifying..." : "Verify email"
|
|
102
|
-
}
|
|
103
|
-
),
|
|
104
|
-
/* @__PURE__ */ jsx(
|
|
105
|
-
"button",
|
|
106
|
-
{
|
|
107
|
-
type: "button",
|
|
108
|
-
onClick: handleResend,
|
|
109
|
-
disabled: isResending,
|
|
110
|
-
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",
|
|
111
|
-
children: isResending ? "Sending..." : "Resend code"
|
|
112
|
-
}
|
|
113
|
-
)
|
|
114
|
-
] }),
|
|
115
|
-
/* @__PURE__ */ jsxs("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
116
|
-
"Already verified?",
|
|
117
|
-
" ",
|
|
118
|
-
/* @__PURE__ */ jsx(
|
|
119
|
-
"a",
|
|
120
|
-
{
|
|
121
|
-
href: "/login",
|
|
122
|
-
className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
|
|
123
|
-
children: "Sign in"
|
|
124
|
-
}
|
|
125
|
-
)
|
|
126
|
-
] })
|
|
127
|
-
] });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/components/forgot-password-form.tsx
|
|
131
|
-
import { useState as useState2 } from "react";
|
|
132
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
133
|
-
function ForgotPasswordForm({
|
|
134
|
-
onSuccess,
|
|
135
|
-
loginUrl = "/login",
|
|
136
|
-
authClient
|
|
137
|
-
}) {
|
|
138
|
-
const [email, setEmail] = useState2("");
|
|
139
|
-
const [error, setError] = useState2();
|
|
140
|
-
const [isPending, setIsPending] = useState2(false);
|
|
141
|
-
const handleSubmit = async (e) => {
|
|
142
|
-
e.preventDefault();
|
|
143
|
-
if (!email.trim()) {
|
|
144
|
-
setError("Please enter your email address");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
setError(void 0);
|
|
148
|
-
setIsPending(true);
|
|
149
|
-
try {
|
|
150
|
-
const res = await authClient.emailOtp.sendVerificationOtp({
|
|
151
|
-
email,
|
|
152
|
-
type: "forget-password"
|
|
153
|
-
});
|
|
154
|
-
if (res?.error) {
|
|
155
|
-
setError(res.error.message ?? "Failed to send reset code");
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
onSuccess?.(email);
|
|
159
|
-
} catch (err) {
|
|
160
|
-
setError(err instanceof Error ? err.message : "Failed to send reset code");
|
|
161
|
-
} finally {
|
|
162
|
-
setIsPending(false);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
return /* @__PURE__ */ jsxs2("div", { className: "space-y-8", children: [
|
|
166
|
-
/* @__PURE__ */ jsxs2("div", { children: [
|
|
167
|
-
/* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold tracking-tight", children: "Forgot your password?" }),
|
|
168
|
-
/* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-muted-foreground", children: "Enter your email address and we'll send you a code to reset your password." })
|
|
169
|
-
] }),
|
|
170
|
-
error && /* @__PURE__ */ jsx2("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
|
|
171
|
-
/* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
|
|
172
|
-
/* @__PURE__ */ jsx2(
|
|
173
|
-
"input",
|
|
174
|
-
{
|
|
175
|
-
type: "email",
|
|
176
|
-
value: email,
|
|
177
|
-
onChange: (e) => setEmail(e.target.value),
|
|
178
|
-
placeholder: "Email address",
|
|
179
|
-
required: true,
|
|
180
|
-
disabled: isPending,
|
|
181
|
-
autoComplete: "email",
|
|
182
|
-
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"
|
|
183
|
-
}
|
|
184
|
-
),
|
|
185
|
-
/* @__PURE__ */ jsx2(
|
|
186
|
-
"button",
|
|
187
|
-
{
|
|
188
|
-
type: "submit",
|
|
189
|
-
disabled: isPending || !email.trim(),
|
|
190
|
-
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",
|
|
191
|
-
children: isPending ? "Sending..." : "Send reset code"
|
|
192
|
-
}
|
|
193
|
-
)
|
|
194
|
-
] }),
|
|
195
|
-
/* @__PURE__ */ jsxs2("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
196
|
-
"Remember your password?",
|
|
197
|
-
" ",
|
|
198
|
-
/* @__PURE__ */ jsx2(
|
|
199
|
-
"a",
|
|
200
|
-
{
|
|
201
|
-
href: loginUrl,
|
|
202
|
-
className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
|
|
203
|
-
children: "Sign in"
|
|
204
|
-
}
|
|
205
|
-
)
|
|
206
|
-
] })
|
|
207
|
-
] });
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// src/components/reset-password-form.tsx
|
|
211
|
-
import { useState as useState3 } from "react";
|
|
212
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
213
|
-
function ResetPasswordForm({
|
|
214
|
-
email,
|
|
215
|
-
onSuccess,
|
|
216
|
-
loginUrl = "/login",
|
|
217
|
-
authClient
|
|
218
|
-
}) {
|
|
219
|
-
const [otp, setOtp] = useState3("");
|
|
220
|
-
const [password, setPassword] = useState3("");
|
|
221
|
-
const [confirmPassword, setConfirmPassword] = useState3("");
|
|
222
|
-
const [error, setError] = useState3();
|
|
223
|
-
const [isResetting, setIsResetting] = useState3(false);
|
|
224
|
-
const [isResending, setIsResending] = useState3(false);
|
|
225
|
-
const [resendMessage, setResendMessage] = useState3();
|
|
226
|
-
const handleSubmit = async (e) => {
|
|
227
|
-
e.preventDefault();
|
|
228
|
-
if (!otp.trim() || otp.length < 6) {
|
|
229
|
-
setError("Please enter the 6-digit code");
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (password.length < 8) {
|
|
233
|
-
setError("Password must be at least 8 characters");
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
if (password !== confirmPassword) {
|
|
237
|
-
setError("Passwords do not match");
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
setError(void 0);
|
|
241
|
-
setIsResetting(true);
|
|
242
|
-
try {
|
|
243
|
-
const res = await fetch("/api/auth/email-otp/reset-password", {
|
|
244
|
-
method: "POST",
|
|
245
|
-
headers: { "Content-Type": "application/json" },
|
|
246
|
-
body: JSON.stringify({ email, otp, password })
|
|
247
|
-
});
|
|
248
|
-
if (!res.ok) {
|
|
249
|
-
const body = await res.json().catch(() => null);
|
|
250
|
-
setError(body?.message ?? "Failed to reset password");
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
onSuccess?.();
|
|
254
|
-
} catch (err) {
|
|
255
|
-
setError(
|
|
256
|
-
err instanceof Error ? err.message : "Failed to reset password"
|
|
257
|
-
);
|
|
258
|
-
} finally {
|
|
259
|
-
setIsResetting(false);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
const handleResend = async () => {
|
|
263
|
-
setError(void 0);
|
|
264
|
-
setResendMessage(void 0);
|
|
265
|
-
setIsResending(true);
|
|
266
|
-
try {
|
|
267
|
-
await authClient.emailOtp.sendVerificationOtp({
|
|
268
|
-
email,
|
|
269
|
-
type: "forget-password"
|
|
270
|
-
});
|
|
271
|
-
setResendMessage("A new code has been sent to your inbox.");
|
|
272
|
-
} catch (err) {
|
|
273
|
-
setError(err instanceof Error ? err.message : "Failed to resend code.");
|
|
274
|
-
} finally {
|
|
275
|
-
setIsResending(false);
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
return /* @__PURE__ */ jsxs3("div", { className: "space-y-8", children: [
|
|
279
|
-
/* @__PURE__ */ jsxs3("div", { children: [
|
|
280
|
-
/* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold tracking-tight", children: "Reset your password" }),
|
|
281
|
-
/* @__PURE__ */ jsxs3("p", { className: "mt-1 text-sm text-muted-foreground", children: [
|
|
282
|
-
"Enter the 6-digit code sent to",
|
|
283
|
-
" ",
|
|
284
|
-
/* @__PURE__ */ jsx3("span", { className: "font-medium text-foreground", children: email }),
|
|
285
|
-
" and your new password."
|
|
286
|
-
] })
|
|
287
|
-
] }),
|
|
288
|
-
error && /* @__PURE__ */ jsx3("div", { className: "rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
|
|
289
|
-
resendMessage && /* @__PURE__ */ jsx3("div", { className: "rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-xs text-green-700", children: resendMessage }),
|
|
290
|
-
/* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "space-y-4", noValidate: true, children: [
|
|
291
|
-
/* @__PURE__ */ jsx3(
|
|
292
|
-
"input",
|
|
293
|
-
{
|
|
294
|
-
type: "text",
|
|
295
|
-
inputMode: "numeric",
|
|
296
|
-
pattern: "[0-9]*",
|
|
297
|
-
maxLength: 6,
|
|
298
|
-
value: otp,
|
|
299
|
-
onChange: (e) => setOtp(e.target.value.replace(/\D/g, "")),
|
|
300
|
-
placeholder: "000000",
|
|
301
|
-
required: true,
|
|
302
|
-
disabled: isResetting,
|
|
303
|
-
autoComplete: "one-time-code",
|
|
304
|
-
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"
|
|
305
|
-
}
|
|
306
|
-
),
|
|
307
|
-
/* @__PURE__ */ jsx3(
|
|
308
|
-
"input",
|
|
309
|
-
{
|
|
310
|
-
type: "password",
|
|
311
|
-
value: password,
|
|
312
|
-
onChange: (e) => setPassword(e.target.value),
|
|
313
|
-
placeholder: "New password",
|
|
314
|
-
required: true,
|
|
315
|
-
disabled: isResetting,
|
|
316
|
-
autoComplete: "new-password",
|
|
317
|
-
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"
|
|
318
|
-
}
|
|
319
|
-
),
|
|
320
|
-
/* @__PURE__ */ jsx3(
|
|
321
|
-
"input",
|
|
322
|
-
{
|
|
323
|
-
type: "password",
|
|
324
|
-
value: confirmPassword,
|
|
325
|
-
onChange: (e) => setConfirmPassword(e.target.value),
|
|
326
|
-
placeholder: "Confirm new password",
|
|
327
|
-
required: true,
|
|
328
|
-
disabled: isResetting,
|
|
329
|
-
autoComplete: "new-password",
|
|
330
|
-
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"
|
|
331
|
-
}
|
|
332
|
-
),
|
|
333
|
-
/* @__PURE__ */ jsx3(
|
|
334
|
-
"button",
|
|
335
|
-
{
|
|
336
|
-
type: "submit",
|
|
337
|
-
disabled: isResetting || otp.length < 6 || !password,
|
|
338
|
-
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",
|
|
339
|
-
children: isResetting ? "Resetting..." : "Reset password"
|
|
340
|
-
}
|
|
341
|
-
),
|
|
342
|
-
/* @__PURE__ */ jsx3(
|
|
343
|
-
"button",
|
|
344
|
-
{
|
|
345
|
-
type: "button",
|
|
346
|
-
onClick: handleResend,
|
|
347
|
-
disabled: isResending,
|
|
348
|
-
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",
|
|
349
|
-
children: isResending ? "Sending..." : "Resend code"
|
|
350
|
-
}
|
|
351
|
-
)
|
|
352
|
-
] }),
|
|
353
|
-
/* @__PURE__ */ jsxs3("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
354
|
-
"Remember your password?",
|
|
355
|
-
" ",
|
|
356
|
-
/* @__PURE__ */ jsx3(
|
|
357
|
-
"a",
|
|
358
|
-
{
|
|
359
|
-
href: loginUrl,
|
|
360
|
-
className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
|
|
361
|
-
children: "Sign in"
|
|
362
|
-
}
|
|
363
|
-
)
|
|
364
|
-
] })
|
|
365
|
-
] });
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// src/components/auth-layout.tsx
|
|
369
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
370
|
-
function AuthLayout({
|
|
371
|
-
logo,
|
|
372
|
-
title,
|
|
373
|
-
subtitle,
|
|
374
|
-
children,
|
|
375
|
-
footer
|
|
376
|
-
}) {
|
|
377
|
-
return /* @__PURE__ */ jsx4("div", { className: "flex min-h-screen items-center justify-center bg-background", children: /* @__PURE__ */ jsxs4("div", { className: "w-full max-w-md", children: [
|
|
378
|
-
/* @__PURE__ */ jsxs4("div", { className: "mb-10 text-center", children: [
|
|
379
|
-
logo && /* @__PURE__ */ jsx4("div", { className: "mb-6 flex justify-center", children: logo }),
|
|
380
|
-
/* @__PURE__ */ jsx4("h1", { className: "text-[32px] font-light tracking-tight", children: title }),
|
|
381
|
-
subtitle && /* @__PURE__ */ jsx4("p", { className: "mt-2 text-sm text-muted-foreground", children: subtitle })
|
|
382
|
-
] }),
|
|
383
|
-
/* @__PURE__ */ jsx4("div", { className: "rounded-xl border bg-card p-8 shadow-sm", children }),
|
|
384
|
-
footer && /* @__PURE__ */ jsx4("div", { className: "mt-6 text-center text-xs text-muted-foreground", children: footer })
|
|
385
|
-
] }) });
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// src/components/invitation-notice.tsx
|
|
389
|
-
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
390
|
-
function defaultSupportEmail() {
|
|
391
|
-
if (typeof window === "undefined") return void 0;
|
|
392
|
-
const host = window.location.hostname;
|
|
393
|
-
if (!host || host === "localhost" || /^[\d.]+$/.test(host)) return void 0;
|
|
394
|
-
const apex = host.split(".").slice(-2).join(".");
|
|
395
|
-
return `contact@${apex}`;
|
|
396
|
-
}
|
|
397
|
-
var REASON_MESSAGE = {
|
|
398
|
-
expired: "Cette invitation a expir\xE9.",
|
|
399
|
-
claimed: "Cette invitation a d\xE9j\xE0 \xE9t\xE9 utilis\xE9e.",
|
|
400
|
-
unknown: "Ce lien d'invitation n'est pas valide."
|
|
401
|
-
};
|
|
402
|
-
function InvitationNotice({
|
|
403
|
-
reason = "unknown",
|
|
404
|
-
supportEmail,
|
|
405
|
-
title = "Invitation indisponible",
|
|
406
|
-
action
|
|
407
|
-
}) {
|
|
408
|
-
const contact = supportEmail ?? defaultSupportEmail();
|
|
409
|
-
return /* @__PURE__ */ jsxs5("div", { className: "space-y-8", children: [
|
|
410
|
-
/* @__PURE__ */ jsxs5("div", { children: [
|
|
411
|
-
/* @__PURE__ */ jsx5("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
|
|
412
|
-
/* @__PURE__ */ jsx5("p", { className: "mt-1 text-sm text-muted-foreground", children: REASON_MESSAGE[reason] ?? REASON_MESSAGE.unknown })
|
|
413
|
-
] }),
|
|
414
|
-
contact ? /* @__PURE__ */ jsxs5("p", { className: "text-sm text-muted-foreground", children: [
|
|
415
|
-
"\xC9crivez-nous \xE0",
|
|
416
|
-
" ",
|
|
417
|
-
/* @__PURE__ */ jsx5(
|
|
418
|
-
"a",
|
|
419
|
-
{
|
|
420
|
-
href: `mailto:${contact}`,
|
|
421
|
-
className: "font-medium text-foreground underline underline-offset-4 hover:text-foreground/80",
|
|
422
|
-
children: contact
|
|
423
|
-
}
|
|
424
|
-
),
|
|
425
|
-
" ",
|
|
426
|
-
"pour en recevoir une nouvelle."
|
|
427
|
-
] }) : null,
|
|
428
|
-
action
|
|
429
|
-
] });
|
|
430
|
-
}
|
|
431
|
-
export {
|
|
432
|
-
AuthLayout,
|
|
433
|
-
ForgotPasswordForm,
|
|
434
|
-
InvitationNotice,
|
|
435
|
-
ResetPasswordForm,
|
|
436
|
-
VerifyEmailForm,
|
|
437
|
-
useLogout,
|
|
438
|
-
useSession
|
|
439
|
-
};
|
|
440
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/use-session.ts","../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 { 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\nexport function AuthLayout({\n logo,\n title,\n subtitle,\n children,\n footer,\n}: AuthLayoutProps) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-background\">\n <div className=\"w-full max-w-md\">\n <div className=\"mb-10 text-center\">\n {logo && <div className=\"mb-6 flex justify-center\">{logo}</div>}\n <h1 className=\"text-[32px] font-light tracking-tight\">{title}</h1>\n {subtitle && (\n <p className=\"mt-2 text-sm text-muted-foreground\">{subtitle}</p>\n )}\n </div>\n\n <div className=\"rounded-xl border bg-card p-8 shadow-sm\">\n {children}\n </div>\n\n {footer && (\n <div className=\"mt-6 text-center text-xs 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":["useState","jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
|
package/dist/server.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Auth, BetterAuthOptions } from 'better-auth';
|
|
2
|
-
import { b as PlatformAuthConfig } from './types-DKEKNQkL.js';
|
|
3
|
-
export { f as PlatformSession, g as PlatformSessionData, h as PlatformUser } from './types-DKEKNQkL.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a Better Auth instance with platform defaults.
|
|
7
|
-
* Each app calls this with its own config (DB, secret, providers, plugins).
|
|
8
|
-
*/
|
|
9
|
-
declare function createPlatformAuth(config: PlatformAuthConfig): Auth<BetterAuthOptions>;
|
|
10
|
-
type PlatformAuth = ReturnType<typeof createPlatformAuth>;
|
|
11
|
-
|
|
12
|
-
export { type PlatformAuth, createPlatformAuth };
|
package/dist/server.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// src/server.ts
|
|
2
|
-
import { betterAuth, APIError } from "better-auth";
|
|
3
|
-
import { emailOTP, admin } from "better-auth/plugins";
|
|
4
|
-
var DEFAULT_EMAIL_SUBJECTS = {
|
|
5
|
-
"email-verification": "Verify your account",
|
|
6
|
-
"forget-password": "Reset your password",
|
|
7
|
-
"sign-in": "Your sign-in code"
|
|
8
|
-
};
|
|
9
|
-
function defaultRenderOtpEmail(otp) {
|
|
10
|
-
return `
|
|
11
|
-
<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px">
|
|
12
|
-
<h2 style="font-size:20px;font-weight:600;margin-bottom:16px">Your verification code</h2>
|
|
13
|
-
<p style="color:#555;margin-bottom:24px">Use the code below to continue. It expires in 5 minutes.</p>
|
|
14
|
-
<div style="background:#f5f5f5;border-radius:8px;padding:24px;text-align:center;letter-spacing:8px;font-size:32px;font-weight:700">
|
|
15
|
-
${otp}
|
|
16
|
-
</div>
|
|
17
|
-
<p style="color:#999;font-size:12px;margin-top:24px">If you didn't request this, you can safely ignore this email.</p>
|
|
18
|
-
</div>
|
|
19
|
-
`;
|
|
20
|
-
}
|
|
21
|
-
function createPlatformAuth(config) {
|
|
22
|
-
const {
|
|
23
|
-
database,
|
|
24
|
-
baseURL,
|
|
25
|
-
secret,
|
|
26
|
-
appName,
|
|
27
|
-
mailer,
|
|
28
|
-
google,
|
|
29
|
-
github,
|
|
30
|
-
plugins = [],
|
|
31
|
-
betaMode = false,
|
|
32
|
-
isInvited,
|
|
33
|
-
emailSubjects,
|
|
34
|
-
renderOtpEmail
|
|
35
|
-
} = config;
|
|
36
|
-
const subjects = { ...DEFAULT_EMAIL_SUBJECTS, ...emailSubjects };
|
|
37
|
-
const renderEmail = renderOtpEmail ?? defaultRenderOtpEmail;
|
|
38
|
-
return betterAuth({
|
|
39
|
-
database,
|
|
40
|
-
baseURL,
|
|
41
|
-
secret,
|
|
42
|
-
emailAndPassword: {
|
|
43
|
-
enabled: true,
|
|
44
|
-
requireEmailVerification: true
|
|
45
|
-
},
|
|
46
|
-
// Never auto-merge a social identity into an existing account by matching
|
|
47
|
-
// email. Better Auth links by default (email-verified providers are trusted),
|
|
48
|
-
// so signing in with Google/GitHub on an email already registered would fold
|
|
49
|
-
// that identity into the existing account. We keep each sign-in method its
|
|
50
|
-
// own account: a social login on a taken email is refused, not linked.
|
|
51
|
-
account: {
|
|
52
|
-
accountLinking: {
|
|
53
|
-
enabled: false
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
hooks: {
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
-
before: async (ctx) => {
|
|
59
|
-
if (!betaMode) return;
|
|
60
|
-
if (ctx.path !== "/sign-up/email") return;
|
|
61
|
-
const body = ctx.body;
|
|
62
|
-
const email = body?.email;
|
|
63
|
-
const inviteToken = body?.inviteToken;
|
|
64
|
-
if (email && inviteToken && isInvited) {
|
|
65
|
-
const ok = await isInvited(email, inviteToken);
|
|
66
|
-
if (ok) return;
|
|
67
|
-
}
|
|
68
|
-
throw new APIError("FORBIDDEN", {
|
|
69
|
-
message: "Registration is invite-only during the private beta."
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
plugins: [
|
|
74
|
-
emailOTP({
|
|
75
|
-
async sendVerificationOTP({ email, otp, type }) {
|
|
76
|
-
const subject = subjects[type] ? `${subjects[type]} - ${appName}` : `Your ${appName} code`;
|
|
77
|
-
const html = renderEmail(otp, type);
|
|
78
|
-
if (mailer) {
|
|
79
|
-
await mailer({
|
|
80
|
-
to: email,
|
|
81
|
-
subject,
|
|
82
|
-
html,
|
|
83
|
-
type,
|
|
84
|
-
otp
|
|
85
|
-
});
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
console.warn(
|
|
89
|
-
`[EMAIL] No mailer configured \u2014 logging OTP to stdout for ${email} (${type}): ${otp}`
|
|
90
|
-
);
|
|
91
|
-
},
|
|
92
|
-
otpLength: 6,
|
|
93
|
-
expiresIn: 300,
|
|
94
|
-
overrideDefaultEmailVerification: true
|
|
95
|
-
}),
|
|
96
|
-
admin(),
|
|
97
|
-
...plugins
|
|
98
|
-
// app-specific plugins (e.g. tanstackStartCookies)
|
|
99
|
-
],
|
|
100
|
-
socialProviders: {
|
|
101
|
-
...google ? {
|
|
102
|
-
google: {
|
|
103
|
-
clientId: google.clientId,
|
|
104
|
-
clientSecret: google.clientSecret
|
|
105
|
-
}
|
|
106
|
-
} : {},
|
|
107
|
-
...github ? {
|
|
108
|
-
github: {
|
|
109
|
-
clientId: github.clientId,
|
|
110
|
-
clientSecret: github.clientSecret
|
|
111
|
-
}
|
|
112
|
-
} : {}
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
export {
|
|
117
|
-
createPlatformAuth
|
|
118
|
-
};
|
|
119
|
-
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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 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 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"],"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,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,IACA,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":[]}
|
package/dist/types-DKEKNQkL.d.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { BetterAuthOptions } from 'better-auth';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Session user shape exposed by the platform auth instance.
|
|
5
|
-
*
|
|
6
|
-
* The instance returned by {@link createPlatformAuth} is widened to the base
|
|
7
|
-
* `Auth` type so the published `.d.ts` stays portable (inferring the full
|
|
8
|
-
* plugin-augmented type triggers TS2742). That widening hides the fields the
|
|
9
|
-
* email-otp/admin plugins add at runtime — notably `role` from `admin()`.
|
|
10
|
-
*
|
|
11
|
-
* This is the hand-maintained contract for what `api.getSession()` actually
|
|
12
|
-
* returns. Consumers cast the session to {@link PlatformSession} to read these
|
|
13
|
-
* fields with types. Keep it in sync with the enabled plugins.
|
|
14
|
-
*/
|
|
15
|
-
interface PlatformUser {
|
|
16
|
-
id: string;
|
|
17
|
-
email: string;
|
|
18
|
-
emailVerified: boolean;
|
|
19
|
-
name: string;
|
|
20
|
-
image?: string | null;
|
|
21
|
-
createdAt: Date;
|
|
22
|
-
updatedAt: Date;
|
|
23
|
-
/** From the admin() plugin. Absent until a role is assigned. */
|
|
24
|
-
role?: string | null;
|
|
25
|
-
/** From the admin() plugin. */
|
|
26
|
-
banned?: boolean | null;
|
|
27
|
-
}
|
|
28
|
-
interface PlatformSessionData {
|
|
29
|
-
id: string;
|
|
30
|
-
userId: string;
|
|
31
|
-
expiresAt: Date;
|
|
32
|
-
token: string;
|
|
33
|
-
createdAt: Date;
|
|
34
|
-
updatedAt: Date;
|
|
35
|
-
ipAddress?: string | null;
|
|
36
|
-
userAgent?: string | null;
|
|
37
|
-
}
|
|
38
|
-
/** Return shape of `auth.api.getSession()` for platform apps. */
|
|
39
|
-
interface PlatformSession {
|
|
40
|
-
user: PlatformUser;
|
|
41
|
-
session: PlatformSessionData;
|
|
42
|
-
}
|
|
43
|
-
type PlatformAuthMailerType = "email-verification" | "forget-password" | "sign-in" | "change-email";
|
|
44
|
-
interface PlatformAuthMailerArgs {
|
|
45
|
-
/** Recipient address */
|
|
46
|
-
to: string;
|
|
47
|
-
/** Pre-rendered subject line */
|
|
48
|
-
subject: string;
|
|
49
|
-
/** Pre-rendered HTML body */
|
|
50
|
-
html: string;
|
|
51
|
-
/** Better Auth verification kind */
|
|
52
|
-
type: PlatformAuthMailerType;
|
|
53
|
-
/** The OTP value, in case the consumer wants to render its own template */
|
|
54
|
-
otp: string;
|
|
55
|
-
}
|
|
56
|
-
type PlatformAuthMailer = (args: PlatformAuthMailerArgs) => Promise<void>;
|
|
57
|
-
interface PlatformAuthConfig {
|
|
58
|
-
/** PostgreSQL connection pool or connection string */
|
|
59
|
-
database: BetterAuthOptions["database"];
|
|
60
|
-
/** Base URL for Better Auth callbacks (e.g. http://localhost:3001) */
|
|
61
|
-
baseURL: string;
|
|
62
|
-
/** Secret for signing sessions */
|
|
63
|
-
secret: string;
|
|
64
|
-
/** Application name (used in emails) */
|
|
65
|
-
appName: string;
|
|
66
|
-
/**
|
|
67
|
-
* Transactional mailer. Receives the fully-rendered subject and HTML body
|
|
68
|
-
* and is responsible for pushing the message onto the wire (e.g. via the
|
|
69
|
-
* @digstack/spore-sdk, SES, postfix, …). When omitted, OTPs are logged to
|
|
70
|
-
* stdout — useful in dev/test, useless in production.
|
|
71
|
-
*/
|
|
72
|
-
mailer?: PlatformAuthMailer;
|
|
73
|
-
/** Google OAuth config (omit to disable) */
|
|
74
|
-
google?: {
|
|
75
|
-
clientId: string;
|
|
76
|
-
clientSecret: string;
|
|
77
|
-
};
|
|
78
|
-
/** GitHub OAuth config (omit to disable) */
|
|
79
|
-
github?: {
|
|
80
|
-
clientId: string;
|
|
81
|
-
clientSecret: string;
|
|
82
|
-
};
|
|
83
|
-
/**
|
|
84
|
-
* Override the OTP email subject line per verification type. Merged over
|
|
85
|
-
* the platform defaults — provide only the keys you want to change. The
|
|
86
|
-
* resulting subject is suffixed with ` - ${appName}` like the defaults.
|
|
87
|
-
*/
|
|
88
|
-
emailSubjects?: Partial<Record<PlatformAuthMailerType, string>>;
|
|
89
|
-
/**
|
|
90
|
-
* Override the OTP email HTML renderer. Receives the OTP code and the
|
|
91
|
-
* verification type, returns the HTML body. When omitted, the platform's
|
|
92
|
-
* default branded template is used.
|
|
93
|
-
*/
|
|
94
|
-
renderOtpEmail?: (otp: string, type: PlatformAuthMailerType) => string;
|
|
95
|
-
/** Additional Better Auth plugins to append */
|
|
96
|
-
plugins?: BetterAuthOptions["plugins"];
|
|
97
|
-
/** Enable private beta mode (blocks public registration) */
|
|
98
|
-
betaMode?: boolean;
|
|
99
|
-
/** Check if an email+token pair has been invited (required when betaMode is true) */
|
|
100
|
-
isInvited?: (email: string, inviteToken: string) => Promise<boolean>;
|
|
101
|
-
}
|
|
102
|
-
interface PlatformAuthClientConfig {
|
|
103
|
-
/** Base URL override (defaults to window.location.origin in browser) */
|
|
104
|
-
baseURL?: string;
|
|
105
|
-
/** Additional client plugins */
|
|
106
|
-
plugins?: any[];
|
|
107
|
-
}
|
|
108
|
-
interface VerifyEmailFormProps {
|
|
109
|
-
/** Email to verify */
|
|
110
|
-
email: string;
|
|
111
|
-
/** Callback on successful verification */
|
|
112
|
-
onSuccess?: () => void;
|
|
113
|
-
/** URL to navigate to on success */
|
|
114
|
-
successUrl?: string;
|
|
115
|
-
/** Auth client instance */
|
|
116
|
-
authClient: any;
|
|
117
|
-
}
|
|
118
|
-
/** Why an invitation link did not work, as far as the invitee needs to know. */
|
|
119
|
-
type InvitationFailure = "expired" | "claimed" | "unknown";
|
|
120
|
-
interface InvitationNoticeProps {
|
|
121
|
-
reason?: InvitationFailure;
|
|
122
|
-
/** Defaults to contact@ the apex domain the app is served from. */
|
|
123
|
-
supportEmail?: string;
|
|
124
|
-
title?: string;
|
|
125
|
-
/** Rendered under the contact line — typically a link back to the site. */
|
|
126
|
-
action?: React.ReactNode;
|
|
127
|
-
}
|
|
128
|
-
interface ForgotPasswordFormProps {
|
|
129
|
-
/** Callback on successful OTP send, receives the email */
|
|
130
|
-
onSuccess?: (email: string) => void;
|
|
131
|
-
/** Link to login page */
|
|
132
|
-
loginUrl?: string;
|
|
133
|
-
/** Auth client instance */
|
|
134
|
-
authClient: any;
|
|
135
|
-
}
|
|
136
|
-
interface ResetPasswordFormProps {
|
|
137
|
-
/** Email address to reset password for */
|
|
138
|
-
email: string;
|
|
139
|
-
/** Callback on successful password reset */
|
|
140
|
-
onSuccess?: () => void;
|
|
141
|
-
/** Link to login page */
|
|
142
|
-
loginUrl?: string;
|
|
143
|
-
/** Auth client instance */
|
|
144
|
-
authClient: any;
|
|
145
|
-
}
|
|
146
|
-
interface AuthLayoutProps {
|
|
147
|
-
/** Logo element to display at the top */
|
|
148
|
-
logo?: React.ReactNode;
|
|
149
|
-
/** Page title */
|
|
150
|
-
title: string;
|
|
151
|
-
/** Subtitle below the title */
|
|
152
|
-
subtitle?: string;
|
|
153
|
-
/** Content to render inside the card */
|
|
154
|
-
children: React.ReactNode;
|
|
155
|
-
/** Footer content below the card (e.g. legal links) */
|
|
156
|
-
footer?: React.ReactNode;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export type { AuthLayoutProps as A, ForgotPasswordFormProps as F, InvitationNoticeProps as I, PlatformAuthClientConfig as P, ResetPasswordFormProps as R, VerifyEmailFormProps as V, InvitationFailure as a, PlatformAuthConfig as b, PlatformAuthMailer as c, PlatformAuthMailerArgs as d, PlatformAuthMailerType as e, PlatformSession as f, PlatformSessionData as g, PlatformUser as h };
|