nuxt-auth-utils 0.5.34 → 0.6.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 (139) hide show
  1. package/README.md +89 -15
  2. package/dist/module.d.mts +3 -3
  3. package/dist/module.json +4 -1
  4. package/dist/module.mjs +18 -31
  5. package/dist/runtime/app/composables/session.js +3 -2
  6. package/dist/runtime/server/api/session.delete.d.ts +1 -1
  7. package/dist/runtime/server/api/session.delete.js +2 -2
  8. package/dist/runtime/server/api/session.get.d.ts +1 -1
  9. package/dist/runtime/server/api/session.get.js +2 -2
  10. package/dist/runtime/server/lib/atproto/bluesky.d.ts +3 -3
  11. package/dist/runtime/server/lib/atproto/bluesky.js +6 -6
  12. package/dist/runtime/server/lib/oauth/apple.d.ts +1 -1
  13. package/dist/runtime/server/lib/oauth/apple.js +3 -4
  14. package/dist/runtime/server/lib/oauth/atlassian.d.ts +1 -1
  15. package/dist/runtime/server/lib/oauth/atlassian.js +8 -8
  16. package/dist/runtime/server/lib/oauth/auth0.d.ts +1 -1
  17. package/dist/runtime/server/lib/oauth/auth0.js +4 -4
  18. package/dist/runtime/server/lib/oauth/authentik.d.ts +1 -1
  19. package/dist/runtime/server/lib/oauth/authentik.js +6 -6
  20. package/dist/runtime/server/lib/oauth/azureb2c.d.ts +1 -1
  21. package/dist/runtime/server/lib/oauth/azureb2c.js +5 -5
  22. package/dist/runtime/server/lib/oauth/battledotnet.d.ts +1 -1
  23. package/dist/runtime/server/lib/oauth/battledotnet.js +6 -6
  24. package/dist/runtime/server/lib/oauth/box.d.ts +1 -1
  25. package/dist/runtime/server/lib/oauth/box.js +5 -5
  26. package/dist/runtime/server/lib/oauth/cloudflare.d.ts +1 -1
  27. package/dist/runtime/server/lib/oauth/cloudflare.js +5 -5
  28. package/dist/runtime/server/lib/oauth/cognito.d.ts +1 -1
  29. package/dist/runtime/server/lib/oauth/cognito.js +4 -4
  30. package/dist/runtime/server/lib/oauth/discord.d.ts +1 -1
  31. package/dist/runtime/server/lib/oauth/discord.js +4 -4
  32. package/dist/runtime/server/lib/oauth/dropbox.d.ts +1 -1
  33. package/dist/runtime/server/lib/oauth/dropbox.js +5 -5
  34. package/dist/runtime/server/lib/oauth/facebook.d.ts +1 -1
  35. package/dist/runtime/server/lib/oauth/facebook.js +7 -7
  36. package/dist/runtime/server/lib/oauth/gitea.d.ts +1 -1
  37. package/dist/runtime/server/lib/oauth/gitea.js +5 -5
  38. package/dist/runtime/server/lib/oauth/github.d.ts +1 -1
  39. package/dist/runtime/server/lib/oauth/github.js +6 -6
  40. package/dist/runtime/server/lib/oauth/gitlab.d.ts +1 -1
  41. package/dist/runtime/server/lib/oauth/gitlab.js +5 -5
  42. package/dist/runtime/server/lib/oauth/google.d.ts +1 -1
  43. package/dist/runtime/server/lib/oauth/google.js +4 -4
  44. package/dist/runtime/server/lib/oauth/heroku.d.ts +1 -1
  45. package/dist/runtime/server/lib/oauth/heroku.js +5 -5
  46. package/dist/runtime/server/lib/oauth/hubspot.d.ts +1 -1
  47. package/dist/runtime/server/lib/oauth/hubspot.js +4 -4
  48. package/dist/runtime/server/lib/oauth/instagram.d.ts +1 -1
  49. package/dist/runtime/server/lib/oauth/instagram.js +6 -6
  50. package/dist/runtime/server/lib/oauth/keycloak.d.ts +1 -1
  51. package/dist/runtime/server/lib/oauth/keycloak.js +6 -6
  52. package/dist/runtime/server/lib/oauth/kick.d.ts +1 -1
  53. package/dist/runtime/server/lib/oauth/kick.js +5 -5
  54. package/dist/runtime/server/lib/oauth/line.d.ts +1 -1
  55. package/dist/runtime/server/lib/oauth/line.js +6 -6
  56. package/dist/runtime/server/lib/oauth/linear.d.ts +1 -1
  57. package/dist/runtime/server/lib/oauth/linear.js +6 -6
  58. package/dist/runtime/server/lib/oauth/linkedin.d.ts +1 -1
  59. package/dist/runtime/server/lib/oauth/linkedin.js +4 -4
  60. package/dist/runtime/server/lib/oauth/livechat.d.ts +1 -1
  61. package/dist/runtime/server/lib/oauth/livechat.js +4 -4
  62. package/dist/runtime/server/lib/oauth/mastodon.d.ts +1 -1
  63. package/dist/runtime/server/lib/oauth/mastodon.js +8 -9
  64. package/dist/runtime/server/lib/oauth/microsoft.d.ts +1 -1
  65. package/dist/runtime/server/lib/oauth/microsoft.js +5 -5
  66. package/dist/runtime/server/lib/oauth/oidc.d.ts +1 -1
  67. package/dist/runtime/server/lib/oauth/oidc.js +6 -6
  68. package/dist/runtime/server/lib/oauth/okta.d.ts +4 -4
  69. package/dist/runtime/server/lib/oauth/okta.js +8 -8
  70. package/dist/runtime/server/lib/oauth/ory.d.ts +1 -1
  71. package/dist/runtime/server/lib/oauth/ory.js +6 -6
  72. package/dist/runtime/server/lib/oauth/osu.d.ts +1 -1
  73. package/dist/runtime/server/lib/oauth/osu.js +4 -4
  74. package/dist/runtime/server/lib/oauth/paypal.d.ts +1 -1
  75. package/dist/runtime/server/lib/oauth/paypal.js +5 -5
  76. package/dist/runtime/server/lib/oauth/polar.d.ts +1 -1
  77. package/dist/runtime/server/lib/oauth/polar.js +5 -5
  78. package/dist/runtime/server/lib/oauth/riotgames.d.ts +1 -1
  79. package/dist/runtime/server/lib/oauth/riotgames.js +5 -5
  80. package/dist/runtime/server/lib/oauth/roblox.d.ts +1 -1
  81. package/dist/runtime/server/lib/oauth/roblox.js +4 -4
  82. package/dist/runtime/server/lib/oauth/salesforce.d.ts +1 -1
  83. package/dist/runtime/server/lib/oauth/salesforce.js +5 -5
  84. package/dist/runtime/server/lib/oauth/seznam.d.ts +1 -1
  85. package/dist/runtime/server/lib/oauth/seznam.js +4 -4
  86. package/dist/runtime/server/lib/oauth/shopifyCustomer.d.ts +1 -1
  87. package/dist/runtime/server/lib/oauth/shopifyCustomer.js +7 -7
  88. package/dist/runtime/server/lib/oauth/slack.d.ts +1 -1
  89. package/dist/runtime/server/lib/oauth/slack.js +5 -5
  90. package/dist/runtime/server/lib/oauth/spotify.d.ts +1 -1
  91. package/dist/runtime/server/lib/oauth/spotify.js +4 -4
  92. package/dist/runtime/server/lib/oauth/steam.d.ts +1 -1
  93. package/dist/runtime/server/lib/oauth/steam.js +7 -7
  94. package/dist/runtime/server/lib/oauth/strava.d.ts +1 -1
  95. package/dist/runtime/server/lib/oauth/strava.js +5 -5
  96. package/dist/runtime/server/lib/oauth/tiktok.d.ts +1 -1
  97. package/dist/runtime/server/lib/oauth/tiktok.js +5 -5
  98. package/dist/runtime/server/lib/oauth/twitch.d.ts +1 -1
  99. package/dist/runtime/server/lib/oauth/twitch.js +5 -5
  100. package/dist/runtime/server/lib/oauth/vk.d.ts +1 -1
  101. package/dist/runtime/server/lib/oauth/vk.js +5 -5
  102. package/dist/runtime/server/lib/oauth/wechat.d.ts +1 -1
  103. package/dist/runtime/server/lib/oauth/wechat.js +7 -7
  104. package/dist/runtime/server/lib/oauth/workos.d.ts +1 -1
  105. package/dist/runtime/server/lib/oauth/workos.js +3 -4
  106. package/dist/runtime/server/lib/oauth/x.d.ts +1 -1
  107. package/dist/runtime/server/lib/oauth/x.js +4 -4
  108. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +1 -1
  109. package/dist/runtime/server/lib/oauth/xsuaa.js +4 -4
  110. package/dist/runtime/server/lib/oauth/yandex.d.ts +1 -1
  111. package/dist/runtime/server/lib/oauth/yandex.js +4 -4
  112. package/dist/runtime/server/lib/oauth/zendesk.d.ts +1 -1
  113. package/dist/runtime/server/lib/oauth/zendesk.js +4 -4
  114. package/dist/runtime/server/lib/oauth/zitadel.d.ts +1 -1
  115. package/dist/runtime/server/lib/oauth/zitadel.js +6 -6
  116. package/dist/runtime/server/lib/utils.d.ts +8 -8
  117. package/dist/runtime/server/lib/utils.js +5 -7
  118. package/dist/runtime/server/lib/webauthn/authenticate.d.ts +1 -1
  119. package/dist/runtime/server/lib/webauthn/authenticate.js +7 -8
  120. package/dist/runtime/server/lib/webauthn/register.d.ts +2 -2
  121. package/dist/runtime/server/lib/webauthn/register.js +9 -10
  122. package/dist/runtime/server/plugins/oauth-hash.d.ts +2 -0
  123. package/dist/runtime/server/plugins/oauth-hash.js +8 -0
  124. package/dist/runtime/server/routes/atproto/client-metadata.json.get.d.ts +1 -1
  125. package/dist/runtime/server/routes/atproto/client-metadata.json.get.js +5 -5
  126. package/dist/runtime/server/utils/atproto.d.ts +2 -2
  127. package/dist/runtime/server/utils/atproto.js +2 -3
  128. package/dist/runtime/server/utils/password.js +1 -1
  129. package/dist/runtime/server/utils/session.d.ts +29 -16
  130. package/dist/runtime/server/utils/session.js +72 -29
  131. package/dist/runtime/types/event.d.ts +27 -3
  132. package/dist/runtime/types/index.d.ts +1 -1
  133. package/dist/runtime/types/oauth-config.d.ts +3 -3
  134. package/dist/runtime/types/webauthn.d.ts +14 -17
  135. package/package.json +10 -6
  136. package/dist/runtime/server/plugins/oauth.d.ts +0 -2
  137. package/dist/runtime/server/plugins/oauth.js +0 -16
  138. package/dist/runtime/server/plugins/ws.d.ts +0 -2
  139. package/dist/runtime/server/plugins/ws.js +0 -7
