@rudderjs/auth 0.2.0 → 0.2.1
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 +63 -21
- package/boost/skills/auth-setup/SKILL.md +273 -0
- package/dist/auth-manager.d.ts +31 -1
- package/dist/auth-manager.d.ts.map +1 -1
- package/dist/auth-manager.js +53 -12
- package/dist/auth-manager.js.map +1 -1
- package/dist/gate-observers.d.ts +37 -0
- package/dist/gate-observers.d.ts.map +1 -0
- package/dist/gate-observers.js +38 -0
- package/dist/gate-observers.js.map +1 -0
- package/dist/gate.d.ts +3 -0
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +72 -15
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +13 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -39
- package/dist/index.js.map +1 -1
- package/dist/require-guest.d.ts +10 -0
- package/dist/require-guest.d.ts.map +1 -0
- package/dist/require-guest.js +25 -0
- package/dist/require-guest.js.map +1 -0
- package/dist/routes.d.ts +43 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +43 -0
- package/dist/routes.js.map +1 -0
- package/package.json +38 -8
- package/{pages/react/forgot-password/+Page.tsx → views/react/ForgotPassword.tsx} +17 -4
- package/{pages/react/login/+Page.tsx → views/react/Login.tsx} +25 -7
- package/{pages/react/register/+Page.tsx → views/react/Register.tsx} +17 -4
- package/{pages/react/reset-password/+Page.tsx → views/react/ResetPassword.tsx} +20 -5
- package/pages/react/login/+guard.ts +0 -15
- package/pages/react/register/+guard.ts +0 -15
- package/pages/solid/forgot-password/+Page.tsx +0 -62
- package/pages/solid/login/+Page.tsx +0 -66
- package/pages/solid/login/+guard.ts +0 -15
- package/pages/solid/register/+Page.tsx +0 -72
- package/pages/solid/register/+guard.ts +0 -15
- package/pages/solid/reset-password/+Page.tsx +0 -94
- package/pages/vue/forgot-password/+Page.vue +0 -60
- package/pages/vue/login/+Page.vue +0 -63
- package/pages/vue/login/+guard.ts +0 -15
- package/pages/vue/register/+Page.vue +0 -68
- package/pages/vue/register/+guard.ts +0 -15
- package/pages/vue/reset-password/+Page.vue +0 -93
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import '@/index.css'
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// URL this view is served at — see Login.tsx for rationale.
|
|
5
|
+
export const route = '/forgot-password'
|
|
6
|
+
|
|
7
|
+
export interface ForgotPasswordProps {
|
|
8
|
+
submitUrl?: string
|
|
9
|
+
loginUrl?: string
|
|
10
|
+
resetPasswordUrl?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function ForgotPassword(props: ForgotPasswordProps) {
|
|
14
|
+
const submitUrl = props.submitUrl ?? '/api/auth/request-password-reset'
|
|
15
|
+
const loginUrl = props.loginUrl ?? '/login'
|
|
16
|
+
const resetPasswordUrl = props.resetPasswordUrl ?? '/reset-password'
|
|
17
|
+
|
|
5
18
|
const [email, setEmail] = useState('')
|
|
6
19
|
const [error, setError] = useState('')
|
|
7
20
|
const [success, setSuccess] = useState('')
|
|
@@ -13,10 +26,10 @@ export default function ForgotPasswordPage() {
|
|
|
13
26
|
setSuccess('')
|
|
14
27
|
setLoading(true)
|
|
15
28
|
try {
|
|
16
|
-
const res = await fetch(
|
|
29
|
+
const res = await fetch(submitUrl, {
|
|
17
30
|
method: 'POST',
|
|
18
31
|
headers: { 'Content-Type': 'application/json' },
|
|
19
|
-
body: JSON.stringify({ email, redirectTo:
|
|
32
|
+
body: JSON.stringify({ email, redirectTo: resetPasswordUrl }),
|
|
20
33
|
})
|
|
21
34
|
if (res.ok) {
|
|
22
35
|
setSuccess('If an account exists with that email, a password reset link has been sent.')
|
|
@@ -55,7 +68,7 @@ export default function ForgotPasswordPage() {
|
|
|
55
68
|
</button>
|
|
56
69
|
<p className="text-center text-sm text-gray-500">
|
|
57
70
|
Remember your password?{' '}
|
|
58
|
-
<a href=
|
|
71
|
+
<a href={loginUrl} className="underline hover:text-black">Sign in</a>
|
|
59
72
|
</p>
|
|
60
73
|
</form>
|
|
61
74
|
</div>
|
|
@@ -2,7 +2,25 @@ import '@/index.css'
|
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { navigate } from 'vike/client/router'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// URL this view is served at — MUST match the controller route registered
|
|
6
|
+
// by registerAuthRoutes() in the consumer project. If you override
|
|
7
|
+
// `opts.paths.login` when calling registerAuthRoutes, update this too so
|
|
8
|
+
// Vike's client router can SPA-navigate here instead of full-reloading.
|
|
9
|
+
export const route = '/login'
|
|
10
|
+
|
|
11
|
+
export interface LoginProps {
|
|
12
|
+
submitUrl?: string
|
|
13
|
+
registerUrl?: string
|
|
14
|
+
forgotPasswordUrl?: string
|
|
15
|
+
homeUrl?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function Login(props: LoginProps) {
|
|
19
|
+
const submitUrl = props.submitUrl ?? '/api/auth/sign-in/email'
|
|
20
|
+
const registerUrl = props.registerUrl ?? '/register'
|
|
21
|
+
const forgotPasswordUrl = props.forgotPasswordUrl ?? '/forgot-password'
|
|
22
|
+
const homeUrl = props.homeUrl ?? '/'
|
|
23
|
+
|
|
6
24
|
const [email, setEmail] = useState('')
|
|
7
25
|
const [password, setPassword] = useState('')
|
|
8
26
|
const [error, setError] = useState('')
|
|
@@ -12,7 +30,7 @@ export default function LoginPage() {
|
|
|
12
30
|
e.preventDefault()
|
|
13
31
|
setError('')
|
|
14
32
|
setLoading(true)
|
|
15
|
-
const res = await fetch(
|
|
33
|
+
const res = await fetch(submitUrl, {
|
|
16
34
|
method: 'POST',
|
|
17
35
|
headers: { 'Content-Type': 'application/json' },
|
|
18
36
|
body: JSON.stringify({ email, password }),
|
|
@@ -20,7 +38,7 @@ export default function LoginPage() {
|
|
|
20
38
|
if (res.ok) {
|
|
21
39
|
const params = new URLSearchParams(window.location.search)
|
|
22
40
|
const redirect = params.get('redirect')
|
|
23
|
-
await navigate(redirect && redirect.startsWith('/') ? redirect :
|
|
41
|
+
await navigate(redirect && redirect.startsWith('/') ? redirect : homeUrl)
|
|
24
42
|
} else {
|
|
25
43
|
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
26
44
|
setError(body.message ?? 'Invalid email or password.')
|
|
@@ -59,10 +77,10 @@ export default function LoginPage() {
|
|
|
59
77
|
className="w-full rounded-md bg-black px-4 py-2 text-sm font-medium text-white hover:bg-black/90 disabled:opacity-50">
|
|
60
78
|
{loading ? 'Signing in…' : 'Sign in'}
|
|
61
79
|
</button>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
<a href=
|
|
65
|
-
</
|
|
80
|
+
<div className="flex items-center justify-between text-sm text-gray-500">
|
|
81
|
+
<a href={forgotPasswordUrl} className="underline hover:text-black">Forgot password?</a>
|
|
82
|
+
<a href={registerUrl} className="underline hover:text-black">Register</a>
|
|
83
|
+
</div>
|
|
66
84
|
</form>
|
|
67
85
|
</div>
|
|
68
86
|
</div>
|
|
@@ -2,7 +2,20 @@ import '@/index.css'
|
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { navigate } from 'vike/client/router'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// URL this view is served at — see Login.tsx for rationale.
|
|
6
|
+
export const route = '/register'
|
|
7
|
+
|
|
8
|
+
export interface RegisterProps {
|
|
9
|
+
submitUrl?: string
|
|
10
|
+
loginUrl?: string
|
|
11
|
+
homeUrl?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function Register(props: RegisterProps) {
|
|
15
|
+
const submitUrl = props.submitUrl ?? '/api/auth/sign-up/email'
|
|
16
|
+
const loginUrl = props.loginUrl ?? '/login'
|
|
17
|
+
const homeUrl = props.homeUrl ?? '/'
|
|
18
|
+
|
|
6
19
|
const [name, setName] = useState('')
|
|
7
20
|
const [email, setEmail] = useState('')
|
|
8
21
|
const [password, setPassword] = useState('')
|
|
@@ -13,13 +26,13 @@ export default function RegisterPage() {
|
|
|
13
26
|
e.preventDefault()
|
|
14
27
|
setError('')
|
|
15
28
|
setLoading(true)
|
|
16
|
-
const res = await fetch(
|
|
29
|
+
const res = await fetch(submitUrl, {
|
|
17
30
|
method: 'POST',
|
|
18
31
|
headers: { 'Content-Type': 'application/json' },
|
|
19
32
|
body: JSON.stringify({ name, email, password }),
|
|
20
33
|
})
|
|
21
34
|
if (res.ok) {
|
|
22
|
-
await navigate(
|
|
35
|
+
await navigate(homeUrl)
|
|
23
36
|
} else {
|
|
24
37
|
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
25
38
|
setError(body.message ?? 'Could not create account. Please try again.')
|
|
@@ -69,7 +82,7 @@ export default function RegisterPage() {
|
|
|
69
82
|
</button>
|
|
70
83
|
<p className="text-center text-sm text-gray-500">
|
|
71
84
|
Already have an account?{' '}
|
|
72
|
-
<a href=
|
|
85
|
+
<a href={loginUrl} className="underline hover:text-black">Sign in</a>
|
|
73
86
|
</p>
|
|
74
87
|
</form>
|
|
75
88
|
</div>
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
import '@/index.css'
|
|
2
2
|
import { useState, useEffect } from 'react'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// URL this view is served at — see Login.tsx for rationale.
|
|
5
|
+
export const route = '/reset-password'
|
|
6
|
+
|
|
7
|
+
export interface ResetPasswordProps {
|
|
8
|
+
submitUrl?: string
|
|
9
|
+
loginUrl?: string
|
|
10
|
+
forgotPasswordUrl?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function ResetPassword(props: ResetPasswordProps) {
|
|
14
|
+
const submitUrl = props.submitUrl ?? '/api/auth/reset-password'
|
|
15
|
+
const loginUrl = props.loginUrl ?? '/login'
|
|
16
|
+
const forgotPasswordUrl = props.forgotPasswordUrl ?? '/forgot-password'
|
|
17
|
+
|
|
5
18
|
const [password, setPassword] = useState('')
|
|
6
19
|
const [confirmPassword, setConfirm] = useState('')
|
|
7
20
|
const [error, setError] = useState('')
|
|
8
21
|
const [success, setSuccess] = useState('')
|
|
9
22
|
const [loading, setLoading] = useState(false)
|
|
10
23
|
const [token, setToken] = useState<string | null>(null)
|
|
24
|
+
const [email, setEmail] = useState<string | null>(null)
|
|
11
25
|
const [mounted, setMounted] = useState(false)
|
|
12
26
|
|
|
13
27
|
useEffect(() => {
|
|
14
28
|
const params = new URLSearchParams(window.location.search)
|
|
15
29
|
setToken(params.get('token'))
|
|
30
|
+
setEmail(params.get('email'))
|
|
16
31
|
setMounted(true)
|
|
17
32
|
}, [])
|
|
18
33
|
|
|
@@ -28,10 +43,10 @@ export default function ResetPasswordPage() {
|
|
|
28
43
|
|
|
29
44
|
setLoading(true)
|
|
30
45
|
try {
|
|
31
|
-
const res = await fetch(
|
|
46
|
+
const res = await fetch(submitUrl, {
|
|
32
47
|
method: 'POST',
|
|
33
48
|
headers: { 'Content-Type': 'application/json' },
|
|
34
|
-
body: JSON.stringify({ token, newPassword: password }),
|
|
49
|
+
body: JSON.stringify({ token, email, newPassword: password }),
|
|
35
50
|
})
|
|
36
51
|
if (res.ok) {
|
|
37
52
|
setSuccess('Your password has been reset successfully.')
|
|
@@ -60,7 +75,7 @@ export default function ResetPasswordPage() {
|
|
|
60
75
|
<div className="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
61
76
|
<p className="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">Missing reset token.</p>
|
|
62
77
|
<p className="text-center text-sm text-gray-500">
|
|
63
|
-
<a href=
|
|
78
|
+
<a href={forgotPasswordUrl} className="underline hover:text-black">Request a new reset link</a>
|
|
64
79
|
</p>
|
|
65
80
|
</div>
|
|
66
81
|
</div>
|
|
@@ -81,7 +96,7 @@ export default function ResetPasswordPage() {
|
|
|
81
96
|
<div className="space-y-2">
|
|
82
97
|
<p className="rounded-md bg-green-50 px-3 py-2 text-sm text-green-600">{success}</p>
|
|
83
98
|
<p className="text-center text-sm text-gray-500">
|
|
84
|
-
<a href=
|
|
99
|
+
<a href={loginUrl} className="underline hover:text-black">Sign in</a>
|
|
85
100
|
</p>
|
|
86
101
|
</div>
|
|
87
102
|
)}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { redirect } from 'vike/abort'
|
|
2
|
-
import type { GuardAsync } from 'vike/types'
|
|
3
|
-
import type { BetterAuthInstance } from '@rudderjs/auth'
|
|
4
|
-
|
|
5
|
-
export const guard: GuardAsync = async (pageContext): ReturnType<GuardAsync> => {
|
|
6
|
-
// import.meta.env.SSR is a Vite compile-time constant — tree-shaken from client bundle
|
|
7
|
-
if (!import.meta.env.SSR) return
|
|
8
|
-
const { app } = await import('@rudderjs/core')
|
|
9
|
-
const auth = app().make<BetterAuthInstance>('auth')
|
|
10
|
-
const session = await auth.api.getSession({
|
|
11
|
-
headers: new Headers(pageContext.headers ?? {}),
|
|
12
|
-
})
|
|
13
|
-
// Already logged in — redirect to home
|
|
14
|
-
if (session?.user) throw redirect('/')
|
|
15
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { redirect } from 'vike/abort'
|
|
2
|
-
import type { GuardAsync } from 'vike/types'
|
|
3
|
-
import type { BetterAuthInstance } from '@rudderjs/auth'
|
|
4
|
-
|
|
5
|
-
export const guard: GuardAsync = async (pageContext): ReturnType<GuardAsync> => {
|
|
6
|
-
// import.meta.env.SSR is a Vite compile-time constant — tree-shaken from client bundle
|
|
7
|
-
if (!import.meta.env.SSR) return
|
|
8
|
-
const { app } = await import('@rudderjs/core')
|
|
9
|
-
const auth = app().make<BetterAuthInstance>('auth')
|
|
10
|
-
const session = await auth.api.getSession({
|
|
11
|
-
headers: new Headers(pageContext.headers ?? {}),
|
|
12
|
-
})
|
|
13
|
-
// Already registered and logged in — redirect to home
|
|
14
|
-
if (session?.user) throw redirect('/')
|
|
15
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import '@/index.css'
|
|
2
|
-
import { createSignal } from 'solid-js'
|
|
3
|
-
|
|
4
|
-
export default function ForgotPasswordPage() {
|
|
5
|
-
const [email, setEmail] = createSignal('')
|
|
6
|
-
const [error, setError] = createSignal('')
|
|
7
|
-
const [success, setSuccess] = createSignal('')
|
|
8
|
-
const [loading, setLoading] = createSignal(false)
|
|
9
|
-
|
|
10
|
-
async function handleSubmit(e: Event) {
|
|
11
|
-
e.preventDefault()
|
|
12
|
-
setError('')
|
|
13
|
-
setSuccess('')
|
|
14
|
-
setLoading(true)
|
|
15
|
-
try {
|
|
16
|
-
const res = await fetch('/api/auth/request-password-reset', {
|
|
17
|
-
method: 'POST',
|
|
18
|
-
headers: { 'Content-Type': 'application/json' },
|
|
19
|
-
body: JSON.stringify({ email: email(), redirectTo: '/reset-password' }),
|
|
20
|
-
})
|
|
21
|
-
if (res.ok) {
|
|
22
|
-
setSuccess('If an account exists with that email, a password reset link has been sent.')
|
|
23
|
-
} else {
|
|
24
|
-
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
25
|
-
setError(body.message ?? 'Something went wrong. Please try again.')
|
|
26
|
-
}
|
|
27
|
-
} catch {
|
|
28
|
-
setError('Something went wrong. Please try again.')
|
|
29
|
-
}
|
|
30
|
-
setLoading(false)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div class="flex min-h-svh items-center justify-center p-4">
|
|
35
|
-
<div class="w-full max-w-sm space-y-6">
|
|
36
|
-
<div class="text-center">
|
|
37
|
-
<h1 class="text-2xl font-bold">Forgot password</h1>
|
|
38
|
-
<p class="text-sm text-gray-500 mt-1">Enter your email to receive a reset link</p>
|
|
39
|
-
</div>
|
|
40
|
-
<form onSubmit={handleSubmit} class="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
41
|
-
{error() && <p class="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{error()}</p>}
|
|
42
|
-
{success() && <p class="rounded-md bg-green-50 px-3 py-2 text-sm text-green-600">{success()}</p>}
|
|
43
|
-
<div>
|
|
44
|
-
<label class="block text-sm font-medium mb-1" for="email">Email</label>
|
|
45
|
-
<input id="email" type="email" placeholder="you@example.com"
|
|
46
|
-
value={email()} onInput={e => setEmail(e.currentTarget.value)}
|
|
47
|
-
required autocomplete="email"
|
|
48
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
49
|
-
</div>
|
|
50
|
-
<button type="submit" disabled={loading()}
|
|
51
|
-
class="w-full rounded-md bg-black px-4 py-2 text-sm font-medium text-white hover:bg-black/90 disabled:opacity-50">
|
|
52
|
-
{loading() ? 'Sending...' : 'Send reset link'}
|
|
53
|
-
</button>
|
|
54
|
-
<p class="text-center text-sm text-gray-500">
|
|
55
|
-
Remember your password?{' '}
|
|
56
|
-
<a href="/login" class="underline hover:text-black">Sign in</a>
|
|
57
|
-
</p>
|
|
58
|
-
</form>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import '@/index.css'
|
|
2
|
-
import { createSignal } from 'solid-js'
|
|
3
|
-
import { navigate } from 'vike/client/router'
|
|
4
|
-
|
|
5
|
-
export default function LoginPage() {
|
|
6
|
-
const [email, setEmail] = createSignal('')
|
|
7
|
-
const [password, setPassword] = createSignal('')
|
|
8
|
-
const [error, setError] = createSignal('')
|
|
9
|
-
const [loading, setLoading] = createSignal(false)
|
|
10
|
-
|
|
11
|
-
async function handleSubmit(e: Event) {
|
|
12
|
-
e.preventDefault()
|
|
13
|
-
setError('')
|
|
14
|
-
setLoading(true)
|
|
15
|
-
const res = await fetch('/api/auth/sign-in/email', {
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: { 'Content-Type': 'application/json' },
|
|
18
|
-
body: JSON.stringify({ email: email(), password: password() }),
|
|
19
|
-
})
|
|
20
|
-
if (res.ok) {
|
|
21
|
-
const params = new URLSearchParams(window.location.search)
|
|
22
|
-
const redirect = params.get('redirect')
|
|
23
|
-
await navigate(redirect && redirect.startsWith('/') ? redirect : '/')
|
|
24
|
-
} else {
|
|
25
|
-
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
26
|
-
setError(body.message ?? 'Invalid email or password.')
|
|
27
|
-
}
|
|
28
|
-
setLoading(false)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div class="flex min-h-svh items-center justify-center p-4">
|
|
33
|
-
<div class="w-full max-w-sm space-y-6">
|
|
34
|
-
<div class="text-center">
|
|
35
|
-
<h1 class="text-2xl font-bold">Welcome back</h1>
|
|
36
|
-
<p class="text-sm text-gray-500 mt-1">Sign in to your account</p>
|
|
37
|
-
</div>
|
|
38
|
-
<form onSubmit={handleSubmit} class="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
39
|
-
{error() && <p class="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{error()}</p>}
|
|
40
|
-
<div>
|
|
41
|
-
<label class="block text-sm font-medium mb-1" for="email">Email</label>
|
|
42
|
-
<input id="email" type="email" placeholder="you@example.com"
|
|
43
|
-
value={email()} onInput={e => setEmail(e.currentTarget.value)}
|
|
44
|
-
required autocomplete="email"
|
|
45
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
46
|
-
</div>
|
|
47
|
-
<div>
|
|
48
|
-
<label class="block text-sm font-medium mb-1" for="password">Password</label>
|
|
49
|
-
<input id="password" type="password" placeholder="••••••••"
|
|
50
|
-
value={password()} onInput={e => setPassword(e.currentTarget.value)}
|
|
51
|
-
required autocomplete="current-password"
|
|
52
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
53
|
-
</div>
|
|
54
|
-
<button type="submit" disabled={loading()}
|
|
55
|
-
class="w-full rounded-md bg-black px-4 py-2 text-sm font-medium text-white hover:bg-black/90 disabled:opacity-50">
|
|
56
|
-
{loading() ? 'Signing in…' : 'Sign in'}
|
|
57
|
-
</button>
|
|
58
|
-
<p class="text-center text-sm text-gray-500">
|
|
59
|
-
Don't have an account?{' '}
|
|
60
|
-
<a href="/register" class="underline hover:text-black">Register</a>
|
|
61
|
-
</p>
|
|
62
|
-
</form>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { redirect } from 'vike/abort'
|
|
2
|
-
import type { GuardAsync } from 'vike/types'
|
|
3
|
-
import type { BetterAuthInstance } from '@rudderjs/auth'
|
|
4
|
-
|
|
5
|
-
export const guard: GuardAsync = async (pageContext): ReturnType<GuardAsync> => {
|
|
6
|
-
// import.meta.env.SSR is a Vite compile-time constant — tree-shaken from client bundle
|
|
7
|
-
if (!import.meta.env.SSR) return
|
|
8
|
-
const { app } = await import('@rudderjs/core')
|
|
9
|
-
const auth = app().make<BetterAuthInstance>('auth')
|
|
10
|
-
const session = await auth.api.getSession({
|
|
11
|
-
headers: new Headers(pageContext.headers ?? {}),
|
|
12
|
-
})
|
|
13
|
-
// Already logged in — redirect to home
|
|
14
|
-
if (session?.user) throw redirect('/')
|
|
15
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import '@/index.css'
|
|
2
|
-
import { createSignal } from 'solid-js'
|
|
3
|
-
import { navigate } from 'vike/client/router'
|
|
4
|
-
|
|
5
|
-
export default function RegisterPage() {
|
|
6
|
-
const [name, setName] = createSignal('')
|
|
7
|
-
const [email, setEmail] = createSignal('')
|
|
8
|
-
const [password, setPassword] = createSignal('')
|
|
9
|
-
const [error, setError] = createSignal('')
|
|
10
|
-
const [loading, setLoading] = createSignal(false)
|
|
11
|
-
|
|
12
|
-
async function handleSubmit(e: Event) {
|
|
13
|
-
e.preventDefault()
|
|
14
|
-
setError('')
|
|
15
|
-
setLoading(true)
|
|
16
|
-
const res = await fetch('/api/auth/sign-up/email', {
|
|
17
|
-
method: 'POST',
|
|
18
|
-
headers: { 'Content-Type': 'application/json' },
|
|
19
|
-
body: JSON.stringify({ name: name(), email: email(), password: password() }),
|
|
20
|
-
})
|
|
21
|
-
if (res.ok) {
|
|
22
|
-
await navigate('/')
|
|
23
|
-
} else {
|
|
24
|
-
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
25
|
-
setError(body.message ?? 'Could not create account. Please try again.')
|
|
26
|
-
}
|
|
27
|
-
setLoading(false)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div class="flex min-h-svh items-center justify-center p-4">
|
|
32
|
-
<div class="w-full max-w-sm space-y-6">
|
|
33
|
-
<div class="text-center">
|
|
34
|
-
<h1 class="text-2xl font-bold">Create an account</h1>
|
|
35
|
-
<p class="text-sm text-gray-500 mt-1">Get started in seconds</p>
|
|
36
|
-
</div>
|
|
37
|
-
<form onSubmit={handleSubmit} class="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
38
|
-
{error() && <p class="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{error()}</p>}
|
|
39
|
-
<div>
|
|
40
|
-
<label class="block text-sm font-medium mb-1" for="name">Name</label>
|
|
41
|
-
<input id="name" type="text" placeholder="Alice Smith"
|
|
42
|
-
value={name()} onInput={e => setName(e.currentTarget.value)}
|
|
43
|
-
required autocomplete="name"
|
|
44
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
45
|
-
</div>
|
|
46
|
-
<div>
|
|
47
|
-
<label class="block text-sm font-medium mb-1" for="email">Email</label>
|
|
48
|
-
<input id="email" type="email" placeholder="you@example.com"
|
|
49
|
-
value={email()} onInput={e => setEmail(e.currentTarget.value)}
|
|
50
|
-
required autocomplete="email"
|
|
51
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
52
|
-
</div>
|
|
53
|
-
<div>
|
|
54
|
-
<label class="block text-sm font-medium mb-1" for="password">Password</label>
|
|
55
|
-
<input id="password" type="password" placeholder="••••••••"
|
|
56
|
-
value={password()} onInput={e => setPassword(e.currentTarget.value)}
|
|
57
|
-
required autocomplete="new-password" minLength={8}
|
|
58
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
59
|
-
</div>
|
|
60
|
-
<button type="submit" disabled={loading()}
|
|
61
|
-
class="w-full rounded-md bg-black px-4 py-2 text-sm font-medium text-white hover:bg-black/90 disabled:opacity-50">
|
|
62
|
-
{loading() ? 'Creating account…' : 'Create account'}
|
|
63
|
-
</button>
|
|
64
|
-
<p class="text-center text-sm text-gray-500">
|
|
65
|
-
Already have an account?{' '}
|
|
66
|
-
<a href="/login" class="underline hover:text-black">Sign in</a>
|
|
67
|
-
</p>
|
|
68
|
-
</form>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { redirect } from 'vike/abort'
|
|
2
|
-
import type { GuardAsync } from 'vike/types'
|
|
3
|
-
import type { BetterAuthInstance } from '@rudderjs/auth'
|
|
4
|
-
|
|
5
|
-
export const guard: GuardAsync = async (pageContext): ReturnType<GuardAsync> => {
|
|
6
|
-
// import.meta.env.SSR is a Vite compile-time constant — tree-shaken from client bundle
|
|
7
|
-
if (!import.meta.env.SSR) return
|
|
8
|
-
const { app } = await import('@rudderjs/core')
|
|
9
|
-
const auth = app().make<BetterAuthInstance>('auth')
|
|
10
|
-
const session = await auth.api.getSession({
|
|
11
|
-
headers: new Headers(pageContext.headers ?? {}),
|
|
12
|
-
})
|
|
13
|
-
// Already registered and logged in — redirect to home
|
|
14
|
-
if (session?.user) throw redirect('/')
|
|
15
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import '@/index.css'
|
|
2
|
-
import { createSignal, Show } from 'solid-js'
|
|
3
|
-
|
|
4
|
-
export default function ResetPasswordPage() {
|
|
5
|
-
const [password, setPassword] = createSignal('')
|
|
6
|
-
const [confirmPassword, setConfirm] = createSignal('')
|
|
7
|
-
const [error, setError] = createSignal('')
|
|
8
|
-
const [success, setSuccess] = createSignal('')
|
|
9
|
-
const [loading, setLoading] = createSignal(false)
|
|
10
|
-
|
|
11
|
-
const params = new URLSearchParams(window.location.search)
|
|
12
|
-
const token = params.get('token')
|
|
13
|
-
|
|
14
|
-
async function handleSubmit(e: Event) {
|
|
15
|
-
e.preventDefault()
|
|
16
|
-
setError('')
|
|
17
|
-
setSuccess('')
|
|
18
|
-
|
|
19
|
-
if (password() !== confirmPassword()) {
|
|
20
|
-
setError('Passwords do not match.')
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
setLoading(true)
|
|
25
|
-
try {
|
|
26
|
-
const res = await fetch('/api/auth/reset-password', {
|
|
27
|
-
method: 'POST',
|
|
28
|
-
headers: { 'Content-Type': 'application/json' },
|
|
29
|
-
body: JSON.stringify({ token, newPassword: password() }),
|
|
30
|
-
})
|
|
31
|
-
if (res.ok) {
|
|
32
|
-
setSuccess('Your password has been reset successfully.')
|
|
33
|
-
} else {
|
|
34
|
-
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
35
|
-
setError(body.message ?? 'Invalid or expired token.')
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
setError('Something went wrong. Please try again.')
|
|
39
|
-
}
|
|
40
|
-
setLoading(false)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<div class="flex min-h-svh items-center justify-center p-4">
|
|
45
|
-
<div class="w-full max-w-sm space-y-6">
|
|
46
|
-
<Show when={token} fallback={
|
|
47
|
-
<div class="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
48
|
-
<p class="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">Missing reset token.</p>
|
|
49
|
-
<p class="text-center text-sm text-gray-500">
|
|
50
|
-
<a href="/forgot-password" class="underline hover:text-black">Request a new reset link</a>
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
53
|
-
}>
|
|
54
|
-
<div class="text-center">
|
|
55
|
-
<h1 class="text-2xl font-bold">Reset password</h1>
|
|
56
|
-
<p class="text-sm text-gray-500 mt-1">Enter your new password</p>
|
|
57
|
-
</div>
|
|
58
|
-
<form onSubmit={handleSubmit} class="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
59
|
-
{error() && <p class="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{error()}</p>}
|
|
60
|
-
<Show when={success()} fallback={
|
|
61
|
-
<>
|
|
62
|
-
<div>
|
|
63
|
-
<label class="block text-sm font-medium mb-1" for="password">New password</label>
|
|
64
|
-
<input id="password" type="password" placeholder="••••••••"
|
|
65
|
-
value={password()} onInput={e => setPassword(e.currentTarget.value)}
|
|
66
|
-
required minLength={8} autocomplete="new-password"
|
|
67
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
68
|
-
</div>
|
|
69
|
-
<div>
|
|
70
|
-
<label class="block text-sm font-medium mb-1" for="confirm-password">Confirm password</label>
|
|
71
|
-
<input id="confirm-password" type="password" placeholder="••••••••"
|
|
72
|
-
value={confirmPassword()} onInput={e => setConfirm(e.currentTarget.value)}
|
|
73
|
-
required minLength={8} autocomplete="new-password"
|
|
74
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
75
|
-
</div>
|
|
76
|
-
<button type="submit" disabled={loading()}
|
|
77
|
-
class="w-full rounded-md bg-black px-4 py-2 text-sm font-medium text-white hover:bg-black/90 disabled:opacity-50">
|
|
78
|
-
{loading() ? 'Resetting...' : 'Reset password'}
|
|
79
|
-
</button>
|
|
80
|
-
</>
|
|
81
|
-
}>
|
|
82
|
-
<div class="space-y-2">
|
|
83
|
-
<p class="rounded-md bg-green-50 px-3 py-2 text-sm text-green-600">{success()}</p>
|
|
84
|
-
<p class="text-center text-sm text-gray-500">
|
|
85
|
-
<a href="/login" class="underline hover:text-black">Sign in</a>
|
|
86
|
-
</p>
|
|
87
|
-
</div>
|
|
88
|
-
</Show>
|
|
89
|
-
</form>
|
|
90
|
-
</Show>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import '@/index.css'
|
|
3
|
-
import { ref } from 'vue'
|
|
4
|
-
|
|
5
|
-
const email = ref('')
|
|
6
|
-
const error = ref('')
|
|
7
|
-
const success = ref('')
|
|
8
|
-
const loading = ref(false)
|
|
9
|
-
|
|
10
|
-
async function handleSubmit() {
|
|
11
|
-
error.value = ''
|
|
12
|
-
success.value = ''
|
|
13
|
-
loading.value = true
|
|
14
|
-
try {
|
|
15
|
-
const res = await fetch('/api/auth/request-password-reset', {
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: { 'Content-Type': 'application/json' },
|
|
18
|
-
body: JSON.stringify({ email: email.value, redirectTo: '/reset-password' }),
|
|
19
|
-
})
|
|
20
|
-
if (res.ok) {
|
|
21
|
-
success.value = 'If an account exists with that email, a password reset link has been sent.'
|
|
22
|
-
} else {
|
|
23
|
-
const body = await res.json().catch(() => ({})) as { message?: string }
|
|
24
|
-
error.value = body.message ?? 'Something went wrong. Please try again.'
|
|
25
|
-
}
|
|
26
|
-
} catch {
|
|
27
|
-
error.value = 'Something went wrong. Please try again.'
|
|
28
|
-
}
|
|
29
|
-
loading.value = false
|
|
30
|
-
}
|
|
31
|
-
</script>
|
|
32
|
-
|
|
33
|
-
<template>
|
|
34
|
-
<div class="flex min-h-svh items-center justify-center p-4">
|
|
35
|
-
<div class="w-full max-w-sm space-y-6">
|
|
36
|
-
<div class="text-center">
|
|
37
|
-
<h1 class="text-2xl font-bold">Forgot password</h1>
|
|
38
|
-
<p class="text-sm text-gray-500 mt-1">Enter your email to receive a reset link</p>
|
|
39
|
-
</div>
|
|
40
|
-
<form @submit.prevent="handleSubmit" class="space-y-4 rounded-lg border p-6 shadow-sm">
|
|
41
|
-
<p v-if="error" class="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{{ error }}</p>
|
|
42
|
-
<p v-if="success" class="rounded-md bg-green-50 px-3 py-2 text-sm text-green-600">{{ success }}</p>
|
|
43
|
-
<div>
|
|
44
|
-
<label class="block text-sm font-medium mb-1" for="email">Email</label>
|
|
45
|
-
<input id="email" v-model="email" type="email" placeholder="you@example.com"
|
|
46
|
-
required autocomplete="email"
|
|
47
|
-
class="w-full rounded-md border px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-black" />
|
|
48
|
-
</div>
|
|
49
|
-
<button type="submit" :disabled="loading"
|
|
50
|
-
class="w-full rounded-md bg-black px-4 py-2 text-sm font-medium text-white hover:bg-black/90 disabled:opacity-50">
|
|
51
|
-
{{ loading ? 'Sending...' : 'Send reset link' }}
|
|
52
|
-
</button>
|
|
53
|
-
<p class="text-center text-sm text-gray-500">
|
|
54
|
-
Remember your password?
|
|
55
|
-
<a href="/login" class="underline hover:text-black">Sign in</a>
|
|
56
|
-
</p>
|
|
57
|
-
</form>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
</template>
|