@contentgrowth/content-auth 0.5.1 → 0.5.3
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/backend/index.d.ts +13 -1
- package/dist/backend/index.js +1 -1
- package/dist/{chunk-3HNFZJ7S.js → chunk-7QOUBGQS.js} +44 -7
- package/dist/{chunk-52ZT54FA.js → chunk-XN2NSYRD.js} +14 -0
- package/dist/frontend/components/astro/AuthForm.astro +50 -7
- package/dist/frontend/components/vue/AuthForm.vue +46 -5
- package/dist/frontend/index.d.ts +8 -0
- package/dist/frontend/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/styles.css +4 -0
- package/package.json +1 -1
- package/schema/auth.sql +2 -0
package/dist/backend/index.d.ts
CHANGED
|
@@ -759,6 +759,12 @@ interface EmailNormalizationConfig {
|
|
|
759
759
|
/** Column name in users table. Default: 'normalized_email' */
|
|
760
760
|
columnName?: string;
|
|
761
761
|
}
|
|
762
|
+
interface SignInTrackingConfig {
|
|
763
|
+
/** Enable sign-in tracking (lastSignedInAt column). Default: false */
|
|
764
|
+
enabled: boolean;
|
|
765
|
+
/** Column name for last sign-in timestamp. Default: 'lastSignedInAt' */
|
|
766
|
+
columnName?: string;
|
|
767
|
+
}
|
|
762
768
|
/**
|
|
763
769
|
* Field attribute for additional fields in custom schema
|
|
764
770
|
*/
|
|
@@ -861,6 +867,12 @@ interface AuthConfig {
|
|
|
861
867
|
* Requires a 'normalized_email' column in the users table.
|
|
862
868
|
*/
|
|
863
869
|
emailNormalization?: EmailNormalizationConfig;
|
|
870
|
+
/**
|
|
871
|
+
* Sign-in tracking to record when users last signed in.
|
|
872
|
+
* Useful for engagement analytics and identifying inactive users.
|
|
873
|
+
* Requires a 'lastSignedInAt' column (or custom name) in the users table.
|
|
874
|
+
*/
|
|
875
|
+
signInTracking?: SignInTrackingConfig;
|
|
864
876
|
/**
|
|
865
877
|
* Custom schema mapping to use existing tables with Better Auth.
|
|
866
878
|
* Maps Better Auth's default table/column names to your existing database schema.
|
|
@@ -876,4 +888,4 @@ declare const createAuthApp: (config: AuthConfig) => {
|
|
|
876
888
|
auth: better_auth.Auth<any>;
|
|
877
889
|
};
|
|
878
890
|
|
|
879
|
-
export { type AuthConfig, type EmailNormalizationConfig, type FieldAttribute, type SchemaMapping, type TableMapping, type TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, isGmailAddress, normalizeEmail, schema, verifyTurnstile };
|
|
891
|
+
export { type AuthConfig, type EmailNormalizationConfig, type FieldAttribute, type SchemaMapping, type SignInTrackingConfig, type TableMapping, type TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, isGmailAddress, normalizeEmail, schema, verifyTurnstile };
|
package/dist/backend/index.js
CHANGED
|
@@ -20,11 +20,16 @@ var AuthForm = ({
|
|
|
20
20
|
defaultEmail = "",
|
|
21
21
|
lockEmail = false,
|
|
22
22
|
forgotPasswordUrl,
|
|
23
|
-
turnstileSiteKey
|
|
23
|
+
turnstileSiteKey,
|
|
24
|
+
redirectUrl,
|
|
25
|
+
showName = true,
|
|
26
|
+
signinUrl,
|
|
27
|
+
signupUrl
|
|
24
28
|
}) => {
|
|
25
29
|
const [isLogin, setIsLogin] = useState(view !== "signup");
|
|
26
30
|
const [email, setEmail] = useState(defaultEmail);
|
|
27
31
|
const [password, setPassword] = useState("");
|
|
32
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
28
33
|
const [name, setName] = useState("");
|
|
29
34
|
const [loading, setLoading] = useState(false);
|
|
30
35
|
const [error, setError] = useState(null);
|
|
@@ -55,6 +60,10 @@ var AuthForm = ({
|
|
|
55
60
|
};
|
|
56
61
|
const handleSubmit = async (e) => {
|
|
57
62
|
e.preventDefault();
|
|
63
|
+
if (!isLogin && password !== confirmPassword) {
|
|
64
|
+
setError("Passwords do not match");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
58
67
|
if (turnstileRequired && !turnstileToken) {
|
|
59
68
|
setError("Please complete the security challenge");
|
|
60
69
|
return;
|
|
@@ -72,9 +81,11 @@ var AuthForm = ({
|
|
|
72
81
|
} else {
|
|
73
82
|
const signupData = {
|
|
74
83
|
email,
|
|
75
|
-
password
|
|
76
|
-
name
|
|
84
|
+
password
|
|
77
85
|
};
|
|
86
|
+
if (showName) {
|
|
87
|
+
signupData.name = name;
|
|
88
|
+
}
|
|
78
89
|
if (turnstileToken) {
|
|
79
90
|
signupData.turnstileToken = turnstileToken;
|
|
80
91
|
}
|
|
@@ -82,6 +93,9 @@ var AuthForm = ({
|
|
|
82
93
|
if (response.error) throw response.error;
|
|
83
94
|
}
|
|
84
95
|
onSuccess?.(response.data);
|
|
96
|
+
if (redirectUrl) {
|
|
97
|
+
window.location.href = redirectUrl;
|
|
98
|
+
}
|
|
85
99
|
} catch (err) {
|
|
86
100
|
const errorMessage = err.message || "";
|
|
87
101
|
const errorCode = err.code || "";
|
|
@@ -117,7 +131,7 @@ var AuthForm = ({
|
|
|
117
131
|
try {
|
|
118
132
|
await client.signIn.social({
|
|
119
133
|
provider,
|
|
120
|
-
callbackURL: window.location.href
|
|
134
|
+
callbackURL: redirectUrl || window.location.href
|
|
121
135
|
});
|
|
122
136
|
} catch (err) {
|
|
123
137
|
setError(err.message || `Failed to sign in with ${provider}`);
|
|
@@ -181,7 +195,7 @@ var AuthForm = ({
|
|
|
181
195
|
] }) });
|
|
182
196
|
}
|
|
183
197
|
return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "ca-form", children: [
|
|
184
|
-
!isLogin && /* @__PURE__ */ jsxs("div", { className: "ca-input-group", children: [
|
|
198
|
+
!isLogin && showName && /* @__PURE__ */ jsxs("div", { className: "ca-input-group", children: [
|
|
185
199
|
/* @__PURE__ */ jsx("label", { className: "ca-label", htmlFor: "name", children: "Name" }),
|
|
186
200
|
/* @__PURE__ */ jsx(
|
|
187
201
|
"input",
|
|
@@ -227,6 +241,20 @@ var AuthForm = ({
|
|
|
227
241
|
}
|
|
228
242
|
)
|
|
229
243
|
] }),
|
|
244
|
+
!isLogin && /* @__PURE__ */ jsxs("div", { className: "ca-input-group", children: [
|
|
245
|
+
/* @__PURE__ */ jsx("label", { className: "ca-label", htmlFor: "confirm-password", children: "Confirm Password" }),
|
|
246
|
+
/* @__PURE__ */ jsx(
|
|
247
|
+
"input",
|
|
248
|
+
{
|
|
249
|
+
id: "confirm-password",
|
|
250
|
+
type: "password",
|
|
251
|
+
className: "ca-input",
|
|
252
|
+
value: confirmPassword,
|
|
253
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
254
|
+
required: true
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
] }),
|
|
230
258
|
renderTurnstile(),
|
|
231
259
|
error && /* @__PURE__ */ jsx("div", { className: "ca-error", children: error }),
|
|
232
260
|
/* @__PURE__ */ jsx(
|
|
@@ -247,11 +275,20 @@ var AuthForm = ({
|
|
|
247
275
|
{
|
|
248
276
|
className: "ca-link",
|
|
249
277
|
onClick: () => {
|
|
278
|
+
if (isLogin && signupUrl) {
|
|
279
|
+
window.location.href = signupUrl;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!isLogin && signinUrl) {
|
|
283
|
+
window.location.href = signinUrl;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
250
286
|
if (onSwitchMode) {
|
|
251
287
|
onSwitchMode();
|
|
252
288
|
} else {
|
|
253
289
|
setIsLogin(!isLogin);
|
|
254
290
|
}
|
|
291
|
+
setConfirmPassword("");
|
|
255
292
|
if (turnstileRef.current) {
|
|
256
293
|
turnstileRef.current.reset();
|
|
257
294
|
setTurnstileToken(null);
|
|
@@ -262,7 +299,7 @@ var AuthForm = ({
|
|
|
262
299
|
}
|
|
263
300
|
)
|
|
264
301
|
] });
|
|
265
|
-
const titleContent = title ? typeof title === "string" ? /* @__PURE__ */ jsx("h2", { className: "ca-title", children: title }) : title :
|
|
302
|
+
const titleContent = title ? typeof title === "string" ? /* @__PURE__ */ jsx("h2", { className: "ca-title", children: title }) : title : null;
|
|
266
303
|
if (layout === "split") {
|
|
267
304
|
return /* @__PURE__ */ jsxs("div", { className: containerClass, children: [
|
|
268
305
|
titleContent,
|
|
@@ -271,7 +308,7 @@ var AuthForm = ({
|
|
|
271
308
|
renderForm(),
|
|
272
309
|
renderFooter()
|
|
273
310
|
] }),
|
|
274
|
-
socialProviders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
311
|
+
!lockEmail && socialProviders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
275
312
|
/* @__PURE__ */ jsx("div", { className: "ca-split-divider", children: /* @__PURE__ */ jsx("span", { className: "ca-split-divider-text", children: "Or" }) }),
|
|
276
313
|
/* @__PURE__ */ jsxs("div", { className: "ca-split-social", children: [
|
|
277
314
|
/* @__PURE__ */ jsx("h3", { className: "ca-social-header", children: isLogin ? "Sign In With" : "Sign Up With" }),
|
|
@@ -365,6 +365,7 @@ var createAuth = (config) => {
|
|
|
365
365
|
emailVerification,
|
|
366
366
|
turnstile: turnstileConfig,
|
|
367
367
|
emailNormalization,
|
|
368
|
+
signInTracking,
|
|
368
369
|
schemaMapping,
|
|
369
370
|
user,
|
|
370
371
|
session,
|
|
@@ -496,6 +497,19 @@ var createAuth = (config) => {
|
|
|
496
497
|
}
|
|
497
498
|
}
|
|
498
499
|
}
|
|
500
|
+
if (signInTracking?.enabled && rawDb?.prepare && user2?.id) {
|
|
501
|
+
if (path.includes("/sign-in") || path.includes("/sign-up") || path.includes("/callback")) {
|
|
502
|
+
try {
|
|
503
|
+
const lastSignedInColumn = signInTracking.columnName || "lastSignedInAt";
|
|
504
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
505
|
+
await rawDb.prepare(
|
|
506
|
+
`UPDATE ${userTableName} SET ${lastSignedInColumn} = ? WHERE ${userIdColumn} = ?`
|
|
507
|
+
).bind(now, user2.id).run();
|
|
508
|
+
} catch (e) {
|
|
509
|
+
console.error(`[ContentAuth] Failed to update lastSignedInAt: ${e.message}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
499
513
|
if (userHooks?.after) {
|
|
500
514
|
return userHooks.after(context);
|
|
501
515
|
}
|
|
@@ -14,6 +14,9 @@ export interface Props {
|
|
|
14
14
|
forgotPasswordUrl?: string;
|
|
15
15
|
turnstileSiteKey?: string;
|
|
16
16
|
redirectUrl?: string;
|
|
17
|
+
showName?: boolean;
|
|
18
|
+
signinUrl?: string;
|
|
19
|
+
signupUrl?: string;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const {
|
|
@@ -29,7 +32,10 @@ const {
|
|
|
29
32
|
lockEmail = false,
|
|
30
33
|
forgotPasswordUrl,
|
|
31
34
|
turnstileSiteKey,
|
|
32
|
-
redirectUrl
|
|
35
|
+
redirectUrl,
|
|
36
|
+
showName = true,
|
|
37
|
+
signinUrl,
|
|
38
|
+
signupUrl
|
|
33
39
|
} = Astro.props;
|
|
34
40
|
|
|
35
41
|
const isLogin = view !== 'signup';
|
|
@@ -42,14 +48,14 @@ const googleSvg = `<svg class="ca-icon" viewBox="0 0 24 24" width="20" height="2
|
|
|
42
48
|
const githubSvg = `<svg class="ca-icon" viewBox="0 0 24 24" width="20" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.026A9.564 9.564 0 0 1 12 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0 0 22 12c0-5.523-4.477-10-10-10z" fill="currentColor" /></svg>`;
|
|
43
49
|
---
|
|
44
50
|
|
|
45
|
-
<div class={containerClass} data-auth-form data-base-url={baseUrl} data-view={view} data-redirect-url={redirectUrl} data-lock-email={lockEmail} data-turnstile-site-key={turnstileSiteKey}>
|
|
46
|
-
<h2 class="ca-title" data-title>{title
|
|
51
|
+
<div class={containerClass} data-auth-form data-base-url={baseUrl} data-view={view} data-redirect-url={redirectUrl} data-lock-email={lockEmail} data-turnstile-site-key={turnstileSiteKey} data-show-name={showName} data-signin-url={signinUrl} data-signup-url={signupUrl}>
|
|
52
|
+
{title && <h2 class="ca-title" data-title>{title}</h2>}
|
|
47
53
|
|
|
48
54
|
{layout === 'split' ? (
|
|
49
55
|
<div class="ca-split-body">
|
|
50
56
|
<div class="ca-split-main">
|
|
51
57
|
<form class="ca-form" data-auth-form-element>
|
|
52
|
-
{!isLogin && (
|
|
58
|
+
{!isLogin && showName && (
|
|
53
59
|
<div class="ca-input-group" data-name-group>
|
|
54
60
|
<label class="ca-label" for="name">Name</label>
|
|
55
61
|
<input id="name" type="text" class="ca-input" name="name" required />
|
|
@@ -79,6 +85,13 @@ const githubSvg = `<svg class="ca-icon" viewBox="0 0 24 24" width="20" height="2
|
|
|
79
85
|
<input id="password" type="password" class="ca-input" name="password" required />
|
|
80
86
|
</div>
|
|
81
87
|
|
|
88
|
+
{!isLogin && (
|
|
89
|
+
<div class="ca-input-group" data-confirm-password-group>
|
|
90
|
+
<label class="ca-label" for="confirm-password">Confirm Password</label>
|
|
91
|
+
<input id="confirm-password" type="password" class="ca-input" name="confirm-password" required />
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
|
|
82
95
|
{turnstileSiteKey && !isLogin && (
|
|
83
96
|
<div class="ca-turnstile" data-turnstile-container>
|
|
84
97
|
<p class="ca-turnstile-hint">Please complete the security check above</p>
|
|
@@ -147,7 +160,7 @@ const githubSvg = `<svg class="ca-icon" viewBox="0 0 24 24" width="20" height="2
|
|
|
147
160
|
)}
|
|
148
161
|
|
|
149
162
|
<form class="ca-form" data-auth-form-element>
|
|
150
|
-
{!isLogin && (
|
|
163
|
+
{!isLogin && showName && (
|
|
151
164
|
<div class="ca-input-group" data-name-group>
|
|
152
165
|
<label class="ca-label" for="name">Name</label>
|
|
153
166
|
<input id="name" type="text" class="ca-input" name="name" required />
|
|
@@ -177,6 +190,13 @@ const githubSvg = `<svg class="ca-icon" viewBox="0 0 24 24" width="20" height="2
|
|
|
177
190
|
<input id="password" type="password" class="ca-input" name="password" required />
|
|
178
191
|
</div>
|
|
179
192
|
|
|
193
|
+
{!isLogin && (
|
|
194
|
+
<div class="ca-input-group" data-confirm-password-group>
|
|
195
|
+
<label class="ca-label" for="confirm-password">Confirm Password</label>
|
|
196
|
+
<input id="confirm-password" type="password" class="ca-input" name="confirm-password" required />
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
|
|
180
200
|
{turnstileSiteKey && !isLogin && (
|
|
181
201
|
<div class="ca-turnstile" data-turnstile-container>
|
|
182
202
|
<p class="ca-turnstile-hint">Please complete the security check above</p>
|
|
@@ -230,6 +250,9 @@ document.querySelectorAll('[data-auth-form]').forEach((container) => {
|
|
|
230
250
|
const redirectUrl = container.getAttribute('data-redirect-url');
|
|
231
251
|
const lockEmail = container.getAttribute('data-lock-email') === 'true';
|
|
232
252
|
const turnstileSiteKey = container.getAttribute('data-turnstile-site-key');
|
|
253
|
+
const showName = container.getAttribute('data-show-name') !== 'false';
|
|
254
|
+
const signinUrl = container.getAttribute('data-signin-url');
|
|
255
|
+
const signupUrl = container.getAttribute('data-signup-url');
|
|
233
256
|
|
|
234
257
|
const form = container.querySelector('[data-auth-form-element]') as HTMLFormElement;
|
|
235
258
|
const submitBtn = container.querySelector('[data-submit-button]') as HTMLButtonElement;
|
|
@@ -238,6 +261,7 @@ document.querySelectorAll('[data-auth-form]').forEach((container) => {
|
|
|
238
261
|
const modeText = container.querySelector('[data-mode-text]') as HTMLSpanElement;
|
|
239
262
|
const titleEl = container.querySelector('[data-title]') as HTMLHeadingElement;
|
|
240
263
|
const nameGroup = container.querySelector('[data-name-group]') as HTMLDivElement | null;
|
|
264
|
+
const confirmPasswordGroup = container.querySelector('[data-confirm-password-group]') as HTMLDivElement | null;
|
|
241
265
|
const socialHeader = container.querySelector('[data-social-header]') as HTMLHeadingElement | null;
|
|
242
266
|
const socialButtons = container.querySelectorAll('[data-social-provider]');
|
|
243
267
|
const turnstileContainer = container.querySelector('[data-turnstile-container]') as HTMLDivElement | null;
|
|
@@ -287,7 +311,8 @@ document.querySelectorAll('[data-auth-form]').forEach((container) => {
|
|
|
287
311
|
if (modeText) modeText.textContent = isLogin ? "Don't have an account? " : "Already have an account? ";
|
|
288
312
|
if (switchBtn) switchBtn.textContent = isLogin ? 'Sign up' : 'Sign in';
|
|
289
313
|
if (submitBtn) submitBtn.textContent = isLogin ? 'Sign In' : 'Sign Up';
|
|
290
|
-
if (nameGroup) nameGroup.style.display = isLogin ? 'none' : 'flex';
|
|
314
|
+
if (nameGroup) nameGroup.style.display = (isLogin || !showName) ? 'none' : 'flex';
|
|
315
|
+
if (confirmPasswordGroup) confirmPasswordGroup.style.display = isLogin ? 'none' : 'flex';
|
|
291
316
|
if (socialHeader) socialHeader.textContent = isLogin ? 'Sign In With' : 'Sign Up With';
|
|
292
317
|
if (turnstileContainer) {
|
|
293
318
|
turnstileContainer.style.display = isLogin ? 'none' : 'flex';
|
|
@@ -300,6 +325,15 @@ document.querySelectorAll('[data-auth-form]').forEach((container) => {
|
|
|
300
325
|
|
|
301
326
|
// Handle mode switch
|
|
302
327
|
switchBtn?.addEventListener('click', () => {
|
|
328
|
+
if (isLogin && signupUrl) {
|
|
329
|
+
window.location.href = signupUrl;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!isLogin && signinUrl) {
|
|
333
|
+
window.location.href = signinUrl;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
303
337
|
isLogin = !isLogin;
|
|
304
338
|
updateMode();
|
|
305
339
|
hideError();
|
|
@@ -332,8 +366,16 @@ document.querySelectorAll('[data-auth-form]').forEach((container) => {
|
|
|
332
366
|
const formData = new FormData(form);
|
|
333
367
|
const email = formData.get('email') as string;
|
|
334
368
|
const password = formData.get('password') as string;
|
|
369
|
+
const confirmPassword = formData.get('confirm-password') as string;
|
|
335
370
|
const name = formData.get('name') as string;
|
|
336
371
|
|
|
372
|
+
if (!isLogin) {
|
|
373
|
+
if (password !== confirmPassword) {
|
|
374
|
+
showError('Passwords do not match');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
337
379
|
if (!isLogin && turnstileSiteKey && !turnstileToken) {
|
|
338
380
|
showError('Please complete the security challenge');
|
|
339
381
|
return;
|
|
@@ -347,7 +389,8 @@ document.querySelectorAll('[data-auth-form]').forEach((container) => {
|
|
|
347
389
|
response = await client.signIn.email({ email, password });
|
|
348
390
|
if (response.error) throw response.error;
|
|
349
391
|
} else {
|
|
350
|
-
const signupData: any = { email, password
|
|
392
|
+
const signupData: any = { email, password };
|
|
393
|
+
if (showName) signupData.name = name;
|
|
351
394
|
if (turnstileToken) signupData.turnstileToken = turnstileToken;
|
|
352
395
|
response = await client.signUp.email(signupData);
|
|
353
396
|
if (response.error) throw response.error;
|
|
@@ -16,6 +16,9 @@ interface Props {
|
|
|
16
16
|
forgotPasswordUrl?: string;
|
|
17
17
|
turnstileSiteKey?: string;
|
|
18
18
|
redirectUrl?: string;
|
|
19
|
+
showName?: boolean;
|
|
20
|
+
signinUrl?: string;
|
|
21
|
+
signupUrl?: string;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -27,6 +30,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
27
30
|
socialPosition: 'top',
|
|
28
31
|
defaultEmail: '',
|
|
29
32
|
lockEmail: false,
|
|
33
|
+
showName: true,
|
|
30
34
|
});
|
|
31
35
|
|
|
32
36
|
const emit = defineEmits<{
|
|
@@ -40,6 +44,7 @@ const client = computed(() => createClient(props.baseUrl));
|
|
|
40
44
|
const isLogin = ref(props.view !== 'signup');
|
|
41
45
|
const email = ref(props.defaultEmail);
|
|
42
46
|
const password = ref('');
|
|
47
|
+
const confirmPassword = ref('');
|
|
43
48
|
const name = ref('');
|
|
44
49
|
const loading = ref(false);
|
|
45
50
|
const error = ref<string | null>(null);
|
|
@@ -61,7 +66,7 @@ const socialClass = computed(() =>
|
|
|
61
66
|
);
|
|
62
67
|
|
|
63
68
|
const displayTitle = computed(() =>
|
|
64
|
-
props.title
|
|
69
|
+
props.title
|
|
65
70
|
);
|
|
66
71
|
|
|
67
72
|
const turnstileEnabled = computed(() => !!props.turnstileSiteKey);
|
|
@@ -77,6 +82,11 @@ onMounted(() => {
|
|
|
77
82
|
});
|
|
78
83
|
|
|
79
84
|
const handleSubmit = async () => {
|
|
85
|
+
if (!isLogin.value && password.value !== confirmPassword.value) {
|
|
86
|
+
error.value = 'Passwords do not match';
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
if (turnstileRequired.value && !turnstileToken.value) {
|
|
81
91
|
error.value = 'Please complete the security challenge';
|
|
82
92
|
return;
|
|
@@ -97,8 +107,10 @@ const handleSubmit = async () => {
|
|
|
97
107
|
const signupData: any = {
|
|
98
108
|
email: email.value,
|
|
99
109
|
password: password.value,
|
|
100
|
-
name: name.value,
|
|
101
110
|
};
|
|
111
|
+
if (props.showName) {
|
|
112
|
+
signupData.name = name.value;
|
|
113
|
+
}
|
|
102
114
|
if (turnstileToken.value) {
|
|
103
115
|
signupData.turnstileToken = turnstileToken.value;
|
|
104
116
|
}
|
|
@@ -154,8 +166,19 @@ const handleSocialLogin = async (provider: string) => {
|
|
|
154
166
|
};
|
|
155
167
|
|
|
156
168
|
const switchMode = () => {
|
|
169
|
+
// Navigate to separate auth pages if URLs provided
|
|
170
|
+
if (isLogin.value && props.signupUrl) {
|
|
171
|
+
window.location.href = props.signupUrl;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!isLogin.value && props.signinUrl) {
|
|
175
|
+
window.location.href = props.signinUrl;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
157
179
|
isLogin.value = !isLogin.value;
|
|
158
180
|
error.value = null;
|
|
181
|
+
confirmPassword.value = '';
|
|
159
182
|
turnstileToken.value = null;
|
|
160
183
|
emit('switchMode');
|
|
161
184
|
};
|
|
@@ -172,7 +195,7 @@ const handleTurnstileError = () => {
|
|
|
172
195
|
|
|
173
196
|
<template>
|
|
174
197
|
<div :class="containerClass">
|
|
175
|
-
<h2 class="ca-title">{{ displayTitle }}</h2>
|
|
198
|
+
<h2 v-if="displayTitle" class="ca-title">{{ displayTitle }}</h2>
|
|
176
199
|
|
|
177
200
|
<!-- Default Layout -->
|
|
178
201
|
<template v-if="layout !== 'split'">
|
|
@@ -199,7 +222,7 @@ const handleTurnstileError = () => {
|
|
|
199
222
|
|
|
200
223
|
<!-- Form -->
|
|
201
224
|
<form class="ca-form" @submit.prevent="handleSubmit">
|
|
202
|
-
<div v-if="!isLogin" class="ca-input-group">
|
|
225
|
+
<div v-if="!isLogin && showName" class="ca-input-group">
|
|
203
226
|
<label class="ca-label" for="name">Name</label>
|
|
204
227
|
<input
|
|
205
228
|
id="name"
|
|
@@ -238,6 +261,17 @@ const handleTurnstileError = () => {
|
|
|
238
261
|
/>
|
|
239
262
|
</div>
|
|
240
263
|
|
|
264
|
+
<div v-if="!isLogin" class="ca-input-group">
|
|
265
|
+
<label class="ca-label" for="confirm-password">Confirm Password</label>
|
|
266
|
+
<input
|
|
267
|
+
id="confirm-password"
|
|
268
|
+
v-model="confirmPassword"
|
|
269
|
+
type="password"
|
|
270
|
+
class="ca-input"
|
|
271
|
+
required
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
241
275
|
<!-- Turnstile placeholder - implement with vue-turnstile if needed -->
|
|
242
276
|
<div v-if="turnstileSiteKey && !isLogin" class="ca-turnstile">
|
|
243
277
|
<p class="ca-turnstile-hint">Please complete the security check above</p>
|
|
@@ -289,7 +323,7 @@ const handleTurnstileError = () => {
|
|
|
289
323
|
<div class="ca-split-body">
|
|
290
324
|
<div class="ca-split-main">
|
|
291
325
|
<form class="ca-form" @submit.prevent="handleSubmit">
|
|
292
|
-
<div v-if="!isLogin" class="ca-input-group">
|
|
326
|
+
<div v-if="!isLogin && showName" class="ca-input-group">
|
|
293
327
|
<label class="ca-label" for="name">Name</label>
|
|
294
328
|
<input id="name" v-model="name" type="text" class="ca-input" required />
|
|
295
329
|
</div>
|
|
@@ -304,6 +338,13 @@ const handleTurnstileError = () => {
|
|
|
304
338
|
</div>
|
|
305
339
|
<input id="password" v-model="password" type="password" class="ca-input" required />
|
|
306
340
|
</div>
|
|
341
|
+
<div v-if="!isLogin" class="ca-input-group">
|
|
342
|
+
<label class="ca-label" for="confirm-password">Confirm Password</label>
|
|
343
|
+
<input id="confirm-password" v-model="confirmPassword" type="password" class="ca-input" required />
|
|
344
|
+
</div>
|
|
345
|
+
<div v-if="turnstileSiteKey && !isLogin" class="ca-turnstile">
|
|
346
|
+
<p class="ca-turnstile-hint">Please complete the security check above</p>
|
|
347
|
+
</div>
|
|
307
348
|
<div v-if="error" class="ca-error">{{ error }}</div>
|
|
308
349
|
<button type="submit" class="ca-button" :disabled="loading || !canSubmit">
|
|
309
350
|
{{ loading ? 'Loading...' : (isLogin ? 'Sign In' : 'Sign Up') }}
|
package/dist/frontend/index.d.ts
CHANGED
|
@@ -26,6 +26,14 @@ interface AuthFormProps {
|
|
|
26
26
|
forgotPasswordUrl?: string;
|
|
27
27
|
/** Cloudflare Turnstile site key. When set, shows Turnstile widget on signup. */
|
|
28
28
|
turnstileSiteKey?: string;
|
|
29
|
+
/** URL to redirect to after successful authentication */
|
|
30
|
+
redirectUrl?: string;
|
|
31
|
+
/** Whether to show the name field on signup (default: true) */
|
|
32
|
+
showName?: boolean;
|
|
33
|
+
/** URL to navigate to for signin (if set, navigates instead of switching mode inline) */
|
|
34
|
+
signinUrl?: string;
|
|
35
|
+
/** URL to navigate to for signup (if set, navigates instead of switching mode inline) */
|
|
36
|
+
signupUrl?: string;
|
|
29
37
|
}
|
|
30
38
|
declare const AuthForm: React.FC<AuthFormProps>;
|
|
31
39
|
|
package/dist/frontend/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AuthConfig, EmailNormalizationConfig, FieldAttribute, SchemaMapping, TableMapping, TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, isGmailAddress, normalizeEmail, schema, verifyTurnstile } from './backend/index.js';
|
|
1
|
+
export { AuthConfig, EmailNormalizationConfig, FieldAttribute, SchemaMapping, SignInTrackingConfig, TableMapping, TurnstileConfig, authMiddleware, createAuth, createAuthApp, getInvitationLink, getSessionToken, isGmailAddress, normalizeEmail, schema, verifyTurnstile } from './backend/index.js';
|
|
2
2
|
export { AuthForm, CreateOrganizationForm, ForgotPasswordForm, InviteMemberForm, OrganizationSwitcher, PasswordChanger, PasswordChangerProps, ProfileEditor, ProfileEditorProps, ResetPasswordForm } from './frontend/index.js';
|
|
3
3
|
export { authClient, createClient } from './frontend/client.js';
|
|
4
4
|
export * from 'better-auth';
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
normalizeEmail,
|
|
10
10
|
schema_exports,
|
|
11
11
|
verifyTurnstile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XN2NSYRD.js";
|
|
13
13
|
import {
|
|
14
14
|
AuthForm,
|
|
15
15
|
CreateOrganizationForm,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
PasswordChanger,
|
|
20
20
|
ProfileEditor,
|
|
21
21
|
ResetPasswordForm
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-7QOUBGQS.js";
|
|
23
23
|
import {
|
|
24
24
|
authClient,
|
|
25
25
|
createClient
|
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentgrowth/content-auth",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Better Auth wrapper with UI components for Cloudflare Workers & Pages. Includes custom schema mapping, Turnstile bot protection, and email normalization.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/schema/auth.sql
CHANGED
|
@@ -29,6 +29,8 @@ CREATE TABLE IF NOT EXISTS users (
|
|
|
29
29
|
updatedAt TIMESTAMP NOT NULL
|
|
30
30
|
-- Optional: For Gmail duplicate prevention (see docs)
|
|
31
31
|
-- normalized_email TEXT
|
|
32
|
+
-- Optional: Sign-in tracking for engagement analytics
|
|
33
|
+
-- lastSignedInAt TIMESTAMP -- Updated on each sign-in
|
|
32
34
|
);
|
|
33
35
|
|
|
34
36
|
-- Sessions
|