nuxt-auth-utils 0.3.9 → 0.4.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.
Files changed (63) hide show
  1. package/README.md +241 -5
  2. package/dist/module.d.mts +22 -0
  3. package/dist/module.d.ts +22 -0
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +40 -2
  6. package/dist/runtime/app/composables/webauthn.d.ts +13 -0
  7. package/dist/runtime/app/composables/webauthn.js +62 -0
  8. package/dist/runtime/server/api/session.get.js +1 -1
  9. package/dist/runtime/server/lib/oauth/auth0.d.ts +1 -1
  10. package/dist/runtime/server/lib/oauth/auth0.js +1 -1
  11. package/dist/runtime/server/lib/oauth/battledotnet.d.ts +1 -1
  12. package/dist/runtime/server/lib/oauth/battledotnet.js +1 -1
  13. package/dist/runtime/server/lib/oauth/cognito.d.ts +1 -1
  14. package/dist/runtime/server/lib/oauth/cognito.js +1 -1
  15. package/dist/runtime/server/lib/oauth/discord.d.ts +1 -1
  16. package/dist/runtime/server/lib/oauth/discord.js +1 -1
  17. package/dist/runtime/server/lib/oauth/dropbox.d.ts +47 -0
  18. package/dist/runtime/server/lib/oauth/dropbox.js +72 -0
  19. package/dist/runtime/server/lib/oauth/facebook.d.ts +1 -1
  20. package/dist/runtime/server/lib/oauth/facebook.js +1 -1
  21. package/dist/runtime/server/lib/oauth/github.d.ts +1 -1
  22. package/dist/runtime/server/lib/oauth/github.js +1 -1
  23. package/dist/runtime/server/lib/oauth/gitlab.d.ts +1 -1
  24. package/dist/runtime/server/lib/oauth/gitlab.js +1 -1
  25. package/dist/runtime/server/lib/oauth/google.d.ts +1 -1
  26. package/dist/runtime/server/lib/oauth/google.js +2 -1
  27. package/dist/runtime/server/lib/oauth/instagram.d.ts +1 -1
  28. package/dist/runtime/server/lib/oauth/instagram.js +1 -1
  29. package/dist/runtime/server/lib/oauth/keycloak.d.ts +1 -1
  30. package/dist/runtime/server/lib/oauth/keycloak.js +1 -1
  31. package/dist/runtime/server/lib/oauth/linkedin.d.ts +1 -1
  32. package/dist/runtime/server/lib/oauth/linkedin.js +1 -1
  33. package/dist/runtime/server/lib/oauth/microsoft.d.ts +1 -1
  34. package/dist/runtime/server/lib/oauth/microsoft.js +1 -1
  35. package/dist/runtime/server/lib/oauth/paypal.d.ts +1 -1
  36. package/dist/runtime/server/lib/oauth/paypal.js +1 -1
  37. package/dist/runtime/server/lib/oauth/spotify.d.ts +1 -1
  38. package/dist/runtime/server/lib/oauth/spotify.js +1 -1
  39. package/dist/runtime/server/lib/oauth/steam.d.ts +1 -1
  40. package/dist/runtime/server/lib/oauth/steam.js +11 -8
  41. package/dist/runtime/server/lib/oauth/tiktok.d.ts +1 -1
  42. package/dist/runtime/server/lib/oauth/tiktok.js +1 -1
  43. package/dist/runtime/server/lib/oauth/twitch.d.ts +1 -1
  44. package/dist/runtime/server/lib/oauth/twitch.js +1 -1
  45. package/dist/runtime/server/lib/oauth/vk.d.ts +1 -1
  46. package/dist/runtime/server/lib/oauth/vk.js +1 -1
  47. package/dist/runtime/server/lib/oauth/x.d.ts +1 -1
  48. package/dist/runtime/server/lib/oauth/x.js +1 -1
  49. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +1 -1
  50. package/dist/runtime/server/lib/oauth/xsuaa.js +1 -1
  51. package/dist/runtime/server/lib/oauth/yandex.d.ts +1 -1
  52. package/dist/runtime/server/lib/oauth/yandex.js +1 -1
  53. package/dist/runtime/server/lib/webauthn/authenticate.d.ts +5 -0
  54. package/dist/runtime/server/lib/webauthn/authenticate.js +73 -0
  55. package/dist/runtime/server/lib/webauthn/register.d.ts +7 -0
  56. package/dist/runtime/server/lib/webauthn/register.js +116 -0
  57. package/dist/runtime/server/utils/password.d.ts +23 -0
  58. package/dist/runtime/server/utils/password.js +18 -0
  59. package/dist/runtime/types/index.d.ts +1 -0
  60. package/dist/runtime/types/oauth-config.d.ts +1 -1
  61. package/dist/runtime/types/webauthn.d.ts +76 -0
  62. package/dist/runtime/types/webauthn.js +0 -0
  63. package/package.json +23 -9
