nuxt-auth-utils 0.5.31 → 0.5.33

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 (66) hide show
  1. package/README.md +55 -4
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -1
  4. package/dist/runtime/app/composables/session.js +8 -3
  5. package/dist/runtime/app/plugins/session.client.js +8 -4
  6. package/dist/runtime/server/lib/oauth/apple.js +4 -1
  7. package/dist/runtime/server/lib/oauth/atlassian.js +1 -1
  8. package/dist/runtime/server/lib/oauth/auth0.js +1 -1
  9. package/dist/runtime/server/lib/oauth/authentik.js +1 -1
  10. package/dist/runtime/server/lib/oauth/azureb2c.js +1 -1
  11. package/dist/runtime/server/lib/oauth/battledotnet.js +1 -1
  12. package/dist/runtime/server/lib/oauth/box.js +1 -1
  13. package/dist/runtime/server/lib/oauth/cloudflare.d.ts +86 -0
  14. package/dist/runtime/server/lib/oauth/cloudflare.js +72 -0
  15. package/dist/runtime/server/lib/oauth/cognito.js +1 -1
  16. package/dist/runtime/server/lib/oauth/discord.js +1 -1
  17. package/dist/runtime/server/lib/oauth/dropbox.js +1 -1
  18. package/dist/runtime/server/lib/oauth/facebook.d.ts +13 -2
  19. package/dist/runtime/server/lib/oauth/facebook.js +16 -4
  20. package/dist/runtime/server/lib/oauth/gitea.js +1 -1
  21. package/dist/runtime/server/lib/oauth/github.js +1 -1
  22. package/dist/runtime/server/lib/oauth/gitlab.js +1 -1
  23. package/dist/runtime/server/lib/oauth/google.js +1 -1
  24. package/dist/runtime/server/lib/oauth/heroku.js +1 -1
  25. package/dist/runtime/server/lib/oauth/hubspot.js +1 -1
  26. package/dist/runtime/server/lib/oauth/instagram.js +1 -1
  27. package/dist/runtime/server/lib/oauth/keycloak.js +1 -1
  28. package/dist/runtime/server/lib/oauth/kick.js +1 -1
  29. package/dist/runtime/server/lib/oauth/line.js +1 -1
  30. package/dist/runtime/server/lib/oauth/linear.js +1 -1
  31. package/dist/runtime/server/lib/oauth/linkedin.js +1 -1
  32. package/dist/runtime/server/lib/oauth/livechat.js +1 -1
  33. package/dist/runtime/server/lib/oauth/mastodon.d.ts +62 -0
  34. package/dist/runtime/server/lib/oauth/mastodon.js +178 -0
  35. package/dist/runtime/server/lib/oauth/microsoft.js +1 -1
  36. package/dist/runtime/server/lib/oauth/oidc.js +5 -4
  37. package/dist/runtime/server/lib/oauth/okta.js +1 -1
  38. package/dist/runtime/server/lib/oauth/ory.js +1 -1
  39. package/dist/runtime/server/lib/oauth/osu.js +1 -1
  40. package/dist/runtime/server/lib/oauth/paypal.js +1 -1
  41. package/dist/runtime/server/lib/oauth/polar.js +1 -1
  42. package/dist/runtime/server/lib/oauth/riotgames.js +1 -1
  43. package/dist/runtime/server/lib/oauth/roblox.js +1 -1
  44. package/dist/runtime/server/lib/oauth/salesforce.d.ts +8 -3
  45. package/dist/runtime/server/lib/oauth/salesforce.js +9 -5
  46. package/dist/runtime/server/lib/oauth/seznam.js +1 -1
  47. package/dist/runtime/server/lib/oauth/shopifyCustomer.js +2 -2
  48. package/dist/runtime/server/lib/oauth/slack.js +1 -1
  49. package/dist/runtime/server/lib/oauth/spotify.js +1 -1
  50. package/dist/runtime/server/lib/oauth/steam.js +1 -1
  51. package/dist/runtime/server/lib/oauth/strava.js +1 -1
  52. package/dist/runtime/server/lib/oauth/tiktok.js +1 -1
  53. package/dist/runtime/server/lib/oauth/twitch.js +1 -1
  54. package/dist/runtime/server/lib/oauth/vk.js +1 -1
  55. package/dist/runtime/server/lib/oauth/wechat.d.ts +67 -0
  56. package/dist/runtime/server/lib/oauth/wechat.js +107 -0
  57. package/dist/runtime/server/lib/oauth/workos.js +1 -1
  58. package/dist/runtime/server/lib/oauth/x.js +1 -1
  59. package/dist/runtime/server/lib/oauth/xsuaa.js +1 -1
  60. package/dist/runtime/server/lib/oauth/yandex.js +1 -1
  61. package/dist/runtime/server/lib/oauth/zitadel.js +1 -1
  62. package/dist/runtime/server/lib/utils.d.ts +1 -0
  63. package/dist/runtime/server/lib/utils.js +3 -3
  64. package/dist/runtime/server/utils/session.js +6 -1
  65. package/dist/runtime/types/oauth-config.d.ts +1 -1
  66. package/package.json +12 -16
