Skip to content

Commit a1c13c2

Browse files
committed
feat(auth): add auth implementation; support oauth & email/pw
1 parent 2bc59dc commit a1c13c2

29 files changed

Lines changed: 1630 additions & 118 deletions

README.md

Lines changed: 300 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,16 @@
55
[![License][license-src]][license-href]
66
[![Nuxt][nuxt-src]][nuxt-href]
77

8-
[Nuxt 3](https://nuxt.com) integration for [EdgeDB](https://www.edgedb.com) that aims at being the fastest way to add a fully typed database layer to your Nuxt 3 project.
9-
10-
- [ Release Notes](/CHANGELOG.md)
8+
Integrate [Nuxt 3](https://nuxt.com) with [EdgeDB](https://www.edgedb.com) effortlessly, adding a robust database layer to your app with minimal configuration.
119

1210
## Features
1311

14-
- 🍱  Zero-config setup; just add `nuxt-edgedb-module` to your `modules`
15-
- 🧙  [EdgeDB CLI install](https://www.edgedb.com/docs/cli/index) and [project init](https://www.edgedb.com/docs/cli/edgedb_project/edgedb_project_init) wizards
16-
- 🎩  Watchers on `dbschema/*`, `queries/*`, `dbschema/migrations/*` bringing _HMR_ to your database
17-
- 🛟  Auto-imported typed database query client provided by [`@edgedb/generate`](https://www.edgedb.com/docs/clients/js/generation)
18-
- 🍩  [`edgedb ui`](https://www.edgedb.com/docs/cli/edgedb_ui) injected into [Nuxt Devtools](https://github.com/nuxt/devtools)
19-
20-
⚠️ This is still very experimental project. Please do not use until it is properly announced.
12+
- 🍱 **Effortless Integration**: Set up a database with just one line of configuration.
13+
- 🧙 **Seamless Initialization**: Automates [EdgeDB CLI](https://www.edgedb.com/docs/cli/index) setup and [project initialization](https://www.edgedb.com/docs/cli/edgedb_project/edgedb_project_init).
14+
- 🎩 **Live Schema Updates**: Experience _HMR-like DX_ with watchers on `dbschema/*`, `queries/*`, and `dbschema/migrations/*`.
15+
- 🛟 **Typed Query Generation**: Automatically generate a typed query client with [@edgedb/generate](https://www.edgedb.com/docs/clients/js/generation).
16+
- 🍩 **Integrated Database Management**: Administer your database directly from [Nuxt DevTools](https://github.com/nuxt/devtools).
17+
- 🔐 **Flexible Authentication**: Easily enable [Email](https://www.edgedb.com/docs/guides/auth/email_password) or [OAuth](https://www.edgedb.com/docs/guides/auth/oauth) authentication, with support for custom auth providers.
2118

2219
## Quick Setup
2320

@@ -40,27 +37,17 @@ npm install --save-dev nuxt-edgedb-module
4037
export default defineNuxtConfig({
4138
modules: [
4239
'nuxt-edgedb-module'
43-
],
44-
// Optional, all options has sufficient defaults.
45-
edgedb: {
46-
devtools: true,
47-
watch: true,
48-
dbschemaDir: 'dbschema',
49-
queriesDir: 'queries',
50-
generateInterfaces: true,
51-
generateQueries: true,
52-
generateQueryBuilder: true,
53-
}
40+
]
5441
})
5542
```
5643

57-
That's it! You can now use Nuxt EdgeDB in your Nuxt app. ✨
44+
That's it! Your Nuxt project now has a database. ✨
5845

59-
If you do not already have [EdgeDB](https://www.edgedb.com) installed on your machine, the install wizard will prompt you to install it. 🧙
46+
If you do not already have [EdgeDB](https://www.edgedb.com) installed on your machine, the install wizard will help you to install it.
6047

61-
## Usage
48+
## Server usage
6249

63-
The module provides 2 auto-imported composables available anywhere inside `server/` context of your Nuxt app.
50+
The module provides auto-imported composables available anywhere inside `server/` context of your Nuxt app.
6451

6552
### useEdgeDb
6653

@@ -183,6 +170,281 @@ defineProps<{ blogPost: BlogPost }>()
183170
</script>
184171
```
185172

173+
## Authentification
174+
175+
You can use EdgeDB like a server-only database exposed via `server/api` endpoints and `$fetch` on the client, avoiding the need for authentication.
176+
177+
But in some projects, you might want your users to login and have an identity on the server as well, luckily, the module got you covered.
178+
179+
> Before going through these auth installation steps, we strongly recommend you to check [EdgeDB Auth](https://www.edgedb.com/docs/guides/auth/index#auth) documentation.
180+
181+
### Enable `auth` option in your Nuxt configuration
182+
183+
```ts
184+
export default defineNuxtConfig({
185+
modules: ['nuxt-edgedb-module'],
186+
edgedb: {
187+
auth: true
188+
}
189+
})
190+
```
191+
192+
### Setup EdgeDB Auth in your schema
193+
194+
In this example, you can notice:
195+
196+
- `global current_user` which defines a [global property](https://www.edgedb.com/docs/datamodel/globals) linked to the client token identity
197+
- `type User` is the model if your user, you are free to change it, that can be done later on thanks to migrations
198+
- `access policy author_has_full_access` & `using (.author ?= global current_user);` defines the policy for the users to only have access to their own `BlogPost`
199+
200+
```esdl
201+
// dbschema/default.esdl
202+
using extension auth;
203+
204+
module default {
205+
global current_user := (
206+
assert_single((
207+
select User { id, name }
208+
filter .identity = global ext::auth::ClientTokenIdentity
209+
))
210+
);
211+
212+
type User {
213+
required name: str;
214+
required identity: ext::auth::Identity;
215+
}
216+
217+
type BlogPost {
218+
property content: str {
219+
default := 'My blog post content.';
220+
};
221+
property title: str {
222+
default := 'My blog post';
223+
};
224+
required author: User;
225+
226+
access policy author_has_full_access
227+
allow all
228+
using (.author ?= global current_user);
229+
230+
access policy others_read_only
231+
allow select;
232+
}
233+
}
234+
```
235+
236+
You can edit that schema while your server is running and accept the prompt to auto-migrate.
237+
238+
If you are doing these edits while the server is off, you can run `edgedb migration create` and `edgedb migrate`.
239+
240+
### Setup EdgeDB auth in your server
241+
242+
You will need to enable auth providers on your EdgeDB server.
243+
244+
That can be done through DevTools, in the `EdgeDB`.
245+
246+
Browse to your database, then inside `Auth Admin` tab.
247+
248+
There, you must specify:
249+
250+
- `auth_signing_key`
251+
- `allowed_redirect_urls`
252+
253+
You also must enable some providers, you can usually start with `Email + Password`.
254+
255+
If you enable `required_verification`, you will need to configure a SMTP server for you EdgeDB instance.
256+
257+
You can find further instructions on how to use [Mailtrap](https://mailpit.axllent.org/docs/configuration/) locally to debug this feature [here](https://www.edgedb.com/docs/guides/auth/index#email-and-password).
258+
259+
> Do not forget these steps must also be performed on your production environment.
260+
261+
### Use authentication components on the client
262+
263+
As you enabled auth in your config, the module injected these components in your project:
264+
265+
- [`EdgeDbAuthEmailLogin`](./src/runtime/components/EdgeDbAuthEmailLogin.vue)
266+
- [`EdgeDbAuthEmailVerify`](./src/runtime/components/EdgeDbAuthEmailVerify.vue)
267+
- [`EdgeDbAuthLogout`](./src/runtime/components/EdgeDbAuthLogout.vue)
268+
- [`EdgeDbAuthResetPassword`](./src/runtime/components/EdgeDbAuthResetPassword.vue)
269+
- [`EdgeDbAuthSendPasswordReset`](./src/runtime/components/EdgeDbAuthSendPasswordReset.vue)
270+
- [`EdgeDbAuthSignup`](./src/runtime/components/EdgeDbAuthSignup.vue)
271+
- [`EdgeDbAuthProviders`](./src/runtime/components/EdgeDbAuthProviders.vue)
272+
273+
You can look at the sources of these components to learn more about their props.
274+
275+
They all are unstyled components that only expose the logic required to achieve smooth authentication flows.
276+
277+
```vue
278+
<template>
279+
<EdgeDbAuthEmailLogin
280+
v-slot="{ email, updateEmail, password, updatePassword, submit, loading }"
281+
redirect-to="/"
282+
>
283+
<div>
284+
<input
285+
type="email"
286+
:value="email"
287+
placeholder="your@email.com"
288+
@change="(e) => updateEmail(e.target.value)"
289+
>
290+
<input
291+
type="password"
292+
:value="password"
293+
placeholder="password"
294+
@change="(e) => updatePassword(e.target.value)"
295+
>
296+
<button
297+
type="button"
298+
@click="(e) => !loading && submit()"
299+
>
300+
{{ loading ? 'Loading' : 'Login' }}
301+
</button>
302+
</div>
303+
</EdgeDbAuthEmailLogin>
304+
</template>
305+
```
306+
307+
You can totally rewrite any of these components locally to implement your own authentication flows.
308+
309+
### OAuth
310+
311+
EdgeDB currently supports [OAuth](https://www.edgedb.com/docs/guides/auth/oauth#oauth) on following providers:
312+
313+
- Apple
314+
- Azure (Microsoft)
315+
- GitHub
316+
- Google
317+
318+
- [`EdgeDbOAuthButton`](./src/runtime/components/EdgeDbOAuthButton.vue)
319+
- [`EdgeDbOAuthCallback`](./src/runtime/components/EdgeDbOAuthCallback.vue)
320+
321+
In order to get OAuth working, you will have to visit your EdgeDB Instance UI, via the Nuxt DevTools.
322+
323+
Browse to your database and visit "Auth Admin" tab.
324+
325+
In your list of providers, you can then add any provider you want and configure according keys (usually client `appid` and `secret`).
326+
327+
> Do not forget to set the callback url of your provider to the one listed at the top of your EdgeDB Auth Admin.
328+
329+
Once done, you can then create a simple OAuth button in your app like this:
330+
331+
```vue
332+
<template>
333+
<!-- Gives access to all available auth providers -->
334+
<EdgeDbAuthProviders v-slot="{ oAuthProviders: providers }">
335+
<!-- Create a OAuth button behavior from a provider name -->
336+
<EdgeDbOAuthButton
337+
v-for="provider of providers"
338+
:key="provider.name"
339+
v-slot="{ redirect }"
340+
:provider="provider.name"
341+
>
342+
<!-- Call `redirect` from the OAuthButton -->
343+
<button @click="() => redirect()">
344+
{{ provider.display_name }}
345+
</button>
346+
</EdgeDbOAuthButton>
347+
</EdgeDbAuthProviders>
348+
</template>
349+
```
350+
351+
You will also need a call back page, that can use `EdgeDbAuthCallback`.
352+
353+
```vue
354+
<template>
355+
<EdgeDbOAuthCallback
356+
v-slot="{ loading }"
357+
redirect-to="/"
358+
>
359+
<div>
360+
<h2>OAuth callback</h2>
361+
<p v-if="loading">
362+
Loading...
363+
</p>
364+
</UCard>
365+
</EdgeDbOAuthCallback>
366+
</template>
367+
```
368+
369+
In just a few lines, you added a basic authentication to your application.
370+
371+
### Client-side usage
372+
373+
Now that you added authentication to your project, you also have access to the `useEdgeDbIdentity` composable in your Nuxt app.
374+
375+
```vue
376+
<script setup lang="ts">
377+
const { isLoggedIn } = useEdgeDbIdentity()
378+
</script>
379+
380+
<template>
381+
<div>
382+
<LoginButton v-if="isLoggedIn" />
383+
<LogoutButton v-else />
384+
</div>
385+
</template>
386+
```
387+
388+
You can look at the [`useEdgeDbIdentity`](./src/runtime/composables/useEdgeDbIdentity.ts) for further usage.
389+
390+
### Server-side usage
391+
392+
The authentication does use a cookie called `edgedb-auth-token`.
393+
394+
On the server, if you want to authenticate your requests to the database for the current user, you only have to pass the current request object to composables:
395+
396+
```typescript
397+
export default defineEventHandler(async (req) => {
398+
const { deleteBlogPost } = useEdgeDbQueries()
399+
400+
// Will throw an error, as you cannot delete a BlogPost without being its author.
401+
await deleteBlogPost({ blogpost_id: id })
402+
403+
const { deleteBlogPost: deleteBlogPostAuthenticated } = useEdgeDbQueries(req)
404+
405+
// Succeed
406+
await deleteBlogPostAuthenticated({ blogpost_id: id })
407+
408+
return { id }
409+
})
410+
```
411+
412+
### Other authentication solutions
413+
414+
EdgeDDB Auth is a great solution, but eventually your app may require more features than what it offers.
415+
416+
Do not forget that EdgeDB can also be used just as a database, it's totally possible to build your own auth or use existing solutions like:
417+
418+
- [Sidebase Nuxt Auth](https://github.com/sidebase/nuxt-auth)
419+
- [Nuxt Auth (when ready)](https://github.com/nuxt-community/auth-module)
420+
- [Nuxt Auth Utils](https://github.com/Atinux/nuxt-auth-utils#supported-oauth-providers)
421+
- Your own implementation
422+
423+
You can also use _both_ and create Identity objects from your own authentication provider, and use `edgedb-auth-token` as your cookie.
424+
425+
I would recommend looking at [https://github.com/edgedb/edgedb-examples] that is filled with great examples of custom authentications built on EdgeDB.
426+
427+
### Authentication environment variables
428+
429+
```sh
430+
# Your EdgeDB instance auth extension base URL
431+
NUXT_EDGEDB_AUTH_BASE_URL=http://localhost:10702/db/edgedb/ext/auth/
432+
# Your EdgeDB instance OAuth callback URL
433+
NUXT_EDGEDB_OAUTH_CALLBACK=http://localhost:10702/db/edgedb/ext/auth/callback
434+
# Your app callback page
435+
NUXT_EDGEDB_OAUTH_REDIRECT_URL=http://localhost:3000/auth/callback
436+
# Your app app reset password URL (receiving the token from the forgot password email)
437+
NUXT_EDGEDB_AUTH_RESET_PASSWORD_URL=http://localhost:3000/auth/reset-password
438+
# Your app email verify url (receiving the token from email verify feature)
439+
NUXT_EDGEDB_AUTH_VERIFY_REDIRECT_URL=http://localhost:3000/auth/verify
440+
```
441+
442+
### Going further with authentication
443+
444+
EdgeDB Auth only provides bare minimal identity feature for authentication.
445+
446+
In the code example I provided, I added a `User` type that goes along with the `Identity` interface.
447+
186448
## Production
187449

188450
If you want to get out of development and deploy your database to prodution, you must follow [EdgeDB guides](https://www.edgedb.com/docs/guides/deployment/index).
@@ -191,6 +453,18 @@ If you want to get out of development and deploy your database to prodution, you
191453

192454
However, they also offer a [Cloud](https://www.edgedb.com/docs/guides/cloud), which is fully compatible with this module thanks to environment variables.
193455

456+
If you want to customize the [DSN] used by the composables, you can use the environment variables provided by the module:
457+
458+
```
459+
NUXT_EDGEDB_HOST=
460+
NUXT_EDGEDB_PORT=
461+
NUXT_EDGEDB_USER=
462+
NUXT_EDGEDB_PASS=
463+
NUXT_EDGEDB_DATABASE=
464+
```
465+
466+
> If you want to use the env variables, you have to specify **ALL** of them, otherwise the client will fallback on default values.
467+
194468
## Q&A
195469

196470
### Will my database client be exposed in userland?
@@ -242,6 +516,7 @@ No, as they are generated with your Nuxt client, you should add them to your `.g
242516
**/*.edgeql.ts
243517
dbschema/queries.*
244518
dbschema/query-builder
519+
queries/*.query.ts
245520
```
246521

247522
You must change these paths accordingly if you change the `**Dir` options.

playground/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"dependencies": {
1515
"@edgedb/generate": "^0.4.1",
16+
"@iconify-json/heroicons": "^1.1.13",
1617
"@nuxt/ui": "^2.10.0"
1718
}
1819
}

0 commit comments

Comments
 (0)