@opensaas/stack-auth 0.31.1 → 0.33.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +54 -0
- package/CLAUDE.md +42 -7
- package/README.md +64 -18
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -2
- package/dist/server/index.js.map +1 -1
- package/dist/ui/components/ForgotPasswordForm.d.ts +10 -7
- package/dist/ui/components/ForgotPasswordForm.d.ts.map +1 -1
- package/dist/ui/components/ForgotPasswordForm.js +11 -10
- package/dist/ui/components/ForgotPasswordForm.js.map +1 -1
- package/dist/ui/components/ResetPasswordForm.d.ts +60 -0
- package/dist/ui/components/ResetPasswordForm.d.ts.map +1 -0
- package/dist/ui/components/ResetPasswordForm.js +70 -0
- package/dist/ui/components/ResetPasswordForm.js.map +1 -0
- package/dist/ui/components/SignInForm.d.ts +22 -9
- package/dist/ui/components/SignInForm.d.ts.map +1 -1
- package/dist/ui/components/SignInForm.js +25 -19
- package/dist/ui/components/SignInForm.js.map +1 -1
- package/dist/ui/components/SignUpForm.d.ts +16 -8
- package/dist/ui/components/SignUpForm.d.ts.map +1 -1
- package/dist/ui/components/SignUpForm.js +19 -23
- package/dist/ui/components/SignUpForm.js.map +1 -1
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/lib/clean-error-message.d.ts +12 -0
- package/dist/ui/lib/clean-error-message.d.ts.map +1 -0
- package/dist/ui/lib/clean-error-message.js +20 -0
- package/dist/ui/lib/clean-error-message.js.map +1 -0
- package/dist/ui/types.d.ts +55 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +12 -0
- package/dist/ui/types.js.map +1 -0
- package/package.json +3 -3
- package/src/server/index.ts +8 -2
- package/src/ui/components/ForgotPasswordForm.tsx +18 -14
- package/src/ui/components/ResetPasswordForm.tsx +182 -0
- package/src/ui/components/SignInForm.tsx +43 -26
- package/src/ui/components/SignUpForm.tsx +37 -29
- package/src/ui/index.ts +17 -0
- package/src/ui/lib/clean-error-message.ts +21 -0
- package/src/ui/types.ts +49 -0
- package/tests/clean-error-message.test.ts +29 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SignInAction, SignInSocialAction } from '../types.js';
|
|
2
2
|
export type SignInFormProps = {
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Pass your client from lib/auth-client.ts
|
|
4
|
+
* Server action that signs a user in with email + password.
|
|
5
|
+
* Define it in your app (`'use server'`) against your own auth instance.
|
|
7
6
|
*/
|
|
8
|
-
|
|
7
|
+
signInAction: SignInAction;
|
|
8
|
+
/**
|
|
9
|
+
* Server action that starts an OAuth sign-in and redirects to the provider.
|
|
10
|
+
* Required to render the social provider buttons; omit to hide them.
|
|
11
|
+
*/
|
|
12
|
+
signInSocialAction?: SignInSocialAction;
|
|
9
13
|
/**
|
|
10
14
|
* URL to redirect to after successful sign in
|
|
11
15
|
* @default '/'
|
|
12
16
|
*/
|
|
13
17
|
redirectTo?: string;
|
|
14
18
|
/**
|
|
15
|
-
* Show OAuth provider buttons
|
|
19
|
+
* Show OAuth provider buttons (requires `signInSocialAction`)
|
|
16
20
|
* @default true
|
|
17
21
|
*/
|
|
18
22
|
showSocialProviders?: boolean;
|
|
@@ -38,15 +42,24 @@ export type SignInFormProps = {
|
|
|
38
42
|
* Sign in form component
|
|
39
43
|
* Provides email/password sign in and OAuth provider buttons
|
|
40
44
|
*
|
|
45
|
+
* Submits through app-owned server actions rather than calling the auth API
|
|
46
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
47
|
+
*
|
|
41
48
|
* @example
|
|
42
49
|
* ```typescript
|
|
43
50
|
* import { SignInForm } from '@opensaas/stack-auth/ui'
|
|
44
|
-
* import {
|
|
51
|
+
* import { signInAction, signInSocialAction } from '@/lib/actions/auth'
|
|
45
52
|
*
|
|
46
53
|
* export default function SignInPage() {
|
|
47
|
-
* return
|
|
54
|
+
* return (
|
|
55
|
+
* <SignInForm
|
|
56
|
+
* signInAction={signInAction}
|
|
57
|
+
* signInSocialAction={signInSocialAction}
|
|
58
|
+
* redirectTo="/admin"
|
|
59
|
+
* />
|
|
60
|
+
* )
|
|
48
61
|
* }
|
|
49
62
|
* ```
|
|
50
63
|
*/
|
|
51
|
-
export declare function SignInForm({
|
|
64
|
+
export declare function SignInForm({ signInAction, signInSocialAction, redirectTo, showSocialProviders, socialProviders, className, onSuccess, onError, }: SignInFormProps): import("react/jsx-runtime").JSX.Element;
|
|
52
65
|
//# sourceMappingURL=SignInForm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignInForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/SignInForm.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SignInForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/SignInForm.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEnE,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,YAAY,EAAE,YAAY,CAAA;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,EACzB,YAAY,EACZ,kBAAkB,EAClB,UAAgB,EAChB,mBAA0B,EAC1B,eAAsC,EACtC,SAAc,EACd,SAAS,EACT,OAAO,GACR,EAAE,eAAe,2CAyIjB"}
|
|
@@ -2,21 +2,31 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { cleanAuthErrorMessage } from '../lib/clean-error-message.js';
|
|
5
6
|
/**
|
|
6
7
|
* Sign in form component
|
|
7
8
|
* Provides email/password sign in and OAuth provider buttons
|
|
8
9
|
*
|
|
10
|
+
* Submits through app-owned server actions rather than calling the auth API
|
|
11
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
12
|
+
*
|
|
9
13
|
* @example
|
|
10
14
|
* ```typescript
|
|
11
15
|
* import { SignInForm } from '@opensaas/stack-auth/ui'
|
|
12
|
-
* import {
|
|
16
|
+
* import { signInAction, signInSocialAction } from '@/lib/actions/auth'
|
|
13
17
|
*
|
|
14
18
|
* export default function SignInPage() {
|
|
15
|
-
* return
|
|
19
|
+
* return (
|
|
20
|
+
* <SignInForm
|
|
21
|
+
* signInAction={signInAction}
|
|
22
|
+
* signInSocialAction={signInSocialAction}
|
|
23
|
+
* redirectTo="/admin"
|
|
24
|
+
* />
|
|
25
|
+
* )
|
|
16
26
|
* }
|
|
17
27
|
* ```
|
|
18
28
|
*/
|
|
19
|
-
export function SignInForm({
|
|
29
|
+
export function SignInForm({ signInAction, signInSocialAction, redirectTo = '/', showSocialProviders = true, socialProviders = ['github', 'google'], className = '', onSuccess, onError, }) {
|
|
20
30
|
const router = useRouter();
|
|
21
31
|
const [email, setEmail] = useState('');
|
|
22
32
|
const [password, setPassword] = useState('');
|
|
@@ -27,13 +37,9 @@ export function SignInForm({ authClient, redirectTo = '/', showSocialProviders =
|
|
|
27
37
|
setError('');
|
|
28
38
|
setLoading(true);
|
|
29
39
|
try {
|
|
30
|
-
const result = await
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
callbackURL: redirectTo,
|
|
34
|
-
});
|
|
35
|
-
if (result.error) {
|
|
36
|
-
throw new Error(result.error.message);
|
|
40
|
+
const result = await signInAction({ email, password });
|
|
41
|
+
if (!result.success) {
|
|
42
|
+
throw new Error(cleanAuthErrorMessage(result.error, 'Sign in failed'));
|
|
37
43
|
}
|
|
38
44
|
// If onSuccess is provided, call it. Otherwise, automatically redirect
|
|
39
45
|
if (onSuccess) {
|
|
@@ -44,7 +50,7 @@ export function SignInForm({ authClient, redirectTo = '/', showSocialProviders =
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
catch (err) {
|
|
47
|
-
const message = err instanceof Error ? err.message : 'Sign in failed';
|
|
53
|
+
const message = cleanAuthErrorMessage(err instanceof Error ? err.message : undefined, 'Sign in failed');
|
|
48
54
|
setError(message);
|
|
49
55
|
onError?.(err instanceof Error ? err : new Error(message));
|
|
50
56
|
}
|
|
@@ -53,24 +59,24 @@ export function SignInForm({ authClient, redirectTo = '/', showSocialProviders =
|
|
|
53
59
|
}
|
|
54
60
|
};
|
|
55
61
|
const handleSocialSignIn = async (provider) => {
|
|
62
|
+
if (!signInSocialAction)
|
|
63
|
+
return;
|
|
56
64
|
setError('');
|
|
57
65
|
setLoading(true);
|
|
58
66
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
// Social sign-in handles its own redirect via OAuth flow
|
|
64
|
-
// Only call onSuccess if provided
|
|
67
|
+
// The action performs a server-side redirect to the provider, so on
|
|
68
|
+
// success control does not return here.
|
|
69
|
+
await signInSocialAction(provider);
|
|
65
70
|
onSuccess?.();
|
|
66
71
|
}
|
|
67
72
|
catch (err) {
|
|
68
|
-
const message = err instanceof Error ? err.message : 'Sign in failed';
|
|
73
|
+
const message = cleanAuthErrorMessage(err instanceof Error ? err.message : undefined, 'Sign in failed');
|
|
69
74
|
setError(message);
|
|
70
75
|
onError?.(err instanceof Error ? err : new Error(message));
|
|
71
76
|
setLoading(false);
|
|
72
77
|
}
|
|
73
78
|
};
|
|
74
|
-
|
|
79
|
+
const canShowSocial = showSocialProviders && socialProviders.length > 0 && !!signInSocialAction;
|
|
80
|
+
return (_jsxs("div", { className: `w-full max-w-md mx-auto p-6 ${className}`, children: [_jsx("h2", { className: "text-2xl font-bold mb-6", children: "Sign In" }), error && (_jsx("div", { className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4", children: error })), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "email", className: "block text-sm font-medium mb-2", children: "Email" }), _jsx("input", { id: "email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "password", className: "block text-sm font-medium mb-2", children: "Password" }), _jsx("input", { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] }), _jsx("button", { type: "submit", disabled: loading, className: "w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed", children: loading ? 'Signing in...' : 'Sign In' })] }), canShowSocial && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative my-6", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("div", { className: "w-full border-t border-gray-300" }) }), _jsx("div", { className: "relative flex justify-center text-sm", children: _jsx("span", { className: "px-2 bg-white text-gray-500", children: "Or continue with" }) })] }), _jsx("div", { className: "space-y-2", children: socialProviders.map((provider) => (_jsxs("button", { onClick: () => handleSocialSignIn(provider), disabled: loading, className: "w-full bg-white border border-gray-300 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-50 disabled:bg-gray-100 disabled:cursor-not-allowed", children: ["Sign in with ", provider.charAt(0).toUpperCase() + provider.slice(1)] }, provider))) })] }))] }));
|
|
75
81
|
}
|
|
76
82
|
//# sourceMappingURL=SignInForm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignInForm.js","sourceRoot":"","sources":["../../../src/ui/components/SignInForm.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"SignInForm.js","sourceRoot":"","sources":["../../../src/ui/components/SignInForm.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SignUpAction, SignInSocialAction } from '../types.js';
|
|
2
2
|
export type SignUpFormProps = {
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Server action that creates an account with email + password.
|
|
5
|
+
* Define it in your app (`'use server'`) against your own auth instance.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
signUpAction: SignUpAction;
|
|
8
|
+
/**
|
|
9
|
+
* Server action that starts an OAuth sign-in and redirects to the provider.
|
|
10
|
+
* Required to render the social provider buttons; omit to hide them.
|
|
11
|
+
*/
|
|
12
|
+
signInSocialAction?: SignInSocialAction;
|
|
8
13
|
/**
|
|
9
14
|
* URL to redirect to after successful sign up
|
|
10
15
|
* @default '/'
|
|
11
16
|
*/
|
|
12
17
|
redirectTo?: string;
|
|
13
18
|
/**
|
|
14
|
-
* Show OAuth provider buttons
|
|
19
|
+
* Show OAuth provider buttons (requires `signInSocialAction`)
|
|
15
20
|
* @default true
|
|
16
21
|
*/
|
|
17
22
|
showSocialProviders?: boolean;
|
|
@@ -42,15 +47,18 @@ export type SignUpFormProps = {
|
|
|
42
47
|
* Sign up form component
|
|
43
48
|
* Provides email/password registration and OAuth provider buttons
|
|
44
49
|
*
|
|
50
|
+
* Submits through app-owned server actions rather than calling the auth API
|
|
51
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
52
|
+
*
|
|
45
53
|
* @example
|
|
46
54
|
* ```typescript
|
|
47
55
|
* import { SignUpForm } from '@opensaas/stack-auth/ui'
|
|
48
|
-
* import {
|
|
56
|
+
* import { signUpAction, signInSocialAction } from '@/lib/actions/auth'
|
|
49
57
|
*
|
|
50
58
|
* export default function SignUpPage() {
|
|
51
|
-
* return <SignUpForm
|
|
59
|
+
* return <SignUpForm signUpAction={signUpAction} redirectTo="/admin" />
|
|
52
60
|
* }
|
|
53
61
|
* ```
|
|
54
62
|
*/
|
|
55
|
-
export declare function SignUpForm({
|
|
63
|
+
export declare function SignUpForm({ signUpAction, signInSocialAction, redirectTo, showSocialProviders, socialProviders, requirePasswordConfirmation, className, onSuccess, onError, }: SignUpFormProps): import("react/jsx-runtime").JSX.Element;
|
|
56
64
|
//# sourceMappingURL=SignUpForm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignUpForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/SignUpForm.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SignUpForm.d.ts","sourceRoot":"","sources":["../../../src/ui/components/SignUpForm.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEnE,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,YAAY,EAAE,YAAY,CAAA;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,EACzB,YAAY,EACZ,kBAAkB,EAClB,UAAgB,EAChB,mBAA0B,EAC1B,eAAsC,EACtC,2BAAkC,EAClC,SAAc,EACd,SAAS,EACT,OAAO,GACR,EAAE,eAAe,2CAkLjB"}
|
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { cleanAuthErrorMessage } from '../lib/clean-error-message.js';
|
|
5
6
|
/**
|
|
6
7
|
* Sign up form component
|
|
7
8
|
* Provides email/password registration and OAuth provider buttons
|
|
8
9
|
*
|
|
10
|
+
* Submits through app-owned server actions rather than calling the auth API
|
|
11
|
+
* from the browser. See the "Auth action" contract in `@opensaas/stack-auth/ui`.
|
|
12
|
+
*
|
|
9
13
|
* @example
|
|
10
14
|
* ```typescript
|
|
11
15
|
* import { SignUpForm } from '@opensaas/stack-auth/ui'
|
|
12
|
-
* import {
|
|
16
|
+
* import { signUpAction, signInSocialAction } from '@/lib/actions/auth'
|
|
13
17
|
*
|
|
14
18
|
* export default function SignUpPage() {
|
|
15
|
-
* return <SignUpForm
|
|
19
|
+
* return <SignUpForm signUpAction={signUpAction} redirectTo="/admin" />
|
|
16
20
|
* }
|
|
17
21
|
* ```
|
|
18
22
|
*/
|
|
19
|
-
export function SignUpForm({
|
|
23
|
+
export function SignUpForm({ signUpAction, signInSocialAction, redirectTo = '/', showSocialProviders = true, socialProviders = ['github', 'google'], requirePasswordConfirmation = true, className = '', onSuccess, onError, }) {
|
|
20
24
|
const router = useRouter();
|
|
21
25
|
const [name, setName] = useState('');
|
|
22
26
|
const [email, setEmail] = useState('');
|
|
@@ -34,17 +38,9 @@ export function SignUpForm({ authClient, redirectTo = '/', showSocialProviders =
|
|
|
34
38
|
}
|
|
35
39
|
setLoading(true);
|
|
36
40
|
try {
|
|
37
|
-
const result = await
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
name,
|
|
41
|
-
callbackURL: redirectTo,
|
|
42
|
-
});
|
|
43
|
-
if (result.error) {
|
|
44
|
-
// Strip [body.field] prefixes from better-call validation errors for user-friendly display
|
|
45
|
-
const rawMessage = result.error.message ?? 'Sign up failed';
|
|
46
|
-
const cleanMessage = rawMessage.replace(/\[body\.\w+\]\s*/g, '').trim();
|
|
47
|
-
throw new Error(cleanMessage);
|
|
41
|
+
const result = await signUpAction({ name, email, password });
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
throw new Error(cleanAuthErrorMessage(result.error, 'Sign up failed'));
|
|
48
44
|
}
|
|
49
45
|
// If onSuccess is provided, call it. Otherwise, automatically redirect
|
|
50
46
|
if (onSuccess) {
|
|
@@ -55,7 +51,7 @@ export function SignUpForm({ authClient, redirectTo = '/', showSocialProviders =
|
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
catch (err) {
|
|
58
|
-
const message = err instanceof Error ? err.message : 'Sign up failed';
|
|
54
|
+
const message = cleanAuthErrorMessage(err instanceof Error ? err.message : undefined, 'Sign up failed');
|
|
59
55
|
setError(message);
|
|
60
56
|
onError?.(err instanceof Error ? err : new Error(message));
|
|
61
57
|
}
|
|
@@ -64,24 +60,24 @@ export function SignUpForm({ authClient, redirectTo = '/', showSocialProviders =
|
|
|
64
60
|
}
|
|
65
61
|
};
|
|
66
62
|
const handleSocialSignUp = async (provider) => {
|
|
63
|
+
if (!signInSocialAction)
|
|
64
|
+
return;
|
|
67
65
|
setError('');
|
|
68
66
|
setLoading(true);
|
|
69
67
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
// Social sign-in handles its own redirect via OAuth flow
|
|
75
|
-
// Only call onSuccess if provided
|
|
68
|
+
// The action performs a server-side redirect to the provider, so on
|
|
69
|
+
// success control does not return here.
|
|
70
|
+
await signInSocialAction(provider);
|
|
76
71
|
onSuccess?.();
|
|
77
72
|
}
|
|
78
73
|
catch (err) {
|
|
79
|
-
const message = err instanceof Error ? err.message : 'Sign up failed';
|
|
74
|
+
const message = cleanAuthErrorMessage(err instanceof Error ? err.message : undefined, 'Sign up failed');
|
|
80
75
|
setError(message);
|
|
81
76
|
onError?.(err instanceof Error ? err : new Error(message));
|
|
82
77
|
setLoading(false);
|
|
83
78
|
}
|
|
84
79
|
};
|
|
85
|
-
|
|
80
|
+
const canShowSocial = showSocialProviders && socialProviders.length > 0 && !!signInSocialAction;
|
|
81
|
+
return (_jsxs("div", { className: `w-full max-w-md mx-auto p-6 ${className}`, children: [_jsx("h2", { className: "text-2xl font-bold mb-6", children: "Sign Up" }), error && (_jsx("div", { className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4", children: error })), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "name", className: "block text-sm font-medium mb-2", children: "Name" }), _jsx("input", { id: "name", type: "text", value: name, onChange: (e) => setName(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "email", className: "block text-sm font-medium mb-2", children: "Email" }), _jsx("input", { id: "email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "password", className: "block text-sm font-medium mb-2", children: "Password" }), _jsx("input", { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] }), requirePasswordConfirmation && (_jsxs("div", { children: [_jsx("label", { htmlFor: "confirmPassword", className: "block text-sm font-medium mb-2", children: "Confirm Password" }), _jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", disabled: loading })] })), _jsx("button", { type: "submit", disabled: loading, className: "w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed", children: loading ? 'Creating account...' : 'Sign Up' })] }), canShowSocial && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative my-6", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("div", { className: "w-full border-t border-gray-300" }) }), _jsx("div", { className: "relative flex justify-center text-sm", children: _jsx("span", { className: "px-2 bg-white text-gray-500", children: "Or continue with" }) })] }), _jsx("div", { className: "space-y-2", children: socialProviders.map((provider) => (_jsxs("button", { onClick: () => handleSocialSignUp(provider), disabled: loading, className: "w-full bg-white border border-gray-300 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-50 disabled:bg-gray-100 disabled:cursor-not-allowed", children: ["Sign up with ", provider.charAt(0).toUpperCase() + provider.slice(1)] }, provider))) })] }))] }));
|
|
86
82
|
}
|
|
87
83
|
//# sourceMappingURL=SignUpForm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignUpForm.js","sourceRoot":"","sources":["../../../src/ui/components/SignUpForm.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"SignUpForm.js","sourceRoot":"","sources":["../../../src/ui/components/SignUpForm.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAgDrE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,YAAY,EACZ,kBAAkB,EAClB,UAAU,GAAG,GAAG,EAChB,mBAAmB,GAAG,IAAI,EAC1B,eAAe,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,EACtC,2BAA2B,GAAG,IAAI,EAClC,SAAS,GAAG,EAAE,EACd,SAAS,EACT,OAAO,GACS;IAChB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACpC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACtC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC5C,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC1D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACtC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE7C,MAAM,YAAY,GAAG,KAAK,EAAE,CAAkB,EAAE,EAAE;QAChD,CAAC,CAAC,cAAc,EAAE,CAAA;QAClB,QAAQ,CAAC,EAAE,CAAC,CAAA;QAEZ,iCAAiC;QACjC,IAAI,2BAA2B,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;YAChE,QAAQ,CAAC,wBAAwB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,CAAA;QAEhB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;YAE5D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAA;YACxE,CAAC;YAED,uEAAuE;YACvE,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,EAAE,CAAA;YACb,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,qBAAqB,CACnC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAC9C,gBAAgB,CACjB,CAAA;YACD,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;QAC5D,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,KAAK,CAAC,CAAA;QACnB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,kBAAkB,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;QACpD,IAAI,CAAC,kBAAkB;YAAE,OAAM;QAC/B,QAAQ,CAAC,EAAE,CAAC,CAAA;QACZ,UAAU,CAAC,IAAI,CAAC,CAAA;QAEhB,IAAI,CAAC;YACH,oEAAoE;YACpE,wCAAwC;YACxC,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAA;YAClC,SAAS,EAAE,EAAE,CAAA;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,qBAAqB,CACnC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAC9C,gBAAgB,CACjB,CAAA;YACD,QAAQ,CAAC,OAAO,CAAC,CAAA;YACjB,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;YAC1D,UAAU,CAAC,KAAK,CAAC,CAAA;QACnB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,mBAAmB,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,kBAAkB,CAAA;IAE/F,OAAO,CACL,eAAK,SAAS,EAAE,+BAA+B,SAAS,EAAE,aACxD,aAAI,SAAS,EAAC,yBAAyB,wBAAa,EAEnD,KAAK,IAAI,CACR,cAAK,SAAS,EAAC,qEAAqE,YACjF,KAAK,GACF,CACP,EAED,gBAAM,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAC,WAAW,aACjD,0BACE,gBAAO,OAAO,EAAC,MAAM,EAAC,SAAS,EAAC,gCAAgC,qBAExD,EACR,gBACE,EAAE,EAAC,MAAM,EACT,IAAI,EAAC,MAAM,EACX,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,EAC9D,QAAQ,QACR,SAAS,EAAC,wGAAwG,EAClH,QAAQ,EAAE,OAAO,GACjB,IACE,EAEN,0BACE,gBAAO,OAAO,EAAC,OAAO,EAAC,SAAS,EAAC,gCAAgC,sBAEzD,EACR,gBACE,EAAE,EAAC,OAAO,EACV,IAAI,EAAC,OAAO,EACZ,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,EAC/D,QAAQ,QACR,SAAS,EAAC,wGAAwG,EAClH,QAAQ,EAAE,OAAO,GACjB,IACE,EAEN,0BACE,gBAAO,OAAO,EAAC,UAAU,EAAC,SAAS,EAAC,gCAAgC,yBAE5D,EACR,gBACE,EAAE,EAAC,UAAU,EACb,IAAI,EAAC,UAAU,EACf,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,EAClE,QAAQ,QACR,SAAS,EAAC,wGAAwG,EAClH,QAAQ,EAAE,OAAO,GACjB,IACE,EAEL,2BAA2B,IAAI,CAC9B,0BACE,gBAAO,OAAO,EAAC,iBAAiB,EAAC,SAAS,EAAC,gCAAgC,iCAEnE,EACR,gBACE,EAAE,EAAC,iBAAiB,EACpB,IAAI,EAAC,UAAU,EACf,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,EACzE,QAAQ,QACR,SAAS,EAAC,wGAAwG,EAClH,QAAQ,EAAE,OAAO,GACjB,IACE,CACP,EAED,iBACE,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAC,uHAAuH,YAEhI,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,GACrC,IACJ,EAEN,aAAa,IAAI,CAChB,8BACE,eAAK,SAAS,EAAC,eAAe,aAC5B,cAAK,SAAS,EAAC,oCAAoC,YACjD,cAAK,SAAS,EAAC,iCAAiC,GAAO,GACnD,EACN,cAAK,SAAS,EAAC,sCAAsC,YACnD,eAAM,SAAS,EAAC,6BAA6B,iCAAwB,GACjE,IACF,EAEN,cAAK,SAAS,EAAC,WAAW,YACvB,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CACjC,kBAEE,OAAO,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAC3C,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAC,6IAA6I,8BAEzI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,KAL7D,QAAQ,CAMN,CACV,CAAC,GACE,IACL,CACJ,IACG,CACP,CAAA;AACH,CAAC"}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export { SignInForm } from './components/SignInForm.js';
|
|
2
2
|
export { SignUpForm } from './components/SignUpForm.js';
|
|
3
3
|
export { ForgotPasswordForm } from './components/ForgotPasswordForm.js';
|
|
4
|
+
export { ResetPasswordForm } from './components/ResetPasswordForm.js';
|
|
4
5
|
export type { SignInFormProps } from './components/SignInForm.js';
|
|
5
6
|
export type { SignUpFormProps } from './components/SignUpForm.js';
|
|
6
7
|
export type { ForgotPasswordFormProps } from './components/ForgotPasswordForm.js';
|
|
8
|
+
export type { ResetPasswordFormProps } from './components/ResetPasswordForm.js';
|
|
9
|
+
export type { AuthActionResult, SignInInput, SignUpInput, RequestPasswordResetInput, ResetPasswordInput, SignInAction, SignUpAction, RequestPasswordResetAction, ResetPasswordAction, SignInSocialAction, } from './types.js';
|
|
7
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAErE,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAA;AACjF,YAAY,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAA;AAI/E,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,yBAAyB,EACzB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,0BAA0B,EAC1B,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
|
package/dist/ui/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SignInForm } from './components/SignInForm.js';
|
|
2
2
|
export { SignUpForm } from './components/SignUpForm.js';
|
|
3
3
|
export { ForgotPasswordForm } from './components/ForgotPasswordForm.js';
|
|
4
|
+
export { ResetPasswordForm } from './components/ResetPasswordForm.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip better-call's `[body.field]` validation prefixes from an auth error
|
|
3
|
+
* message so forms can display something user-friendly.
|
|
4
|
+
*
|
|
5
|
+
* Better-auth surfaces validation errors like `[body.password] Password is too
|
|
6
|
+
* short`. This removes those bracketed prefixes and normalises whitespace,
|
|
7
|
+
* falling back to a default when the message is empty or missing.
|
|
8
|
+
*
|
|
9
|
+
* Internal to the auth forms — not part of the package's public contract.
|
|
10
|
+
*/
|
|
11
|
+
export declare function cleanAuthErrorMessage(message: string | null | undefined, fallback?: string): string;
|
|
12
|
+
//# sourceMappingURL=clean-error-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean-error-message.d.ts","sourceRoot":"","sources":["../../../src/ui/lib/clean-error-message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,QAAQ,SAAyB,GAChC,MAAM,CAOR"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip better-call's `[body.field]` validation prefixes from an auth error
|
|
3
|
+
* message so forms can display something user-friendly.
|
|
4
|
+
*
|
|
5
|
+
* Better-auth surfaces validation errors like `[body.password] Password is too
|
|
6
|
+
* short`. This removes those bracketed prefixes and normalises whitespace,
|
|
7
|
+
* falling back to a default when the message is empty or missing.
|
|
8
|
+
*
|
|
9
|
+
* Internal to the auth forms — not part of the package's public contract.
|
|
10
|
+
*/
|
|
11
|
+
export function cleanAuthErrorMessage(message, fallback = 'Something went wrong') {
|
|
12
|
+
if (!message)
|
|
13
|
+
return fallback;
|
|
14
|
+
const cleaned = message
|
|
15
|
+
.replace(/\[body\.\w+\]\s*/g, '')
|
|
16
|
+
.replace(/\s+/g, ' ')
|
|
17
|
+
.trim();
|
|
18
|
+
return cleaned || fallback;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=clean-error-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean-error-message.js","sourceRoot":"","sources":["../../../src/ui/lib/clean-error-message.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAkC,EAClC,QAAQ,GAAG,sBAAsB;IAEjC,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,OAAO,GAAG,OAAO;SACpB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAA;IACT,OAAO,OAAO,IAAI,QAAQ,CAAA;AAC5B,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract types shared between the pre-built auth forms and the app-owned
|
|
3
|
+
* server actions they invoke.
|
|
4
|
+
*
|
|
5
|
+
* The forms live in this package but never own an `auth` instance. Instead each
|
|
6
|
+
* form receives **Auth actions** as props — `'use server'` functions the app
|
|
7
|
+
* defines against its own better-auth instance. These types are the agreement
|
|
8
|
+
* between the two: the app implements actions matching them, the forms call
|
|
9
|
+
* actions matching them.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The result an email/password (non-redirecting) auth action returns.
|
|
13
|
+
* Success carries no payload; failure carries a display-ready message.
|
|
14
|
+
*/
|
|
15
|
+
export type AuthActionResult = {
|
|
16
|
+
success: true;
|
|
17
|
+
} | {
|
|
18
|
+
success: false;
|
|
19
|
+
error: string;
|
|
20
|
+
};
|
|
21
|
+
/** Input to the sign-in action. */
|
|
22
|
+
export type SignInInput = {
|
|
23
|
+
email: string;
|
|
24
|
+
password: string;
|
|
25
|
+
};
|
|
26
|
+
/** Input to the sign-up action. */
|
|
27
|
+
export type SignUpInput = {
|
|
28
|
+
name: string;
|
|
29
|
+
email: string;
|
|
30
|
+
password: string;
|
|
31
|
+
};
|
|
32
|
+
/** Input to the request-password-reset action. */
|
|
33
|
+
export type RequestPasswordResetInput = {
|
|
34
|
+
email: string;
|
|
35
|
+
};
|
|
36
|
+
/** Input to the reset-password action. */
|
|
37
|
+
export type ResetPasswordInput = {
|
|
38
|
+
token: string;
|
|
39
|
+
password: string;
|
|
40
|
+
};
|
|
41
|
+
/** Signs a user in with email + password. */
|
|
42
|
+
export type SignInAction = (input: SignInInput) => Promise<AuthActionResult>;
|
|
43
|
+
/** Creates an account with email + password. */
|
|
44
|
+
export type SignUpAction = (input: SignUpInput) => Promise<AuthActionResult>;
|
|
45
|
+
/** Requests a password-reset email. */
|
|
46
|
+
export type RequestPasswordResetAction = (input: RequestPasswordResetInput) => Promise<AuthActionResult>;
|
|
47
|
+
/** Completes a password reset using a token from the reset email. */
|
|
48
|
+
export type ResetPasswordAction = (input: ResetPasswordInput) => Promise<AuthActionResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Starts an OAuth sign-in for the given provider. Unlike the email actions this
|
|
51
|
+
* one navigates away (it performs a server-side redirect to the provider), so
|
|
52
|
+
* it resolves to `void` and never returns a result to the form.
|
|
53
|
+
*/
|
|
54
|
+
export type SignInSocialAction = (provider: string) => Promise<void>;
|
|
55
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ui/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpF,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAE7D,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3E,kDAAkD;AAClD,MAAM,MAAM,yBAAyB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzD,0CAA0C;AAC1C,MAAM,MAAM,kBAAkB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpE,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE5E,gDAAgD;AAChD,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE5E,uCAAuC;AACvC,MAAM,MAAM,0BAA0B,GAAG,CACvC,KAAK,EAAE,yBAAyB,KAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE9B,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE1F;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA"}
|
package/dist/ui/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract types shared between the pre-built auth forms and the app-owned
|
|
3
|
+
* server actions they invoke.
|
|
4
|
+
*
|
|
5
|
+
* The forms live in this package but never own an `auth` instance. Instead each
|
|
6
|
+
* form receives **Auth actions** as props — `'use server'` functions the app
|
|
7
|
+
* defines against its own better-auth instance. These types are the agreement
|
|
8
|
+
* between the two: the app implements actions matching them, the forms call
|
|
9
|
+
* actions matching them.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/ui/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opensaas/stack-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Better-auth integration for OpenSaas Stack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"react": "^19.2.4",
|
|
66
66
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
67
67
|
"vitest": "^4.1.10",
|
|
68
|
-
"@opensaas/stack-cli": "0.
|
|
69
|
-
"@opensaas/stack-core": "0.
|
|
68
|
+
"@opensaas/stack-cli": "0.33.0",
|
|
69
|
+
"@opensaas/stack-core": "0.33.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsc",
|
package/src/server/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { betterAuth } from 'better-auth'
|
|
2
2
|
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
3
|
+
import { nextCookies } from 'better-auth/next-js'
|
|
3
4
|
import type { BetterAuthOptions } from 'better-auth'
|
|
4
5
|
import type { OpenSaasConfig, AccessContext } from '@opensaas/stack-core'
|
|
5
6
|
import type { DatabaseConfig } from '@opensaas/stack-core/internal'
|
|
@@ -130,8 +131,13 @@ export function createAuth(
|
|
|
130
131
|
}
|
|
131
132
|
: undefined,
|
|
132
133
|
|
|
133
|
-
// Pass through any additional Better Auth plugins
|
|
134
|
-
|
|
134
|
+
// Pass through any additional Better Auth plugins, then append
|
|
135
|
+
// nextCookies LAST so it can write the Set-Cookie headers produced by
|
|
136
|
+
// any auth.api.* call made inside a Next.js server action into Next's
|
|
137
|
+
// cookie store. This is what makes the server-action auth forms (which
|
|
138
|
+
// call auth.api.signInEmail/signUpEmail/etc. server-side) actually
|
|
139
|
+
// persist a session. It must be the final plugin in the array.
|
|
140
|
+
plugins: [...(authConfig.betterAuthPlugins || []), nextCookies()],
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
authInstance = betterAuth(betterAuthConfig)
|