nuxt-auth-utils 0.3.6 → 0.3.8

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
@@ -15,9 +15,9 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
15
15
  ## Features
16
16
 
17
17
  - [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
18
- - [15+ OAuth Providers](#supported-oauth-providers)
19
- - [Vue composable](#vue-composable)
20
- - [Server utils](#server-utils)
18
+ - [20+ OAuth Providers](#supported-oauth-providers)
19
+ - [`useUserSession()` Vue composable](#vue-composable)
20
+ - [Tree-shakable server utils](#server-utils)
21
21
  - [`<AuthState>` component](#authstate-component)
22
22
  - [Extendable with hooks](#extend-session)
23
23
 
@@ -105,6 +105,9 @@ interface UserSessionComposable {
105
105
  }
106
106
  ```
107
107
 
108
+ > [!IMPORTANT]
109
+ > Nuxt Auth Utils uses the `/api/_auth/session` route for session management. Ensure your API route middleware doesn't interfere with this path.
110
+
108
111
  ## Server Utils
109
112
 
110
113
  The following helpers are auto-imported in your `server/` directory.
@@ -114,13 +117,18 @@ The following helpers are auto-imported in your `server/` directory.
114
117
  ```ts
115
118
  // Set a user session, note that this data is encrypted in the cookie but can be decrypted with an API call
116
119
  // Only store the data that allow you to recognize a user, but do not store sensitive data
117
- // Merges new data with existing data using defu()
120
+ // Merges new data with existing data using unjs/defu library
118
121
  await setUserSession(event, {
122
+ // User data
119
123
  user: {
120
- // ... user data
124
+ login: 'atinux'
125
+ },
126
+ // Private data accessible on server/ routes
127
+ secure: {
128
+ apiToken: '1234567890'
121
129
  },
130
+ // Any extra fields for the session data
122
131
  loggedInAt: new Date()
123
- // Any extra fields
124
132
  })
125
133
 
126
134
  // Replace a user session. Same behaviour as setUserSession, except it does not merge data with existing data
@@ -153,6 +161,9 @@ declare module '#auth-utils' {
153
161
  export {}
154
162
  ```
155
163
 
164
+ > [!IMPORTANT]
165
+ > Since we encrypt and store session data in cookies, we're constrained by the 4096-byte cookie size limit. Store only essential information.
166
+
156
167
  ### OAuth Event Handlers
157
168
 
158
169
  All handlers can be auto-imported and used in your server routes or API routes.
@@ -192,7 +203,9 @@ It can also be set using environment variables:
192
203
  - Discord
193
204
  - Facebook
194
205
  - GitHub
206
+ - GitLab
195
207
  - Google
208
+ - Instagram
196
209
  - Keycloak
197
210
  - LinkedIn
198
211
  - Microsoft
@@ -201,6 +214,7 @@ It can also be set using environment variables:
201
214
  - Steam
202
215
  - TikTok
203
216
  - Twitch
217
+ - VK
204
218
  - X (Twitter)
205
219
  - XSUAA
206
220
  - Yandex
@@ -350,6 +364,14 @@ Our defaults are:
350
364
  }
351
365
  ```
352
366
 
367
+ You can also overwrite the session config by passing it as 3rd argument of the `setUserSession` and `replaceUserSession` functions:
368
+
369
+ ```ts
370
+ await setUserSession(event, { ... } , {
371
+ maxAge: 60 * 60 * 24 * 7 // 1 week
372
+ })
373
+ ```
374
+
353
375
  Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15c1647e1369e7d7ef19f567d/src/utils/session.ts#L20) for all options.
354
376
 
355
377
  ## More
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.3.6",
4
+ "version": "0.3.8",
5
5
  "builder": {
6
- "@nuxt/module-builder": "0.8.3",
6
+ "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -13,7 +13,9 @@ const module = defineNuxtModule({
13
13
  defaults: {},
14
14
  async setup(options, nuxt) {
15
15
  const resolver = createResolver(import.meta.url);
16
- nuxt.options.alias["#auth-utils"] = resolver.resolve("./runtime/types/index");
16
+ nuxt.options.alias["#auth-utils"] = resolver.resolve(
17
+ "./runtime/types/index"
18
+ );
17
19
  addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
18
20
  addImportsDir(resolver.resolve("./runtime/app/composables"));
19
21
  addPlugin(resolver.resolve("./runtime/app/plugins/session.server"));
@@ -58,6 +60,11 @@ const module = defineNuxtModule({
58
60
  clientSecret: "",
59
61
  redirectURL: ""
60
62
  });
63
+ runtimeConfig.oauth.gitlab = defu(runtimeConfig.oauth.gitlab, {
64
+ clientId: "",
65
+ clientSecret: "",
66
+ redirectURL: ""
67
+ });
61
68
  runtimeConfig.oauth.spotify = defu(runtimeConfig.oauth.spotify, {
62
69
  clientId: "",
63
70
  clientSecret: "",
@@ -124,6 +131,11 @@ const module = defineNuxtModule({
124
131
  clientSecret: "",
125
132
  redirectURL: ""
126
133
  });
134
+ runtimeConfig.oauth.instagram = defu(runtimeConfig.oauth.instagram, {
135
+ clientId: "",
136
+ clientSecret: "",
137
+ redirectURL: ""
138
+ });
127
139
  runtimeConfig.oauth.paypal = defu(runtimeConfig.oauth.paypal, {
128
140
  clientId: "",
129
141
  clientSecret: "",
@@ -144,6 +156,11 @@ const module = defineNuxtModule({
144
156
  domain: "",
145
157
  redirectURL: ""
146
158
  });
159
+ runtimeConfig.oauth.vk = defu(runtimeConfig.oauth.vk, {
160
+ clientId: "",
161
+ clientSecret: "",
162
+ redirectURL: ""
163
+ });
147
164
  runtimeConfig.oauth.yandex = defu(runtimeConfig.oauth.yandex, {
148
165
  clientId: "",
149
166
  clientSecret: "",
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -1,2 +1,5 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("#auth-utils").UserSession>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ [key: string]: unknown;
3
+ user?: import("#auth-utils").User;
4
+ }>>;
2
5
  export default _default;
@@ -5,5 +5,6 @@ export default eventHandler(async (event) => {
5
5
  if (session.user) {
6
6
  await sessionHooks.callHookParallel("fetch", session, event);
7
7
  }
8
- return session;
8
+ const { secure, ...data } = session;
9
+ return data;
9
10
  });
@@ -0,0 +1,47 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthGitLabConfig {
3
+ /**
4
+ * GitLab OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_GITLAB_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * GitLab OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_GITLAB_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * GitLab OAuth Scope
15
+ * @default ['read_user']
16
+ * @see https://docs.gitlab.com/ee/integration/oauth_provider.html#view-all-authorized-applications
17
+ * @example ['read_user']
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
+ * GitLab OAuth Authorization URL
27
+ * @default 'https://gitlab.com/oauth/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * GitLab OAuth Token URL
32
+ * @default 'https://gitlab.com/oauth/token'
33
+ */
34
+ tokenURL?: string;
35
+ /**
36
+ * Extra authorization parameters to provide to the authorization URL
37
+ * @see https://docs.gitlab.com/ee/integration/oauth_provider.html#view-all-authorized-applications
38
+ * @example { allow_signup: 'true' }
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_GITLAB_REDIRECT_URL
44
+ */
45
+ redirectURL?: string;
46
+ }
47
+ export declare function oauthGitLabEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGitLabConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,83 @@
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import {
5
+ handleMissingConfiguration,
6
+ handleAccessTokenErrorResponse,
7
+ getOAuthRedirectURL,
8
+ requestAccessToken
9
+ } from "../utils.js";
10
+ import { useRuntimeConfig, createError } from "#imports";
11
+ export function oauthGitLabEventHandler({
12
+ config,
13
+ onSuccess,
14
+ onError
15
+ }) {
16
+ return eventHandler(async (event) => {
17
+ config = defu(config, useRuntimeConfig(event).oauth?.gitlab, {
18
+ authorizationURL: "https://gitlab.com/oauth/authorize",
19
+ tokenURL: "https://gitlab.com/oauth/token",
20
+ authorizationParams: {}
21
+ });
22
+ const query = getQuery(event);
23
+ if (query.error) {
24
+ const error = createError({
25
+ statusCode: 401,
26
+ message: `GitLab login failed: ${query.error || "Unknown error"}`,
27
+ data: query
28
+ });
29
+ if (!onError) throw error;
30
+ return onError(event, error);
31
+ }
32
+ if (!config.clientId || !config.clientSecret) {
33
+ return handleMissingConfiguration(
34
+ event,
35
+ "gitlab",
36
+ ["clientId", "clientSecret"],
37
+ onError
38
+ );
39
+ }
40
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
41
+ if (!query.code) {
42
+ config.scope = config.scope || [];
43
+ if (!config.scope.length) {
44
+ config.scope.push("read_user");
45
+ }
46
+ if (config.emailRequired && !config.scope.includes("email")) {
47
+ config.scope.push("email");
48
+ }
49
+ return sendRedirect(
50
+ event,
51
+ withQuery(config.authorizationURL, {
52
+ response_type: "code",
53
+ client_id: config.clientId,
54
+ redirect_uri: redirectURL,
55
+ scope: config.scope.join(" "),
56
+ ...config.authorizationParams
57
+ })
58
+ );
59
+ }
60
+ const tokens = await requestAccessToken(config.tokenURL, {
61
+ body: {
62
+ grant_type: "authorization_code",
63
+ client_id: config.clientId,
64
+ client_secret: config.clientSecret,
65
+ redirect_uri: redirectURL,
66
+ code: query.code
67
+ }
68
+ });
69
+ if (tokens.error) {
70
+ return handleAccessTokenErrorResponse(event, "gitlab", tokens, onError);
71
+ }
72
+ const accessToken = tokens.access_token;
73
+ const user = await $fetch("https://gitlab.com/api/v4/user", {
74
+ headers: {
75
+ Authorization: `Bearer ${accessToken}`
76
+ }
77
+ });
78
+ return onSuccess(event, {
79
+ user,
80
+ tokens
81
+ });
82
+ });
83
+ }
@@ -0,0 +1,48 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthInstagramConfig {
3
+ /**
4
+ * Instagram OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_INSTAGRAM_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Instagram OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_INSTAGRAM_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Instagram OAuth Scope
15
+ * @default [ 'user_profile' ]
16
+ * @see https://developers.facebook.com/docs/instagram-basic-display-api/overview#permissions
17
+ * @example [ 'user_profile', 'user_media' ],
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Instagram OAuth User Fields
22
+ * @default [ 'id', 'username'],
23
+ * @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user#fields
24
+ * @example [ 'id', 'username', 'account_type', 'media_count' ],
25
+ */
26
+ fields?: string[];
27
+ /**
28
+ * Instagram OAuth Authorization URL
29
+ * @default 'https://api.instagram.com/oauth/authorize'
30
+ */
31
+ authorizationURL?: string;
32
+ /**
33
+ * Instagram OAuth Token URL
34
+ * @default 'https://api.instagram.com/oauth/access_token'
35
+ */
36
+ tokenURL?: string;
37
+ /**
38
+ * Extra authorization parameters to provide to the authorization URL
39
+ * @see https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow/
40
+ */
41
+ authorizationParams?: Record<string, string>;
42
+ /**
43
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
44
+ * @default process.env.NUXT_OAUTH_INSTAGRAM_REDIRECT_URL or current URL
45
+ */
46
+ redirectURL?: string;
47
+ }
48
+ export declare function oauthInstagramEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthInstagramConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,70 @@
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, createError } from "#imports";
6
+ export function oauthInstagramEventHandler({
7
+ config,
8
+ onSuccess,
9
+ onError
10
+ }) {
11
+ return eventHandler(async (event) => {
12
+ config = defu(config, useRuntimeConfig(event).oauth?.instagram, {
13
+ scope: ["user_profile"],
14
+ authorizationURL: "https://api.instagram.com/oauth/authorize",
15
+ tokenURL: "https://api.instagram.com/oauth/access_token",
16
+ authorizationParams: {}
17
+ });
18
+ const query = getQuery(event);
19
+ if (query.error) {
20
+ const error = createError({
21
+ statusCode: 401,
22
+ message: `Instagram login failed: ${query.error || "Unknown error"}`,
23
+ data: query
24
+ });
25
+ if (!onError) throw error;
26
+ return onError(event, error);
27
+ }
28
+ if (!config.clientId) {
29
+ return handleMissingConfiguration(event, "instagram", ["clientId"], onError);
30
+ }
31
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
32
+ if (!query.code) {
33
+ config.scope = config.scope || [];
34
+ return sendRedirect(
35
+ event,
36
+ withQuery(config.authorizationURL, {
37
+ client_id: config.clientId,
38
+ redirect_uri: redirectURL,
39
+ scope: config.scope.join(" "),
40
+ response_type: "code"
41
+ })
42
+ );
43
+ }
44
+ const tokens = await requestAccessToken(config.tokenURL, {
45
+ body: {
46
+ client_id: config.clientId,
47
+ client_secret: config.clientSecret,
48
+ grant_type: "authorization_code",
49
+ redirect_uri: redirectURL,
50
+ code: query.code
51
+ }
52
+ });
53
+ if (tokens.error) {
54
+ return handleAccessTokenErrorResponse(event, "instagram", tokens, onError);
55
+ }
56
+ const accessToken = tokens.access_token;
57
+ config.fields = config.fields || ["id", "username"];
58
+ const fields = config.fields.join(",");
59
+ const user = await $fetch(
60
+ `https://graph.instagram.com/v20.0/me?fields=${fields}&access_token=${accessToken}`
61
+ );
62
+ if (!user) {
63
+ throw new Error("Instagram login failed: no user found");
64
+ }
65
+ return onSuccess(event, {
66
+ user,
67
+ tokens
68
+ });
69
+ });
70
+ }
@@ -48,7 +48,8 @@ export function oauthPaypalEventHandler({ config, onSuccess, onError }) {
48
48
  },
49
49
  params: {
50
50
  grant_type: "authorization_code",
51
- redirect_uri: redirectURL,
51
+ redirect_uri: encodeURIComponent(redirectURL),
52
+ // <- PayPal requires to be URL encoded
52
53
  code: query.code
53
54
  }
54
55
  });
@@ -0,0 +1,46 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthVKConfig {
3
+ /**
4
+ * VK OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_VK_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * VK OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_VK_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * VK OAuth Scope
15
+ * @default []
16
+ * @see https://id.vk.com/about/business/go/docs/en/vkid/latest/vk-id/connection/api-integration/api-description#App-access-to-user-data
17
+ * @example ["email", "phone"]
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Require email from user, adds the ['login:email'] scope if not present
22
+ * @default false
23
+ */
24
+ emailRequired?: boolean;
25
+ /**
26
+ * VK OAuth Authorization URL
27
+ * @default 'https://id.vk.com/authorize'
28
+ */
29
+ authorizationURL?: string;
30
+ /**
31
+ * VK OAuth Token URL
32
+ * @default 'https://id.vk.com/oauth2/auth'
33
+ */
34
+ tokenURL?: string;
35
+ /**
36
+ * VK OAuth User URL
37
+ * @default 'https://id.vk.com/oauth2/user_info'
38
+ */
39
+ userURL?: string;
40
+ /**
41
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
42
+ * @default process.env.NUXT_OAUTH_VK_REDIRECT_URL or current URL
43
+ */
44
+ redirectURL?: string;
45
+ }
46
+ export declare function oauthVKEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthVKConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -0,0 +1,87 @@
1
+ import crypto from "node:crypto";
2
+ import { eventHandler, getQuery, sendRedirect } from "h3";
3
+ import { withQuery } from "ufo";
4
+ import { defu } from "defu";
5
+ import {
6
+ handleMissingConfiguration,
7
+ handleAccessTokenErrorResponse,
8
+ getOAuthRedirectURL,
9
+ requestAccessToken
10
+ } from "../utils.js";
11
+ import { useRuntimeConfig, createError } from "#imports";
12
+ export function oauthVKEventHandler({
13
+ config,
14
+ onSuccess,
15
+ onError
16
+ }) {
17
+ return eventHandler(async (event) => {
18
+ config = defu(config, useRuntimeConfig(event).oauth?.vk, {
19
+ authorizationURL: "https://id.vk.com/authorize",
20
+ tokenURL: "https://id.vk.com/oauth2/auth",
21
+ userURL: "https://id.vk.com/oauth2/user_info"
22
+ });
23
+ const query = getQuery(event);
24
+ if (!config.clientId || !config.clientSecret) {
25
+ return handleMissingConfiguration(
26
+ event,
27
+ "vk",
28
+ ["clientId", "clientSecret"],
29
+ onError
30
+ );
31
+ }
32
+ const codeVerifier = "verify";
33
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
34
+ if (!query.code) {
35
+ config.scope = config.scope || [];
36
+ if (config.emailRequired && !config.scope.includes("email")) {
37
+ config.scope.push("email");
38
+ }
39
+ return sendRedirect(
40
+ event,
41
+ withQuery(config.authorizationURL, {
42
+ response_type: "code",
43
+ client_id: config.clientId,
44
+ code_challenge: crypto.createHash("sha256").update(codeVerifier).digest("base64url"),
45
+ code_challenge_method: "s256",
46
+ state: crypto.randomUUID(),
47
+ redirect_uri: redirectURL,
48
+ scope: config.scope.join(" ")
49
+ })
50
+ );
51
+ }
52
+ const tokens = await requestAccessToken(config.tokenURL, {
53
+ body: {
54
+ grant_type: "authorization_code",
55
+ code: query.code,
56
+ code_verifier: codeVerifier,
57
+ client_id: config.clientId,
58
+ device_id: query.device_id,
59
+ redirect_uri: redirectURL
60
+ }
61
+ });
62
+ if (tokens.error) {
63
+ return handleAccessTokenErrorResponse(event, "vk", tokens, onError);
64
+ }
65
+ const accessToken = tokens.access_token;
66
+ const user = await $fetch(config.userURL, {
67
+ method: "POST",
68
+ body: {
69
+ access_token: accessToken,
70
+ client_id: config.clientId
71
+ }
72
+ });
73
+ if (!user) {
74
+ const error = createError({
75
+ statusCode: 500,
76
+ message: "Could not get VK user",
77
+ data: tokens
78
+ });
79
+ if (!onError) throw error;
80
+ return onError(event, error);
81
+ }
82
+ return onSuccess(event, {
83
+ tokens,
84
+ user
85
+ });
86
+ });
87
+ }
@@ -1,12 +1,13 @@
1
- import {
2
- eventHandler,
3
- getQuery,
4
- sendRedirect
5
- } from "h3";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
6
2
  import { withQuery } from "ufo";
7
3
  import { defu } from "defu";
8
4
  import { randomUUID } from "uncrypto";
9
- import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
+ import {
6
+ handleMissingConfiguration,
7
+ handleAccessTokenErrorResponse,
8
+ getOAuthRedirectURL,
9
+ requestAccessToken
10
+ } from "../utils.js";
10
11
  import { useRuntimeConfig } from "#imports";
11
12
  export function oauthXEventHandler({
12
13
  config,
@@ -29,7 +30,11 @@ export function oauthXEventHandler({
29
30
  }
30
31
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
31
32
  if (!query.code) {
32
- config.scope = config.scope || ["tweet.read", "users.read", "offline.access"];
33
+ config.scope = config.scope || [
34
+ "tweet.read",
35
+ "users.read",
36
+ "offline.access"
37
+ ];
33
38
  return sendRedirect(
34
39
  event,
35
40
  withQuery(config.authorizationURL, {
@@ -44,7 +49,9 @@ export function oauthXEventHandler({
44
49
  }
45
50
  const tokens = await requestAccessToken(config.tokenURL, {
46
51
  headers: {
47
- Authorization: `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64")}`
52
+ Authorization: `Basic ${Buffer.from(
53
+ `${config.clientId}:${config.clientSecret}`
54
+ ).toString("base64")}`
48
55
  },
49
56
  params: {
50
57
  grant_type: "authorization_code",
@@ -57,17 +64,14 @@ export function oauthXEventHandler({
57
64
  return handleAccessTokenErrorResponse(event, "x", tokens, onError);
58
65
  }
59
66
  const accessToken = tokens.access_token;
60
- const user = await $fetch(
61
- config.userURL,
62
- {
63
- headers: {
64
- Authorization: `Bearer ${accessToken}`
65
- },
66
- query: {
67
- "user.fields": "description,id,name,profile_image_url,username,verified,verified_type"
68
- }
67
+ const user = await $fetch(config.userURL, {
68
+ headers: {
69
+ Authorization: `Bearer ${accessToken}`
70
+ },
71
+ query: {
72
+ "user.fields": "description,id,name,profile_image_url,username,verified,verified_type"
69
73
  }
70
- ).catch((error) => {
74
+ }).catch((error) => {
71
75
  return error;
72
76
  });
73
77
  return onSuccess(event, {
@@ -1,7 +1,12 @@
1
1
  import { eventHandler, getQuery, sendRedirect } 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 {
5
+ handleMissingConfiguration,
6
+ handleAccessTokenErrorResponse,
7
+ getOAuthRedirectURL,
8
+ requestAccessToken
9
+ } from "../utils.js";
5
10
  import { useRuntimeConfig } from "#imports";
6
11
  export function oauthYandexEventHandler({
7
12
  config,
@@ -16,7 +21,12 @@ export function oauthYandexEventHandler({
16
21
  });
17
22
  const query = getQuery(event);
18
23
  if (!config.clientId || !config.clientSecret) {
19
- return handleMissingConfiguration(event, "yandex", ["clientId", "clientSecret"], onError);
24
+ return handleMissingConfiguration(
25
+ event,
26
+ "yandex",
27
+ ["clientId", "clientSecret"],
28
+ onError
29
+ );
20
30
  }
21
31
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
22
32
  if (!query.code) {
@@ -1,6 +1,6 @@
1
1
  import { defineNitroPlugin } from "nitropack/runtime";
2
2
  export default defineNitroPlugin((nitroApp) => {
3
- if (process.env.NUXT_OAUTH_FACEBOOK_CLIENT_ID && process.env.NUXT_OAUTH_FACEBOOK_CLIENT_SECRET) {
3
+ if (process.env.NUXT_OAUTH_FACEBOOK_CLIENT_ID && process.env.NUXT_OAUTH_FACEBOOK_CLIENT_SECRET || process.env.NUXT_OAUTH_INSTAGRAM_CLIENT_ID && process.env.NUXT_OAUTH_INSTAGRAM_CLIENT_SECRET) {
4
4
  nitroApp.hooks.hook("render:html", (html) => {
5
5
  html.head.unshift(`
6
6
  <script>
@@ -1,4 +1,4 @@
1
- import type { H3Event } from 'h3';
1
+ import type { H3Event, SessionConfig } from 'h3';
2
2
  import type { UserSession, UserSessionRequired } from '#auth-utils';
3
3
  export interface SessionHooks {
4
4
  /**
@@ -25,13 +25,13 @@ export declare function getUserSession(event: H3Event): Promise<UserSession>;
25
25
  * @param data User session data, please only store public information since it can be decoded with API calls
26
26
  * @see https://github.com/atinux/nuxt-auth-utils
27
27
  */
28
- export declare function setUserSession(event: H3Event, data: UserSession): Promise<UserSession>;
28
+ export declare function setUserSession(event: H3Event, data: UserSession, config?: Partial<SessionConfig>): Promise<UserSession>;
29
29
  /**
30
30
  * Replace a user session
31
31
  * @param event The Request (h3) event
32
32
  * @param data User session data, please only store public information since it can be decoded with API calls
33
33
  */
34
- export declare function replaceUserSession(event: H3Event, data: UserSession): Promise<UserSession>;
34
+ export declare function replaceUserSession(event: H3Event, data: UserSession, config?: Partial<SessionConfig>): Promise<UserSession>;
35
35
  /**
36
36
  * Clear the user session and removing the session cookie
37
37
  * @param event The Request (h3) event
@@ -6,13 +6,13 @@ export const sessionHooks = createHooks();
6
6
  export async function getUserSession(event) {
7
7
  return (await _useSession(event)).data;
8
8
  }
9
- export async function setUserSession(event, data) {
10
- const session = await _useSession(event);
9
+ export async function setUserSession(event, data, config) {
10
+ const session = await _useSession(event, config);
11
11
  await session.update(defu(data, session.data));
12
12
  return session.data;
13
13
  }
14
- export async function replaceUserSession(event, data) {
15
- const session = await _useSession(event);
14
+ export async function replaceUserSession(event, data, config) {
15
+ const session = await _useSession(event, config);
16
16
  await session.clear();
17
17
  await session.update(data);
18
18
  return session.data;
@@ -34,11 +34,12 @@ export async function requireUserSession(event, opts = {}) {
34
34
  return userSession;
35
35
  }
36
36
  let sessionConfig;
37
- function _useSession(event) {
37
+ function _useSession(event, config = {}) {
38
38
  if (!sessionConfig) {
39
39
  const runtimeConfig = useRuntimeConfig(event);
40
40
  const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
41
41
  sessionConfig = defu({ password: process.env[envSessionPassword] }, runtimeConfig.session);
42
42
  }
43
- return useSession(event, sessionConfig);
43
+ const finalConfig = defu(config, sessionConfig);
44
+ return useSession(event, finalConfig);
44
45
  }
@@ -1,5 +1,5 @@
1
1
  import type { H3Event, H3Error } from 'h3';
2
- export type OAuthProvider = 'auth0' | 'battledotnet' | 'cognito' | 'discord' | 'facebook' | 'github' | 'google' | 'keycloak' | 'linkedin' | 'microsoft' | 'paypal' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'x' | 'xsuaa' | 'yandex' | (string & {});
2
+ export type OAuthProvider = 'auth0' | 'battledotnet' | 'cognito' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linkedin' | 'microsoft' | 'paypal' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'vk' | 'x' | 'xsuaa' | 'yandex' | (string & {});
3
3
  export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
4
4
  export interface OAuthConfig<TConfig, TUser = any, TTokens = any> {
5
5
  config?: TConfig;
@@ -2,7 +2,18 @@ import type { ComputedRef, Ref } from 'vue';
2
2
  export interface User {
3
3
  }
4
4
  export interface UserSession {
5
+ /**
6
+ * User session data, available on client and server
7
+ */
5
8
  user?: User;
9
+ /**
10
+ * Private session data, only available on server/ code
11
+ */
12
+ secure?: Record<string, unknown>;
13
+ /**
14
+ * Extra session data, available on client and server
15
+ */
16
+ [key: string]: unknown;
6
17
  }
7
18
  export interface UserSessionRequired extends UserSession {
8
19
  user: User;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Minimalist Auth module for Nuxt with SSR",
5
5
  "repository": "Atinux/nuxt-auth-utils",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
- "packageManager": "pnpm@9.9.0",
8
+ "packageManager": "pnpm@9.10.0",
9
9
  "exports": {
10
10
  ".": {
11
11
  "types": "./dist/types.d.ts",
@@ -42,19 +42,19 @@
42
42
  "uncrypto": "^0.1.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@iconify-json/simple-icons": "^1.2.1",
45
+ "@iconify-json/simple-icons": "^1.2.2",
46
46
  "@nuxt/devtools": "latest",
47
- "@nuxt/eslint-config": "^0.5.5",
48
- "@nuxt/module-builder": "^0.8.3",
47
+ "@nuxt/eslint-config": "^0.5.7",
48
+ "@nuxt/module-builder": "^0.8.4",
49
49
  "@nuxt/schema": "^3.13.1",
50
- "@nuxt/test-utils": "^3.14.1",
50
+ "@nuxt/test-utils": "^3.14.2",
51
51
  "@nuxt/ui": "^2.18.4",
52
- "@nuxt/ui-pro": "^1.4.1",
52
+ "@nuxt/ui-pro": "^1.4.2",
53
53
  "changelogen": "^0.5.5",
54
- "eslint": "^9.9.1",
54
+ "eslint": "^9.10.0",
55
55
  "nuxt": "^3.13.1",
56
- "typescript": "^5.5.4",
56
+ "typescript": "^5.6.2",
57
57
  "vitest": "^2.0.5",
58
- "vue-tsc": "^2.1.4"
58
+ "vue-tsc": "^2.1.6"
59
59
  }
60
60
  }