nuxt-auth-utils 0.3.8 → 0.4.0

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 (68) hide show
  1. package/README.md +243 -4
  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/app/plugins/session.client.d.ts +1 -1
  9. package/dist/runtime/app/plugins/session.server.d.ts +1 -1
  10. package/dist/runtime/server/api/session.get.js +1 -1
  11. package/dist/runtime/server/lib/oauth/auth0.d.ts +1 -1
  12. package/dist/runtime/server/lib/oauth/auth0.js +1 -1
  13. package/dist/runtime/server/lib/oauth/battledotnet.d.ts +1 -1
  14. package/dist/runtime/server/lib/oauth/battledotnet.js +1 -1
  15. package/dist/runtime/server/lib/oauth/cognito.d.ts +1 -1
  16. package/dist/runtime/server/lib/oauth/cognito.js +1 -1
  17. package/dist/runtime/server/lib/oauth/discord.d.ts +1 -1
  18. package/dist/runtime/server/lib/oauth/discord.js +1 -1
  19. package/dist/runtime/server/lib/oauth/dropbox.d.ts +47 -0
  20. package/dist/runtime/server/lib/oauth/dropbox.js +72 -0
  21. package/dist/runtime/server/lib/oauth/facebook.d.ts +1 -1
  22. package/dist/runtime/server/lib/oauth/facebook.js +1 -1
  23. package/dist/runtime/server/lib/oauth/github.d.ts +1 -1
  24. package/dist/runtime/server/lib/oauth/github.js +1 -1
  25. package/dist/runtime/server/lib/oauth/gitlab.d.ts +1 -1
  26. package/dist/runtime/server/lib/oauth/gitlab.js +1 -1
  27. package/dist/runtime/server/lib/oauth/google.d.ts +1 -1
  28. package/dist/runtime/server/lib/oauth/google.js +2 -1
  29. package/dist/runtime/server/lib/oauth/instagram.d.ts +1 -1
  30. package/dist/runtime/server/lib/oauth/instagram.js +1 -1
  31. package/dist/runtime/server/lib/oauth/keycloak.d.ts +1 -1
  32. package/dist/runtime/server/lib/oauth/keycloak.js +1 -1
  33. package/dist/runtime/server/lib/oauth/linkedin.d.ts +1 -1
  34. package/dist/runtime/server/lib/oauth/linkedin.js +1 -1
  35. package/dist/runtime/server/lib/oauth/microsoft.d.ts +1 -1
  36. package/dist/runtime/server/lib/oauth/microsoft.js +1 -1
  37. package/dist/runtime/server/lib/oauth/paypal.d.ts +1 -1
  38. package/dist/runtime/server/lib/oauth/paypal.js +1 -1
  39. package/dist/runtime/server/lib/oauth/spotify.d.ts +1 -1
  40. package/dist/runtime/server/lib/oauth/spotify.js +1 -1
  41. package/dist/runtime/server/lib/oauth/steam.d.ts +1 -1
  42. package/dist/runtime/server/lib/oauth/steam.js +11 -8
  43. package/dist/runtime/server/lib/oauth/tiktok.d.ts +1 -1
  44. package/dist/runtime/server/lib/oauth/tiktok.js +1 -1
  45. package/dist/runtime/server/lib/oauth/twitch.d.ts +1 -1
  46. package/dist/runtime/server/lib/oauth/twitch.js +1 -1
  47. package/dist/runtime/server/lib/oauth/vk.d.ts +1 -1
  48. package/dist/runtime/server/lib/oauth/vk.js +1 -1
  49. package/dist/runtime/server/lib/oauth/x.d.ts +1 -1
  50. package/dist/runtime/server/lib/oauth/x.js +1 -1
  51. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +1 -1
  52. package/dist/runtime/server/lib/oauth/xsuaa.js +1 -1
  53. package/dist/runtime/server/lib/oauth/yandex.d.ts +1 -1
  54. package/dist/runtime/server/lib/oauth/yandex.js +1 -1
  55. package/dist/runtime/server/lib/webauthn/authenticate.d.ts +5 -0
  56. package/dist/runtime/server/lib/webauthn/authenticate.js +73 -0
  57. package/dist/runtime/server/lib/webauthn/register.d.ts +7 -0
  58. package/dist/runtime/server/lib/webauthn/register.js +116 -0
  59. package/dist/runtime/server/lib/webauthn/utils.d.ts +3 -0
  60. package/dist/runtime/server/lib/webauthn/utils.js +35 -0
  61. package/dist/runtime/server/utils/password.d.ts +23 -0
  62. package/dist/runtime/server/utils/password.js +18 -0
  63. package/dist/runtime/types/index.d.ts +2 -1
  64. package/dist/runtime/types/oauth-config.d.ts +1 -1
  65. package/dist/runtime/types/session.d.ts +3 -1
  66. package/dist/runtime/types/webauthn.d.ts +76 -0
  67. package/dist/runtime/types/webauthn.js +0 -0
  68. package/package.json +29 -15
