@floatingpixels/supabase-nuxt 0.1.6 → 0.1.8
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 +120 -6
- package/dist/module.d.mts +15 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -3
- package/dist/runtime/plugins/{auth-redirect.mjs → middleware-auth-redirect.mjs} +5 -15
- package/dist/runtime/plugins/middleware-auth.mjs +14 -0
- package/dist/runtime/server/auth/callback.d.ts +2 -0
- package/dist/runtime/server/auth/callback.mjs +17 -0
- package/dist/runtime/server/auth/confirm.d.ts +2 -0
- package/dist/runtime/server/services/supabaseServerClient.mjs +2 -2
- package/dist/runtime/server/services/supabaseServiceRole.mjs +1 -1
- package/dist/runtime/types/index.d.ts +15 -0
- package/package.json +1 -1
- /package/dist/runtime/plugins/{auth-redirect.d.ts → middleware-auth-redirect.d.ts} +0 -0
- /package/dist/runtime/{server/api/confirm.d.ts → plugins/middleware-auth.d.ts} +0 -0
- /package/dist/runtime/server/{api → auth}/confirm.mjs +0 -0
package/README.md
CHANGED
|
@@ -154,9 +154,123 @@ const signInWithOtp = async () => {
|
|
|
154
154
|
|
|
155
155
|
> ⚠️ Ensure to activate and configure the authentication providers you want to use in the Supabase Dashboard under `Authentication -> Providers`.
|
|
156
156
|
|
|
157
|
-
Once the authorization flow is triggered using the `auth` wrapper of the `useSupabaseClient` composable, the session management is handled automatically.
|
|
157
|
+
Once the authorization flow is triggered using the `auth` wrapper of the `useSupabaseClient` composable, the session management is handled automatically. For the authentication flow PKCE is used, which requires an exchange between your server and the Supabase authentication server for some authentication methods. Please make sure you update your Supabase settings accordingly.
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
### E-Mail Authentication
|
|
160
|
+
|
|
161
|
+
When using e-mail authentication, a confirmation e-mail is sent to new users, and an e-mail containing a magic link is sent to existing users. For those links to work with your application, you need to adjust the e-mail templates in your Supabase settings under `Authentication -> Email Templates`. The generated links must contain a `token_hash` and `type` URL parameter, and point to the confirmation URL of your app, which is `/supabase/confirm` by default. In addition you can set the URL parameter `next` to determine the route users will be forwarded to in your app when authorization is successful. If `next` is omitted, it will route to `/`. An example template looks like this:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<h2>Confirm your signup</h2>
|
|
165
|
+
|
|
166
|
+
<p>Follow this link to confirm your user:</p>
|
|
167
|
+
<p>
|
|
168
|
+
<a href="{{ .SiteURL }}/supabase/confirm?token_hash={{ .TokenHash }}&type=email&next=/path-to-your-page"
|
|
169
|
+
>Confirm your email</a
|
|
170
|
+
>
|
|
171
|
+
</p>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The confirmation route on your server is provided by this module, so you don't need to implement it yourself. It's available at `/supabase/confirm`. It will automatically confirm the user and redirect to the `next` route.
|
|
175
|
+
|
|
176
|
+
If you want to customize the confirmation route, you can do so by creating a server route to handle the request, and point to it in your Supabase e-mail template. Your custom route will receive the `token_hash` and `type` URL parameters, and the `next` URL parameter if provided. You can use the `useSupabaseClient` composable to confirm the user and redirect to the `next` route:
|
|
177
|
+
|
|
178
|
+
> ⚠️ You can use the provided confirm route at `/supabase/confirm`, the implementation of a custom route is optional!
|
|
179
|
+
|
|
180
|
+
```ts [server/api/confirm.ts]
|
|
181
|
+
import { EmailOtpType } from '@supabase/supabase-js'
|
|
182
|
+
import { supabaseServerClient } from '#supabase/server'
|
|
183
|
+
|
|
184
|
+
export default defineEventHandler(async event => {
|
|
185
|
+
const query = getQuery(event)
|
|
186
|
+
const token_hash = query.token_hash as string
|
|
187
|
+
const type = query.type as EmailOtpType | null
|
|
188
|
+
const next = (query.next as string) ?? '/'
|
|
189
|
+
|
|
190
|
+
if (!token_hash || !type) {
|
|
191
|
+
throw createError({ statusMessage: 'Invalid token' })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const supabase = await supabaseServerClient(event)
|
|
195
|
+
const { error } = await supabase.auth.verifyOtp({ type, token_hash })
|
|
196
|
+
|
|
197
|
+
if (error) {
|
|
198
|
+
throw createError({ statusMessage: error.message })
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await sendRedirect(event, next, 302)
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### OAuth Authentication
|
|
206
|
+
|
|
207
|
+
When using OAuth authentication, you need to configure the OAuth provider in your Supabase settings under `Authentication -> Providers`. You can then use the `signInWithOAuth` method of the `auth` wrapper of the `useSupabaseClient` composable to initiate the authorization flow. This module provides a default callback under `/supabase/callback` that you can provide to the authentication function:
|
|
208
|
+
|
|
209
|
+
```ts [pages/login.vue]
|
|
210
|
+
const signInWithOAuth = async () => {
|
|
211
|
+
const { error } = await supabase.auth.signInWithOAuth({
|
|
212
|
+
provider: 'github',
|
|
213
|
+
options: {
|
|
214
|
+
redirectTo: 'http://<your-site-url>/supabase/callback',
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
if (error) console.log(error)
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
You can customize the callback by creating your own server route, and point to it when calling `signInWithOAuth`. The callback route will receive a code, that needs to be exchanged for a session. Here is an example:
|
|
222
|
+
|
|
223
|
+
> ⚠️ You can use the provided callback route at `/supabase/callback`, the implementation of a custom callback is optional!
|
|
224
|
+
|
|
225
|
+
```ts [server/api/callback.ts]
|
|
226
|
+
import { supabaseServerClient } from '#supabase/server'
|
|
227
|
+
|
|
228
|
+
export default defineEventHandler(async event => {
|
|
229
|
+
const query = getQuery(event)
|
|
230
|
+
const code = query.code as string
|
|
231
|
+
const next = (query.next as string) ?? '/'
|
|
232
|
+
|
|
233
|
+
if (!code) {
|
|
234
|
+
throw createError({ statusMessage: 'No code provided' })
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const supabase = await supabaseServerClient(event)
|
|
238
|
+
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
239
|
+
|
|
240
|
+
if (error) {
|
|
241
|
+
throw createError({ statusMessage: error.message })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await sendRedirect(event, next, 302)
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Redirection for non-authorized users
|
|
249
|
+
|
|
250
|
+
If `redirect` is set to `true` in the module options, users will be automatically routed to the login page when they are not authenticated. If you want to allow access to "public" pages, you just need to add them in the `exclude` `redirect` option, and they will not redirect unauthenticated users.
|
|
251
|
+
|
|
252
|
+
### Error Handling
|
|
253
|
+
|
|
254
|
+
When an authentication error occurs, an exception is thrown. You can create an error page in the root of your app, to show an appropriate error message, clear the error and send the user to an appropriate route to continue. Here is an example for `error.vue`:
|
|
255
|
+
|
|
256
|
+
```vue
|
|
257
|
+
<script setup lang="ts">
|
|
258
|
+
import type { H3Error } from 'h3'
|
|
259
|
+
const { error } = defineProps<{
|
|
260
|
+
error: H3Error
|
|
261
|
+
}>()
|
|
262
|
+
|
|
263
|
+
const handleError = () => clearError({ redirect: '/' })
|
|
264
|
+
</script>
|
|
265
|
+
|
|
266
|
+
<template>
|
|
267
|
+
<div>
|
|
268
|
+
<h2>{{ error.statusCode }}</h2>
|
|
269
|
+
<p>{{ error.message }}</p>
|
|
270
|
+
<button @click="handleError">Clear errors</button>
|
|
271
|
+
</div>
|
|
272
|
+
</template>
|
|
273
|
+
```
|
|
160
274
|
|
|
161
275
|
## Composables
|
|
162
276
|
|
|
@@ -164,7 +278,7 @@ If `redirect` is set to `true` in the module options, users will be automaticall
|
|
|
164
278
|
|
|
165
279
|
This composable can be used to make requests to the Supabase API. It's autoimported and ready to use in your components. It's using [supabase-js](https://github.com/supabase/supabase-js/) under the hood, it gives access to the [Supabase client](https://supabase.com/docs/reference/javascript/initializing) and all of its features.
|
|
166
280
|
|
|
167
|
-
|
|
281
|
+
#### Database Request
|
|
168
282
|
|
|
169
283
|
Please check [Supabase](https://supabase.com/docs/reference/javascript/select) documentation on how to fully use the Supabase client.
|
|
170
284
|
|
|
@@ -182,7 +296,7 @@ const { data: restaurant } = await useAsyncData('restaurant', async () => {
|
|
|
182
296
|
</script>
|
|
183
297
|
```
|
|
184
298
|
|
|
185
|
-
|
|
299
|
+
#### Realtime
|
|
186
300
|
|
|
187
301
|
Based on [Supabase Realtime](https://github.com/supabase/realtime), listen to changes in your PostgreSQL Database and broadcasts them over WebSockets.
|
|
188
302
|
|
|
@@ -221,7 +335,7 @@ onUnmounted(() => {
|
|
|
221
335
|
</script>
|
|
222
336
|
```
|
|
223
337
|
|
|
224
|
-
|
|
338
|
+
#### Type Support
|
|
225
339
|
|
|
226
340
|
You can pass Database typings to the client. Check Supabase [documentation](https://supabase.com/docs/reference/javascript/release-notes#typescript-support) for further information.
|
|
227
341
|
|
|
@@ -232,7 +346,7 @@ const client = useSupabaseClient<Database>()
|
|
|
232
346
|
</script>
|
|
233
347
|
```
|
|
234
348
|
|
|
235
|
-
|
|
349
|
+
#### Authentication Client
|
|
236
350
|
|
|
237
351
|
The useSupabaseClient composable is providing all methods to manage authorization under `useSupabaseClient().auth`. For more details please see the [supabase-js auth documentation](https://supabase.com/docs/reference/javascript/auth-api). Here is an example for signing in and out:
|
|
238
352
|
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { SupabaseClientOptions } from '@supabase/supabase-js';
|
|
3
|
+
import { CookieOptions } from 'nuxt/app';
|
|
2
4
|
|
|
5
|
+
declare module '@nuxt/schema' {
|
|
6
|
+
interface PublicRuntimeConfig {
|
|
7
|
+
supabase: {
|
|
8
|
+
url: string
|
|
9
|
+
key: string
|
|
10
|
+
redirect: boolean
|
|
11
|
+
redirectOptions: RedirectOptions
|
|
12
|
+
cookieName: string
|
|
13
|
+
cookieOptions: CookieOptions
|
|
14
|
+
clientOptions: SupabaseClientOptions<string>
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
3
18
|
interface ModuleOptions {
|
|
4
19
|
/**
|
|
5
20
|
* Supabase API URL
|
package/dist/module.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { SupabaseClientOptions } from '@supabase/supabase-js';
|
|
3
|
+
import { CookieOptions } from 'nuxt/app';
|
|
2
4
|
|
|
5
|
+
declare module '@nuxt/schema' {
|
|
6
|
+
interface PublicRuntimeConfig {
|
|
7
|
+
supabase: {
|
|
8
|
+
url: string
|
|
9
|
+
key: string
|
|
10
|
+
redirect: boolean
|
|
11
|
+
redirectOptions: RedirectOptions
|
|
12
|
+
cookieName: string
|
|
13
|
+
cookieOptions: CookieOptions
|
|
14
|
+
clientOptions: SupabaseClientOptions<string>
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
3
18
|
interface ModuleOptions {
|
|
4
19
|
/**
|
|
5
20
|
* Supabase API URL
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -33,9 +33,13 @@ const module = defineNuxtModule({
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
+
// hooks: {
|
|
37
|
+
// 'pages:routerOptions': options => {
|
|
38
|
+
// console.info(`Router options: ${options}`)
|
|
39
|
+
// },
|
|
40
|
+
// },
|
|
36
41
|
setup(options, nuxt) {
|
|
37
42
|
const { resolve } = createResolver(import.meta.url);
|
|
38
|
-
console.log("Module setup function");
|
|
39
43
|
if (!options.url) {
|
|
40
44
|
console.warn("Missing `SUPABASE_URL` in `.env`");
|
|
41
45
|
}
|
|
@@ -55,15 +59,20 @@ const module = defineNuxtModule({
|
|
|
55
59
|
});
|
|
56
60
|
addPlugin(resolve("./runtime/plugins/supabase.server"));
|
|
57
61
|
addPlugin(resolve("./runtime/plugins/supabase.client"));
|
|
62
|
+
addPlugin(resolve("./runtime/plugins/middleware-auth"));
|
|
58
63
|
nuxt.hook("imports:dirs", (dirs) => {
|
|
59
64
|
dirs.push(resolve("./runtime/composables"));
|
|
60
65
|
});
|
|
61
66
|
addServerHandler({
|
|
62
67
|
route: "/supabase/confirm",
|
|
63
|
-
handler: resolve("./runtime/server/
|
|
68
|
+
handler: resolve("./runtime/server/auth/confirm")
|
|
69
|
+
});
|
|
70
|
+
addServerHandler({
|
|
71
|
+
route: "/supabase/callback",
|
|
72
|
+
handler: resolve("./runtime/server/auth/callback")
|
|
64
73
|
});
|
|
65
74
|
if (options.redirect) {
|
|
66
|
-
addPlugin(resolve("./runtime/plugins/auth-redirect"));
|
|
75
|
+
addPlugin(resolve("./runtime/plugins/middleware-auth-redirect"));
|
|
67
76
|
}
|
|
68
77
|
nuxt.hook("nitro:config", (nitroConfig) => {
|
|
69
78
|
nitroConfig.alias = nitroConfig.alias || {};
|
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import { useSupabaseUser } from "../composables/useSupabaseUser.mjs";
|
|
2
|
-
import {
|
|
3
|
-
defineNuxtPlugin,
|
|
4
|
-
addRouteMiddleware,
|
|
5
|
-
defineNuxtRouteMiddleware,
|
|
6
|
-
useRuntimeConfig,
|
|
7
|
-
navigateTo,
|
|
8
|
-
abortNavigation
|
|
9
|
-
} from "#imports";
|
|
2
|
+
import { defineNuxtPlugin, addRouteMiddleware, defineNuxtRouteMiddleware, useRuntimeConfig, navigateTo } from "#imports";
|
|
10
3
|
export default defineNuxtPlugin({
|
|
11
|
-
name: "auth-redirect",
|
|
4
|
+
name: "middleware-auth-redirect",
|
|
12
5
|
setup() {
|
|
13
6
|
addRouteMiddleware(
|
|
14
|
-
"global-auth",
|
|
7
|
+
"02-global-auth-redirect",
|
|
15
8
|
defineNuxtRouteMiddleware(async (to) => {
|
|
16
|
-
if (to.path === "/supabase/confirm")
|
|
17
|
-
abortNavigation();
|
|
18
9
|
const config = useRuntimeConfig().public.supabase;
|
|
19
10
|
const { login, exclude } = config.redirectOptions;
|
|
20
|
-
const isExcluded = [...exclude, login
|
|
11
|
+
const isExcluded = [...exclude || [], login ? login : "/login"]?.some((path) => {
|
|
21
12
|
const regex = new RegExp(`^${path.replace(/\*/g, ".*")}$`);
|
|
22
13
|
return regex.test(to.path);
|
|
23
14
|
});
|
|
@@ -25,8 +16,7 @@ export default defineNuxtPlugin({
|
|
|
25
16
|
return;
|
|
26
17
|
const user = await useSupabaseUser();
|
|
27
18
|
if (!user) {
|
|
28
|
-
|
|
29
|
-
return navigateTo("/login");
|
|
19
|
+
return navigateTo("/login");
|
|
30
20
|
}
|
|
31
21
|
}),
|
|
32
22
|
{ global: true }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineNuxtPlugin, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin({
|
|
3
|
+
name: "middleware-auth",
|
|
4
|
+
setup() {
|
|
5
|
+
addRouteMiddleware(
|
|
6
|
+
"01-global-auth",
|
|
7
|
+
defineNuxtRouteMiddleware(async (to) => {
|
|
8
|
+
if (to.path.startsWith("/supabase/"))
|
|
9
|
+
navigateTo("/");
|
|
10
|
+
}),
|
|
11
|
+
{ global: true }
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineEventHandler } from "#imports";
|
|
2
|
+
import { createError, getQuery, sendRedirect } from "h3";
|
|
3
|
+
import { supabaseServerClient } from "#supabase/server";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const query = getQuery(event);
|
|
6
|
+
const code = query.code;
|
|
7
|
+
const next = query.next ?? "/";
|
|
8
|
+
if (!code) {
|
|
9
|
+
throw createError({ statusMessage: "No code provided" });
|
|
10
|
+
}
|
|
11
|
+
const supabase = await supabaseServerClient(event);
|
|
12
|
+
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
|
13
|
+
if (error) {
|
|
14
|
+
throw createError({ statusMessage: error.message });
|
|
15
|
+
}
|
|
16
|
+
await sendRedirect(event, next, 302);
|
|
17
|
+
});
|
|
@@ -4,7 +4,7 @@ import { useRuntimeConfig } from "#imports";
|
|
|
4
4
|
export const supabaseServerClient = async (event) => {
|
|
5
5
|
const {
|
|
6
6
|
supabase: { url, key, cookieOptions }
|
|
7
|
-
} = useRuntimeConfig(
|
|
7
|
+
} = useRuntimeConfig().public;
|
|
8
8
|
let supabaseClient = event.context._supabaseClient;
|
|
9
9
|
if (!supabaseClient) {
|
|
10
10
|
supabaseClient = createServerClient(url, key, {
|
|
@@ -13,7 +13,7 @@ export const supabaseServerClient = async (event) => {
|
|
|
13
13
|
return getCookie(event, name);
|
|
14
14
|
},
|
|
15
15
|
set(name, value) {
|
|
16
|
-
setCookie(event, name, value);
|
|
16
|
+
setCookie(event, name, value, cookieOptions);
|
|
17
17
|
},
|
|
18
18
|
remove(key2, options) {
|
|
19
19
|
setCookie(event, key2, "", { ...options, expires: 0 });
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import { SupabaseClientOptions } from '@supabase/supabase-js'
|
|
2
|
+
import { CookieOptions } from 'nuxt/app'
|
|
3
|
+
declare module '@nuxt/schema' {
|
|
4
|
+
interface PublicRuntimeConfig {
|
|
5
|
+
supabase: {
|
|
6
|
+
url: string
|
|
7
|
+
key: string
|
|
8
|
+
redirect: boolean
|
|
9
|
+
redirectOptions: RedirectOptions
|
|
10
|
+
cookieName: string
|
|
11
|
+
cookieOptions: CookieOptions
|
|
12
|
+
clientOptions: SupabaseClientOptions<string>
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
1
16
|
export interface ModuleOptions {
|
|
2
17
|
/**
|
|
3
18
|
* Supabase API URL
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|