nuxt-auth-utils 0.3.3 → 0.3.5

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 (45) hide show
  1. package/README.md +2 -0
  2. package/dist/module.d.mts +1 -1
  3. package/dist/module.d.ts +1 -1
  4. package/dist/module.json +2 -2
  5. package/dist/module.mjs +33 -17
  6. package/dist/runtime/app/plugins/session.client.js +3 -1
  7. package/dist/runtime/server/lib/oauth/auth0.d.ts +5 -0
  8. package/dist/runtime/server/lib/oauth/auth0.js +7 -17
  9. package/dist/runtime/server/lib/oauth/battledotnet.d.ts +5 -0
  10. package/dist/runtime/server/lib/oauth/battledotnet.js +11 -17
  11. package/dist/runtime/server/lib/oauth/cognito.d.ts +10 -0
  12. package/dist/runtime/server/lib/oauth/cognito.js +11 -20
  13. package/dist/runtime/server/lib/oauth/discord.d.ts +5 -0
  14. package/dist/runtime/server/lib/oauth/discord.js +7 -17
  15. package/dist/runtime/server/lib/oauth/facebook.d.ts +5 -0
  16. package/dist/runtime/server/lib/oauth/facebook.js +6 -16
  17. package/dist/runtime/server/lib/oauth/github.d.ts +6 -0
  18. package/dist/runtime/server/lib/oauth/github.js +5 -15
  19. package/dist/runtime/server/lib/oauth/google.d.ts +5 -0
  20. package/dist/runtime/server/lib/oauth/google.js +6 -17
  21. package/dist/runtime/server/lib/oauth/keycloak.d.ts +5 -0
  22. package/dist/runtime/server/lib/oauth/keycloak.js +6 -16
  23. package/dist/runtime/server/lib/oauth/linkedin.d.ts +5 -0
  24. package/dist/runtime/server/lib/oauth/linkedin.js +7 -17
  25. package/dist/runtime/server/lib/oauth/microsoft.d.ts +1 -1
  26. package/dist/runtime/server/lib/oauth/microsoft.js +6 -16
  27. package/dist/runtime/server/lib/oauth/paypal.d.ts +5 -0
  28. package/dist/runtime/server/lib/oauth/paypal.js +6 -16
  29. package/dist/runtime/server/lib/oauth/spotify.d.ts +5 -0
  30. package/dist/runtime/server/lib/oauth/spotify.js +7 -17
  31. package/dist/runtime/server/lib/oauth/steam.d.ts +5 -0
  32. package/dist/runtime/server/lib/oauth/steam.js +4 -8
  33. package/dist/runtime/server/lib/oauth/twitch.d.ts +5 -0
  34. package/dist/runtime/server/lib/oauth/twitch.js +6 -16
  35. package/dist/runtime/server/lib/oauth/x.d.ts +6 -6
  36. package/dist/runtime/server/lib/oauth/x.js +10 -44
  37. package/dist/runtime/server/lib/oauth/xsuaa.d.ts +5 -0
  38. package/dist/runtime/server/lib/oauth/xsuaa.js +7 -17
  39. package/dist/runtime/server/lib/oauth/yandex.d.ts +5 -0
  40. package/dist/runtime/server/lib/oauth/yandex.js +6 -17
  41. package/dist/runtime/server/lib/utils.d.ts +9 -0
  42. package/dist/runtime/server/lib/utils.js +21 -0
  43. package/dist/runtime/types/index.d.ts +1 -1
  44. package/dist/runtime/types/oauth-config.d.ts +3 -1
  45. package/package.json +11 -10
@@ -1,13 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import {
3
3
  eventHandler,
4
- createError,
5
4
  getQuery,
6
5
  getRequestURL,
7
6
  sendRedirect
8
7
  } from "h3";
9
8
  import { withQuery, parsePath } from "ufo";
10
9
  import { defu } from "defu";
10
+ import { handleAccessTokenErrorResponse, handleMissingConfiguration } from "../utils.js";
11
11
  import { useRuntimeConfig } from "#imports";
