nuxt-auth-utils 0.5.22 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -240,6 +240,7 @@ It can also be set using environment variables:
240
240
  - LiveChat
241
241
  - Microsoft
242
242
  - Okta
243
+ - Ory
243
244
  - PayPal
244
245
  - Polar
245
246
  - Salesforce
@@ -599,6 +600,18 @@ This is because the user session is stored in a secure cookie and cannot be acce
599
600
 
600
601
  **This means that you should not rely on the user session during prerendering.**
601
602
 
603
+ You may also choose to instruct Nuxt AUth Utils to fetch the user session only on the client side, with the `loadStrategy` option in your `nuxt.config.ts`:
604
+
605
+ ```ts
606
+ export default defineNuxtConfig({
607
+ auth: {
608
+ loadStrategy: 'client-only'
609
+ }
610
+ })
611
+ ```
612
+
613
+ When using the `client-only` load strategy, the user session can still be manually fetched on the server side by calling `fetch` from the `useUserSession` composable.
614
+
602
615
  ### `<AuthState>` component
603
616
 
604
617
  You can use the `<AuthState>` component to safely display auth-related data in your components without worrying about the rendering mode.
@@ -616,7 +629,7 @@ One common use case if the Login button in the header:
616
629
  </template>
617
630
  ```
618
631
 
619
- If the page is cached or prerendered, nothing will be rendered until the user session is fetched on the client-side.
632
+ If the page is cached or prerendered or the load strategy set as `client-only`, nothing will be rendered until the user session is fetched on the client-side.
620
633
 
621
634
  You can use the `placeholder` slot to show a placeholder on server-side and while the user session is being fetched on client-side for the prerendered pages:
622
635
 
package/dist/module.d.mts CHANGED
@@ -22,6 +22,11 @@ interface ModuleOptions {
22
22
  */
23
23
  scrypt?: ScryptConfig;
24
24
  };
25
+ /**
26
+ * Session load strategy
27
+ * @default 'server-first'
28
+ */
29
+ loadStrategy?: 'server-first' | 'client-only';
25
30
  }
26
31
  declare module 'nuxt/schema' {
27
32
  interface RuntimeConfig {
@@ -33,7 +38,13 @@ declare module 'nuxt/schema' {
33
38
  */
34
39
  session: SessionConfig;
35
40
  }
41
+ interface PublicRuntimeConfig {
42
+ auth: {
43
+ loadStrategy: 'server-first' | 'client-only';
44
+ };
45
+ }
36
46
  }
37
47
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
38
48
 
39
- export { type ModuleOptions, _default as default };
49
+ export { _default as default };
50
+ export type { ModuleOptions };
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.5.22",
4
+ "version": "0.5.24",
5
5
  "builder": {
6
- "@nuxt/module-builder": "0.8.4",
7
- "unbuild": "2.0.0"
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "3.6.1"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -16,7 +16,8 @@ const module = defineNuxtModule({
16
16
  atproto: false,
17
17
  hash: {
18
18
  scrypt: {}
19
- }
19
+ },
20
+ loadStrategy: "server-first"
20
21
  },
21
22
  async setup(options, nuxt) {
22
23
  const resolver = createResolver(import.meta.url);
@@ -74,7 +75,7 @@ const module = defineNuxtModule({
74
75
  const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
75
76
  runtimeConfig.session = defu(runtimeConfig.session, {
76
77
  name: "nuxt-session",
77
- password: process.env[envSessionPassword] || "",
78
+ password: "",
78
79
  cookie: {
79
80
  sameSite: "lax"
80
81
  }
@@ -82,18 +83,21 @@ const module = defineNuxtModule({
82
83
  runtimeConfig.hash = defu(runtimeConfig.hash, {
83
84
  scrypt: options.hash?.scrypt
84
85
  });
85
- if (nuxt.options.dev && !runtimeConfig.session.password) {
86
- runtimeConfig.session.password = randomUUID().replace(/-/g, "");
86
+ if (nuxt.options.dev && !process.env[envSessionPassword]) {
87
+ const password = process.env[envSessionPassword] = randomUUID().replace(/-/g, "");
87
88
  const envPath = join(nuxt.options.rootDir, ".env");
88
89
  const envContent = await readFile(envPath, "utf-8").catch(() => "");
89
90
  if (!envContent.includes(envSessionPassword)) {
90
91
  await writeFile(
91
92
  envPath,
92
- `${envContent ? envContent + "\n" : envContent}${envSessionPassword}=${runtimeConfig.session.password}`,
93
+ `${envContent ? envContent + "\n" : envContent}${envSessionPassword}=${password}`,
93
94
  "utf-8"
94
95
  );
95
96
  }
96
97
  }
98
+ runtimeConfig.public.auth = defu(runtimeConfig.public.auth, {
99
+ loadStrategy: options.loadStrategy ?? "server-first"
100
+ });
97
101
  runtimeConfig.webauthn = defu(runtimeConfig.webauthn, {
98
102
  register: {},
99
103
  authenticate: {}
@@ -353,6 +357,12 @@ const module = defineNuxtModule({
353
357
  redirectURL: "",
354
358
  scope: ""
355
359
  });
360
+ runtimeConfig.oauth.roblox = defu(runtimeConfig.oauth.roblox, {
361
+ clientId: "",
362
+ clientSecret: "",
363
+ redirectURL: "",
364
+ scope: ""
365
+ });
356
366
  runtimeConfig.oauth.okta = defu(runtimeConfig.oauth.okta, {
357
367
  clientId: "",
358
368
  clientSecret: "",
@@ -361,6 +371,16 @@ const module = defineNuxtModule({
361
371
  scope: [],
362
372
  redirectURL: ""
363
373
  });
374
+ runtimeConfig.oauth.ory = defu(runtimeConfig.oauth.ory, {
375
+ clientId: "",
376
+ clientSecret: "",
377
+ sdkURL: "",
378
+ redirectURL: "",
379
+ scope: [],
380
+ authorizationURL: "",
381
+ tokenURL: "",
382
+ userURL: ""
383
+ });
364
384
  }
365
385
  });
366
386
 
@@ -1,7 +1,6 @@
1
- <script lang="ts" setup>
2
- import { useUserSession } from '#imports'
3
-
4
- const { loggedIn, user, session, clear, ready } = useUserSession()
1
+ <script setup>
2
+ import { useUserSession } from "#imports";
3
+ const { loggedIn, user, session, clear, ready } = useUserSession();
5
4
  </script>
6
5
 
7
6
  <template>
@@ -0,0 +1,19 @@
1
+ declare var __VLS_1: {
2
+ loggedIn: boolean;
3
+ user: import("../../types/index.js").User | null;
4
+ session: import("../../types/index.js").UserSession | null;
5
+ clear: () => Promise<void>;
6
+ }, __VLS_3: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_1) => any;
9
+ } & {
10
+ placeholder?: (props: typeof __VLS_3) => any;
11
+ };
12
+ declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
13
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
14
+ export default _default;
15
+ type __VLS_WithSlots<T, S> = T & {
16
+ new (): {
17
+ $slots: S;
18
+ };
19
+ };
@@ -2,7 +2,7 @@ import { defineNuxtPlugin, useUserSession, useError } from "#imports";
2
2
  export default defineNuxtPlugin(async (nuxtApp) => {
3
3
  if (!nuxtApp.payload.serverRendered) {
4
4
  await useUserSession().fetch();
5
- } else if (Boolean(nuxtApp.payload.prerenderedAt) || Boolean(nuxtApp.payload.isCached)) {
5
+ } else if (Boolean(nuxtApp.payload.prerenderedAt) || Boolean(nuxtApp.payload.isCached) || nuxtApp.$config.public.auth.loadStrategy === "client-only") {
6
6
  nuxtApp.hook("app:mounted", async () => {
7
7
  await useUserSession().fetch();
8
8
  });
@@ -4,7 +4,7 @@ export default defineNuxtPlugin({
4
4
  enforce: "pre",
5
5
  async setup(nuxtApp) {
6
6
  nuxtApp.payload.isCached = Boolean(useRequestEvent()?.context.cache);
7
- if (nuxtApp.payload.serverRendered && !nuxtApp.payload.prerenderedAt && !nuxtApp.payload.isCached) {
7
+ if (nuxtApp.payload.serverRendered && !nuxtApp.payload.prerenderedAt && !nuxtApp.payload.isCached && nuxtApp.$config.public.auth.loadStrategy !== "client-only") {
8
8
  await useUserSession().fetch();
9
9
  }
10
10
  }
@@ -1,6 +1,6 @@
1
1
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
2
  [key: string]: unknown;
3
3
  id: string;
4
- user?: import("../../types").User;
4
+ user?: import("../../types/index.js").User;
5
5
  }>>;
6
6
  export default _default;
@@ -21,5 +21,10 @@ export interface OAuthAuthentikConfig {
21
21
  * @default process.env.NUXT_OAUTH_AUTHENTIK_REDIRECT_URL or current URL
22
22
  */
23
23
  redirectURL?: string;
24
+ /**
25
+ * Authentik Scope
26
+ * @default ['openid', 'profile', 'email']
27
+ */
28
+ scope?: string[];
24
29
  }
25
30
  export declare function defineOAuthAuthentikEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuthentikConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -29,7 +29,7 @@ export function defineOAuthAuthentikEventHandler({ config, onSuccess, onError })
29
29
  response_type: "code",
30
30
  client_id: config.clientId,
31
31
  redirect_uri: redirectURL,
32
- scope: ["openid", "profile", "email"].join(" ")
32
+ scope: (config.scope || ["openid", "profile", "email"]).join(" ")
33
33
  })
34
34
  );
35
35
  }
@@ -1,7 +1,7 @@
1
1
  import { eventHandler, getQuery, sendRedirect, createError } from "h3";
2
2
  import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
- import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
4
+ import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handleState, requestAccessToken } from "../utils.js";
5
5
  import { useRuntimeConfig } from "#imports";
6
6
  export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
7
7
  return eventHandler(async (event) => {
@@ -25,6 +25,7 @@ export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
25
25
  return handleMissingConfiguration(event, "github", ["clientId", "clientSecret"], onError);
26
26
  }
27
27
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
28
+ const state = await handleState(event);
28
29
  if (!query.code) {
29
30
  config.scope = config.scope || [];
30
31
  if (config.emailRequired && !config.scope.includes("user:email")) {
@@ -36,10 +37,14 @@ export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
36
37
  client_id: config.clientId,
37
38
  redirect_uri: redirectURL,
38
39
  scope: config.scope.join(" "),
40
+ state,
39
41
  ...config.authorizationParams
40
42
  })
41
43
  );
42
44
  }
45
+ if (query.state !== state) {
46
+ return handleInvalidState(event, "github", onError);
47
+ }
43
48
  const tokens = await requestAccessToken(config.tokenURL, {
44
49
  body: {
45
50
  grant_type: "authorization_code",
@@ -0,0 +1,51 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ /**
3
+ * Ory OAuth2
4
+ * @see https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow
5
+ */
6
+ export interface OAuthOryConfig {
7
+ /**
8
+ * Ory OAuth Client ID
9
+ * @default process.env.NUXT_OAUTH_ORY_CLIENT_ID
10
+ */
11
+ clientId?: string;
12
+ /**
13
+ * Ory OAuth Client Secret
14
+ * @default process.env.NUXT_OAUTH_ORY_CLIENT_SECRET
15
+ */
16
+ clientSecret?: string;
17
+ /**
18
+ * Ory OAuth SDK URL
19
+ * @default "https://playground.projects.oryapis.com" || process.env.NUXT_OAUTH_ORY_SDK_URL
20
+ */
21
+ sdkURL?: string;
22
+ /**
23
+ * Ory OAuth Scope
24
+ * @default ['openid', 'offline']
25
+ * @see https://www.ory.sh/docs/oauth2-oidc/openid-connect-claims-scope-custom
26
+ * @example ['openid', 'offline', 'email']
27
+ */
28
+ scope?: string[] | string;
29
+ /**
30
+ * Ory OAuth Authorization URL
31
+ * @default '/oauth2/auth'
32
+ */
33
+ authorizationURL?: string;
34
+ /**
35
+ * Ory OAuth Token URL
36
+ * @default '/oauth2/token'
37
+ */
38
+ tokenURL?: string;
39
+ /**
40
+ * Extra authorization parameters to provide to the authorization URL
41
+ * @example { allow_signup: 'true' }
42
+ */
43
+ authorizationParams?: Record<string, string>;
44
+ /**
45
+ * Ory OAuth Userinfo URL
46
+ * @see https://www.ory.sh/docs/oauth2-oidc/userinfo-oidc
47
+ * @default '/userinfo'
48
+ */
49
+ userURL?: string;
50
+ }
51
+ export declare function defineOAuthOryEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthOryConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,101 @@
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import {
5
+ getOAuthRedirectURL,
6
+ handleAccessTokenErrorResponse,
7
+ handleInvalidState,
8
+ handleMissingConfiguration,
9
+ handlePkceVerifier,
10
+ handleState,
11
+ requestAccessToken
12
+ } from "../utils.js";
13
+ import { createError, useRuntimeConfig } from "#imports";
14
+ export function defineOAuthOryEventHandler({ config, onSuccess, onError }) {
15
+ return eventHandler(async (event) => {
16
+ config = defu(config, useRuntimeConfig(event).oauth?.ory, {
17
+ scope: ["openid", "offline"],
18
+ sdkURL: "https://playground.projects.oryapis.com",
19
+ authorizationURL: "/oauth2/auth",
20
+ tokenURL: "/oauth2/token",
21
+ userURL: "/userinfo",
22
+ authorizationParams: {}
23
+ });
24
+ const query = getQuery(event);
25
+ if (query.error) {
26
+ const error = createError({
27
+ statusCode: 401,
28
+ message: `Ory login failed: ${query.error || "Unknown error"}`,
29
+ data: query
30
+ });
31
+ if (!onError) throw error;
32
+ return onError(event, error);
33
+ }
34
+ if (!config.clientId || !config.sdkURL) {
35
+ return handleMissingConfiguration(event, "ory", ["clientId", "sdkURL"], onError);
36
+ }
37
+ const redirectURL = getOAuthRedirectURL(event);
38
+ if (Array.isArray(config.scope)) {
39
+ config.scope = Array.from(new Set(config.scope)).join(" ");
40
+ }
41
+ const verifier = await handlePkceVerifier(event);
42
+ const state = await handleState(event);
43
+ if (!query.code) {
44
+ const authorizationURL = `${config.sdkURL}${config.authorizationURL}`;
45
+ return sendRedirect(
46
+ event,
47
+ withQuery(authorizationURL, {
48
+ client_id: config.clientId,
49
+ response_type: "code",
50
+ redirect_uri: redirectURL,
51
+ scope: config.scope,
52
+ state,
53
+ code_challenge: verifier.code_challenge,
54
+ code_challenge_method: verifier.code_challenge_method,
55
+ ...config.authorizationParams
56
+ })
57
+ );
58
+ }
59
+ if (query.state !== state) {
60
+ handleInvalidState(event, "ory", onError);
61
+ }
62
+ const tokenURL = `${config.sdkURL}${config.tokenURL}`;
63
+ const tokens = await requestAccessToken(tokenURL, {
64
+ body: {
65
+ grant_type: "authorization_code",
66
+ client_id: config.clientId,
67
+ code: query.code,
68
+ redirect_uri: redirectURL,
69
+ scope: config.scope,
70
+ code_verifier: verifier.code_verifier
71
+ }
72
+ });
73
+ if (tokens.error) {
74
+ return handleAccessTokenErrorResponse(event, "ory", tokens, onError);
75
+ }
76
+ const tokenType = tokens.token_type;
77
+ const accessToken = tokens.access_token;
78
+ const userURL = `${config.sdkURL}${config.userURL}`;
79
+ const user = await $fetch(userURL, {
80
+ headers: {
81
+ "User-Agent": `Ory-${config.clientId}`,
82
+ "Authorization": `${tokenType} ${accessToken}`
83
+ }
84
+ }).catch((error) => {
85
+ return { error };
86
+ });
87
+ if (user.error) {
88
+ const error = createError({
89
+ statusCode: 401,
90
+ message: `Ory userinfo failed: ${user.error || "Unknown error"}`,
91
+ data: user
92
+ });
93
+ if (!onError) throw error;
94
+ return onError(event, error);
95
+ }
96
+ return onSuccess(event, {
97
+ tokens,
98
+ user
99
+ });
100
+ });
101
+ }
@@ -0,0 +1,126 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthRobloxConfig {
3
+ /**
4
+ * Roblox OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_ROBLOX_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Roblox OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_ROBLOX_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Roblox OAuth Scope
15
+ * Some scopes and claims listed are only available to official Roblox apps, e.g. email
16
+ * @default ['openid', 'profile']
17
+ * @see https://apis.roblox.com/oauth/.well-known/openid-configuration
18
+ * @example ['openid', 'profile', 'asset:read', 'universe-messaging-service:publish']
19
+ */
20
+ scope?: string[];
21
+ /**
22
+ * Roblox OAuth Authorization URL
23
+ * @default 'https://apis.roblox.com/oauth/v1/authorize'
24
+ */
25
+ authorizationURL?: string;
26
+ /**
27
+ * Roblox OAuth Token URL
28
+ * @default 'https://apis.roblox.com/oauth/v1/token'
29
+ */
30
+ tokenURL?: string;
31
+ /**
32
+ * Extra authorization parameters to provide to the authorization URL
33
+ * @see https://create.roblox.com/docs/cloud/auth/oauth2-reference#get-v1authorize
34
+ */
35
+ authorizationParams?: Record<string, string>;
36
+ /**
37
+ * Redirect URL to allow overriding for situations like prod failing to determine public hostname
38
+ * @default process.env.NUXT_OAUTH_ROBLOX_REDIRECT_URL or current URL
39
+ */
40
+ redirectURL?: string;
41
+ }
42
+ export interface OAuthRobloxUser {
43
+ /**
44
+ * The resource path of the user
45
+ * @example "users/123"
46
+ */
47
+ path: string;
48
+ /**
49
+ * The timestamp at which the user was created
50
+ * @readonly
51
+ * @example "2023-07-05T12:34:56Z"
52
+ */
53
+ createTime: string;
54
+ /**
55
+ * Unique ID that identifies a user in Roblox
56
+ * @readonly
57
+ * @example "123456"
58
+ */
59
+ id: string;
60
+ /**
61
+ * Unique username for a user in Roblox
62
+ * @example "exampleUser"
63
+ */
64
+ name: string;
65
+ /**
66
+ * Display name for the user
67
+ * @example "userDefinedName"
68
+ */
69
+ displayName: string;
70
+ /**
71
+ * User-defined information about themselves
72
+ * @example "Example User's bio"
73
+ */
74
+ about: string;
75
+ /**
76
+ * Current locale selected by the user as an IETF language code
77
+ * @example "en-US"
78
+ */
79
+ locale: string;
80
+ /**
81
+ * Whether the user is a premium user
82
+ * @readonly
83
+ * @example true
84
+ */
85
+ premium: boolean;
86
+ /**
87
+ * Specifies if the user is identity-verified
88
+ * Verification includes, but isn't limited to, non-VoIP phone numbers or government IDs
89
+ * Available only with the user.advanced:read scope
90
+ * @readonly
91
+ * @example true
92
+ */
93
+ idVerified: boolean;
94
+ /**
95
+ * User's social network profiles and visibility.
96
+ */
97
+ socialNetworkProfiles: {
98
+ /**
99
+ * Facebook profile URI.
100
+ */
101
+ facebook?: string;
102
+ /**
103
+ * Twitter profile URI.
104
+ */
105
+ twitter?: string;
106
+ /**
107
+ * YouTube profile URI.
108
+ */
109
+ youtube?: string;
110
+ /**
111
+ * Twitch profile URI.
112
+ */
113
+ twitch?: string;
114
+ /**
115
+ * Guilded profile URI.
116
+ */
117
+ guilded?: string;
118
+ /**
119
+ * Visibility of the social network profiles.
120
+ * Available only with the user.social:read scope
121
+ * @example "SOCIAL_NETWORK_VISIBILITY_UNSPECIFIED"
122
+ */
123
+ visibility: 'SOCIAL_NETWORK_VISIBILITY_UNSPECIFIED' | 'NO_ONE' | 'FRIENDS' | 'FRIENDS_AND_FOLLOWING' | 'FRIENDS_FOLLOWING_AND_FOLLOWERS' | 'EVERYONE';
124
+ };
125
+ }
126
+ export declare function defineOAuthRobloxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthRobloxConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,64 @@
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function defineOAuthRobloxEventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.roblox, {
9
+ authorizationURL: "https://apis.roblox.com/oauth/v1/authorize",
10
+ tokenURL: "https://apis.roblox.com/oauth/v1/token",
11
+ authorizationParams: {}
12
+ });
13
+ const query = getQuery(event);
14
+ if (!config.clientId || !config.clientSecret) {
15
+ return handleMissingConfiguration(event, "roblox", ["clientId", "clientSecret"], onError);
16
+ }
17
+ if (query.error) {
18
+ return handleAccessTokenErrorResponse(event, "roblox", query, onError);
19
+ }
20
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
21
+ if (!query.code) {
22
+ config.scope = config.scope || [];
23
+ return sendRedirect(
24
+ event,
25
+ withQuery(config.authorizationURL, {
26
+ response_type: "code",
27
+ client_id: config.clientId,
28
+ redirect_uri: redirectURL,
29
+ scope: config.scope.join(" "),
30
+ ...config.authorizationParams
31
+ })
32
+ );
33
+ }
34
+ const tokens = await requestAccessToken(config.tokenURL, {
35
+ body: {
36
+ client_id: config.clientId,
37
+ client_secret: config.clientSecret,
38
+ grant_type: "authorization_code",
39
+ redirect_uri: redirectURL,
40
+ code: query.code
41
+ }
42
+ });
43
+ if (tokens.error) {
44
+ return handleAccessTokenErrorResponse(event, "roblox", tokens, onError);
45
+ }
46
+ const accessToken = tokens.access_token;
47
+ const userInfo = await $fetch("https://apis.roblox.com/oauth/userinfo", {
48
+ headers: {
49
+ "user-agent": "Nuxt Auth Utils",
50
+ "Authorization": `Bearer ${accessToken}`
51
+ }
52
+ });
53
+ const user = await $fetch(`https://apis.roblox.com/cloud/v2/users/${userInfo.sub}`, {
54
+ headers: {
55
+ "user-agent": "Nuxt Auth Utils",
56
+ "Authorization": `Bearer ${accessToken}`
57
+ }
58
+ });
59
+ return onSuccess(event, {
60
+ tokens,
61
+ user
62
+ });
63
+ });
64
+ }
@@ -1,5 +1,3 @@
1
+ import type { EventHandler } from 'h3';
1
2
  import type { WebAuthnAuthenticateEventHandlerOptions, WebAuthnCredential } from '#auth-utils';