@@ -33,7 +33,7 @@ export function defineOAuthKickEventHandler({ config, onSuccess, onError }) {
33
33
  })
34
34
  );
35
35
  }
36
- if (query.state !== state) {
36
+ if (!state || query.state !== state) {
37
37
  return handleInvalidState(event, "kick", onError);
38
38
  }
39
39
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -44,7 +44,7 @@ export function defineOAuthLineEventHandler({
44
44
  })
45
45
  );
46
46
  }
47
- if (query.state !== state) {
47
+ if (!state || query.state !== state) {
48
48
  return handleInvalidState(event, "line", onError);
49
49
  }
50
50
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -39,7 +39,7 @@ export function defineOAuthLinearEventHandler({ config, onSuccess, onError }) {
39
39
  })
40
40
  );
41
41
  }
42
- if (query.state !== state) {
42
+ if (!state || query.state !== state) {
43
43
  return handleInvalidState(event, "linear", onError);
44
44
  }
45
45
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -36,7 +36,7 @@ export function defineOAuthLinkedInEventHandler({ config, onSuccess, onError })
36
36
  })
37
37
  );
38
38
  }
39
- if (query.state !== state) {
39
+ if (!state || query.state !== state) {
40
40
  return handleInvalidState(event, "linkedin", onError);
41
41
  }
42
42
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -46,7 +46,7 @@ export function defineOAuthLiveChatEventHandler({
46
46
  })
47
47
  );
48
48
  }
