@plutocms/supabase 0.0.1-alpha.13 → 0.0.1-alpha.15
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/app/assets/css/tailwind.css +2 -0
- package/app/composables/auth.ts +68 -0
- package/app/middleware/auth.ts +15 -9
- package/app/pages/admin/(auth)/forgot-password.vue +88 -0
- package/app/pages/admin/(auth)/login.vue +5 -1
- package/app/pages/admin/(auth)/update-password.vue +120 -0
- package/app/pages/admin/setup.vue +1 -1
- package/bun.lock +435 -652
- package/nuxt.config.ts +7 -1
- package/package.json +6 -5
- package/server/api/auth/reset-password.post.ts +36 -0
package/app/composables/auth.ts
CHANGED
|
@@ -100,6 +100,72 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
100
100
|
if (authOptions?.route && authOptions.route.path.startsWith('/admin')) {
|
|
101
101
|
return navigateTo('/admin/login')
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
return navigateTo('/admin/login')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function requestPasswordReset(email: string) {
|
|
108
|
+
isSubmitting.value = true
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await $fetch('/api/auth/reset-password', {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: { email },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return { success: true }
|
|
117
|
+
} catch (error) {
|
|
118
|
+
toast.add({
|
|
119
|
+
title: 'Could not send reset email',
|
|
120
|
+
description:
|
|
121
|
+
error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
122
|
+
icon: 'lucide:circle-x',
|
|
123
|
+
color: 'error',
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return { success: false }
|
|
127
|
+
} finally {
|
|
128
|
+
isSubmitting.value = false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function updatePassword(password: string) {
|
|
133
|
+
isSubmitting.value = true
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const { error } = await supabase.auth.updateUser({ password })
|
|
137
|
+
|
|
138
|
+
if (error) {
|
|
139
|
+
toast.add({
|
|
140
|
+
title: 'Could not update password',
|
|
141
|
+
description: error.message,
|
|
142
|
+
icon: 'lucide:circle-x',
|
|
143
|
+
color: 'error',
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return { success: false }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
toast.add({
|
|
150
|
+
title: 'Password updated successfully',
|
|
151
|
+
icon: 'lucide:check',
|
|
152
|
+
color: 'success',
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
return { success: true }
|
|
156
|
+
} catch (error) {
|
|
157
|
+
toast.add({
|
|
158
|
+
title: 'Could not update password',
|
|
159
|
+
description:
|
|
160
|
+
error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
161
|
+
icon: 'lucide:circle-x',
|
|
162
|
+
color: 'error',
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return { success: false }
|
|
166
|
+
} finally {
|
|
167
|
+
isSubmitting.value = false
|
|
168
|
+
}
|
|
103
169
|
}
|
|
104
170
|
|
|
105
171
|
return {
|
|
@@ -108,5 +174,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
108
174
|
user,
|
|
109
175
|
login,
|
|
110
176
|
logout,
|
|
177
|
+
requestPasswordReset,
|
|
178
|
+
updatePassword,
|
|
111
179
|
}
|
|
112
180
|
}
|
package/app/middleware/auth.ts
CHANGED
|
@@ -5,10 +5,15 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
|
5
5
|
route: to as RouteLocationNormalizedGeneric,
|
|
6
6
|
})
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const allowedUnauthenticatedPaths = [
|
|
9
|
+
'/admin/login',
|
|
10
|
+
'/admin/signup',
|
|
11
|
+
'/admin/confirm',
|
|
12
|
+
'/admin/setup',
|
|
13
|
+
'/admin/forgot-password',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
if (isLoggedIn.value && allowedUnauthenticatedPaths.includes(to.path)) {
|
|
12
17
|
return navigateTo(
|
|
13
18
|
to.query.redirect
|
|
14
19
|
? decodeURIComponent(to.query.redirect as string)
|
|
@@ -19,14 +24,15 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
|
19
24
|
if (
|
|
20
25
|
!isLoggedIn.value &&
|
|
21
26
|
to.path.startsWith('/admin') &&
|
|
22
|
-
to.path
|
|
23
|
-
to.path !== '/admin/signup' &&
|
|
24
|
-
to.path !== '/admin/login'
|
|
27
|
+
!allowedUnauthenticatedPaths.includes(to.path)
|
|
25
28
|
) {
|
|
26
29
|
if (from.query.redirect) {
|
|
27
|
-
logout({
|
|
30
|
+
return logout({
|
|
31
|
+
redirectTo: from.query.redirect as string,
|
|
32
|
+
showToast: true,
|
|
33
|
+
})
|
|
28
34
|
} else {
|
|
29
|
-
logout()
|
|
35
|
+
return logout()
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
38
|
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
definePageMeta({
|
|
3
|
+
layout: 'auth',
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
useHead({
|
|
7
|
+
title: 'Forgot Password',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const { isSubmitting, requestPasswordReset } = await useAuth()
|
|
11
|
+
|
|
12
|
+
const email = ref('')
|
|
13
|
+
const isEmailSent = ref(false)
|
|
14
|
+
|
|
15
|
+
async function submitForm() {
|
|
16
|
+
const result = await requestPasswordReset(email.value)
|
|
17
|
+
|
|
18
|
+
if (result.success) {
|
|
19
|
+
isEmailSent.value = true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div class="grid h-full place-items-center">
|
|
26
|
+
<UCard class="w-100">
|
|
27
|
+
<div v-if="isEmailSent" class="space-y-6 py-10">
|
|
28
|
+
<div class="text-center space-y-4">
|
|
29
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
30
|
+
<span class="text-5xl">
|
|
31
|
+
<UIcon name="lucide:mail" class="inline-block" />
|
|
32
|
+
</span>
|
|
33
|
+
|
|
34
|
+
<h1 class="text-3xl font-bold">Check your inbox</h1>
|
|
35
|
+
</hgroup>
|
|
36
|
+
|
|
37
|
+
<p>
|
|
38
|
+
If an account exists for <strong>{{ email }}</strong
|
|
39
|
+
>, a password reset link has been sent. Check your email and follow
|
|
40
|
+
the link.
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<div class="flex items-center justify-center">
|
|
44
|
+
<UButton variant="link" to="/admin/login" class="px-0">
|
|
45
|
+
Back to Login
|
|
46
|
+
</UButton>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<form v-else @submit.prevent="submitForm">
|
|
52
|
+
<div class="flex flex-col gap-y-6">
|
|
53
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
54
|
+
<h1 class="text-3xl font-bold">Forgot Password</h1>
|
|
55
|
+
<p class="text-sm text-muted">
|
|
56
|
+
Enter your email and we'll send you a reset link.
|
|
57
|
+
</p>
|
|
58
|
+
</hgroup>
|
|
59
|
+
|
|
60
|
+
<UFormField label="Email">
|
|
61
|
+
<UInput
|
|
62
|
+
v-model="email"
|
|
63
|
+
placeholder="victor@example.com"
|
|
64
|
+
type="email"
|
|
65
|
+
class="w-full"
|
|
66
|
+
autocomplete="email"
|
|
67
|
+
required
|
|
68
|
+
/>
|
|
69
|
+
</UFormField>
|
|
70
|
+
|
|
71
|
+
<div class="flex items-center justify-between gap-x-4">
|
|
72
|
+
<UFormField>
|
|
73
|
+
<UButton :loading="isSubmitting" type="submit" icon="lucide:send">
|
|
74
|
+
Send Reset Link
|
|
75
|
+
</UButton>
|
|
76
|
+
</UFormField>
|
|
77
|
+
|
|
78
|
+
<UFormField>
|
|
79
|
+
<UButton variant="link" to="/admin/login" class="px-0">
|
|
80
|
+
Back to Login
|
|
81
|
+
</UButton>
|
|
82
|
+
</UFormField>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</form>
|
|
86
|
+
</UCard>
|
|
87
|
+
</div>
|
|
88
|
+
</template>
|
|
@@ -67,7 +67,7 @@ function submitForm() {
|
|
|
67
67
|
</UFormField>
|
|
68
68
|
</div>
|
|
69
69
|
|
|
70
|
-
<div>
|
|
70
|
+
<div class="flex items-center justify-between">
|
|
71
71
|
<UButton
|
|
72
72
|
variant="link"
|
|
73
73
|
icon="lucide:arrow-left"
|
|
@@ -76,6 +76,10 @@ function submitForm() {
|
|
|
76
76
|
>
|
|
77
77
|
Back to Home
|
|
78
78
|
</UButton>
|
|
79
|
+
|
|
80
|
+
<UButton variant="link" to="/admin/forgot-password" class="px-0">
|
|
81
|
+
Forgot password?
|
|
82
|
+
</UButton>
|
|
79
83
|
</div>
|
|
80
84
|
</div>
|
|
81
85
|
</form>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
definePageMeta({
|
|
3
|
+
layout: 'auth',
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
useHead({
|
|
7
|
+
title: 'Update Password',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const { isSubmitting, updatePassword } = await useAuth()
|
|
11
|
+
|
|
12
|
+
const form = ref({
|
|
13
|
+
password: '',
|
|
14
|
+
confirm_password: '',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const passwordMatch = computed(() => {
|
|
18
|
+
return form.value.password === form.value.confirm_password
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const isPasswordUpdated = ref(false)
|
|
22
|
+
|
|
23
|
+
async function submitForm() {
|
|
24
|
+
if (!passwordMatch.value) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await updatePassword(form.value.password)
|
|
29
|
+
|
|
30
|
+
if (result.success) {
|
|
31
|
+
isPasswordUpdated.value = true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div class="grid h-full place-items-center">
|
|
38
|
+
<UCard class="w-100">
|
|
39
|
+
<div v-if="isPasswordUpdated" class="space-y-6 py-10">
|
|
40
|
+
<div class="text-center space-y-4">
|
|
41
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
42
|
+
<span class="text-5xl">
|
|
43
|
+
<UIcon name="lucide:lock-keyhole" class="inline-block" />
|
|
44
|
+
</span>
|
|
45
|
+
|
|
46
|
+
<h1 class="text-3xl font-bold">Password Updated!</h1>
|
|
47
|
+
</hgroup>
|
|
48
|
+
|
|
49
|
+
<p>
|
|
50
|
+
Your password has been changed. You can now log in with your new
|
|
51
|
+
password.
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<div class="flex items-center justify-center">
|
|
55
|
+
<UButton to="/admin/login" color="primary" icon="lucide:log-in">
|
|
56
|
+
Go to Login
|
|
57
|
+
</UButton>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<form v-else @submit.prevent="submitForm">
|
|
63
|
+
<div class="flex flex-col gap-y-6">
|
|
64
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
65
|
+
<h1 class="text-3xl font-bold">Update Password</h1>
|
|
66
|
+
<p class="text-sm text-muted">Enter your new password below.</p>
|
|
67
|
+
</hgroup>
|
|
68
|
+
|
|
69
|
+
<UFormField label="New Password">
|
|
70
|
+
<UInput
|
|
71
|
+
v-model="form.password"
|
|
72
|
+
placeholder="••••"
|
|
73
|
+
type="password"
|
|
74
|
+
class="w-full"
|
|
75
|
+
autocomplete="new-password"
|
|
76
|
+
required
|
|
77
|
+
/>
|
|
78
|
+
</UFormField>
|
|
79
|
+
|
|
80
|
+
<UFormField
|
|
81
|
+
:error="
|
|
82
|
+
form.confirm_password && !passwordMatch
|
|
83
|
+
? 'Passwords do not match.'
|
|
84
|
+
: undefined
|
|
85
|
+
"
|
|
86
|
+
label="Confirm New Password"
|
|
87
|
+
>
|
|
88
|
+
<UInput
|
|
89
|
+
v-model="form.confirm_password"
|
|
90
|
+
placeholder="••••"
|
|
91
|
+
type="password"
|
|
92
|
+
class="w-full"
|
|
93
|
+
autocomplete="new-password"
|
|
94
|
+
required
|
|
95
|
+
/>
|
|
96
|
+
</UFormField>
|
|
97
|
+
|
|
98
|
+
<div class="flex items-center justify-between gap-x-4">
|
|
99
|
+
<UFormField>
|
|
100
|
+
<UButton
|
|
101
|
+
:loading="isSubmitting"
|
|
102
|
+
:disabled="!passwordMatch"
|
|
103
|
+
type="submit"
|
|
104
|
+
icon="lucide:lock-keyhole"
|
|
105
|
+
>
|
|
106
|
+
Update Password
|
|
107
|
+
</UButton>
|
|
108
|
+
</UFormField>
|
|
109
|
+
|
|
110
|
+
<UFormField>
|
|
111
|
+
<UButton variant="link" to="/admin/login" class="px-0">
|
|
112
|
+
Back to Login
|
|
113
|
+
</UButton>
|
|
114
|
+
</UFormField>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</form>
|
|
118
|
+
</UCard>
|
|
119
|
+
</div>
|
|
120
|
+
</template>
|
|
@@ -124,7 +124,7 @@ function handleStepChange(step: number) {
|
|
|
124
124
|
<template>
|
|
125
125
|
<UApp>
|
|
126
126
|
<div class="flex justify-center items-center h-screen">
|
|
127
|
-
<UCard class="w-
|
|
127
|
+
<UCard class="w-125">
|
|
128
128
|
<div class="flex flex-col gap-y-8">
|
|
129
129
|
<UStepper ref="stepper" v-model="currentStep" :items="items" linear />
|
|
130
130
|
|