2
- export declare function defineWebAuthnAuthenticateEventHandler<T extends WebAuthnCredential>({ storeChallenge, getChallenge, getCredential, allowCredentials, getOptions, onSuccess, onError, }: WebAuthnAuthenticateEventHandlerOptions<T>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | import("@simplewebauthn/server").VerifiedAuthenticationResponse | {
3
- requestOptions: import("@simplewebauthn/types").PublicKeyCredentialRequestOptionsJSON;
4
- attemptId: string;
5
- }>>;
3
+ export declare function defineWebAuthnAuthenticateEventHandler<T extends WebAuthnCredential>({ storeChallenge, getChallenge, getCredential, allowCredentials, getOptions, onSuccess, onError, }: WebAuthnAuthenticateEventHandlerOptions<T>): EventHandler;
@@ -28,7 +28,7 @@ export function defineWebAuthnAuthenticateEventHandler({
28
28
  _config.allowCredentials = await allowCredentials(event, body.userName);
29
29
  }
30
30
  const options = await generateAuthenticationOptions(_config);
31
- const attemptId = bufferToBase64URLString(getRandomValues(new Uint8Array(32)));
31
+ const attemptId = bufferToBase64URLString(getRandomValues(new Uint8Array(32)).buffer);
32
32
  if (storeChallenge) {
33
33
  await storeChallenge(event, options.challenge, attemptId);
34
34
  }
@@ -1,8 +1,5 @@
1
- import type { H3Event } from 'h3';
1
+ import type { H3Event, EventHandler } from 'h3';
2
2
  import type { ValidateUserFunction } from '../../../types/webauthn.js';
3
3
  import type { WebAuthnUser, WebAuthnRegisterEventHandlerOptions } from '#auth-utils';
4
- export declare function defineWebAuthnRegisterEventHandler<T extends WebAuthnUser>({ storeChallenge, getChallenge, getOptions, validateUser, excludeCredentials, onSuccess, onError, }: WebAuthnRegisterEventHandlerOptions<T>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | import("@simplewebauthn/server").VerifiedRegistrationResponse | {
5
- creationOptions: import("@simplewebauthn/types").PublicKeyCredentialCreationOptionsJSON;
6
- attemptId: string;
7
- }>>;
4
+ export declare function defineWebAuthnRegisterEventHandler<T extends WebAuthnUser>({ storeChallenge, getChallenge, getOptions, validateUser, excludeCredentials, onSuccess, onError, }: WebAuthnRegisterEventHandlerOptions<T>): EventHandler;
8
5
  export declare function validateUserData<T>(userBody: WebAuthnUser, event: H3Event, fn: ValidateUserFunction<T>): Promise<T>;
@@ -43,7 +43,7 @@ export function defineWebAuthnRegisterEventHandler({
43
43
  _config.excludeCredentials = await excludeCredentials(event, user.userName);
44
44
  }
45
45
  const options = await generateRegistrationOptions(_config);
46
- const attemptId = bufferToBase64URLString(getRandomValues(new Uint8Array(32)));
46
+ const attemptId = bufferToBase64URLString(getRandomValues(new Uint8Array(32)).buffer);
47
47
  if (storeChallenge) {
48
48
  await storeChallenge(event, options.challenge, attemptId);
49
49
  }
@@ -81,7 +81,7 @@ export function defineWebAuthnRegisterEventHandler({
81
81
  user,
82
82
  credential: {
83
83
  id: verification.registrationInfo.credential.id,
84
- publicKey: bufferToBase64URLString(verification.registrationInfo.credential.publicKey),
84
+ publicKey: bufferToBase64URLString(verification.registrationInfo.credential.publicKey.buffer),
85
85
  counter: verification.registrationInfo.credential.counter,
86
86
  backedUp: verification.registrationInfo.credentialBackedUp,
87
87
  transports: verification.registrationInfo.credential.transports
@@ -1,2 +1,2 @@
1
- declare const _default: import("nitropack/types").NitroAppPlugin;
1
+ declare const _default: import("nitropack").NitroAppPlugin;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("nitropack/types").NitroAppPlugin;
1
+ declare const _default: import("nitropack").NitroAppPlugin;
2
2
  export default _default;
@@ -49,6 +49,9 @@ function _useSession(event, config = {}) {
49
49
  const runtimeConfig = useRuntimeConfig(isEvent(event) ? event : void 0);
50
50
  const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
51
51
  sessionConfig = defu({ password: process.env[envSessionPassword] }, runtimeConfig.session);
52
+ if (!sessionConfig.password) {
53
+ console.error(`[nuxt-auth-utils] ${envSessionPassword} environment variable or runtimeConfig.session.password was not set.`);
54
+ }
52
55
  }
53
56
  const finalConfig = defu(config, sessionConfig);
54
57
  return useSession(event, finalConfig);
@@ -1,6 +1,6 @@
1
1
  import type { H3Event, H3Error } from 'h3';
2
2
  export type ATProtoProvider = 'bluesky';
3
- export type OAuthProvider = ATProtoProvider | 'atlassian' | 'auth0' | 'authentik' | 'azureb2c' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'kick' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | 'livechat' | 'salesforce' | 'slack' | 'heroku' | 'okta' | (string & {});
3
+ export type OAuthProvider = ATProtoProvider | 'atlassian' | 'auth0' | 'authentik' | 'azureb2c' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'kick' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | 'livechat' | 'salesforce' | 'slack' | 'heroku' | 'roblox' | 'okta' | 'ory' | (string & {});
4
4
  export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
5
5
  export interface OAuthConfig<TConfig, TResult = {
6
6
  user: any;
@@ -2,15 +2,16 @@ export const atprotoProviders = ["bluesky"];
2
2
  export const atprotoProviderDefaultClientMetadata = {
3
3
  clientMetadataFilename: "",
4
4
  clientName: "",
5
- clientUri: "",
6
- logoUri: "",
7
- policyUri: "",
8
- tosUri: "",
5
+ clientUri: void 0,
6
+ logoUri: void 0,
7
+ policyUri: void 0,
8
+ tosUri: void 0,
9
9
  scope: ["atproto"],
10
10
  grantTypes: ["authorization_code"],
11
11
  responseTypes: ["code"],
12
12
  applicationType: "web",
13
- redirectUris: [],
13
+ // @ts-expect-error TypeScript is too smart for its own good
14
+ redirectUris: void 0,
14
15
  dpopBoundAccessTokens: true,
15
16
  tokenEndpointAuthMethod: "none"
16
17
  };
package/dist/types.d.mts CHANGED
@@ -1 +1,3 @@
1
- export { type ModuleOptions, default } from './module.js'
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,16 +8,21 @@
8
8
  },
9
9
  "license": "MIT",
10
10
  "type": "module",
11
- "packageManager": "pnpm@9.15.4",
11
+ "packageManager": "pnpm@10.15.1",
12
12
  "exports": {
13
13
  ".": {
14
- "types": "./dist/types.d.ts",
15
- "import": "./dist/module.mjs",
16
- "require": "./dist/module.cjs"
14
+ "types": "./dist/types.d.mts",
15
+ "import": "./dist/module.mjs"
16
+ }
17
+ },
18
+ "main": "./dist/module.mjs",
19
+ "typesVersions": {
20
+ "*": {
21
+ ".": [
22
+ "./dist/types.d.mts"
23
+ ]
17
24
  }
18
25
  },
19
- "main": "./dist/module.cjs",
20
- "types": "./dist/types.d.ts",
21
26
  "files": [
22
27
  "dist"
23
28
  ],
@@ -30,20 +35,19 @@
30
35
  "lint": "eslint .",
31
36
  "lint:fix": "eslint . --fix",
32
37
  "test": "vitest run",
33
- "test:types": "vue-tsc --noEmit",
34
- "test:types:playground": "nuxt typecheck playground",
38
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
35
39
  "test:watch": "vitest watch"
36
40
  },
37
41
  "dependencies": {
38
- "@adonisjs/hash": "^9.0.5",
39
- "@nuxt/kit": "^3.15.4",
42
+ "@adonisjs/hash": "^9.1.1",
43
+ "@nuxt/kit": "^4.1.0",
40
44
  "defu": "^6.1.4",
41
- "h3": "^1.14.0",
45
+ "h3": "^1.15.4",
42
46
  "hookable": "^5.5.3",
43
- "jose": "^5.9.6",
47
+ "jose": "^6.1.0",
44
48
  "ofetch": "^1.4.1",
45
- "openid-client": "^6.1.7",
46
- "pathe": "^2.0.2",
49
+ "openid-client": "^6.7.1",
50
+ "pathe": "^2.0.3",
47
51
  "scule": "^1.3.0",
48
52
  "uncrypto": "^0.1.3"
49
53
  },
@@ -68,20 +72,20 @@
68
72
  }
69
73
  },
70
74
  "devDependencies": {
71
- "@iconify-json/simple-icons": "^1.2.23",
72
- "@nuxt/devtools": "2.0.0-beta.7",
73
- "@nuxt/eslint-config": "^0.7.6",
74
- "@nuxt/module-builder": "^0.8.4",
75
- "@nuxt/schema": "^3.15.4",
76
- "@nuxt/test-utils": "^3.15.4",
77
- "@nuxt/ui": "^2.21.0",
78
- "@nuxt/ui-pro": "^1.7.0",
79
- "@simplewebauthn/types": "^11.0.0",
80
- "changelogen": "^0.5.7",
81
- "eslint": "^9.19.0",
82
- "nuxt": "^3.15.4",
83
- "typescript": "5.6.3",
84
- "vitest": "^3.0.5",
85
- "vue-tsc": "^2.2.0"
75
+ "@iconify-json/simple-icons": "^1.2.50",
76
+ "@nuxt/devtools": "2.6.3",
77
+ "@nuxt/eslint-config": "^1.9.0",
78
+ "@nuxt/module-builder": "^1.0.2",
79
+ "@nuxt/schema": "^4.1.0",
80
+ "@nuxt/test-utils": "^3.19.2",
81
+ "@types/node": "latest",
82
+ "@nuxt/ui": "^4.0.0-alpha.1",
83
+ "@simplewebauthn/types": "^12.0.0",
84
+ "changelogen": "^0.6.2",
85
+ "eslint": "^9.34.0",
86
+ "nuxt": "^4.1.0",
87
+ "typescript": "5.9.2",
88
+ "vitest": "^3.2.4",
89
+ "vue-tsc": "^3.0.6"
86
90
  }
87
91
  }
package/dist/module.cjs DELETED
@@ -1,5 +0,0 @@
1
- module.exports = function(...args) {
2
- return import('./module.mjs').then(m => m.default.call(this, ...args))
3
- }
4
- const _meta = module.exports.meta = require('./module.json')
5
- module.exports.getMeta = () => Promise.resolve(_meta)
package/dist/module.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
2
- import { ScryptConfig } from '@adonisjs/hash/types';
3
- import { SessionConfig } from 'h3';
4
-
5
- interface ModuleOptions {
6
- /**
7
- * Enable WebAuthn (Passkeys)
8
- * @default false
9
- */
10
- webAuthn?: boolean;
11
- /**
12
- * Enable atproto OAuth (Bluesky, etc.)
13
- * @default false
14
- */
15
- atproto?: boolean;
16
- /**
17
- * Hash options used for password hashing
18
- */
19
- hash?: {
20
- /**
21
- * scrypt options used for password hashing
22
- */
23
- scrypt?: ScryptConfig;
24
- };
25
- }
26
- declare module 'nuxt/schema' {
27
- interface RuntimeConfig {
28
- hash: {
29
- scrypt: ScryptConfig;
30
- };
31
- /**
32
- * Session configuration
33
- */
34
- session: SessionConfig;
35
- }
36
- }
37
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
38
-
39
- export { type ModuleOptions, _default as default };
package/dist/types.d.ts DELETED
@@ -1 +0,0 @@
1
- export { type ModuleOptions, default } from './module'