package/README.md CHANGED
@@ -16,6 +16,8 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
16
16
 
17
17
  - [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
18
18
  - [20+ OAuth Providers](#supported-oauth-providers)
19
+ - [Password hasing](#password-hashing)
20
+ - [WebAuthn (passkey)](#webauthn-passkey)
19
21
  - [`useUserSession()` Vue composable](#vue-composable)
20
22
  - [Tree-shakable server utils](#server-utils)
21
23
  - [`<AuthState>` component](#authstate-component)
@@ -156,6 +158,10 @@ declare module '#auth-utils' {
156
158
  interface UserSession {
157
159
  // Add your own fields
158
160
  }
161
+
162
+ interface SecureSessionData {
163
+ // Add your own fields
164
+ }
159
165
  }
160
166
 
161
167
  export {}
@@ -168,7 +174,7 @@ export {}
168
174
 
169
175
  All handlers can be auto-imported and used in your server routes or API routes.
170
176
 
171
- The pattern is `oauth<Provider>EventHandler({ onSuccess, config?, onError? })`, example: `oauthGitHubEventHandler`.
177
+ The pattern is `defineOAuth<Provider>EventHandler({ onSuccess, config?, onError? })`, example: `defineOAuthGitHubEventHandler`.
172
178
 
173
179
  The helper returns an event handler that automatically redirects to the provider authorization page and then calls `onSuccess` or `onError` depending on the result.
174
180
 
@@ -201,6 +207,7 @@ It can also be set using environment variables:
201
207
  - AWS Cognito
202
208
  - Battle.net
203
209
  - Discord
210
+ - Dropbox
204
211
  - Facebook
205
212
  - GitHub
206
213
  - GitLab
@@ -221,12 +228,12 @@ It can also be set using environment variables:
221
228
 
222
229
  You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
223
230
 
224
- ### Example
231
+ #### Example
225
232
 
226
233
  Example: `~/server/routes/auth/github.get.ts`
227
234
 
228
235
  ```ts
229
- export default oauthGitHubEventHandler({
236
+ export default defineOAuthGitHubEventHandler({
230
237
  config: {
231
238
  emailRequired: true
232
239
  },
@@ -250,6 +257,238 @@ Make sure to set the callback URL in your OAuth app settings as `<your-domain>/a
250
257
 
251
258
  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.
252
259
 
260
+ ### Password Hashing
261
+
262
+ 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.
263
+
264
+ ```ts
265
+ const hashedPassword = await hashPassword('user_password')
266
+
267
+ if (await verifyPassword(hashedPassword, 'user_password')) {
268
+ // Password is valid
269
+ }
270
+ ```
271
+
272
+ You can configure the scrypt options in your `nuxt.config.ts`:
273
+
274
+ ```ts
275
+ export default defineNuxtConfig({
276
+ modules: ['nuxt-auth-utils'],
277
+ auth: {
278
+ hash: {
279
+ scrypt: {
280
+ // See https://github.com/adonisjs/hash/blob/94637029cd526783ac0a763ec581306d98db2036/src/types.ts#L144
281
+ }
282
+ }
283
+ }
284
+ })
285
+ ```
286
+
287
+ ### WebAuthn (passkey)
288
+
289
+ 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.
290
+
291
+ To enable WebAuthn you need to:
292
+
293
+ 1. Install the peer dependencies:
294
+
295
+ ```bash
296
+ npx nypm i @simplewebauthn/server @simplewebauthn/browser
297
+ ```
298
+
299
+ 2. Enable it in your `nuxt.config.ts`
300
+
301
+ ```ts
302
+ export default defineNuxtConfig({
303
+ auth: {
304
+ webAuthn: true
305
+ }
306
+ })
307
+ ```
308
+
309
+ #### Example
310
+
311
+ In this example we will implement the very basic steps to register and authenticate a credential.
312
+
313
+ 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:
314
+
315
+ ```sql
316
+ CREATE TABLE users (
317
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
318
+ email TEXT NOT NULL
319
+ );
320
+
321
+ CREATE TABLE IF NOT EXISTS credentials (
322
+ userId INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
323
+ id TEXT UNIQUE NOT NULL,
324
+ publicKey TEXT NOT NULL,
325
+ counter INTEGER NOT NULL,
326
+ backedUp INTEGER NOT NULL,
327
+ transports TEXT NOT NULL,
328
+ PRIMARY KEY ("userId", "id")
329
+ );
330
+ ```
331
+
332
+ - 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.
333
+ - The `credentials` table stores:
334
+ - The `userId` from the `users` table.
335
+ - The credential `id` (as unique index)
336
+ - The credential `publicKey`
337
+ - 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.
338
+ - 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.
339
+ - 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.
340
+
341
+ 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).
342
+
343
+ ```ts
344
+ // server/api/webauthn/register.post.ts
345
+ import { z } from 'zod'
346
+ export default defineWebAuthnRegisterEventHandler({
347
+ // optional
348
+ validateUser: z.object({
349
+ // we want the userName to be a valid email
350
+ userName: z.string().email()
351
+ }).parse,
352
+ async onSuccess(event, { credential, user }) {
353
+ // The credential creation has been successful
354
+ // We need to create a user if it does not exist
355
+ const db = useDatabase()
356
+
357
+ // Get the user from the database
358
+ let dbUser = await db.sql`...`
359
+ if (!dbUser) {
360
+ // Store new user in database & its credentials
361
+ dbUser = await db.sql`...`
362
+ }
363
+
364
+ // we now need to store the credential in our database and link it to the user
365
+ await db.sql`...`
366
+
367
+ // Set the user session
368
+ await setUserSession(event, {
369
+ user: {
370
+ id: dbUser.id
371
+ },
372
+ loggedInAt: Date.now(),
373
+ })
374
+ },
375
+ })
376
+ ```
377
+
378
+ ```ts
379
+ // server/api/webauthn/authenticate.post.ts
380
+ export default defineWebAuthnAuthenticateEventHandler({
381
+ // Optionally, we can prefetch the credentials if the user gives their userName during login
382
+ async allowCredentials(event, userName) {
383
+ const credentials = await useDatabase().sql`...`
384
+ // If no credentials are found, the authentication cannot be completed
385
+ if (!credentials.length)
386
+ throw createError({ statusCode: 400, message: 'User not found' })
387
+
388
+ // If user is found, only allow credentials that are registered
389
+ // The browser will automatically try to use the credential that it knows about
390
+ // Skipping the step for the user to select a credential for a better user experience
391
+ return credentials
392
+ // example: [{ id: '...' }]
393
+ },
394
+ async getCredential(event, credentialId) {
395
+ // Look for the credential in our database
396
+ const credential = await useDatabase().sql`...`
397
+
398
+ // If the credential is not found, there is no account to log in to
399
+ if (!credential)
400
+ throw createError({ statusCode: 400, message: 'Credential not found' })
401
+
402
+ return credential
403
+ },
404
+ async onSuccess(event, { credential, authenticationInfo }) {
405
+ // The credential authentication has been successful
406
+ // We can look it up in our database and get the corresponding user
407
+ const db = useDatabase()
408
+ const user = await db.sql`...`
409
+
410
+ // Update the counter in the database (authenticationInfo.newCounter)
411
+ await db.sql`...`
412
+
413
+ // Set the user session
414
+ await setUserSession(event, {
415
+ user: {
416
+ id: user.id
417
+ },
418
+ loggedInAt: Date.now(),
419
+ })
420
+ },
421
+ })
422
+ ```
423
+
424
+ > [!IMPORTANT]
425
+ > By default, the webauthn event handlers will store the challenge in a short lived, encrypted session cookie. This is not recommended for applications that require strong security guarantees. On a secure connection (https) it is highly unlikely for this to cause problems. However, if the connection is not secure, there is a possibility of a man-in-the-middle attack. To prevent this, you should use a database or KV store to store the challenge instead. For this the `storeChallenge` and `getChallenge` functions are provided.
426
+
427
+ > ```ts
428
+ > export default defineWebAuthnAuthenticateEventHandler({
429
+ > async storeChallenge(event, challenge, attemptId) {
430
+ > // Store the challenge in a KV store or DB
431
+ > await useStorage().setItem(`attempt:${attemptId}`, challenge)
432
+ > },
433
+ > async getChallenge(event, attemptId) {
434
+ > const challenge = await useStorage().getItem(`attempt:${attemptId}`)
435
+ >
436
+ > // Make sure to always remove the attempt because they are single use only!
437
+ > await useStorage().removeItem(`attempt:${attemptId}`)
438
+ >
439
+ > if (!challenge)
440
+ > throw createError({ statusCode: 400, message: 'Challenge expired' })
441
+ >
442
+ > return challenge
443
+ > },
444
+ > async onSuccess(event, { authenticator }) {
445
+ > // ...
446
+ > },
447
+ > })
448
+ > ```
449
+
450
+ On the frontend it is as simple as:
451
+
452
+ ```vue
453
+ <script setup lang="ts">
454
+ const { register, authenticate } = useWebAuthn({
455
+ registerEndpoint: '/api/webauthn/register', // Default
456
+ authenticateEndpoint: '/api/webauthn/authenticate', // Default
457
+ })
458
+ const { fetch: fetchUserSession } = useUserSession()
459
+
460
+ const userName = ref('')
461
+ async function signUp() {
462
+ await register({ userName: userName.value })
463
+ .then(fetchUserSession) // refetch the user session
464
+ }
465
+
466
+ async function signIn() {
467
+ await authenticate(userName.value)
468
+ .then(fetchUserSession) // refetch the user session
469
+ }
470
+ </script>
471
+
472
+ <template>
473
+ <form @submit.prevent="signUp">
474
+ <input v-model="userName" placeholder="Email or username" />
475
+ <button type="submit">Sign up</button>
476
+ </form>
477
+ <form @submit.prevent="signIn">
478
+ <input v-model="userName" placeholder="Email or username" />
479
+ <button type="submit">Sign in</button>
480
+ </form>
481
+ </template>
482
+ ```
483
+
484
+ Take a look at the [`WebAuthnModal.vue`](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/components/WebAuthnModal.vue) for a full example.
485
+
486
+ #### Demo
487
+
488
+ 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).
489
+
490
+ The source code of the demo is available on https://github.com/atinux/todo-passkeys.
491
+
253
492
  ### Extend Session
254
493
 
255
494
  We leverage hooks to let you extend the session data with your own data or log when the user clears the session.
@@ -409,7 +648,7 @@ npm run release
409
648
  [npm-version-href]: https://npmjs.com/package/nuxt-auth-utils
410
649
 
411
650
  [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
412
- [npm-downloads-href]: https://npmjs.com/package/nuxt-auth-utils
651
+ [npm-downloads-href]: https://npm.chart.dev/nuxt-auth-utils
413
652
 
414
653
  [license-src]: https://img.shields.io/npm/l/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
415
654
  [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.8",
4
+ "version": "0.4.0",
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
+ }
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
@@ -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>>;