package/README.md CHANGED
@@ -8,7 +8,8 @@
8
8
  Add Authentication to Nuxt applications with secured & sealed cookies sessions.
9
9
 
10
10
  - [Release Notes](/CHANGELOG.md)
11
- - [Demo](https://github.com/atinux/nuxt-todos-edge)
11
+ - [Demo with OAuth](https://github.com/atinux/atidone)
12
+ - [Demo with Passkeys](https://github.com/atinux/todo-passkeys)
12
13
  <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/nuxt-auth-utils?file=playground%2Fapp.vue) -->
13
14
  <!-- - [📖 &nbsp;Documentation](https://example.com) -->
14
15
 
@@ -16,6 +17,8 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
16
17
 
17
18
  - [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
18
19
  - [20+ OAuth Providers](#supported-oauth-providers)
20
+ - [Password Hashing](#password-hashing)
21
+ - [WebAuthn (passkey)](#webauthn-passkey)
19
22
  - [`useUserSession()` Vue composable](#vue-composable)
20
23
  - [Tree-shakable server utils](#server-utils)
21
24
  - [`<AuthState>` component](#authstate-component)
@@ -172,7 +175,7 @@ export {}
172
175
 
173
176
  All handlers can be auto-imported and used in your server routes or API routes.
174
177
 
175
- The pattern is `oauth<Provider>EventHandler({ onSuccess, config?, onError? })`, example: `oauthGitHubEventHandler`.
178
+ The pattern is `defineOAuth<Provider>EventHandler({ onSuccess, config?, onError? })`, example: `defineOAuthGitHubEventHandler`.
176
179
 
177
180
  The helper returns an event handler that automatically redirects to the provider authorization page and then calls `onSuccess` or `onError` depending on the result.
178
181
 
@@ -205,6 +208,7 @@ It can also be set using environment variables:
205
208
  - AWS Cognito
206
209
  - Battle.net
207
210
  - Discord
211
+ - Dropbox
208
212
  - Facebook
209
213
  - GitHub
210
214
  - GitLab
@@ -225,12 +229,12 @@ It can also be set using environment variables:
225
229
 
226
230
  You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
227
231
 
228
- ### Example
232
+ #### Example
229
233
 
230
234
  Example: `~/server/routes/auth/github.get.ts`
231
235
 
232
236
  ```ts
233
- export default oauthGitHubEventHandler({
237
+ export default defineOAuthGitHubEventHandler({
234
238
  config: {
235
239
  emailRequired: true
236
240
  },
@@ -254,6 +258,238 @@ Make sure to set the callback URL in your OAuth app settings as `<your-domain>/a
254
258
 
255
259
  If the redirect URL mismatch in production, this means that the module cannot guess the right redirect URL. You can set the `NUXT_OAUTH_<PROVIDER>_REDIRECT_URL` env variable to overwrite the default one.
256
260
 
261
+ ### Password Hashing
262
+
263
+ Nuxt Auth Utils provides password hashing utilities like `hashPassword` and `verifyPassword` to hash and verify passwords by using [scrypt](https://en.wikipedia.org/wiki/Scrypt) as it is supported in many JS runtime.
264
+
265
+ ```ts
266
+ const hashedPassword = await hashPassword('user_password')
267
+
268
+ if (await verifyPassword(hashedPassword, 'user_password')) {
269
+ // Password is valid
270
+ }
271
+ ```
272
+
273
+ You can configure the scrypt options in your `nuxt.config.ts`:
274
+
275
+ ```ts
276
+ export default defineNuxtConfig({
277
+ modules: ['nuxt-auth-utils'],
278
+ auth: {
279
+ hash: {
280
+ scrypt: {
281
+ // See https://github.com/adonisjs/hash/blob/94637029cd526783ac0a763ec581306d98db2036/src/types.ts#L144
282
+ }
283
+ }
284
+ }
285
+ })
286
+ ```
287
+
288
+ ### WebAuthn (passkey)
289
+
290
+ WebAuthn (Web Authentication) is a web standard that enhances security by replacing passwords with passkeys using public key cryptography. Users can authenticate with biometric data (like fingerprints or facial recognition) or physical devices (like USB keys), reducing the risk of phishing and password breaches. This approach offers a more secure and user-friendly authentication method, supported by major browsers and platforms.
291
+
292
+ To enable WebAuthn you need to:
293
+
294
+ 1. Install the peer dependencies:
295
+
296
+ ```bash
297
+ npx nypm i @simplewebauthn/server @simplewebauthn/browser
298
+ ```
299
+
300
+ 2. Enable it in your `nuxt.config.ts`
301
+
302
+ ```ts
303
+ export default defineNuxtConfig({
304
+ auth: {
305
+ webAuthn: true
306
+ }
307
+ })
308
+ ```
309
+
310
+ #### Example
311
+
312
+ In this example we will implement the very basic steps to register and authenticate a credential.
313
+
314
+ The full code can be found in the [playground](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/api/webauthn). The example uses a SQLite database with the following minimal tables:
315
+
316
+ ```sql
317
+ CREATE TABLE users (
318
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
319
+ email TEXT NOT NULL
320
+ );
321
+
322
+ CREATE TABLE IF NOT EXISTS credentials (
323
+ userId INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
324
+ id TEXT UNIQUE NOT NULL,
325
+ publicKey TEXT NOT NULL,
326
+ counter INTEGER NOT NULL,
327
+ backedUp INTEGER NOT NULL,
328
+ transports TEXT NOT NULL,
329
+ PRIMARY KEY ("userId", "id")
330
+ );
331
+ ```
332
+
333
+ - For the `users` table it is important to have a unique identifier such as a username or email (here we use email). When creating a new credential, this identifier is required and stored with the passkey on the user's device, password manager, or authenticator.
334
+ - The `credentials` table stores:
335
+ - The `userId` from the `users` table.
336
+ - The credential `id` (as unique index)
337
+ - The credential `publicKey`
338
+ - A `counter`. Each time a credential is used, the counter is incremented. We can use this value to perform extra security checks. More about `counter` can be read [here](https://simplewebauthn.dev/docs/packages/server#3-post-registration-responsibilities). For this example, we won't be using the counter. But you should update the counter in your database with the new value.
339
+ - A `backedUp` flag. Normally, credentials are stored on the generating device. When you use a password manager or authenticator, the credential is "backed up" because it can be used on multiple devices. See [this section](https://arc.net/l/quote/ugaemxot) for more details.
340
+ - The credential `transports`. It is an array of strings that indicate how the credential communicates with the client. It is used to show the correct UI for the user to utilize the credential. Again, see [this section](https://arc.net/l/quote/ycxtiorp) for more details.
341
+
342
+ The following code does not include the actual database queries, but shows the general steps to follow. The full example can be found in the playground: [registration](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/api/webauthn/register.post.ts), [authentication](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/api/webauthn/authenticate.post.ts) and the [database setup](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/plugins/database.ts).
343
+
344
+ ```ts
345
+ // server/api/webauthn/register.post.ts
346
+ import { z } from 'zod'
347
+ export default defineWebAuthnRegisterEventHandler({
348
+ // optional
349
+ validateUser: z.object({
350
+ // we want the userName to be a valid email
351
+ userName: z.string().email()
352
+ }).parse,
353
+ async onSuccess(event, { credential, user }) {
354
+ // The credential creation has been successful
355
+ // We need to create a user if it does not exist
356
+ const db = useDatabase()
357
+
358
+ // Get the user from the database
359
+ let dbUser = await db.sql`...`
360
+ if (!dbUser) {
361
+ // Store new user in database & its credentials
362
+ dbUser = await db.sql`...`
363
+ }
364
+
365
+ // we now need to store the credential in our database and link it to the user
366
+ await db.sql`...`
367
+
368
+ // Set the user session
369
+ await setUserSession(event, {
370
+ user: {
371
+ id: dbUser.id
372
+ },
373
+ loggedInAt: Date.now(),
374
+ })
375
+ },
376
+ })
377
+ ```
378
+
379
+ ```ts
380
+ // server/api/webauthn/authenticate.post.ts
381
+ export default defineWebAuthnAuthenticateEventHandler({
382
+ // Optionally, we can prefetch the credentials if the user gives their userName during login
383
+ async allowCredentials(event, userName) {
384
+ const credentials = await useDatabase().sql`...`
385
+ // If no credentials are found, the authentication cannot be completed
386
+ if (!credentials.length)
387
+ throw createError({ statusCode: 400, message: 'User not found' })
388
+
389
+ // If user is found, only allow credentials that are registered
390
+ // The browser will automatically try to use the credential that it knows about
391
+ // Skipping the step for the user to select a credential for a better user experience
392
+ return credentials
393
+ // example: [{ id: '...' }]
394
+ },
395
+ async getCredential(event, credentialId) {
396
+ // Look for the credential in our database
397
+ const credential = await useDatabase().sql`...`
398
+
399
+ // If the credential is not found, there is no account to log in to
400
+ if (!credential)
401
+ throw createError({ statusCode: 400, message: 'Credential not found' })
402
+
403
+ return credential
404
+ },
405
+ async onSuccess(event, { credential, authenticationInfo }) {
406
+ // The credential authentication has been successful
407
+ // We can look it up in our database and get the corresponding user
408
+ const db = useDatabase()
409
+ const user = await db.sql`...`
410
+
411
+ // Update the counter in the database (authenticationInfo.newCounter)
412
+ await db.sql`...`
413
+
414
+ // Set the user session
415
+ await setUserSession(event, {
416
+ user: {
417
+ id: user.id
418
+ },
419
+ loggedInAt: Date.now(),
420
+ })
421
+ },
422
+ })
423
+ ```
424
+
425
+ > [!IMPORTANT]
426
+ > Webauthn uses challenges to prevent replay attacks. By default, this module does not make use if this feature. If you want to use challenges (**which is highly recommended**), the `storeChallenge` and `getChallenge` functions are provided. An attempt ID is created and sent with each autentication request. You can use this ID to store the challenge in a database or KV store as shown in the example below.
427
+
428
+ > ```ts
429
+ > export default defineWebAuthnAuthenticateEventHandler({
430
+ > async storeChallenge(event, challenge, attemptId) {
431
+ > // Store the challenge in a KV store or DB
432
+ > await useStorage().setItem(`attempt:${attemptId}`, challenge)
433
+ > },
434
+ > async getChallenge(event, attemptId) {
435
+ > const challenge = await useStorage().getItem(`attempt:${attemptId}`)
436
+ >
437
+ > // Make sure to always remove the attempt because they are single use only!
438
+ > await useStorage().removeItem(`attempt:${attemptId}`)
439
+ >
440
+ > if (!challenge)
441
+ > throw createError({ statusCode: 400, message: 'Challenge expired' })
442
+ >
443
+ > return challenge
444
+ > },
445
+ > async onSuccess(event, { authenticator }) {
446
+ > // ...
447
+ > },
448
+ > })
449
+ > ```
450
+
451
+ On the frontend it is as simple as:
452
+
453
+ ```vue
454
+ <script setup lang="ts">
455
+ const { register, authenticate } = useWebAuthn({
456
+ registerEndpoint: '/api/webauthn/register', // Default
457
+ authenticateEndpoint: '/api/webauthn/authenticate', // Default
458
+ })
459
+ const { fetch: fetchUserSession } = useUserSession()
460
+
461
+ const userName = ref('')
462
+ async function signUp() {
463
+ await register({ userName: userName.value })
464
+ .then(fetchUserSession) // refetch the user session
465
+ }
466
+
467
+ async function signIn() {
468
+ await authenticate(userName.value)
469
+ .then(fetchUserSession) // refetch the user session
470
+ }
471
+ </script>
472
+
473
+ <template>
474
+ <form @submit.prevent="signUp">
475
+ <input v-model="userName" placeholder="Email or username" />
476
+ <button type="submit">Sign up</button>
477
+ </form>
478
+ <form @submit.prevent="signIn">
479
+ <input v-model="userName" placeholder="Email or username" />
480
+ <button type="submit">Sign in</button>
481
+ </form>
482
+ </template>
483
+ ```
484
+
485
+ Take a look at the [`WebAuthnModal.vue`](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/components/WebAuthnModal.vue) for a full example.
486
+
487
+ #### Demo
488
+
489
+ A full demo can be found on https://todo-passkeys.nuxt.dev using [Drizzle ORM](https://orm.drizzle.team/) and [NuxtHub](https://hub.nuxt.com).
490
+
491
+ The source code of the demo is available on https://github.com/atinux/todo-passkeys.
492
+
257
493
  ### Extend Session
258
494
 
259
495
  We leverage hooks to let you extend the session data with your own data or log when the user clears the session.
@@ -413,7 +649,7 @@ npm run release
413
649
  [npm-version-href]: https://npmjs.com/package/nuxt-auth-utils
414
650
 
415
651
  [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
416
- [npm-downloads-href]: https://npmjs.com/package/nuxt-auth-utils
652
+ [npm-downloads-href]: https://npm.chart.dev/nuxt-auth-utils
417
653
 
418
654
  [license-src]: https://img.shields.io/npm/l/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
419
655
  [license-href]: https://npmjs.com/package/nuxt-auth-utils
package/dist/module.d.mts CHANGED
@@ -1,6 +1,28 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ScryptConfig } from '@adonisjs/hash/types';
2
3
 
3
4
  interface ModuleOptions {
5
+ /**
6
+ * Enable WebAuthn (Passkeys)
7
+ * @default false
8
+ */
9
+ webAuthn?: boolean;
10
+ /**
11
+ * Hash options used for password hashing
12
+ */
13
+ hash?: {
14
+ /**
15
+ * scrypt options used for password hashing
16
+ */
17
+ scrypt?: ScryptConfig;
18
+ };
19
+ }
20
+ declare module 'nuxt/schema' {
21
+ interface RuntimeConfig {
22
+ hash: {
23
+ scrypt: ScryptConfig;
24
+ };
25
+ }
4
26
  }
5
27
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
6
28
 
package/dist/module.d.ts CHANGED
@@ -1,6 +1,28 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ScryptConfig } from '@adonisjs/hash/types';
2
3
 
3
4
  interface ModuleOptions {
5
+ /**
6
+ * Enable WebAuthn (Passkeys)
7
+ * @default false
8
+ */
9
+ webAuthn?: boolean;
10
+ /**
11
+ * Hash options used for password hashing
12
+ */
13
+ hash?: {
14
+ /**
15
+ * scrypt options used for password hashing
16
+ */
17
+ scrypt?: ScryptConfig;
18
+ };
19
+ }
20
+ declare module 'nuxt/schema' {
21
+ interface RuntimeConfig {
22
+ hash: {
23
+ scrypt: ScryptConfig;
24
+ };
25
+ }
4
26
  }
5
27
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
6
28
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.3.9",
4
+ "version": "0.4.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, addPlugin, addServerPlugin, addServerImportsDir, addServerHandler } from '@nuxt/kit';
2
+ import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, addPlugin, addServerPlugin, addServerImportsDir, logger, addServerHandler } from '@nuxt/kit';
3
3
  import { join } from 'pathe';
4
4
  import { defu } from 'defu';
5
5
  import { randomUUID } from 'uncrypto';
@@ -10,7 +10,12 @@ const module = defineNuxtModule({
10
10
  configKey: "auth"
11
11
  },
12
12
  // Default configuration options of the Nuxt module
13
- defaults: {},
13
+ defaults: {
14
+ webAuthn: false,
15
+ hash: {
16
+ scrypt: {}
17
+ }
18
+ },
14
19
  async setup(options, nuxt) {
15
20
  const resolver = createResolver(import.meta.url);
16
21
  nuxt.options.alias["#auth-utils"] = resolver.resolve(
@@ -22,6 +27,22 @@ const module = defineNuxtModule({
22
27
  addPlugin(resolver.resolve("./runtime/app/plugins/session.client"));
23
28
  addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth"));
24
29
  addServerImportsDir(resolver.resolve("./runtime/server/lib/oauth"));
30
+ if (options.webAuthn) {
31
+ const missingDeps = [];
32
+ const peerDeps = ["@simplewebauthn/server", "@simplewebauthn/browser"];
33
+ for (const pkg of peerDeps) {
34
+ await import(pkg).catch(() => {
35
+ missingDeps.push(pkg);
36
+ });
37
+ }
38
+ if (missingDeps.length > 0) {
39
+ logger.withTag("nuxt-auth-utils").error(`Missing dependencies for \`WebAuthn\`, please install with:
40
+
41
+ \`npx nypm i ${missingDeps.join(" ")}\``);
42
+ process.exit(1);
43
+ }
44
+ addServerImportsDir(resolver.resolve("./runtime/server/lib/webauthn"));
45
+ }
25
46
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
26
47
  addServerHandler({
27
48
  handler: resolver.resolve("./runtime/server/api/session.delete"),
@@ -33,6 +54,11 @@ const module = defineNuxtModule({
33
54
  route: "/api/_auth/session",
34
55
  method: "get"
35
56
  });
57
+ nuxt.options.nitro.unenv ||= {};
58
+ nuxt.options.nitro.unenv.external ||= [];
59
+ if (!nuxt.options.nitro.unenv.external.includes("node:crypto")) {
60
+ nuxt.options.nitro.unenv.external.push("node:crypto");
61
+ }
36
62
  const runtimeConfig = nuxt.options.runtimeConfig;
37
63
  const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
38
64
  runtimeConfig.session = defu(runtimeConfig.session, {
@@ -42,6 +68,9 @@ const module = defineNuxtModule({
42
68
  sameSite: "lax"
43
69
  }
44
70
  });
71
+ runtimeConfig.hash = defu(runtimeConfig.hash, {
72
+ scrypt: options.hash?.scrypt
73
+ });
45
74
  if (nuxt.options.dev && !runtimeConfig.session.password) {
46
75
  runtimeConfig.session.password = randomUUID().replace(/-/g, "");
47
76
  const envPath = join(nuxt.options.rootDir, ".env");
@@ -54,6 +83,10 @@ const module = defineNuxtModule({
54
83
  );
55
84
  }
56
85
  }
86
+ runtimeConfig.webauthn = defu(runtimeConfig.webauthn, {
87
+ register: {},
88
+ authenticate: {}
89
+ });
57
90
  runtimeConfig.oauth = defu(runtimeConfig.oauth, {});
58
91
  runtimeConfig.oauth.github = defu(runtimeConfig.oauth.github, {
59
92
  clientId: "",
@@ -171,6 +204,11 @@ const module = defineNuxtModule({
171
204
  clientSecret: "",
172
205
  redirectURL: ""
173
206
  });
207
+ runtimeConfig.oauth.dropbox = defu(runtimeConfig.oauth.dropbox, {
208
+ clientId: "",
209
+ clientSecret: "",
210
+ redirectURL: ""
211
+ });
174
212
  }
175
213
  });
176
214
 
@@ -0,0 +1,13 @@
1
+ import type { WebAuthnComposable } from '#auth-utils';
2
+ export declare function useWebAuthn(options?: {
3
+ /**
4
+ * The endpoint to register a new credential.
5
+ * @default '/api/webauthn/register'
6
+ */
7
+ registerEndpoint?: string;
8
+ /**
9
+ * The endpoint to authenticate a user.
10
+ * @default '/api/webauthn/authenticate'
11
+ */
12
+ authenticateEndpoint?: string;
13
+ }): WebAuthnComposable;
@@ -0,0 +1,62 @@
1
+ import {
2
+ browserSupportsWebAuthn,
3
+ browserSupportsWebAuthnAutofill,
4
+ platformAuthenticatorIsAvailable,
5
+ startAuthentication,
6
+ startRegistration
7
+ } from "@simplewebauthn/browser";
8
+ import { ref, onMounted } from "#imports";
9
+ export function useWebAuthn(options = {}) {
10
+ const { registerEndpoint = "/api/webauthn/register", authenticateEndpoint = "/api/webauthn/authenticate" } = options;
11
+ async function register(user) {
12
+ const { creationOptions, attemptId } = await $fetch(registerEndpoint, {
13
+ method: "POST",
14
+ body: {
15
+ user,
16
+ verify: false
17
+ }
18
+ });
19
+ const attestationResponse = await startRegistration(creationOptions);
20
+ const verificationResponse = await $fetch(registerEndpoint, {
21
+ method: "POST",
22
+ body: {
23
+ user,
24
+ attemptId,
25
+ response: attestationResponse,
26
+ verify: true
27
+ }
28
+ });
29
+ return verificationResponse && verificationResponse.verified;
30
+ }
31
+ async function authenticate(userName) {
32
+ const { requestOptions, attemptId } = await $fetch(authenticateEndpoint, {
33
+ method: "POST",
34
+ body: {
35
+ verify: false,
36
+ userName
37
+ }
38
+ });
39
+ const assertionResponse = await startAuthentication(requestOptions);
40
+ const verificationResponse = await $fetch(authenticateEndpoint, {
41
+ method: "POST",
42
+ body: {
43
+ attemptId,
44
+ userName,
45
+ response: assertionResponse,
46
+ verify: true
47
+ }
48
+ });
49
+ return verificationResponse && verificationResponse.verified;
50
+ }
51
+ const isSupported = ref(false);
52
+ onMounted(() => {
53
+ isSupported.value = browserSupportsWebAuthn();
54
+ });
55
+ return {
56
+ register,
57
+ authenticate,
58
+ isSupported,
59
+ isAutofillSupported: browserSupportsWebAuthnAutofill,
60
+ isPlatformAvailable: platformAuthenticatorIsAvailable
61
+ };
62
+ }
@@ -2,7 +2,7 @@ import { eventHandler } from "h3";
2
2
  import { getUserSession, sessionHooks } from "../utils/session.js";
3
3
  export default eventHandler(async (event) => {
4
4
  const session = await getUserSession(event);
5
- if (session.user) {
5
+ if (Object.keys(session).length > 0) {
6
6
  await sessionHooks.callHookParallel("fetch", session, event);
7
7
  }
8
8
  const { secure, ...data } = session;
@@ -57,4 +57,4 @@ export interface OAuthAuth0Config {
57
57
  */
58
58
  redirectURL?: string;
59
59
  }
60
- export declare function oauthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
60
+ export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -3,7 +3,7 @@ import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
4
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
5
  import { useRuntimeConfig } from "#imports";
6
- export function oauthAuth0EventHandler({ config, onSuccess, onError }) {
6
+ export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }) {
7
7
  return eventHandler(async (event) => {
8
8
  config = defu(config, useRuntimeConfig(event).oauth?.auth0, {
9
9
  authorizationParams: {}
@@ -45,4 +45,4 @@ export interface OAuthBattledotnetConfig {
45
45
  */
46
46
  redirectURL?: string;
47
47
  }
48
- export declare function oauthBattledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBattledotnetConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
48
+ export declare function defineOAuthBattledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBattledotnetConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -4,7 +4,7 @@ import { defu } from "defu";
4
4
  import { randomUUID } from "uncrypto";
5
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
6
6
  import { useRuntimeConfig, createError } from "#imports";
7
- export function oauthBattledotnetEventHandler({ config, onSuccess, onError }) {
7
+ export function defineOAuthBattledotnetEventHandler({ config, onSuccess, onError }) {
8
8
  return eventHandler(async (event) => {
9
9
  config = defu(config, useRuntimeConfig(event).oauth?.battledotnet, {
10
10
  authorizationURL: "https://oauth.battle.net/authorize",
@@ -41,4 +41,4 @@ export interface OAuthCognitoConfig {
41
41
  */
42
42
  domain?: string;
43
43
  }
44
- export declare function oauthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
44
+ export declare function defineOAuthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -3,7 +3,7 @@ import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
4
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
5
  import { useRuntimeConfig } from "#imports";
6
- export function oauthCognitoEventHandler({ config, onSuccess, onError }) {
6
+ export function defineOAuthCognitoEventHandler({ config, onSuccess, onError }) {
7
7
  return eventHandler(async (event) => {
8
8
  config = defu(config, useRuntimeConfig(event).oauth?.cognito, {
9
9
  authorizationParams: {}
@@ -50,4 +50,4 @@ export interface OAuthDiscordConfig {
50
50
  */
51
51
  redirectURL?: string;
52
52
  }
53
- export declare function oauthDiscordEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDiscordConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
53
+ export declare function defineOAuthDiscordEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDiscordConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -3,7 +3,7 @@ import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
4
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
5
  import { useRuntimeConfig } from "#imports";
6
- export function oauthDiscordEventHandler({ config, onSuccess, onError }) {
6
+ export function defineOAuthDiscordEventHandler({ config, onSuccess, onError }) {
7
7
  return eventHandler(async (event) => {
8
8
  config = defu(config, useRuntimeConfig(event).oauth?.discord, {
9
9
  authorizationURL: "https://discord.com/oauth2/authorize",
@@ -0,0 +1,47 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthDropboxConfig {
3
+ /**
4
+ * Dropbox Client ID
5
+ * @default process.env.NUXT_OAUTH_DROPBOX_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Dropbox OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_DROPBOX_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Dropbox OAuth Scope
15
+ * @default []
16
+ * @see https://developers.dropbox.com/oauth-guide#dropbox-api-permissions
17
+ * @example ['email', 'profile']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Require email from user, adds the ['email'] scope if not present
22
+ * @default false
23
+ */
24
+ emailRequired?: boolean;
25
+ /**
26
+ * Dropbox OAuth Authorization URL
27
+ * @default 'https://www.dropbox.com/oauth2/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * Dropbox OAuth Token URL
32
+ * @default 'https://api.dropboxapi.com/oauth2/token'
33
+ */
34
+ tokenURL?: string;
35
+ /**
36
+ * Extra authorization parameters to provide to the authorization URL
37
+ * @see https://www.dropbox.com/developers/documentation/http/documentation#authorization
38
+ * @example { locale: 'en-US' }
39
+ */
40
+ authorizationParams?: Record<string, string>;
41
+ /**
42
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
43
+ * @default process.env.NUXT_OAUTH_DROPBOX_REDIRECT_URL or current URL
44
+ */
45
+ redirectURL?: string;
46
+ }
47
+ export declare function defineOAuthDropboxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthDropboxConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;