12
12
  export function oauthXEventHandler({
13
13
  config,
@@ -16,9 +16,9 @@ export function oauthXEventHandler({
16
16
  }) {
17
17
  return eventHandler(async (event) => {
18
18
  config = defu(config, useRuntimeConfig(event).oauth?.x, {
19
- authorizationURL: "https://twitter.com/i/oauth2/authorize",
20
- tokenURL: "https://api.twitter.com/2/oauth2/token",
21
- userURL: "https://api.twitter.com/2/users/me",
19
+ authorizationURL: "https://x.com/i/oauth2/authorize",
20
+ tokenURL: "https://api.x.com/2/oauth2/token",
21
+ userURL: "https://api.x.com/2/users/me",
22
22
  authorizationParams: {
23
23
  state: randomUUID(),
24
24
  code_challenge: randomUUID()
@@ -26,14 +26,9 @@ export function oauthXEventHandler({
26
26
  });
27
27
  const { code } = getQuery(event);
28
28
  if (!config.clientId) {
29
- const error = createError({
30
- statusCode: 500,
31
- message: "Missing NUXT_OAUTH_X_CLIENT_ID env variables."
32
- });
33
- if (!onError) throw error;
34
- return onError(event, error);
29
+ return handleMissingConfiguration(event, "x", ["clientId"], onError);
35
30
  }
36
- const redirectUrl = getRequestURL(event).href;
31
+ const redirectURL = config.redirectURL || getRequestURL(event).href;
37
32
  if (!code) {
38
33
  config.scope = config.scope || ["tweet.read", "users.read", "offline.access"];
39
34
  return sendRedirect(
@@ -42,7 +37,7 @@ export function oauthXEventHandler({
42
37
  response_type: "code",
43
38
  client_id: config.clientId,
44
39
  code_challenge_method: "plain",
45
- redirect_uri: redirectUrl,
40
+ redirect_uri: redirectURL,
46
41
  scope: config.scope.join(" "),
47
42
  ...config.authorizationParams
48
43
  })
@@ -50,8 +45,8 @@ export function oauthXEventHandler({
50
45
  }
51
46
  const params = {
52
47
  grant_type: "authorization_code",
53
- code_verifier: config.authorizationParams.code_challenge,
54
- redirect_uri: parsePath(redirectUrl).pathname,
48
+ code_verifier: config.authorizationParams?.code_challenge,
49
+ redirect_uri: parsePath(redirectURL).pathname,
55
50
  code
56
51
  };
57
52
  const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
@@ -66,13 +61,7 @@ export function oauthXEventHandler({
66
61
  return { error };
67
62
  });
68
63
  if (tokens.error) {
69
- const error = createError({
70
- statusCode: 401,
71
- message: `X login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
72
- data: tokens
73
- });
74
- if (!onError) throw error;
75
- return onError(event, error);
64
+ return handleAccessTokenErrorResponse(event, "x", tokens, onError);
76
65
  }
77
66
  const accessToken = tokens.access_token;
78
67
  const user = await $fetch(
@@ -88,29 +77,6 @@ export function oauthXEventHandler({
88
77
  ).catch((error) => {
89
78
  return error;
90
79
  });
91
- if (config.emailRequired) {
92
- const emailData = await $fetch("https://api.twitter.com/1.1/account/verify_credentials.json", {
93
- headers: {
94
- Authorization: `Bearer ${accessToken}`
95
- },
96
- query: {
97
- include_email: "true",
98
- skip_status: "true"
99
- }
100
- }).catch((error) => {
101
- return error;
102
- });
103
- if (emailData && emailData.email) {
104
- user.email = emailData.email;
105
- } else {
106
- const error = createError({
107
- statusCode: 401,
108
- message: "Twitter login failed: no user email found"
109
- });
110
- if (!onError) throw error;
111
- return onError(event, error);
112
- }
113
- }
114
80
  return onSuccess(event, {
115
81
  tokens,
116
82
  user: user?.data
@@ -22,5 +22,10 @@ export interface OAuthXSUAAConfig {
22
22
  * @example ['openid']
23
23
  */
24
24
  scope?: string[];
25
+ /**
26
+ * Redirect URL to to allow overriding for situations like prod failing to determine public hostname
27
+ * @default process.env.NUXT_OAUTH_XSUAA_REDIRECT_URL or current URL
28
+ */
29
+ redirectURL?: string;
25
30
  }
26
31
  export declare function oauthXSUAAEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthXSUAAConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -1,22 +1,18 @@
1
- import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
1
+ import { eventHandler, getQuery, getRequestURL, sendRedirect } from "h3";
2
2
  import { withQuery, parsePath } from "ufo";
3
3
  import { defu } from "defu";
4
+ import { handleAccessTokenErrorResponse, handleMissingConfiguration } from "../utils.js";
4
5
  import { useRuntimeConfig } from "#imports";
5
6
  export function oauthXSUAAEventHandler({ config, onSuccess, onError }) {
6
7
  return eventHandler(async (event) => {
7
8
  config = defu(config, useRuntimeConfig(event).oauth?.xsuaa);
8
9
  const { code } = getQuery(event);
9
10
  if (!config.clientId || !config.clientSecret || !config.domain) {
10
- const error = createError({
11
- statusCode: 500,
12
- message: "Missing NUXT_OAUTH_XSUAA_CLIENT_ID or NUXT_OAUTH_XSUAA_CLIENT_SECRET or NUXT_OAUTH_XSUAA_DOMAIN env variables."
13
- });
14
- if (!onError) throw error;
15
- return onError(event, error);
11
+ return handleMissingConfiguration(event, "xsuaa", ["clientId", "clientSecret", "domain"], onError);
16
12
  }
17
13
  const authorizationURL = `https://${config.domain}/oauth/authorize`;
18
14
  const tokenURL = `https://${config.domain}/oauth/token`;
19
- const redirectUrl = getRequestURL(event).href;
15
+ const redirectURL = config.redirectURL || getRequestURL(event).href;
20
16
  if (!code) {
21
17
  config.scope = config.scope || [];
22
18
  return sendRedirect(
@@ -24,7 +20,7 @@ export function oauthXSUAAEventHandler({ config, onSuccess, onError }) {
24
20
  withQuery(authorizationURL, {
25
21
  response_type: "code",
26
22
  client_id: config.clientId,
27
- redirect_uri: redirectUrl,
23
+ redirect_uri: redirectURL,
28
24
  scope: config.scope.join(" ")
29
25
  })
30
26
  );
@@ -40,7 +36,7 @@ export function oauthXSUAAEventHandler({ config, onSuccess, onError }) {
40
36
  grant_type: "authorization_code",
41
37
  client_id: config.clientId,
42
38
  client_secret: config.clientSecret,
43
- redirect_uri: parsePath(redirectUrl).pathname,
39
+ redirect_uri: parsePath(redirectURL).pathname,
44
40
  code: `${code}`
45
41
  })
46
42
  }
@@ -48,13 +44,7 @@ export function oauthXSUAAEventHandler({ config, onSuccess, onError }) {
48
44
  return { error };
49
45
  });
50
46
  if (tokens.error) {
51
- const error = createError({
52
- statusCode: 401,
53
- message: `XSUAA login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
54
- data: tokens
55
- });
56
- if (!onError) throw error;
57
- return onError(event, error);
47
+ return handleAccessTokenErrorResponse(event, "auth0", tokens, onError);
58
48
  }
59
49
  const tokenType = tokens.token_type;
60
50
  const accessToken = tokens.access_token;
@@ -37,5 +37,10 @@ export interface OAuthYandexConfig {
37
37
  * @default 'https://login.yandex.ru/info'
38
38
  */
39
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_YANDEX_REDIRECT_URL or current URL
43
+ */
44
+ redirectURL?: string;
40
45
  }
41
46
  export declare function oauthYandexEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthYandexConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  eventHandler,
3
- createError,
4
3
  getQuery,
5
4
  getRequestURL,
6
5
  sendRedirect
7
6
  } from "h3";
8
7
  import { withQuery, parseURL, stringifyParsedURL } from "ufo";
9
8
  import { defu } from "defu";
9
+ import { handleAccessTokenErrorResponse, handleMissingConfiguration } from "../utils.js";
10
10
  import { useRuntimeConfig } from "#imports";
11
11
  export function oauthYandexEventHandler({
12
12
  config,
@@ -21,14 +21,9 @@ export function oauthYandexEventHandler({
21
21
  });
22
22
  const { code } = getQuery(event);
23
23
  if (!config.clientId || !config.clientSecret) {
24
- const error = createError({
25
- statusCode: 500,
26
- message: "Missing NUXT_OAUTH_YANDEX_CLIENT_ID or NUXT_OAUTH_YANDEX_CLIENT_SECRET env variables."
27
- });
28
- if (!onError) throw error;
29
- return onError(event, error);
24
+ return handleMissingConfiguration(event, "yandex", ["clientId", "clientSecret"], onError);
30
25
  }
31
- const redirectUrl = getRequestURL(event).href;
26
+ const redirectURL = config.redirectURL || getRequestURL(event).href;
32
27
  if (!code) {
33
28
  config.scope = config.scope || [];
34
29
  if (config.emailRequired && !config.scope.includes("login:email")) {
@@ -39,7 +34,7 @@ export function oauthYandexEventHandler({
39
34
  withQuery(config.authorizationURL, {
40
35
  response_type: "code",
41
36
  client_id: config.clientId,
42
- redirect_uri: redirectUrl,
37
+ redirect_uri: redirectURL,
43
38
  scope: config.scope.join(" ")
44
39
  })
45
40
  );
@@ -53,20 +48,14 @@ export function oauthYandexEventHandler({
53
48
  client_id: config.clientId,
54
49
  client_secret: config.clientSecret,
55
50
  grant_type: "authorization_code",
56
- redirect_uri: stringifyParsedURL(parseURL(redirectUrl)),
51
+ redirect_uri: stringifyParsedURL(parseURL(redirectURL)),
57
52
  code
58
53
  }).toString()
59
54
  }).catch((error) => {
60
55
  return { error };
61
56
  });
62
57
  if (tokens.error) {
63
- const error = createError({
64
- statusCode: 401,
65
- message: `Yandex login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
66
- data: tokens
67
- });
68
- if (!onError) throw error;
69
- return onError(event, error);
58
+ return handleAccessTokenErrorResponse(event, "yandex", tokens, onError);
70
59
  }
71
60
  const accessToken = tokens.access_token;
72
61
  const user = await $fetch(config.userURL, {
@@ -0,0 +1,9 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { OnError, OAuthProvider } from '#auth-utils';
3
+ /**
4
+ * Handle OAuth access token error response
5
+ *
6
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
7
+ */
8
+ export declare function handleAccessTokenErrorResponse(event: H3Event, oauthProvider: OAuthProvider, oauthError: any, onError?: OnError): void | Promise<void>;
9
+ export declare function handleMissingConfiguration(event: H3Event, provider: OAuthProvider, missingKeys: string[], onError?: OnError): void | Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { snakeCase, upperFirst } from "scule";
2
+ import { createError } from "#imports";
3
+ export function handleAccessTokenErrorResponse(event, oauthProvider, oauthError, onError) {
4
+ const message = `${upperFirst(oauthProvider)} login failed: ${oauthError.error_description || oauthError.error || "Unknown error"}`;
5
+ const error = createError({
6
+ statusCode: 401,
7
+ message,
8
+ data: oauthError
9
+ });
10
+ if (!onError) throw error;
11
+ return onError(event, error);
12
+ }
13
+ export function handleMissingConfiguration(event, provider, missingKeys, onError) {
14
+ const environmentVariables = missingKeys.map((key) => `NUXT_OAUTH_${provider.toUpperCase()}_${snakeCase(key).toUpperCase()}`);
15
+ const error = createError({
16
+ statusCode: 500,
17
+ message: `Missing ${environmentVariables.join(" or ")} env ${missingKeys.length > 1 ? "variables" : "variable"}.`
18
+ });
19
+ if (!onError) throw error;
20
+ return onError(event, error);
21
+ }
@@ -1,2 +1,2 @@
1
1
  export type { User, UserSession, UserSessionRequired, UserSessionComposable } from './session.js';
2
- export type { OAuthConfig } from './oauth-config.js';
2
+ export type { OAuthConfig, OAuthProvider, OnError } from './oauth-config.js';
@@ -1,9 +1,11 @@
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' | 'twitch' | 'x' | 'xsuaa' | 'yandex' | (string & {});
3
+ export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
2
4
  export interface OAuthConfig<TConfig, TUser = any, TTokens = any> {
3
5
  config?: TConfig;
4
6
  onSuccess: (event: H3Event, result: {
5
7
  user: TUser;
6
8
  tokens: TTokens;
7
9
  }) => Promise<void> | void;
8
- onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
10
+ onError?: OnError;
9
11
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
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.7.0",
8
+ "packageManager": "pnpm@9.9.0",
9
9
  "exports": {
10
10
  ".": {
11
11
  "types": "./dist/types.d.ts",
@@ -32,26 +32,27 @@
32
32
  "test:watch": "vitest watch"
33
33
  },
34
34
  "dependencies": {
35
- "@nuxt/kit": "^3.12.4",
35
+ "@nuxt/kit": "^3.13.0",
36
36
  "defu": "^6.1.4",
37
37
  "hookable": "^5.5.3",
38
38
  "ofetch": "^1.3.4",
39
39
  "ohash": "^1.1.3",
40
40
  "pathe": "^1.1.2",
41
+ "scule": "^1.3.0",
41
42
  "uncrypto": "^0.1.3"
42
43
  },
43
44
  "devDependencies": {
44
- "@iconify-json/simple-icons": "^1.1.113",
45
+ "@iconify-json/simple-icons": "^1.1.115",
45
46
  "@nuxt/devtools": "latest",
46
- "@nuxt/eslint-config": "^0.5.0",
47
- "@nuxt/module-builder": "^0.8.1",
48
- "@nuxt/schema": "^3.12.4",
49
- "@nuxt/test-utils": "^3.14.0",
47
+ "@nuxt/eslint-config": "^0.5.3",
48
+ "@nuxt/module-builder": "^0.8.3",
49
+ "@nuxt/schema": "^3.13.0",
50
+ "@nuxt/test-utils": "^3.14.1",
50
51
  "@nuxt/ui": "^2.18.4",
51
52
  "@nuxt/ui-pro": "^1.4.1",
52
53
  "changelogen": "^0.5.5",
53
- "eslint": "^9.9.0",
54
- "nuxt": "^3.12.4",
54
+ "eslint": "^9.9.1",
55
+ "nuxt": "^3.13.0",
55
56
  "typescript": "^5.5.4",
56
57
  "vitest": "^2.0.5",
57
58
  "vue-tsc": "^2.0.29"