49
- if (query.state !== state) {
49
+ if (!state || query.state !== state) {
50
50
  return handleInvalidState(event, "livechat", onError);
51
51
  }
52
52
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -0,0 +1,62 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthMastodonConfig {
3
+ /**
4
+ * The instance domain to authenticate against, e.g. `mastodon.social`.
5
+ *
6
+ * Unlike most OAuth providers, a Mastodon account lives on a single instance and there is no
7
+ * shared authorization server, so the instance must be known before the flow can start. If not
8
+ * set here, it can be provided at runtime as an `instance` query parameter on the initial request
9
+ * (e.g. `/auth/mastodon?instance=mastodon.social`). A full handle such as `@user@mastodon.social`
10
+ * or `user@mastodon.social` is also accepted, only the domain part is used.
11
+ * @default process.env.NUXT_OAUTH_MASTODON_INSTANCE
12
+ * @example 'mastodon.social'
13
+ */
14
+ instance?: string;
15
+ /**
16
+ * The name of the application shown to users on the instance's authorization screen.
17
+ *
18
+ * Mastodon has no concept of a pre-registered, shared client id: an application must be
19
+ * registered against every instance it authenticates users on (`POST /api/v1/apps`). This is
20
+ * that application's display name.
21
+ * @default 'Nuxt Auth Utils'
22
+ */
23
+ clientName?: string;
24
+ /**
25
+ * Mastodon OAuth Scope.
26
+ * @default ['read']
27
+ * @see https://docs.joinmastodon.org/api/oauth-scopes/
28
+ * @example ['read', 'read:accounts']
29
+ */
30
+ scope?: string[];
31
+ /**
32
+ * Redirect URL to allow overriding for situations like prod failing to determine public hostname
33
+ * @default process.env.NUXT_OAUTH_MASTODON_REDIRECT_URL or current URL
34
+ */
35
+ redirectURL?: string;
36
+ }
37
+ export interface OAuthMastodonUser {
38
+ id: string;
39
+ username: string;
40
+ /**
41
+ * Always the full `user@instance` handle here, even for the authenticated user's own account.
42
+ * Mastodon's API normally only qualifies `acct` with `@instance` for remote accounts and returns
43
+ * the bare username for your own, we normalize it since an account only means something together
44
+ * with the instance it lives on.
45
+ */
46
+ acct: string;
47
+ display_name: string;
48
+ url: string;
49
+ avatar: string;
50
+ [key: string]: unknown;
51
+ }
52
+ interface MastodonTokens {
53
+ access_token: string;
54
+ token_type: string;
55
+ scope: string;
56
+ created_at: number;
57
+ }
58
+ export declare function defineOAuthMastodonEventHandler<TUser = OAuthMastodonUser>({ config: userConfig, onSuccess, onError }: OAuthConfig<OAuthMastodonConfig, {
59
+ user: TUser;
60
+ tokens: MastodonTokens;
61
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
62
+ export {};
@@ -0,0 +1,178 @@
1
+ import { createError, deleteCookie, eventHandler, getCookie, getQuery, sendRedirect, setCookie } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { defu } from "defu";
4
+ import { $fetch } from "ofetch";
5
+ import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleState, isDevelopment, requestAccessToken } from "../utils.js";
6
+ import { useRuntimeConfig } from "#imports";
7
+ const FLOW_COOKIE_NAME = "nuxt-auth-mastodon-flow";
8
+ const appRegistry = /* @__PURE__ */ new Map();
9
+ const pendingRegistrations = /* @__PURE__ */ new Map();
10
+ const MAX_REGISTERED_INSTANCES = 1e3;
11
+ function appRegistryKey(instance, redirectURL) {
12
+ return `${instance}::${redirectURL}`;
13
+ }
14
+ function resolveInstanceDomain(input) {
15
+ const withoutProtocol = input.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
16
+ const withoutLeadingAt = withoutProtocol.replace(/^@/, "");
17
+ const domain = withoutLeadingAt.includes("@") ? withoutLeadingAt.split("@").pop() : withoutLeadingAt;
18
+ try {
19
+ return new URL(`https://${domain}`).hostname;
20
+ } catch {
21
+ return domain;
22
+ }
23
+ }
24
+ function isPubliclyAddressableInstance(instance) {
25
+ if (!instance || /\s/.test(instance) || instance.endsWith(".")) return false;
26
+ try {
27
+ if (new URL(`https://${instance}`).hostname !== instance) return false;
28
+ } catch {
29
+ return false;
30
+ }
31
+ if (instance === "localhost" || instance.endsWith(".localhost") || instance.endsWith(".local")) return false;
32
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(instance)) return false;
33
+ if (instance.includes(":")) return false;
34
+ return true;
35
+ }
36
+ async function registerApp(instance, redirectURL, scope, clientName) {
37
+ const app = await $fetch(`https://${instance}/api/v1/apps`, {
38
+ method: "POST",
39
+ body: {
40
+ client_name: clientName,
41
+ redirect_uris: redirectURL,
42
+ scopes: scope
43
+ }
44
+ });
45
+ return { client_id: app.client_id, client_secret: app.client_secret };
46
+ }
47
+ async function getOrRegisterApp(instance, redirectURL, scope, clientName) {
48
+ const key = appRegistryKey(instance, redirectURL);
49
+ const cached = appRegistry.get(key);
50
+ if (cached) return cached;
51
+ const pending = pendingRegistrations.get(key);
52
+ if (pending) return pending;
53
+ const registration = registerApp(instance, redirectURL, scope, clientName).then((app) => {
54
+ if (appRegistry.size >= MAX_REGISTERED_INSTANCES && !appRegistry.has(key)) {
55
+ const oldestKey = appRegistry.keys().next().value;
56
+ if (oldestKey) appRegistry.delete(oldestKey);
57
+ }
58
+ appRegistry.set(key, app);
59
+ return app;
60
+ }).finally(() => {
61
+ pendingRegistrations.delete(key);
62
+ });
63
+ pendingRegistrations.set(key, registration);
64
+ return registration;
65
+ }
66
+ export function defineOAuthMastodonEventHandler({ config: userConfig, onSuccess, onError }) {
67
+ return eventHandler(async (event) => {
68
+ const config = defu({}, userConfig, useRuntimeConfig(event).oauth?.mastodon, {
69
+ clientName: "Nuxt Auth Utils"
70
+ });
71
+ config.scope ||= ["read"];
72
+ const query = getQuery(event);
73
+ if (query.error) {
74
+ const error = createError({
75
+ statusCode: 401,
76
+ message: `Mastodon login failed: ${query.error || "Unknown error"}`,
77
+ data: query
78
+ });
79
+ if (!onError) throw error;
80
+ return onError(event, error);
81
+ }
82
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
83
+ const state = await handleState(event);
84
+ const scope = (config.scope || ["read"]).join(" ");
85
+ if (!query.code) {
86
+ const rawInstance = query.instance?.toString() || config.instance;
87
+ if (!rawInstance) {
88
+ const error = createError({
89
+ statusCode: 400,
90
+ message: "Mastodon login failed: missing instance. Provide it via the `instance` query parameter (e.g. `/auth/mastodon?instance=mastodon.social`), a full handle (`@user@mastodon.social`), or the `instance` config/`NUXT_OAUTH_MASTODON_INSTANCE` env variable."
91
+ });
92
+ if (!onError) throw error;
93
+ return onError(event, error);
94
+ }
95
+ const instance2 = resolveInstanceDomain(rawInstance);
96
+ if (!isPubliclyAddressableInstance(instance2)) {
97
+ const error = createError({
98
+ statusCode: 400,
99
+ message: `Mastodon login failed: "${instance2}" is not a valid instance domain.`
100
+ });
101
+ if (!onError) throw error;
102
+ return onError(event, error);
103
+ }
104
+ let app;
105
+ try {
106
+ app = await getOrRegisterApp(instance2, redirectURL, scope, config.clientName || "Nuxt Auth Utils");
107
+ } catch {
108
+ const error = createError({
109
+ statusCode: 500,
110
+ message: `Mastodon login failed: could not register the application with instance "${instance2}". Make sure it is a reachable Mastodon (or compatible) server.`
111
+ });
112
+ if (!onError) throw error;
113
+ return onError(event, error);
114
+ }
115
+ const flowState2 = { instance: instance2, clientId: app.client_id, clientSecret: app.client_secret };
116
+ setCookie(event, FLOW_COOKIE_NAME, btoa(JSON.stringify(flowState2)), {
117
+ httpOnly: true,
118
+ secure: !isDevelopment,
119
+ sameSite: "lax",
120
+ maxAge: 60 * 10,
121
+ path: "/"
122
+ });
123
+ return sendRedirect(event, withQuery(`https://${instance2}/oauth/authorize`, {
124
+ client_id: app.client_id,
125
+ redirect_uri: redirectURL,
126
+ response_type: "code",
127
+ scope,
128
+ state
129
+ }));
130
+ }
131
+ if (!state || query.state !== state) {
132
+ return handleInvalidState(event, "mastodon", onError);
133
+ }
134
+ const flowCookie = getCookie(event, FLOW_COOKIE_NAME);
135
+ deleteCookie(event, FLOW_COOKIE_NAME, { path: "/" });
136
+ let flowState;
137
+ try {
138
+ flowState = flowCookie ? JSON.parse(atob(flowCookie)) : void 0;
139
+ } catch {
140
+ flowState = void 0;
141
+ }
142
+ if (!flowState || !isPubliclyAddressableInstance(flowState.instance)) {
143
+ const error = createError({
144
+ statusCode: 400,
145
+ message: "Mastodon login failed: could not recover the application credentials for this login (missing or invalid flow cookie, the flow may have taken too long or cookies are blocked)."
146
+ });
147
+ if (!onError) throw error;
148
+ return onError(event, error);
149
+ }
150
+ const { instance, clientId, clientSecret } = flowState;
151
+ const tokens = await requestAccessToken(`https://${instance}/oauth/token`, {
152
+ body: {
153
+ grant_type: "authorization_code",
154
+ client_id: clientId,
155
+ client_secret: clientSecret,
156
+ redirect_uri: redirectURL,
157
+ code: query.code,
158
+ scope
159
+ }
160
+ });
161
+ if (tokens.error) {
162
+ return handleAccessTokenErrorResponse(event, "mastodon", tokens, onError);
163
+ }
164
+ const account = await $fetch(`https://${instance}/api/v1/accounts/verify_credentials`, {
165
+ headers: {
166
+ Authorization: `Bearer ${tokens.access_token}`
167
+ }
168
+ });
169
+ const user = {
170
+ ...account,
171
+ acct: account.acct.includes("@") ? account.acct : `${account.acct}@${instance}`
172
+ };
173
+ return onSuccess(event, {
174
+ user,
175
+ tokens
176
+ });
177
+ });
178
+ }
@@ -31,7 +31,7 @@ export function defineOAuthMicrosoftEventHandler({ config, onSuccess, onError })
31
31
  })
