@plutocms/supabase 0.10.0 → 0.11.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/CHANGELOG.md +24 -0
- package/FEATURES.md +3 -0
- package/README.md +10 -1
- package/app/components/auth/AuthSignIn.vue +56 -0
- package/app/components/migrations/MigrationsBanner.vue +26 -12
- package/app/composables/auth.ts +22 -88
- package/app/pages/admin/(auth)/confirm.vue +17 -21
- package/app/pages/admin/(auth)/forgot-password.vue +57 -64
- package/app/pages/admin/(auth)/signup.vue +104 -107
- package/app/pages/admin/(auth)/update-password.vue +75 -82
- package/app/pages/admin/setup.vue +0 -1
- package/app/plugins/pluto-extension.ts +147 -8
- package/app/utils/auth-driver.ts +110 -0
- package/i18n/locales/en.json +2 -18
- package/i18n/locales/pt-BR.json +2 -18
- package/package.json +2 -2
- package/server/utils/typegen.ts +67 -2
- package/app/components/navbar/NavbarAdmin.vue +0 -219
- package/app/components/navbar/NavbarAdminProvider.vue +0 -18
- package/app/middleware/auth.ts +0 -34
- package/app/middleware/setup-check.ts +0 -44
- package/app/pages/admin/(auth)/login.vue +0 -95
- package/app/pages/admin.vue +0 -57
- package/app/plugins/pluto-locale-sync.client.ts +0 -30
- package/app/plugins/pluto-permissions-sync.ts +0 -38
|
@@ -70,130 +70,127 @@ async function submitForm() {
|
|
|
70
70
|
</script>
|
|
71
71
|
|
|
72
72
|
<template>
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</UAlert>
|
|
104
|
-
</div>
|
|
73
|
+
<PlutoAdminAuthCard
|
|
74
|
+
:title="isEmailVerificationMessageVisible ? t('supabase.auth.signup.almostThere') : t('supabase.auth.signup.title')"
|
|
75
|
+
>
|
|
76
|
+
<div v-if="isEmailVerificationMessageVisible" class="space-y-6 py-10">
|
|
77
|
+
<div class="text-center space-y-4">
|
|
78
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
79
|
+
<span class="text-5xl">
|
|
80
|
+
<UIcon name="lucide:mail-warning" class="inline-block" />
|
|
81
|
+
</span>
|
|
82
|
+
</hgroup>
|
|
83
|
+
|
|
84
|
+
<p>
|
|
85
|
+
{{ t('supabase.auth.signup.verificationSent') }}
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
<UAlert icon="lucide:check" color="success" variant="outline">
|
|
89
|
+
<template #description>
|
|
90
|
+
{{ t('supabase.auth.signup.closePage') }}
|
|
91
|
+
</template>
|
|
92
|
+
</UAlert>
|
|
93
|
+
|
|
94
|
+
<UAlert
|
|
95
|
+
icon="lucide:alert-triangle"
|
|
96
|
+
color="warning"
|
|
97
|
+
variant="outline"
|
|
98
|
+
>
|
|
99
|
+
<template #description>
|
|
100
|
+
{{ t('supabase.auth.signup.checkSpam') }}
|
|
101
|
+
</template>
|
|
102
|
+
</UAlert>
|
|
105
103
|
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<form v-else @submit.prevent="submitForm">
|
|
107
|
+
<div class="flex flex-col gap-y-6">
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
<UFormField :label="t('supabase.auth.fields.email')" required>
|
|
110
|
+
<UInput
|
|
111
|
+
v-model="form.email"
|
|
112
|
+
type="email"
|
|
113
|
+
placeholder="victor@example.com"
|
|
114
|
+
class="w-full"
|
|
115
|
+
required
|
|
116
|
+
/>
|
|
117
|
+
</UFormField>
|
|
110
118
|
|
|
111
|
-
|
|
119
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
120
|
+
<UFormField :label="t('supabase.auth.fields.username')" required>
|
|
112
121
|
<UInput
|
|
113
|
-
v-model="form.
|
|
114
|
-
|
|
115
|
-
placeholder="victor@example.com"
|
|
122
|
+
v-model="form.username"
|
|
123
|
+
:placeholder="t('supabase.auth.signup.usernamePlaceholder')"
|
|
116
124
|
class="w-full"
|
|
125
|
+
minlength="3"
|
|
117
126
|
required
|
|
118
127
|
/>
|
|
119
128
|
</UFormField>
|
|
120
129
|
|
|
121
|
-
<
|
|
122
|
-
<UFormField :label="t('supabase.auth.fields.username')" required>
|
|
123
|
-
<UInput
|
|
124
|
-
v-model="form.username"
|
|
125
|
-
:placeholder="t('supabase.auth.signup.usernamePlaceholder')"
|
|
126
|
-
class="w-full"
|
|
127
|
-
minlength="3"
|
|
128
|
-
required
|
|
129
|
-
/>
|
|
130
|
-
</UFormField>
|
|
131
|
-
|
|
132
|
-
<UFormField :label="t('supabase.auth.fields.displayName')" required>
|
|
133
|
-
<UInput
|
|
134
|
-
v-model="form.display_name"
|
|
135
|
-
:placeholder="t('supabase.auth.signup.displayNamePlaceholder')"
|
|
136
|
-
class="w-full"
|
|
137
|
-
required
|
|
138
|
-
/>
|
|
139
|
-
</UFormField>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<UFormField
|
|
143
|
-
:error="!passwordMatch"
|
|
144
|
-
:label="t('supabase.auth.fields.password')"
|
|
145
|
-
required
|
|
146
|
-
>
|
|
130
|
+
<UFormField :label="t('supabase.auth.fields.displayName')" required>
|
|
147
131
|
<UInput
|
|
148
|
-
v-model="form.
|
|
149
|
-
placeholder="
|
|
132
|
+
v-model="form.display_name"
|
|
133
|
+
:placeholder="t('supabase.auth.signup.displayNamePlaceholder')"
|
|
150
134
|
class="w-full"
|
|
151
|
-
type="password"
|
|
152
135
|
required
|
|
153
136
|
/>
|
|
154
137
|
</UFormField>
|
|
138
|
+
</div>
|
|
155
139
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
140
|
+
<UFormField
|
|
141
|
+
:error="!passwordMatch"
|
|
142
|
+
:label="t('supabase.auth.fields.password')"
|
|
143
|
+
required
|
|
144
|
+
>
|
|
145
|
+
<UInput
|
|
146
|
+
v-model="form.password"
|
|
147
|
+
placeholder="••••"
|
|
148
|
+
class="w-full"
|
|
149
|
+
type="password"
|
|
159
150
|
required
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
151
|
+
/>
|
|
152
|
+
</UFormField>
|
|
153
|
+
|
|
154
|
+
<UFormField
|
|
155
|
+
:error="!passwordMatch"
|
|
156
|
+
:label="t('supabase.auth.fields.confirmPassword')"
|
|
157
|
+
required
|
|
158
|
+
>
|
|
159
|
+
<UInput
|
|
160
|
+
v-model="form.confirm_password"
|
|
161
|
+
placeholder="••••"
|
|
162
|
+
class="w-full"
|
|
163
|
+
type="password"
|
|
164
|
+
required
|
|
165
|
+
/>
|
|
166
|
+
</UFormField>
|
|
167
|
+
|
|
168
|
+
<div
|
|
169
|
+
class="flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
|
|
170
|
+
>
|
|
171
|
+
<UFormField>
|
|
172
|
+
<UButton
|
|
173
|
+
to="/admin/login"
|
|
174
|
+
icon="lucide:arrow-left"
|
|
175
|
+
variant="link"
|
|
176
|
+
class="px-0"
|
|
177
|
+
>
|
|
178
|
+
{{ t('supabase.auth.backToLogin') }}
|
|
179
|
+
</UButton>
|
|
168
180
|
</UFormField>
|
|
169
181
|
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
{{ t('supabase.auth.backToLogin') }}
|
|
181
|
-
</UButton>
|
|
182
|
-
</UFormField>
|
|
183
|
-
|
|
184
|
-
<UFormField>
|
|
185
|
-
<UButton
|
|
186
|
-
:disabled="isSubmitting || !passwordMatch"
|
|
187
|
-
:loading="isSubmitting"
|
|
188
|
-
type="submit"
|
|
189
|
-
class="w-full justify-center sm:w-auto"
|
|
190
|
-
>
|
|
191
|
-
{{ t('supabase.auth.signup.submit') }}
|
|
192
|
-
</UButton>
|
|
193
|
-
</UFormField>
|
|
194
|
-
</div>
|
|
182
|
+
<UFormField>
|
|
183
|
+
<UButton
|
|
184
|
+
:disabled="isSubmitting || !passwordMatch"
|
|
185
|
+
:loading="isSubmitting"
|
|
186
|
+
type="submit"
|
|
187
|
+
class="w-full justify-center sm:w-auto"
|
|
188
|
+
>
|
|
189
|
+
{{ t('supabase.auth.signup.submit') }}
|
|
190
|
+
</UButton>
|
|
191
|
+
</UFormField>
|
|
195
192
|
</div>
|
|
196
|
-
</
|
|
197
|
-
</
|
|
198
|
-
</
|
|
193
|
+
</div>
|
|
194
|
+
</form>
|
|
195
|
+
</PlutoAdminAuthCard>
|
|
199
196
|
</template>
|
|
@@ -21,7 +21,7 @@ onMounted(async () => {
|
|
|
21
21
|
}
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
const { isSubmitting, updatePassword } =
|
|
24
|
+
const { isSubmitting, updatePassword } = useAuth()
|
|
25
25
|
|
|
26
26
|
const form = ref({
|
|
27
27
|
password: '',
|
|
@@ -50,91 +50,84 @@ async function submitForm() {
|
|
|
50
50
|
</script>
|
|
51
51
|
|
|
52
52
|
<template>
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
</div>
|
|
53
|
+
<PlutoAdminAuthCard
|
|
54
|
+
:title="isPasswordUpdated ? t('supabase.auth.update.doneTitle') : t('supabase.auth.update.title')"
|
|
55
|
+
:description="isPasswordUpdated ? undefined : t('supabase.auth.update.description')"
|
|
56
|
+
>
|
|
57
|
+
<div v-if="isPasswordUpdated" class="space-y-6 py-10">
|
|
58
|
+
<div class="text-center space-y-4">
|
|
59
|
+
<hgroup class="flex flex-col gap-y-1">
|
|
60
|
+
<span class="text-5xl">
|
|
61
|
+
<UIcon name="lucide:lock-keyhole" class="inline-block" />
|
|
62
|
+
</span>
|
|
63
|
+
</hgroup>
|
|
64
|
+
|
|
65
|
+
<p>
|
|
66
|
+
{{ t('supabase.auth.update.doneDescription') }}
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<div class="flex items-center justify-center">
|
|
70
|
+
<UButton to="/admin/login" color="primary" icon="lucide:log-in">
|
|
71
|
+
{{ t('supabase.auth.update.goToLogin') }}
|
|
72
|
+
</UButton>
|
|
74
73
|
</div>
|
|
75
74
|
</div>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<form v-else @submit.prevent="submitForm">
|
|
78
|
+
<div class="flex flex-col gap-y-6">
|
|
79
|
+
|
|
80
|
+
<UFormField :label="t('supabase.auth.update.newPassword')">
|
|
81
|
+
<UInput
|
|
82
|
+
v-model="form.password"
|
|
83
|
+
placeholder="••••"
|
|
84
|
+
type="password"
|
|
85
|
+
class="w-full"
|
|
86
|
+
autocomplete="new-password"
|
|
87
|
+
required
|
|
88
|
+
/>
|
|
89
|
+
</UFormField>
|
|
90
|
+
|
|
91
|
+
<UFormField
|
|
92
|
+
:error="
|
|
93
|
+
form.confirm_password && !passwordMatch
|
|
94
|
+
? t('supabase.auth.passwordMismatch')
|
|
95
|
+
: undefined
|
|
96
|
+
"
|
|
97
|
+
:label="t('supabase.auth.update.confirmNewPassword')"
|
|
98
|
+
>
|
|
99
|
+
<UInput
|
|
100
|
+
v-model="form.confirm_password"
|
|
101
|
+
placeholder="••••"
|
|
102
|
+
type="password"
|
|
103
|
+
class="w-full"
|
|
104
|
+
autocomplete="new-password"
|
|
105
|
+
required
|
|
106
|
+
/>
|
|
107
|
+
</UFormField>
|
|
108
|
+
|
|
109
|
+
<div
|
|
110
|
+
class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
|
|
111
|
+
>
|
|
112
|
+
<UFormField>
|
|
113
|
+
<UButton
|
|
114
|
+
:loading="isSubmitting"
|
|
115
|
+
:disabled="!passwordMatch"
|
|
116
|
+
type="submit"
|
|
117
|
+
icon="lucide:lock-keyhole"
|
|
118
|
+
class="w-full justify-center sm:w-auto"
|
|
119
|
+
>
|
|
120
|
+
{{ t('supabase.auth.update.submit') }}
|
|
121
|
+
</UButton>
|
|
95
122
|
</UFormField>
|
|
96
123
|
|
|
97
|
-
<UFormField
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
: undefined
|
|
102
|
-
"
|
|
103
|
-
:label="t('supabase.auth.update.confirmNewPassword')"
|
|
104
|
-
>
|
|
105
|
-
<UInput
|
|
106
|
-
v-model="form.confirm_password"
|
|
107
|
-
placeholder="••••"
|
|
108
|
-
type="password"
|
|
109
|
-
class="w-full"
|
|
110
|
-
autocomplete="new-password"
|
|
111
|
-
required
|
|
112
|
-
/>
|
|
124
|
+
<UFormField>
|
|
125
|
+
<UButton variant="link" to="/admin/login" class="px-0">
|
|
126
|
+
{{ t('supabase.auth.backToLogin') }}
|
|
127
|
+
</UButton>
|
|
113
128
|
</UFormField>
|
|
114
|
-
|
|
115
|
-
<div
|
|
116
|
-
class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
|
|
117
|
-
>
|
|
118
|
-
<UFormField>
|
|
119
|
-
<UButton
|
|
120
|
-
:loading="isSubmitting"
|
|
121
|
-
:disabled="!passwordMatch"
|
|
122
|
-
type="submit"
|
|
123
|
-
icon="lucide:lock-keyhole"
|
|
124
|
-
class="w-full justify-center sm:w-auto"
|
|
125
|
-
>
|
|
126
|
-
{{ t('supabase.auth.update.submit') }}
|
|
127
|
-
</UButton>
|
|
128
|
-
</UFormField>
|
|
129
|
-
|
|
130
|
-
<UFormField>
|
|
131
|
-
<UButton variant="link" to="/admin/login" class="px-0">
|
|
132
|
-
{{ t('supabase.auth.backToLogin') }}
|
|
133
|
-
</UButton>
|
|
134
|
-
</UFormField>
|
|
135
|
-
</div>
|
|
136
129
|
</div>
|
|
137
|
-
</
|
|
138
|
-
</
|
|
139
|
-
</
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
</PlutoAdminAuthCard>
|
|
140
133
|
</template>
|
|
@@ -1,14 +1,157 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { defineAsyncComponent } from 'vue'
|
|
2
|
+
|
|
3
|
+
// The three components load on demand. They stay out of the entry chunk. Core
|
|
4
|
+
// shows a skeleton while each chunk loads.
|
|
5
|
+
const AuthSignIn = defineAsyncComponent(
|
|
6
|
+
() => import('../components/auth/AuthSignIn.vue')
|
|
7
|
+
)
|
|
8
|
+
const MigrationsBanner = defineAsyncComponent(
|
|
9
|
+
() => import('../components/migrations/MigrationsBanner.vue')
|
|
10
|
+
)
|
|
11
|
+
const SettingsGeneralPanel = defineAsyncComponent(
|
|
12
|
+
() => import('../components/settings/SettingsGeneralPanel.vue')
|
|
13
|
+
)
|
|
3
14
|
|
|
4
15
|
export default defineNuxtPlugin(() => {
|
|
5
16
|
// Read at setup, while the Nuxt context exists. The driver below calls it
|
|
6
17
|
// later, after an await, when the context is gone.
|
|
7
18
|
const errorText = usePlutoErrorText()
|
|
19
|
+
const supabase = useSupabaseClient()
|
|
20
|
+
const session = useSupabaseSession()
|
|
21
|
+
const claims = useSupabaseUser()
|
|
22
|
+
|
|
23
|
+
// The site settings that the admin bar shows: the site name and the site
|
|
24
|
+
// URL. They load in the browser after a sign-in, because the settings read
|
|
25
|
+
// needs the session cookie. The state starts empty on the server.
|
|
26
|
+
const siteSettings = useState<Record<string, string>>(
|
|
27
|
+
'supabase-site-settings',
|
|
28
|
+
() => ({})
|
|
29
|
+
)
|
|
30
|
+
const hasSettingsModified = useState<number>('has_settings_modified')
|
|
31
|
+
|
|
32
|
+
async function loadSiteSettings() {
|
|
33
|
+
try {
|
|
34
|
+
const response = await $fetch<{ settings: Record<string, string> }>(
|
|
35
|
+
'/api/settings'
|
|
36
|
+
)
|
|
37
|
+
siteSettings.value = response.settings
|
|
38
|
+
} catch {
|
|
39
|
+
// The bar then shows the fallback name and the "/" URL.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The setup guard. A done setup is cached for the session, so the guard
|
|
44
|
+
// does not fetch on every navigation. A state that is not done is read
|
|
45
|
+
// again each time.
|
|
46
|
+
const setupDone = useState<boolean>('supabase-setup-done', () => false)
|
|
47
|
+
|
|
48
|
+
async function fetchSetupState(): Promise<SetupState> {
|
|
49
|
+
let state: SetupState = 'unknown'
|
|
50
|
+
try {
|
|
51
|
+
state = setupStateFromResponse(
|
|
52
|
+
await $fetch('/api/settings/first_setup')
|
|
53
|
+
)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (import.meta.dev) {
|
|
56
|
+
console.error('Error checking first setup status:', error)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setupDone.value = state === 'done'
|
|
61
|
+
|
|
62
|
+
return state
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// On a cold load of an admin path, the request starts here, at plugin setup.
|
|
66
|
+
// The guard then awaits a request that is already in flight. The guard takes
|
|
67
|
+
// this promise once, so a later navigation reads the state again.
|
|
68
|
+
let earlyState: Promise<SetupState> | null =
|
|
69
|
+
import.meta.client &&
|
|
70
|
+
!setupDone.value &&
|
|
71
|
+
/^\/admin(?:\/|$)/.test(window.location.pathname)
|
|
72
|
+
? fetchSetupState()
|
|
73
|
+
: null
|
|
74
|
+
|
|
75
|
+
async function readSetupState(): Promise<SetupState> {
|
|
76
|
+
if (setupDone.value) {
|
|
77
|
+
return 'done'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const early = earlyState
|
|
81
|
+
earlyState = null
|
|
82
|
+
|
|
83
|
+
return early ?? fetchSetupState()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (import.meta.client) {
|
|
87
|
+
watch(
|
|
88
|
+
[() => Boolean(session.value), hasSettingsModified],
|
|
89
|
+
([signedIn]) => {
|
|
90
|
+
if (signedIn) {
|
|
91
|
+
void loadSiteSettings()
|
|
92
|
+
} else {
|
|
93
|
+
siteSettings.value = {}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{ immediate: true }
|
|
97
|
+
)
|
|
98
|
+
}
|
|
8
99
|
|
|
9
100
|
definePlutoExtension({
|
|
10
101
|
id: 'supabase',
|
|
11
|
-
|
|
102
|
+
// Core renders the navbar, the login page and the route guard from this
|
|
103
|
+
// driver. See the admin-shell skill in @plutocms/pluto.
|
|
104
|
+
authDriver: {
|
|
105
|
+
id: 'supabase-auth',
|
|
106
|
+
// The JWT claims carry the id (`sub`) and the user metadata.
|
|
107
|
+
user: () => mapSupabaseUser(claims.value),
|
|
108
|
+
// The session cookie gives the same answer on the server and in the
|
|
109
|
+
// first client render, so the bar does not cause a hydration mismatch.
|
|
110
|
+
loggedIn: () => Boolean(session.value),
|
|
111
|
+
// The module plugin reads the session before this plugin runs.
|
|
112
|
+
ready: () => Promise.resolve(),
|
|
113
|
+
refresh: async () => {
|
|
114
|
+
try {
|
|
115
|
+
const { data } = await supabase.auth.getSession()
|
|
116
|
+
session.value = data.session
|
|
117
|
+
if (data.session) {
|
|
118
|
+
const { data: claimsData } = await supabase.auth.getClaims()
|
|
119
|
+
claims.value = claimsData?.claims ?? null
|
|
120
|
+
} else {
|
|
121
|
+
claims.value = null
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Keep the state as it is when the read fails.
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
signOut: async () => {
|
|
128
|
+
await supabase.auth.signOut()
|
|
129
|
+
session.value = null
|
|
130
|
+
claims.value = null
|
|
131
|
+
},
|
|
132
|
+
signIn: AuthSignIn,
|
|
133
|
+
signInTitle: 'supabase.auth.login.title',
|
|
134
|
+
// The old login page always showed the sign-up link.
|
|
135
|
+
links: {
|
|
136
|
+
signUp: '/admin/signup',
|
|
137
|
+
forgotPassword: '/admin/forgot-password',
|
|
138
|
+
},
|
|
139
|
+
publicPaths: SUPABASE_PUBLIC_PATHS,
|
|
140
|
+
// Core runs this on `/admin/**`, after ready() and before the sign-in
|
|
141
|
+
// rule, so the first-setup redirect wins over the sign-in redirect.
|
|
142
|
+
// It never rejects.
|
|
143
|
+
guard: async (to) => setupRedirect(to.path, await readSetupState()),
|
|
144
|
+
},
|
|
145
|
+
// The website title and URL settings. An empty value makes core use its
|
|
146
|
+
// own fallback (the runtime config name, and "/" for the URL).
|
|
147
|
+
site: {
|
|
148
|
+
id: 'supabase-site',
|
|
149
|
+
name: () => siteSettings.value.website_title,
|
|
150
|
+
// In development, "View site" opens this dev server, like the old navbar.
|
|
151
|
+
url: () => (import.meta.dev ? null : siteSettings.value.website_url),
|
|
152
|
+
},
|
|
153
|
+
// The pending-migrations notice, shown at the top of the admin pages.
|
|
154
|
+
adminBanners: [{ id: 'migrations', component: MigrationsBanner }],
|
|
12
155
|
settingsPanels: [
|
|
13
156
|
{
|
|
14
157
|
id: 'general',
|
|
@@ -32,11 +175,7 @@ export default defineNuxtPlugin(() => {
|
|
|
32
175
|
body: patch,
|
|
33
176
|
})
|
|
34
177
|
|
|
35
|
-
//
|
|
36
|
-
// read (it shows the site's own URL) — the settings page saving
|
|
37
|
-
// through the new registry must still trigger it, exactly like
|
|
38
|
-
// the old page did.
|
|
39
|
-
const hasSettingsModified = useState<number>('has_settings_modified')
|
|
178
|
+
// The site info above watches this to read the settings again.
|
|
40
179
|
hasSettingsModified.value = Date.now()
|
|
41
180
|
},
|
|
42
181
|
},
|