package/README.md CHANGED
@@ -29,6 +29,8 @@ It has few dependencies (only from [UnJS](https://github.com/unjs)), run on mult
29
29
 
30
30
  ## Requirements
31
31
 
32
+ This module requires Nuxt 4.6 or later. If you upgrade from v0.5, read [Migrate from v0.5](#migrate-from-v05).
33
+
32
34
  This module only works with a Nuxt server running as it uses server API routes (`nuxt build`).
33
35
 
34
36
  This means that you cannot use this module with `nuxt generate`.
@@ -43,14 +45,19 @@ You can anyway use [Hybrid Rendering](#hybrid-rendering) to pre-render pages of
43
45
  npx nuxi@latest module add auth-utils
44
46
  ```
45
47
 
46
- 2. Add a `NUXT_SESSION_PASSWORD` env variable with at least 32 characters in the `.env`.
48
+ 2. Set the `NUXT_APP_SECRET` env variable in production, with at least 32 characters.
49
+
50
+ ```bash
51
+ # Generate a secret
52
+ openssl rand -base64 32
53
+ ```
47
54
 
48
55
  ```bash
49
56
  # .env
50
- NUXT_SESSION_PASSWORD=password-with-at-least-32-characters
57
+ NUXT_APP_SECRET=<the-generated-secret>
51
58
  ```
52
59
 
53
- Nuxt Auth Utils generates one for you when running Nuxt in development the first time if no `NUXT_SESSION_PASSWORD` is set.
60
+ Nuxt Auth Utils seals the session cookie with a secret that it derives from the [Nuxt `appSecret`](https://nuxt.com/blog/v4-6). In development, Nuxt generates an `appSecret` for you if none is set. Builds never generate one, so you must set `NUXT_APP_SECRET` in production.
54
61
 
55
62
  3. That's it! You can now add authentication to your Nuxt app ✨
56
63
 
@@ -122,6 +129,19 @@ interface UserSessionComposable {
122
129
 
123
130
  The following helpers are auto-imported in your `server/` directory.
124
131
 
132
+ They accept the event of a handler defined with `defineEventHandler` from `nuxt/server`. On Nuxt 4, they also accept the event of a handler defined with the auto-imported `defineEventHandler` from h3.
133
+
134
+ ```ts
135
+ // server/api/me.get.ts
136
+ import { defineEventHandler } from 'nuxt/server'
137
+
138
+ export default defineEventHandler(async (event) => {
139
+ const { user } = await requireUserSession(event)
140
+
141
+ return user
142
+ })
143
+ ```
144
+
125
145
  ### Session Management
126
146
 
127
147
  ```ts
@@ -152,6 +172,9 @@ await clearUserSession(event)
152
172
 
153
173
  // Require a user session (send back 401 if no `user` key in session)
154
174
  const session = await requireUserSession(event)
175
+
176
+ // Customize the error status and message
177
+ const session = await requireUserSession(event, { status: 403, message: 'Forbidden' })
155
178
  ```
156
179
 
157
180
  You can define the type for your user session by creating a type declaration file (for example, `auth.d.ts`) in your project to augment the `UserSession` type:
@@ -271,6 +294,8 @@ You can add your favorite provider by creating a new file in [src/runtime/server
271
294
  Example: `~/server/routes/auth/github.get.ts`
272
295
 
273
296
  ```ts
297
+ import { sendRedirect } from 'nuxt/server'
298
+
274
299
  export default defineOAuthGitHubEventHandler({
275
300
  config: {
276
301
  emailRequired: true
@@ -291,6 +316,8 @@ export default defineOAuthGitHubEventHandler({
291
316
  })
292
317
  ```
293
318
 
319
+ The `event` given to `onSuccess` and `onError` is a `RequestEvent` from `nuxt/server`. Import the helpers you use from `nuxt/server`, for example `sendRedirect`. On Nuxt 4, the auto-imported h3 helpers also work, but TypeScript reports a type error.
320
+
294
321
  Make sure to set the callback URL in your OAuth app settings as `<your-domain>/auth/github`.
295
322
 
296
323
  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.
@@ -450,7 +477,7 @@ export default defineWebAuthnRegisterEventHandler({
450
477
  // And verify that the email is the same as the one in session
451
478
  const session = await getUserSession(event)
452
479
  if (session.user?.email && session.user.email !== userBody.userName) {
453
- throw createError({ statusCode: 400, message: 'Email not matching current session' })
480
+ throw createError({ status: 400, message: 'Email not matching current session' })
454
481
  }
455
482
 
456
483
  // If he registers a new account with credentials
@@ -495,7 +522,7 @@ export default defineWebAuthnAuthenticateEventHandler({
495
522
  const credentials = await useDatabase().sql`...`
496
523
  // If no credentials are found, the authentication cannot be completed
497
524
  if (!credentials.length)
498
- throw createError({ statusCode: 400, message: 'User not found' })
525
+ throw createError({ status: 400, message: 'User not found' })
499
526
 
500
527
  // If user is found, only allow credentials that are registered
501
528
  // The browser will automatically try to use the credential that it knows about
@@ -509,7 +536,7 @@ export default defineWebAuthnAuthenticateEventHandler({
509
536
 
510
537
  // If the credential is not found, there is no account to log in to
511
538
  if (!credential)
512
- throw createError({ statusCode: 400, message: 'Credential not found' })
539
+ throw createError({ status: 400, message: 'Credential not found' })
513
540
 
514
541
  return credential
515
542
  },
@@ -549,7 +576,7 @@ export default defineWebAuthnAuthenticateEventHandler({
549
576
  > await useStorage().removeItem(`attempt:${attemptId}`)
550
577
  >
551
578
  > if (!challenge)
552
- > throw createError({ statusCode: 400, message: 'Challenge expired' })
579
+ > throw createError({ status: 400, message: 'Challenge expired' })
553
580
  >
554
581
  > return challenge
555
582
  > },
@@ -607,7 +634,8 @@ We leverage hooks to let you extend the session data with your own data or log w
607
634
 
608
635
  ```ts
609
636
  // server/plugins/session.ts
610
- export default defineNitroPlugin(() => {
637
+ // A server plugin is a function, so this file runs on Nuxt 4 (Nitro 2) and Nuxt 5 (Nitro 3)
638
+ export default () => {
611
639
  // Called when the session is fetched during SSR for the Vue composable (/api/_auth/session)
612
640
  // Or when we call useUserSession().fetch()
613
641
  sessionHooks.hook('fetch', async (session, event) => {
@@ -620,7 +648,7 @@ export default defineNitroPlugin(() => {
620
648
  sessionHooks.hook('clear', async (session, event) => {
621
649
  // Log that user logged out
622
650
  })
623
- })
651
+ }
624
652
  ```
625
653
 
626
654
  ## Server-Side Rendering
@@ -728,10 +756,13 @@ export default defineNuxtConfig({
728
756
  })
729
757
  ```
730
758
 
731
- You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection.
759
+ You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection. In WebSocket handlers, the session is read-only: `setUserSession`, `replaceUserSession` and `clearUserSession` throw an error when given a WebSocket peer or upgrade request.
732
760
 
733
761
  ```ts
734
762
  // server/routes/ws.ts
763
+ // `defineWebSocketHandler` is not auto-imported on Nuxt 5, `h3` works on Nuxt 4 and Nuxt 5
764
+ import { defineWebSocketHandler } from 'h3'
765
+
735
766
  export default defineWebSocketHandler({
736
767
  async upgrade(request) {
737
768
  // Make sure the user is authenticated before upgrading the WebSocket connection
@@ -773,7 +804,7 @@ onMounted(open)
773
804
 
774
805
  ## Configuration
775
806
 
776
- We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).
807
+ Nuxt Auth Utils stores the user session with `useSession` from `nuxt/server`. It uses `runtimeConfig.session` for the default options.
777
808
 
778
809
  You can overwrite the options in your `nuxt.config.ts`:
779
810
 
@@ -792,14 +823,21 @@ Our defaults are:
792
823
 
793
824
  ```ts
794
825
  {
795
- name: 'nuxt-session',
796
- password: process.env.NUXT_SESSION_PASSWORD || '',
826
+ name: 'nuxt-auth-session',
797
827
  cookie: {
798
828
  sameSite: 'lax'
799
829
  }
800
830
  }
801
831
  ```
802
832
 
833
+ The available options are:
834
+
835
+ - `name`: the name of the session cookie.
836
+ - `maxAge`: the lifetime of the cookie and of the sealed session, in seconds.
837
+ - `cookie`: the cookie options, merged over `httpOnly: true`, `secure: true`, `sameSite: 'lax'`, and `path: '/'`.
838
+
839
+ The session is sealed with a secret that Nuxt derives from `appSecret`. The module ignores `runtimeConfig.session.password`.
840
+
803
841
  You can also overwrite the session config by passing it as 3rd argument of the `setUserSession` and `replaceUserSession` functions:
804
842
 
805
843
  ```ts
@@ -808,11 +846,41 @@ await setUserSession(event, { ... } , {
808
846
  })
809
847
  ```
810
848
 
811
- Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15c1647e1369e7d7ef19f567d/src/utils/session.ts#L20) for all options.
849
+ ## Migrate from v0.5
850
+
851
+ Nuxt Auth Utils v0.6 moves to `nuxt/server`, the server API of Nuxt 4.6. The same server code runs on Nuxt 4 and Nuxt 5. This release has breaking changes.
852
+
853
+ > [!WARNING]
854
+ > All users are logged out once after the upgrade. The session cookie has a new name and a new format.
855
+
856
+ To migrate your app, follow these steps:
857
+
858
+ 1. Upgrade Nuxt to 4.6 or later.
859
+ 2. Generate a secret with `openssl rand -base64 32`.
860
+ 3. Set the secret as `NUXT_APP_SECRET` in your production environment.
861
+ 4. On Cloudflare Workers, set `compatibility_date` to `2025-04-01` or later, or add the `nodejs_compat_populate_process_env` compatibility flag. Otherwise the module can't read `NUXT_APP_SECRET` and the `NUXT_OAUTH_*` variables from your Worker variables and secrets.
862
+ 5. Remove `NUXT_SESSION_PASSWORD` from your environment and from your `.env` file. The module logs a warning while it is set.
863
+ 6. Change `statusCode` to `status` in `createError()` calls.
864
+ 7. In `onSuccess` and `onError` callbacks, import helpers such as `sendRedirect` from `nuxt/server`.
865
+
866
+ The other changes in v0.6 are:
867
+
868
+ - The session cookie is `nuxt-auth-session`. It was `nuxt-session`, which is now the default name of `useSession` from `nuxt/server`.
869
+ - The session can't be read from the `x-nuxt-session-session` request header anymore.
870
+ - The `sessionHeader`, `seal`, `crypto`, `generateId`, and `password` session options are removed.
871
+ - The `event` in the OAuth and WebAuthn callbacks is a `RequestEvent` from `nuxt/server`, and `error` is a `NuxtError`.
872
+ - In WebSocket handlers, `setUserSession`, `replaceUserSession` and `clearUserSession` throw an error, as the session cookie can't be sent back to the client.
873
+ - `requireUserSession` accepts a `status` option. The `statusCode` option still works, but it's deprecated.
874
+ - Nuxt throws a 500 error if a session cookie is larger than 4096 bytes.
875
+ - The session `maxAge` counts from the last write: each `setUserSession` call extends the cookie. In v0.5, it counted from when the session was created.
876
+ - The session can't be named `nuxt-session`, the cookie of `useSession()` from `nuxt/server`. The module warns and uses `nuxt-auth-session` instead.
877
+ - WebAuthn requires SimpleWebAuthn v13, with `@simplewebauthn/server` 13.3.2 or later. Import its JSON types from `@simplewebauthn/server` or `@simplewebauthn/browser` instead of `@simplewebauthn/types`.
812
878
 
813
879
  ## Shared session between microservices and subdomains
814
880
 
815
- To share a session between multiple applications, use the same `NUXT_SESSION_PASSWORD` and session `name` in each of them.
881
+ To share a session between multiple applications, use the same `NUXT_APP_SECRET` and session `name` in each of them.
882
+
883
+ Any application with the same `NUXT_APP_SECRET` can also derive the other keys Nuxt derives from it, so share it only between applications you trust equally.
816
884
 
817
885
  If the applications run on different subdomains, also set the session cookie `domain` to the parent domain in each `nuxt.config.ts`:
818
886
 
@@ -847,6 +915,12 @@ pnpm run dev:prepare
847
915
  # Develop with the playground
848
916
  pnpm run dev
849
917
 
918
+ # Develop with the playground on the Nuxt 5 nightly (Nitro 3), run `pnpm install` to go back to the lockfile
919
+ pnpm run dev:v5
920
+
921
+ # Run the tests on the Nuxt 5 nightly
922
+ pnpm run install:nuxt5 && pnpm run dev:prepare && pnpm run test
923
+
850
924
  # Build the playground
851
925
  pnpm run dev:build
852
926
 
package/dist/module.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ScryptConfig } from '@adonisjs/hash/types';
2
- import { SessionConfig } from 'h3';
2
+ import { SessionConfig } from 'nuxt/server';
3
3
 
4
4
  interface ModuleOptions {
5
5
  /**
@@ -33,9 +33,9 @@ declare module 'nuxt/schema' {
33
33
  scrypt: ScryptConfig;
34
34
  };
35
35
  /**
36
- * Session configuration
36
+ * Session configuration, the session is sealed with a secret derived from `appSecret` (`NUXT_APP_SECRET`)
37
37
  */
38
- session: SessionConfig;
38
+ session: Omit<SessionConfig, 'password'>;
39
39
  }
40
40
  interface PublicRuntimeConfig {
41
41
  auth: {
package/dist/module.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.34",
4
+ "compatibility": {
5
+ "nuxt": ">=4.6.0"
6
+ },
7
+ "version": "0.6.0",
5
8
  "builder": {
6
9
  "@nuxt/module-builder": "1.0.3",
7
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,14 +1,14 @@
1
- import { defineNuxtModule, createResolver, addComponentsDir, addImports, addPlugin, addServerPlugin, addServerImportsDir, logger, addServerHandler } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addComponentsDir, addImports, addPlugin, addServerPlugin, addServerImportsDir, logger, addServerHandler, getNitroVersion } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
- import { readFile, writeFile } from 'node:fs/promises';
4
- import { join } from 'pathe';
5
- import { randomUUID } from 'uncrypto';
6
3
  import { atprotoProviders, atprotoProviderDefaultClientMetadata, getClientMetadataFilename } from '../dist/runtime/utils/atproto.js';
7
4
 
8
5
  const module$1 = defineNuxtModule({
9
6
  meta: {
10
7
  name: "auth-utils",
11
- configKey: "auth"
8
+ configKey: "auth",
9
+ compatibility: {
10
+ nuxt: ">=4.6.0"
11
+ }
12
12
  },
13
13
  // Default configuration options of the Nuxt module
14
14
  defaults: {
@@ -36,11 +36,8 @@ const module$1 = defineNuxtModule({
36
36
  addPlugin(resolver.resolve("./runtime/app/plugins/session.server"));
37
37
  addPlugin(resolver.resolve("./runtime/app/plugins/session.client"));
38
38
  }
39
- addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth"));
39
+ addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth-hash"));
40
40
  addServerImportsDir(resolver.resolve("./runtime/server/lib/oauth"));
41
- if (nuxt.options.nitro?.experimental?.websocket) {
42
- addServerPlugin(resolver.resolve("./runtime/server/plugins/ws"));
43
- }
44
41
  if (options.webAuthn) {
45
42
  const missingDeps = [];
46
43
  const peerDeps = ["@simplewebauthn/server", "@simplewebauthn/browser"];
@@ -68,16 +65,22 @@ const module$1 = defineNuxtModule({
68
65
  route: "/api/_auth/session",
69
66
  method: "get"
70
67
  });
71
- nuxt.options.nitro.unenv ||= {};
72
- nuxt.options.nitro.unenv.external ||= [];
73
- if (!nuxt.options.nitro.unenv.external.includes("node:crypto")) {
74
- nuxt.options.nitro.unenv.external.push("node:crypto");
68
+ if (getNitroVersion(nuxt) === 2) {
69
+ const nitroOptions = nuxt.options.nitro;
70
+ nitroOptions.unenv ||= {};
71
+ nitroOptions.unenv.external ||= [];
72
+ if (!nitroOptions.unenv.external.includes("node:crypto")) {
73
+ nitroOptions.unenv.external.push("node:crypto");
74
+ }
75
75
  }
76
76
  const runtimeConfig = nuxt.options.runtimeConfig;
77
77
  const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
78
+ if (process.env[envSessionPassword] || runtimeConfig.session?.password) {
79
+ logger.withTag("nuxt-auth-utils").warn(`\`${envSessionPassword}\` (\`runtimeConfig.session.password\`) is not used anymore. Set \`NUXT_APP_SECRET\` instead, for example with \`openssl rand -base64 32\`. Sessions are now sealed with a secret derived from \`appSecret\`, so users are logged out once.`);
80
+ }
78
81
  runtimeConfig.session = defu(runtimeConfig.session, {
79
- name: "nuxt-session",
80
- password: "",
82
+ // Declared so `NUXT_SESSION_NAME` can override it at runtime
83
+ name: "nuxt-auth-session",
81
84
  cookie: {
82
85
  sameSite: "lax"
83
86
  }
@@ -85,22 +88,6 @@ const module$1 = defineNuxtModule({
85
88
  runtimeConfig.hash = defu(runtimeConfig.hash, {
86
89
  scrypt: options.hash?.scrypt
87
90
  });
88
- if (nuxt.options.dev && !process.env[envSessionPassword]) {
89
- if (nuxt.options.runtimeConfig.session.password) {
90
- process.env[envSessionPassword] = nuxt.options.runtimeConfig.session.password;
91
- } else {
92
- const password = process.env[envSessionPassword] = randomUUID().replace(/-/g, "");
93
- const envPath = join(nuxt.options.rootDir, ".env");
94
- const envContent = await readFile(envPath, "utf-8").catch(() => "");
95
- if (!envContent.includes(envSessionPassword)) {
96
- await writeFile(
97
- envPath,
98
- `${envContent ? envContent + "\n" : envContent}${envSessionPassword}=${password}`,
99
- "utf-8"
100
- );
101
- }
102
- }
103
- }
104
91
  runtimeConfig.public.auth = defu(runtimeConfig.public.auth, {
105
92
  loadStrategy: options.loadStrategy ?? "server-first"
106
93
  });
@@ -1,4 +1,3 @@
1
- import { appendResponseHeader } from "h3";
2
1
  import { useState, computed, useRequestFetch, useRequestEvent } from "#imports";
3
2
  export function useUserSession() {
4
3
  const serverEvent = import.meta.server ? useRequestEvent() : null;
@@ -10,8 +9,10 @@ export function useUserSession() {
10
9
  method: "DELETE",
11
10
  onResponse({ response: { headers } }) {
12
11
  if (import.meta.server && serverEvent) {
12
+ const res = serverEvent.res;
13
13
  for (const setCookie of headers.getSetCookie()) {
14
- appendResponseHeader(serverEvent, "Set-Cookie", setCookie);
14
+ if (res.headers) res.headers.append("set-cookie", setCookie);
15
+ else res.appendHeader?.("set-cookie", setCookie);
15
16
  }
16
17
  }
17
18
  }
@@ -1,4 +1,4 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
1
+ declare const _default: import("nuxt/server").EventHandler<Promise<{
2
2
  loggedOut: boolean;
3
3
  }>>;
4
4
  export default _default;
@@ -1,6 +1,6 @@
1
- import { eventHandler } from "h3";
1
+ import { defineEventHandler } from "nuxt/server";
2
2
  import { clearUserSession } from "../utils/session.js";
3
- export default eventHandler(async (event) => {
3
+ export default defineEventHandler(async (event) => {
4
4
  await clearUserSession(event);
5
5
  return { loggedOut: true };
6
6
  });
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<UserSession>>;
1
+ declare const _default: import("nuxt/server").EventHandler<Promise<UserSession>>;
2
2
  export default _default;
@@ -1,6 +1,6 @@
1
- import { eventHandler } from "h3";
1
+ import { defineEventHandler } from "nuxt/server";
2
2
  import { getUserSession, sessionHooks } from "../utils/session.js";
3
- export default eventHandler(async (event) => {
3
+ export default defineEventHandler(async (event) => {
4
4
  const session = await getUserSession(event);
5
5
  if (Object.keys(session).length > 0) {
6
6
  await sessionHooks.callHookParallel("fetch", session, event);
@@ -1,4 +1,4 @@
1
- import type { H3Event } from 'h3';
1
+ import type { RequestEvent } from 'nuxt/server';
2
2
  import type { NodeSavedSession, NodeSavedSessionStore, NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node';
3
3
  import type { AppBskyActorDefs } from '@atproto/api';
4
4
  import type { OAuthConfig } from '#auth-utils';
@@ -22,11 +22,11 @@ type BlueSkyTokens = NodeSavedSession['tokenSet'];
22
22
  export declare function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBlueskyConfig, {
23
23
  user: BlueSkyUser;
24
24
  tokens: BlueSkyTokens;
25
- }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
25
+ }>): import("nuxt/server").EventHandler<Promise<any>>;
26
26
  export declare class StateStore implements NodeSavedStateStore {
27
27
  private event;
28
28
  private readonly stateKey;
29
- constructor(event: H3Event);
29
+ constructor(event: RequestEvent);
30
30
  get(): Promise<NodeSavedState | undefined>;
31
31
  set(key: string, val: NodeSavedState): Promise<void>;
32
32
  del(): Promise<void>;
@@ -1,9 +1,9 @@
1
- import { createError, eventHandler, getQuery, sendRedirect, getCookie, setCookie, deleteCookie } from "h3";
1
+ import { defineEventHandler, createError, deleteCookie, getCookie, getQuery, sendRedirect, setCookie } from "nuxt/server";
2
2
  import { NodeOAuthClient, OAuthCallbackError, OAuthResolverError, OAuthResponseError } from "@atproto/oauth-client-node";
3
3
  import { Agent } from "@atproto/api";
4
4
  import { getAtprotoClientMetadata } from "../../utils/atproto.js";
5
5
  export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
6
- return eventHandler(async (event) => {
6
+ return defineEventHandler(async (event) => {
7
7
  const clientMetadata = getAtprotoClientMetadata(event, "bluesky", config);
8
8
  const scopes = clientMetadata.scope?.split(" ") ?? [];
9
9
  const sessionStore = new SessionStore();
@@ -18,7 +18,7 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
18
18
  try {
19
19
  const handle = query.handle?.toString();
20
20
  if (!handle) throw createError({
21
- statusCode: 400,
21
+ status: 400,
22
22
  message: "Query parameter `handle` empty or missing. Please provide a valid Bluesky handle."
23
23
  });
24
24
  const url = await client.authorize(handle, { scope: clientMetadata.scope });
@@ -28,13 +28,13 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
28
28
  switch (true) {
29
29
  case err instanceof OAuthResponseError:
30
30
  return createError({
31
- statusCode: 500,
31
+ status: 500,
32
32
  message: `Bluesky login failed: ${err.errorDescription || "Unknown error"}`,
33
33
  data: err.payload
34
34
  });
35
35
  case err instanceof OAuthResolverError:
36
36
  return createError({
37
- statusCode: 400,
37
+ status: 400,
38
38
  message: `Bluesky login failed: ${err.message || "Unknown error"}`
39
39
  });
40
40
  default:
@@ -57,7 +57,7 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
57
57
  } catch (err) {
58
58
  if (!(err instanceof OAuthCallbackError)) throw err;
59
59
  const error = createError({
60
- statusCode: 500,
60
+ status: 500,
61
61
  message: `Bluesky login failed: ${err.message || "Unknown error"}`
62
62
  });
63
63
  if (!onError) throw error;
@@ -74,4 +74,4 @@ export declare function defineOAuthAppleEventHandler({ config, onSuccess, onErro
74
74
  user: OAuthAppleUser;
75
75
  payload: OAuthAppleTokens;
76
76
  tokens: any;
77
- }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
77
+ }>): import("nuxt/server").EventHandler<Promise<any>>;
@@ -1,16 +1,15 @@
1
- import { eventHandler, getRequestHeader, readBody, sendRedirect } from "h3";
1
+ import { defineEventHandler, getRequestHeader, readBody, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
2
  import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
4
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken, signJwt, verifyJwt } from "../utils.js";
5
- import { useRuntimeConfig } from "#imports";
6
5
  export function defineOAuthAppleEventHandler({
7
6
  config,
8
7
  onSuccess,
9
8
  onError
10
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
10
  }) {
12
- return eventHandler(async (event) => {
13
- config = defu(config, useRuntimeConfig(event).oauth?.apple, {
11
+ return defineEventHandler(async (event) => {
12
+ config = defu(config, useRuntimeConfig().oauth?.apple, {
14
13
  authorizationURL: config?.authorizationURL || "https://appleid.apple.com/auth/authorize",
15
14
  authorizationParams: {}
16
15
  });
@@ -91,5 +91,5 @@ export interface OAuthAtlassianConfig {
91
91
  export declare function defineOAuthAtlassianEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthAtlassianConfig, {
92
92
  user: AtlassianUser;
93
93
  tokens: AtlassianTokens;
94
- }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
94
+ }>): import("nuxt/server").EventHandler<Promise<any>>;
95
95
  export {};
@@ -1,14 +1,14 @@
1
- import { eventHandler, getQuery, sendRedirect } from "h3";
1
+ import { defineEventHandler, createError, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
+ import { $fetch } from "ofetch";
2
3
  import { withQuery } from "ufo";
3
4
  import { defu } from "defu";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken } from "../utils.js";
5
- import { useRuntimeConfig, createError } from "#imports";
6
6
  export function defineOAuthAtlassianEventHandler({
7
7
  config,
8
8
  onSuccess,
9
9
  onError
10
10
  }) {
11
- return eventHandler(async (event) => {
11
+ return defineEventHandler(async (event) => {
12
12
  config = defu(config, useRuntimeConfig().oauth?.atlassian, {
13
13
  authorizationURL: "https://auth.atlassian.com/authorize",
14
14
  tokenURL: "https://auth.atlassian.com/oauth/token",
@@ -45,7 +45,7 @@ export function defineOAuthAtlassianEventHandler({
45
45
  }
46
46
  if (query.error) {
47
47
  const error = createError({
48
- statusCode: 401,
48
+ status: 401,
49
49
  message: `Atlassian login failed: ${query.error || "Unknown error"}`,
50
50
  data: query
51
51
  });
@@ -78,8 +78,8 @@ export function defineOAuthAtlassianEventHandler({
78
78
  });
79
79
  if (user.account_status === "inactive") {
80
80
  const error = createError({
81
- statusCode: 403,
82
- statusMessage: "Atlassian account is inactive",
81
+ status: 403,
82
+ message: "Atlassian account is inactive",
83
83
  data: { accountStatus: user.account_status }
84
84
  });
85
85
  if (!onError) throw error;
@@ -87,8 +87,8 @@ export function defineOAuthAtlassianEventHandler({
87
87
  }
88
88
  if (!user.email_verified) {
89
89
  const error = createError({
90
- statusCode: 400,
91
- statusMessage: "Email address is not verified",
90
+ status: 400,
91
+ message: "Email address is not verified",
92
92
  data: { email: user.email }
93
93
  });
94
94
  if (!onError) throw error;
@@ -57,4 +57,4 @@ export interface OAuthAuth0Config {
57
57
  */
58
58
  redirectURL?: string;
59
59
  }
60
- export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
60
+ export declare function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>): import("nuxt/server").EventHandler<Promise<any>>;
@@ -1,11 +1,11 @@
1
- import { eventHandler, getQuery, sendRedirect } from "h3";
1
+ import { defineEventHandler, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
+ import { $fetch } from "ofetch";
2
3
  import { withQuery } from "ufo";
3
4
  import { defu } from "defu";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken } from "../utils.js";
5
- import { useRuntimeConfig } from "#imports";
6
6
  export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }) {
7
- return eventHandler(async (event) => {
8
- config = defu(config, useRuntimeConfig(event).oauth?.auth0, {
7
+ return defineEventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig().oauth?.auth0, {
9
9
  authorizationParams: {}
10
10
  });
11
11
  if (!config.clientId || !config.clientSecret || !config.domain) {
@@ -27,4 +27,4 @@ export interface OAuthAuthentikConfig {
27
27
  */
28
28
  scope?: string[];
29
29
  }
30
- export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
30
+ export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): import("nuxt/server").EventHandler<Promise<any>>;
@@ -1,15 +1,15 @@
1
- import { eventHandler, getQuery, sendRedirect } from "h3";
1
+ import { defineEventHandler, createError, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
+ import { $fetch } from "ofetch";
2
3
  import { withQuery } from "ufo";
3
4
  import { defu } from "defu";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, handleInvalidState, handleState, requestAccessToken } from "../utils.js";
5
- import { useRuntimeConfig, createError } from "#imports";
6
6
  export function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }) {
7
- return eventHandler(async (event) => {
8
- config = defu(config, useRuntimeConfig(event).oauth?.authentik);
7
+ return defineEventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig().oauth?.authentik);
9
9
  const query = getQuery(event);
10
10
  if (query.error) {
11
11
  const error = createError({
12
- statusCode: 401,
12
+ status: 401,
13
13
  message: `Authentik login failed: ${query.error || "Unknown error"}`,
14
14
  data: query
15
15
  });
@@ -62,7 +62,7 @@ export function defineOAuthAuthentikEventHandler({ config, onSuccess, onError })
62
62
  });
63
63
  if (!user) {
64
64
  const error = createError({
65
- statusCode: 500,
65
+ status: 500,
66
66
  message: "Could not get Authentik user",
67
67
  data: tokens
68
68
  });
@@ -51,4 +51,4 @@ export interface OAuthAzureB2CConfig {
51
51
  */
52
52
  redirectURL?: string;
53
53
  }
54
- export declare function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAzureB2CConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
54
+ export declare function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAzureB2CConfig>): import("nuxt/server").EventHandler<Promise<any>>;
@@ -1,11 +1,11 @@
1
- import { eventHandler, getQuery, sendRedirect } from "h3";
1
+ import { defineEventHandler, createError, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
2
+ import { $fetch } from "ofetch";
2
3
  import { withQuery } from "ufo";
3
4
  import { defu } from "defu";
4
5
  import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, handlePkceVerifier, handleState, handleInvalidState } from "../utils.js";
5
- import { useRuntimeConfig, createError } from "#imports";
6
6
  export function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError }) {
7
- return eventHandler(async (event) => {
8
- config = defu(config, useRuntimeConfig(event).oauth?.azureb2c, {
7
+ return defineEventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig().oauth?.azureb2c, {
9
9
  authorizationParams: {}
10
10
  });
11
11
  const query = getQuery(event);
@@ -63,7 +63,7 @@ export function defineOAuthAzureB2CEventHandler({ config, onSuccess, onError })
63
63
  });
64
64
  if (user.error) {
65
65
  const error = createError({
66
- statusCode: 401,
66
+ status: 401,
67
67
  message: `azureb2c login failed: ${user.error || "Unknown error"}`,
68
68
  data: user
69
69
  });