32
32
  );
33
33
  }
34
- if (query.state !== state) {
34
+ if (!state || query.state !== state) {
35
35
  return handleInvalidState(event, "microsoft", onError);
36
36
  }
37
37
  const tokens = await requestAccessToken(tokenURL, {
@@ -5,9 +5,7 @@ import { withQuery } from "ufo";
5
5
  import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken, handleNonce, parseJwt } from "../utils.js";
6
6
  export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
7
7
  return eventHandler(async (event) => {
8
- config = defu(config, useRuntimeConfig(event).oauth.oidc, {
9
- scope: ["openid"]
10
- });
8
+ config = defu(config, useRuntimeConfig(event).oauth.oidc);
11
9
  const query = getQuery(event);
12
10
  if (query.error) {
13
11
  const error = createError({
@@ -28,6 +26,9 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
28
26
  const nonce = handleNonce(event);
29
27
  if (!query.code) {
30
28
  config.scope = config.scope || [];
29
+ if (!config.scope.includes("openid")) {
30
+ config.scope.push("openid");
31
+ }
31
32
  const authQuery = {
32
33
  client_id: config.clientId,
33
34
  redirect_uri: redirectURL,
@@ -44,7 +45,7 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
44
45
  withQuery(oidcConfig.authorization_endpoint, authQuery)
45
46
  );
46
47
  }
47
- if (query.state !== state) {
48
+ if (!state || query.state !== state) {
48
49
  return handleInvalidState(event, "oidc", onError);
49
50
  }
50
51
  const tokenQuery = {
@@ -113,7 +113,7 @@ export function defineOAuthOktaEventHandler({ config, onSuccess, onError }) {
113
113
  })
114
114
  );
115
115
  }
116
- if (query.state !== state) {
116
+ if (!state || query.state !== state) {
117
117
  return handleInvalidState(event, "okta", onError);
118
118
  }
119
119
  const tokens = await requestAccessToken(tokenURL, {
@@ -56,7 +56,7 @@ export function defineOAuthOryEventHandler({ config, onSuccess, onError }) {
56
56
  })
57
57
  );
58
58
  }
59
- if (query.state !== state) {
59
+ if (!state || query.state !== state) {
60
60
  return handleInvalidState(event, "ory", onError);
61
61
  }
62
62
  const tokenURL = `${config.sdkURL}${config.tokenURL}`;
@@ -33,7 +33,7 @@ export function defineOAuthOsuEventHandler({ config, onSuccess, onError }) {
33
33
  })
34
34
  );
35
35
  }
36
- if (query.state !== state) {
36
+ if (!state || query.state !== state) {
37
37
  return handleInvalidState(event, "osu", onError);
38
38
  }
39
39
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -44,7 +44,7 @@ export function defineOAuthPaypalEventHandler({ config, onSuccess, onError }) {
44
44
  })
45
45
  );
46
46
  }
47
- if (query.state !== state) {
47
+ if (!state || query.state !== state) {
48
48
  return handleInvalidState(event, "paypal", onError);
49
49
  }
50
50
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -33,7 +33,7 @@ export function defineOAuthPolarEventHandler({ config, onSuccess, onError }) {
33
33
  })
34
34
  );
35
35
  }
36
- if (query.state !== state) {
36
+ if (!state || query.state !== state) {
37
37
  return handleInvalidState(event, "polar", onError);
38
38
  }
39
39
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -43,7 +43,7 @@ export function defineOAuthRiotGamesEventHandler({ config, onSuccess, onError })
43
43
  })
44
44
  );
45
45
  }
46
- if (query.state !== state) {
46
+ if (!state || query.state !== state) {
47
47
  return handleInvalidState(event, "riotgames", onError);
48
48
  }
49
49
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -33,7 +33,7 @@ export function defineOAuthRobloxEventHandler({ config, onSuccess, onError }) {
33
33
  })
34
34
  );
35
35
  }
36
- if (query.state !== state) {
36
+ if (!state || query.state !== state) {
37
37
  return handleInvalidState(event, "roblox", onError);
38
38
  }
39
39
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -24,14 +24,19 @@ export interface OAuthSalesforceConfig {
24
24
  baseURL?: string;
25
25
  /**
26
26
  * Salesforce OAuth Authorization URL
27
- * @default 'https://login.salesforce.com/services/oauth2/authorize'
27
+ * @default '${baseURL}/services/oauth2/authorize'
28
28
  */
29
29
  authorizationURL?: string;
30
30
  /**
31
- * Salesforce OAuth Authorization URL
32
- * @default 'https://login.salesforce.com/services/oauth2/token'
31
+ * Salesforce OAuth Token URL
32
+ * @default '${baseURL}/services/oauth2/token'
33
33
  */
34
34
  tokenURL?: string;
35
+ /**
36
+ * Salesforce OAuth User URL
37
+ * @default '${baseURL}/services/oauth2/userinfo'
38
+ */
39
+ userURL?: string;
35
40
  /**
36
41
  * Extra authorization parameters to provide to the authorization URL
37
42
  * @default {}
@@ -1,7 +1,7 @@
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, handleState, handleInvalidState } from "../utils.js";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, handleState, handleInvalidState, handlePkceVerifier } from "../utils.js";
5
5
  import { useRuntimeConfig, createError } from "#imports";
6
6
  export function defineOAuthSalesforceEventHandler({
7
7
  config,
@@ -10,7 +10,7 @@ export function defineOAuthSalesforceEventHandler({
10
10
  }) {
11
11
  return eventHandler(async (event) => {
12
12
  const runtimeConfig = useRuntimeConfig(event).oauth?.salesforce;
13
- const baseURL = config?.baseURL || "https://login.salesforce.com";
13
+ const baseURL = config?.baseURL || runtimeConfig?.baseURL || "https://login.salesforce.com";
14
14
  config = defu(config, runtimeConfig, {
15
15
  authorizationURL: `${baseURL}/services/oauth2/authorize`,
16
16
  tokenURL: `${baseURL}/services/oauth2/token`,
@@ -30,6 +30,7 @@ export function defineOAuthSalesforceEventHandler({
30
30
  return handleMissingConfiguration(event, "salesforce", ["clientId", "clientSecret"], onError);
31
31
  }
32
32
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
33
+ const pkce = await handlePkceVerifier(event);
33
34
  const state = await handleState(event);
34
35
  if (!query.code) {
35
36
  config.scope = config.scope || ["id"];
@@ -41,11 +42,13 @@ export function defineOAuthSalesforceEventHandler({
41
42
  redirect_uri: redirectURL,
42
43
  scope: config.scope.join(" "),
43
44
  ...config.authorizationParams,
45
+ code_challenge: pkce.code_challenge,
46
+ code_challenge_method: pkce.code_challenge_method,
44
47
  state
45
48
  })
46
49
  );
47
50
  }
48
- if (query.state !== state) {
51
+ if (!state || query.state !== state) {
49
52
  return handleInvalidState(event, "salesforce", onError);
50
53
  }
51
54
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -54,14 +57,15 @@ export function defineOAuthSalesforceEventHandler({
54
57
  client_id: config.clientId,
55
58
  client_secret: config.clientSecret,
56
59
  redirect_uri: redirectURL,
57
- code: query.code
60
+ code: query.code,
61
+ code_verifier: pkce.code_verifier
58
62
  }
59
63
  });
60
64
  if (tokens.error) {
61
65
  return handleAccessTokenErrorResponse(event, "salesforce", tokens, onError);
62
66
  }
63
67
  const accessToken = tokens.access_token;
64
- const user = await $fetch(`${baseURL}/services/oauth2/userinfo`, {
68
+ const user = await $fetch(config.userURL || `${baseURL}/services/oauth2/userinfo`, {
65
69
  headers: {
66
70
  Authorization: `Bearer ${accessToken}`
67
71
  }
@@ -29,7 +29,7 @@ export function defineOAuthSeznamEventHandler({ config, onSuccess, onError }) {
29
29
  })
30
30
  );
31
31
  }
32
- if (query.state !== state) {
32
+ if (!state || query.state !== state) {
33
33
  return handleInvalidState(event, "seznam", onError);
34
34
  }
35
35
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -12,7 +12,7 @@ export function defineOAuthShopifyCustomerEventHandler({
12
12
  config = defu(config, useRuntimeConfig(event).oauth?.shopifyCustomer, {});
13
13
  const query = getQuery(event);
14
14
  if (!config.clientId || !config.shopDomain) {
15
- return handleMissingConfiguration(event, "spotify", ["clientId", "shopDomain"], onError);
15
+ return handleMissingConfiguration(event, "shopifyCustomer", ["clientId", "shopDomain"], onError);
16
16
  }
17
17
  const verifier = await handlePkceVerifier(event);
18
18
  const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
@@ -42,7 +42,7 @@ export function defineOAuthShopifyCustomerEventHandler({
42
42
  })
43
43
  );
44
44
  }
45
- if (query.state !== state) {
45
+ if (!state || query.state !== state) {
46
46
  return handleInvalidState(event, "shopifyCustomer", onError);
47
47
  }
48
48
  const tokens = await requestAccessToken(discoveryResponse.token_endpoint, {
@@ -45,7 +45,7 @@ export function defineOAuthSlackEventHandler({
45
45
  })
46
46
  );
47
47
  }
48
- if (query.state !== state) {
48
+ if (!state || query.state !== state) {
49
49
  return handleInvalidState(event, "slack", onError);
50
50
  }
51
51
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -33,7 +33,7 @@ export function defineOAuthSpotifyEventHandler({ config, onSuccess, onError }) {
33
33
  })
34
34
  );
35
35
  }
36
- if (query.state !== state) {
36
+ if (!state || query.state !== state) {
37
37
  return handleInvalidState(event, "spotify", onError);
38
38
  }
39
39
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -27,7 +27,7 @@ export function defineOAuthSteamEventHandler({ config, onSuccess, onError }) {
27
27
  };
28
28
  return sendRedirect(event, withQuery(config.authorizationURL, steamOpenIdParams));
29
29
  }
30
- if (query.state !== state) {
30
+ if (!state || query.state !== state) {
31
31
  return handleInvalidState(event, "steam", onError);
32
32
  }
33
33
  if (!query["openid.signed"] || !query["openid.sig"]) {
@@ -52,7 +52,7 @@ export function defineOAuthStravaEventHandler({
52
52
  })
53
53
  );
54
54
  }
55
- if (query.state !== state) {
55
+ if (!state || query.state !== state) {
56
56
  return handleInvalidState(event, "strava", onError);
57
57
  }
58
58
  const tokens = await requestAccessToken(tokenURL, {
@@ -39,7 +39,7 @@ export function defineOAuthTikTokEventHandler({ config, onSuccess, onError }) {
39
39
  })
40
40
  );
41
41
  }
42
- if (query.state !== state) {
42
+ if (!state || query.state !== state) {
43
43
  return handleInvalidState(event, "tiktok", onError);
44
44
  }
45
45
  const tokens = await requestAccessToken(
@@ -33,7 +33,7 @@ export function defineOAuthTwitchEventHandler({ config, onSuccess, onError }) {
33
33
  })
34
34
  );
35
35
  }
36
- if (query.state !== state) {
36
+ if (!state || query.state !== state) {
37
37
  return handleInvalidState(event, "twitch", onError);
38
38
  }
39
39
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -52,7 +52,7 @@ export function defineOAuthVKEventHandler({
52
52
  })
53
53
  );
54
54
  }
55
- if (query.state !== state) {
55
+ if (!state || query.state !== state) {
56
56
  return handleInvalidState(event, "vk", onError);
57
57
  }
58
58
  const tokens = await requestAccessToken(config.tokenURL, {
@@ -0,0 +1,67 @@
1
+ import type { OAuthConfig } from '#auth-utils';
2
+ export interface OAuthWechatConfig {
3
+ /**
4
+ * WeChat App ID
5
+ * @default process.env.NUXT_OAUTH_WECHAT_APP_ID
6
+ */
7
+ appId?: string;
8
+ /**
9
+ * WeChat App Secret
10
+ * @default process.env.NUXT_OAUTH_WECHAT_APP_SECRET
11
+ */
12
+ appSecret?: string;
13
+ /**
14
+ * WeChat OAuth Scope
15
+ * @default ['snsapi_login']
16
+ * @see https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
17
+ */
18
+ scope?: string[];
19
+ /**
20
+ * Authorization URL
21
+ * @default 'https://open.weixin.qq.com/connect/qrconnect'
22
+ */
23
+ authorizationURL?: string;
24
+ /**
25
+ * Access token URL
26
+ * @default 'https://api.weixin.qq.com/sns/oauth2/access_token'
27
+ */
28
+ tokenURL?: string;
29
+ /**
30
+ * User profile URL
31
+ * @default 'https://api.weixin.qq.com/sns/userinfo'
32
+ */
33
+ userURL?: string;
34
+ /**
35
+ * User info language.
36
+ * @default 'zh_CN'
37
+ */
38
+ lang?: 'zh_CN' | 'zh_TW' | 'en';
39
+ /**
40
+ * Redirect URL to allow overriding when public hostname cannot be inferred.
41
+ * @default process.env.NUXT_OAUTH_WECHAT_REDIRECT_URL or current URL
42
+ */
43
+ redirectURL?: string;
44
+ }
45
+ export interface WechatOAuthTokens {
46
+ access_token: string;
47
+ expires_in: number;
48
+ refresh_token: string;
49
+ openid: string;
50
+ scope: string;
51
+ unionid?: string;
52
+ }
53
+ export interface WechatOAuthUser {
54
+ openid: string;
55
+ nickname: string;
56
+ sex: 0 | 1 | 2;
57
+ province: string;
58
+ city: string;
59
+ country: string;
60
+ headimgurl: string;
61
+ privilege: string[];
62
+ unionid?: string;
63
+ }
64
+ export declare function defineOAuthWechatEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthWechatConfig, {
65
+ user: WechatOAuthUser;
66
+ tokens: WechatOAuthTokens;
67
+ }>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;