nuxt-auth-utils 0.3.4 → 0.3.6

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 (34) hide show
  1. package/README.md +2 -1
  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 +5 -0
  6. package/dist/runtime/app/plugins/session.client.js +3 -1
  7. package/dist/runtime/server/lib/oauth/auth0.js +18 -34
  8. package/dist/runtime/server/lib/oauth/battledotnet.js +23 -37
  9. package/dist/runtime/server/lib/oauth/cognito.d.ts +5 -0
  10. package/dist/runtime/server/lib/oauth/cognito.js +21 -30
  11. package/dist/runtime/server/lib/oauth/discord.js +15 -36
  12. package/dist/runtime/server/lib/oauth/facebook.js +8 -24
  13. package/dist/runtime/server/lib/oauth/github.js +14 -26
  14. package/dist/runtime/server/lib/oauth/google.js +16 -36
  15. package/dist/runtime/server/lib/oauth/keycloak.js +14 -37
  16. package/dist/runtime/server/lib/oauth/linkedin.d.ts +2 -11
  17. package/dist/runtime/server/lib/oauth/linkedin.js +15 -36
  18. package/dist/runtime/server/lib/oauth/microsoft.d.ts +2 -11
  19. package/dist/runtime/server/lib/oauth/microsoft.js +16 -35
  20. package/dist/runtime/server/lib/oauth/paypal.js +17 -35
  21. package/dist/runtime/server/lib/oauth/spotify.js +17 -34
  22. package/dist/runtime/server/lib/oauth/steam.js +2 -6
  23. package/dist/runtime/server/lib/oauth/tiktok.d.ts +45 -0
  24. package/dist/runtime/server/lib/oauth/tiktok.js +84 -0
  25. package/dist/runtime/server/lib/oauth/twitch.js +16 -35
  26. package/dist/runtime/server/lib/oauth/x.d.ts +1 -6
  27. package/dist/runtime/server/lib/oauth/x.js +19 -60
  28. package/dist/runtime/server/lib/oauth/xsuaa.js +15 -34
  29. package/dist/runtime/server/lib/oauth/yandex.js +14 -36
  30. package/dist/runtime/server/lib/utils.d.ts +36 -0
  31. package/dist/runtime/server/lib/utils.js +46 -0
  32. package/dist/runtime/types/index.d.ts +1 -1
  33. package/dist/runtime/types/oauth-config.d.ts +3 -1
  34. package/package.json +12 -11
package/README.md CHANGED
@@ -199,6 +199,7 @@ It can also be set using environment variables:
199
199
  - PayPal
200
200
  - Spotify
201
201
  - Steam
202
+ - TikTok
202
203
  - Twitch
203
204
  - X (Twitter)
204
205
  - XSUAA
@@ -250,7 +251,7 @@ export default defineNitroPlugin(() => {
250
251
  // throw createError({ ... }) if session is invalid for example
251
252
  })
252
253
 
253
- // Called when we call useServerSession().clear() or clearUserSession(event)
254
+ // Called when we call useUserSession().clear() or clearUserSession(event)
254
255
  sessionHooks.hook('clear', async (session, event) => {
255
256
  // Log that user logged out
256
257
  })
package/dist/module.d.mts CHANGED
@@ -2,6 +2,6 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
4
  }
5
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
5
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
6
6
 
7
7
  export { type ModuleOptions, _default as default };
package/dist/module.d.ts CHANGED
@@ -2,6 +2,6 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
4
  }
5
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
5
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
6
6
 
7
7
  export { type ModuleOptions, _default as default };
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.3.4",
4
+ "version": "0.3.6",
5
5
  "builder": {
6
- "@nuxt/module-builder": "0.8.1",
6
+ "@nuxt/module-builder": "0.8.3",
7
7
  "unbuild": "2.0.0"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -149,6 +149,11 @@ const module = defineNuxtModule({
149
149
  clientSecret: "",
150
150
  redirectURL: ""
151
151
  });
152
+ runtimeConfig.oauth.tiktok = defu(runtimeConfig.oauth.tiktok, {
153
+ clientKey: "",
154
+ clientSecret: "",
155
+ redirectURL: ""
156
+ });
152
157
  }
153
158
  });
154
159
 
@@ -1,6 +1,8 @@
1
1
  import { defineNuxtPlugin, useUserSession } from "#imports";
2
2
  export default defineNuxtPlugin(async (nuxtApp) => {
3
- if (!nuxtApp.payload.serverRendered || Boolean(nuxtApp.payload.prerenderedAt) || Boolean(nuxtApp.payload.isCached)) {
3
+ if (!nuxtApp.payload.serverRendered) {
4
+ await useUserSession().fetch();
5
+ } else if (Boolean(nuxtApp.payload.prerenderedAt) || Boolean(nuxtApp.payload.isCached)) {
4
6
  nuxtApp.hook("app:mounted", async () => {
5
7
  await useUserSession().fetch();
6
8
  });
@@ -1,25 +1,21 @@
1
- import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
- import { withQuery, parsePath } from "ufo";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
4
5
  import { useRuntimeConfig } from "#imports";
5
6
  export function oauthAuth0EventHandler({ config, onSuccess, onError }) {
6
7
  return eventHandler(async (event) => {
7
8
  config = defu(config, useRuntimeConfig(event).oauth?.auth0, {
8
9
  authorizationParams: {}
9
10
  });
10
- const { code } = getQuery(event);
11
11
  if (!config.clientId || !config.clientSecret || !config.domain) {
12
- const error = createError({
13
- statusCode: 500,
14
- message: "Missing NUXT_OAUTH_AUTH0_CLIENT_ID or NUXT_OAUTH_AUTH0_CLIENT_SECRET or NUXT_OAUTH_AUTH0_DOMAIN env variables."
15
- });
16
- if (!onError) throw error;
17
- return onError(event, error);
12
+ return handleMissingConfiguration(event, "auth0", ["clientId", "clientSecret", "domain"], onError);
18
13
  }
19
14
  const authorizationURL = `https://${config.domain}/authorize`;
20
15
  const tokenURL = `https://${config.domain}/oauth/token`;
21
- const redirectURL = config.redirectURL || getRequestURL(event).href;
22
- if (!code) {
16
+ const query = getQuery(event);
17
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
18
+ if (!query.code) {
23
19
  config.scope = config.scope || ["openid", "offline_access"];
24
20
  if (config.emailRequired && !config.scope.includes("email")) {
25
21
  config.scope.push("email");
@@ -38,32 +34,20 @@ export function oauthAuth0EventHandler({ config, onSuccess, onError }) {
38
34
  })
39
35
  );
40
36
  }
41
- const tokens = await $fetch(
42
- tokenURL,
43
- {
44
- method: "POST",
45
- headers: {
46
- "Content-Type": "application/json"
47
- },
48
- body: {
49
- grant_type: "authorization_code",
50
- client_id: config.clientId,
51
- client_secret: config.clientSecret,
52
- redirect_uri: parsePath(redirectURL).pathname,
53
- code
54
- }
37
+ const tokens = await requestAccessToken(tokenURL, {
38
+ headers: {
39
+ "Content-Type": "application/json"
40
+ },
41
+ body: {
42
+ grant_type: "authorization_code",
43
+ client_id: config.clientId,
44
+ client_secret: config.clientSecret,
45
+ redirect_uri: redirectURL,
46
+ code: query.code
55
47
  }
56
- ).catch((error) => {
57
- return { error };
58
48
  });
59
49
  if (tokens.error) {
60
- const error = createError({
61
- statusCode: 401,
62
- message: `Auth0 login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
63
- data: tokens
64
- });
65
- if (!onError) throw error;
66
- return onError(event, error);
50
+ return handleAccessTokenErrorResponse(event, "auth0", tokens, onError);
67
51
  }
68
52
  const tokenType = tokens.token_type;
69
53
  const accessToken = tokens.access_token;
@@ -1,8 +1,9 @@
1
- import { randomUUID } from "node:crypto";
2
- import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
3
- import { withQuery, parsePath } from "ufo";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
4
3
  import { defu } from "defu";
5
- import { useRuntimeConfig } from "#imports";
4
+ import { randomUUID } from "uncrypto";
5
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
6
+ import { useRuntimeConfig, createError } from "#imports";
6
7
  export function oauthBattledotnetEventHandler({ config, onSuccess, onError }) {
7
8
  return eventHandler(async (event) => {
8
9
  config = defu(config, useRuntimeConfig(event).oauth?.battledotnet, {
@@ -11,7 +12,6 @@ export function oauthBattledotnetEventHandler({ config, onSuccess, onError }) {
11
12
  authorizationParams: {}
12
13
  });
13
14
  const query = getQuery(event);
14
- const { code } = query;
15
15
  if (query.error) {
16
16
  const error = createError({
17
17
  statusCode: 401,
@@ -22,15 +22,15 @@ export function oauthBattledotnetEventHandler({ config, onSuccess, onError }) {
22
22
  return onError(event, error);
23
23
  }
24
24
  if (!config.clientId || !config.clientSecret) {
25
- const error = createError({
26
- statusCode: 500,
27
- message: "Missing NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID or NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET env variables."
28
- });
29
- if (!onError) throw error;
30
- return onError(event, error);
25
+ return handleMissingConfiguration(
26
+ event,
27
+ "battledotnet",
28
+ ["clientId", "clientSecret"],
29
+ onError
30
+ );
31
31
  }
32
- const redirectURL = config.redirectURL || getRequestURL(event).href;
33
- if (!code) {
32
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
33
+ if (!query.code) {
34
34
  config.scope = config.scope || ["openid"];
35
35
  config.region = config.region || "EU";
36
36
  if (config.region === "CN") {
@@ -54,33 +54,19 @@ export function oauthBattledotnetEventHandler({ config, onSuccess, onError }) {
54
54
  if (!config.scope.includes("openid")) {
55
55
  config.scope.push("openid");
56
56
  }
57
- const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
58
- const tokens = await $fetch(
59
- config.tokenURL,
60
- {
61
- method: "POST",
62
- headers: {
63
- "Content-Type": "application/x-www-form-urlencoded",
64
- "Authorization": `Basic ${authCode}`
65
- },
66
- params: {
67
- code,
68
- grant_type: "authorization_code",
69
- scope: config.scope.join(" "),
70
- redirect_uri: parsePath(redirectURL).pathname
71
- }
57
+ const tokens = await requestAccessToken(config.tokenURL, {
58
+ headers: {
59
+ Authorization: `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64")}`
60
+ },
61
+ params: {
62
+ grant_type: "authorization_code",
63
+ scope: config.scope.join(" "),
64
+ redirect_uri: redirectURL,
65
+ code: query.code
72
66
  }
73
- ).catch((error) => {
74
- return { error };
75
67
  });
76
68
  if (tokens.error) {
77
- const error = createError({
78
- statusCode: 401,
79
- message: `Battle.net login failed: ${tokens.error || "Unknown error"}`,
80
- data: tokens
81
- });
82
- if (!onError) throw error;
83
- return onError(event, error);
69
+ return handleAccessTokenErrorResponse(event, "battle.net", tokens, onError);
84
70
  }
85
71
  const accessToken = tokens.access_token;
86
72
  const user = await $fetch("https://oauth.battle.net/userinfo", {
@@ -35,5 +35,10 @@ export interface OAuthCognitoConfig {
35
35
  * @default process.env.NUXT_OAUTH_COGNITO_REDIRECT_URL or current URL
36
36
  */
37
37
  redirectURL?: string;
38
+ /**
39
+ * AWS Cognito App Custom Domain – some pool configurations require this
40
+ * @default ''
41
+ */
42
+ domain?: string;
38
43
  }
39
44
  export declare function oauthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
@@ -1,25 +1,22 @@
1
- import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
- import { withQuery, parsePath } from "ufo";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
4
5
  import { useRuntimeConfig } from "#imports";
5
6
  export function oauthCognitoEventHandler({ config, onSuccess, onError }) {
6
7
  return eventHandler(async (event) => {
7
8
  config = defu(config, useRuntimeConfig(event).oauth?.cognito, {
8
9
  authorizationParams: {}
9
10
  });
10
- const { code } = getQuery(event);
11
11
  if (!config.clientId || !config.clientSecret || !config.userPoolId || !config.region) {
12
- const error = createError({
13
- statusCode: 500,
14
- message: "Missing NUXT_OAUTH_COGNITO_CLIENT_ID, NUXT_OAUTH_COGNITO_CLIENT_SECRET, NUXT_OAUTH_COGNITO_USER_POOL_ID, or NUXT_OAUTH_COGNITO_REGION env variables."
15
- });
16
- if (!onError) throw error;
17
- return onError(event, error);
12
+ return handleMissingConfiguration(event, "cognito", ["clientId", "clientSecret", "userPoolId", "region"], onError);
18
13
  }
19
- const authorizationURL = `https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/authorize`;
20
- const tokenURL = `https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/token`;
21
- const redirectURL = config.redirectURL || getRequestURL(event).href;
22
- if (!code) {
14
+ const urlBase = config?.domain || `${config.userPoolId}.auth.${config.region}.amazoncognito.com`;
15
+ const authorizationURL = `https://${urlBase}/oauth2/authorize`;
16
+ const tokenURL = `https://${urlBase}/oauth2/token`;
17
+ const query = getQuery(event);
18
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
19
+ if (!query.code) {
23
20
  config.scope = config.scope || ["openid", "profile"];
24
21
  return sendRedirect(
25
22
  event,
@@ -32,30 +29,24 @@ export function oauthCognitoEventHandler({ config, onSuccess, onError }) {
32
29
  })
33
30
  );
34
31
  }
35
- const tokens = await $fetch(
32
+ const tokens = await requestAccessToken(
36
33
  tokenURL,
37
34
  {
38
- method: "POST",
39
- headers: {
40
- "Content-Type": "application/x-www-form-urlencoded"
41
- },
42
- body: `grant_type=authorization_code&client_id=${config.clientId}&client_secret=${config.clientSecret}&redirect_uri=${parsePath(redirectURL).pathname}&code=${code}`
35
+ body: {
36
+ grant_type: "authorization_code",
37
+ client_id: config.clientId,
38
+ client_secret: config.clientSecret,
39
+ redirect_uri: redirectURL,
40
+ code: query.code
41
+ }
43
42
  }
44
- ).catch((error) => {
45
- return { error };
46
- });
43
+ );
47
44
  if (tokens.error) {
48
- const error = createError({
49
- statusCode: 401,
50
- message: `Cognito login failed: ${tokens.error_description || "Unknown error"}`,
51
- data: tokens
52
- });
53
- if (!onError) throw error;
54
- return onError(event, error);
45
+ return handleAccessTokenErrorResponse(event, "cognito", tokens, onError);
55
46
  }
56
47
  const tokenType = tokens.token_type;
57
48
  const accessToken = tokens.access_token;
58
- const user = await $fetch(`https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/userInfo`, {
49
+ const user = await $fetch(`https://${urlBase}/oauth2/userInfo`, {
59
50
  headers: {
60
51
  Authorization: `${tokenType} ${accessToken}`
61
52
  }
@@ -1,6 +1,7 @@
1
- import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
- import { withQuery, parseURL, stringifyParsedURL } from "ufo";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
4
5
  import { useRuntimeConfig } from "#imports";
5
6
  export function oauthDiscordEventHandler({ config, onSuccess, onError }) {
6
7
  return eventHandler(async (event) => {
@@ -10,17 +11,12 @@ export function oauthDiscordEventHandler({ config, onSuccess, onError }) {
10
11
  profileRequired: true,
11
12
  authorizationParams: {}
12
13
  });
13
- const { code } = getQuery(event);
14
+ const query = getQuery(event);
14
15
  if (!config.clientId || !config.clientSecret) {
15
- const error = createError({
16
- statusCode: 500,
17
- message: "Missing NUXT_OAUTH_DISCORD_CLIENT_ID or NUXT_OAUTH_DISCORD_CLIENT_SECRET env variables."
18
- });
19
- if (!onError) throw error;
20
- return onError(event, error);
16
+ return handleMissingConfiguration(event, "discord", ["clientId", "clientSecret"], onError);
21
17
  }
22
- const redirectURL = config.redirectURL || getRequestURL(event).href;
23
- if (!code) {
18
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
19
+ if (!query.code) {
24
20
  config.scope = config.scope || [];
25
21
  if (config.emailRequired && !config.scope.includes("email")) {
26
22
  config.scope.push("email");
@@ -39,34 +35,17 @@ export function oauthDiscordEventHandler({ config, onSuccess, onError }) {
39
35
  })
40
36
  );
41
37
  }
42
- const parsedRedirectUrl = parseURL(redirectURL);
43
- parsedRedirectUrl.search = "";
44
- const tokens = await $fetch(
45
- config.tokenURL,
46
- {
47
- method: "POST",
48
- headers: {
49
- "Content-Type": "application/x-www-form-urlencoded"
50
- },
51
- body: new URLSearchParams({
52
- client_id: config.clientId,
53
- client_secret: config.clientSecret,
54
- grant_type: "authorization_code",
55
- redirect_uri: stringifyParsedURL(parsedRedirectUrl),
56
- code
57
- }).toString()
38
+ const tokens = await requestAccessToken(config.tokenURL, {
39
+ body: {
40
+ client_id: config.clientId,
41
+ client_secret: config.clientSecret,
42
+ grant_type: "authorization_code",
43
+ redirect_uri: redirectURL,
44
+ code: query.code
58
45
  }
59
- ).catch((error) => {
60
- return { error };
61
46
  });
62
47
  if (tokens.error) {
63
- const error = createError({
64
- statusCode: 401,
65
- message: `Discord login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
66
- data: tokens
67
- });
68
- if (!onError) throw error;
69
- return onError(event, error);
48
+ return handleAccessTokenErrorResponse(event, "discord", tokens, onError);
70
49
  }
71
50
  const accessToken = tokens.access_token;
72
51
  const user = await $fetch("https://discord.com/api/users/@me", {
@@ -1,13 +1,8 @@
1
- import {
2
- eventHandler,
3
- createError,
4
- getQuery,
5
- getRequestURL,
6
- sendRedirect
7
- } from "h3";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
8
2
  import { withQuery } from "ufo";
9
3
  import { defu } from "defu";
10
- import { useRuntimeConfig } from "#imports";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
+ import { useRuntimeConfig, createError } from "#imports";
11
6
  export function oauthFacebookEventHandler({
12
7
  config,
13
8
  onSuccess,
@@ -30,14 +25,9 @@ export function oauthFacebookEventHandler({
30
25
  return onError(event, error);
31
26
  }
32
27
  if (!config.clientId) {
33
- const error = createError({
34
- statusCode: 500,
35
- message: "Missing NUXT_OAUTH_FACEBOOK_CLIENT_ID or NUXT_OAUTH_FACEBOOK_CLIENT_SECRET env variables."
36
- });
37
- if (!onError) throw error;
38
- return onError(event, error);
28
+ return handleMissingConfiguration(event, "facebook", ["clientId"], onError);
39
29
  }
40
- const redirectURL = config.redirectURL || getRequestURL(event).href;
30
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
41
31
  if (!query.code) {
42
32
  config.scope = config.scope || [];
43
33
  return sendRedirect(
@@ -49,23 +39,17 @@ export function oauthFacebookEventHandler({
49
39
  })
50
40
  );
51
41
  }
52
- const tokens = await $fetch(config.tokenURL, {
53
- method: "POST",
42
+ const tokens = await requestAccessToken(config.tokenURL, {
54
43
  body: {
55
44
  client_id: config.clientId,
56
45
  client_secret: config.clientSecret,
46
+ grant_type: "authorization_code",
57
47
  redirect_uri: redirectURL,
58
48
  code: query.code
59
49
  }
60
50
  });
61
51
  if (tokens.error) {
62
- const error = createError({
63
- statusCode: 401,
64
- message: `Facebook login failed: ${tokens.error || "Unknown error"}`,
65
- data: tokens
66
- });
67
- if (!onError) throw error;
68
- return onError(event, error);
52
+ return handleAccessTokenErrorResponse(event, "facebook", tokens, onError);
69
53
  }
70
54
  const accessToken = tokens.access_token;
71
55
  config.fields = config.fields || ["id", "name"];
@@ -1,7 +1,8 @@
1
- import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
2
  import { withQuery } from "ufo";
3
3
  import { defu } from "defu";
4
- import { useRuntimeConfig } from "#imports";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
5
+ import { useRuntimeConfig, createError } from "#imports";
5
6
  export function oauthGitHubEventHandler({ config, onSuccess, onError }) {
6
7
  return eventHandler(async (event) => {
7
8
  config = defu(config, useRuntimeConfig(event).oauth?.github, {
@@ -20,19 +21,14 @@ export function oauthGitHubEventHandler({ config, onSuccess, onError }) {
20
21
  return onError(event, error);
21
22
  }
22
23
  if (!config.clientId || !config.clientSecret) {
23
- const error = createError({
24
- statusCode: 500,
25
- message: "Missing NUXT_OAUTH_GITHUB_CLIENT_ID or NUXT_OAUTH_GITHUB_CLIENT_SECRET env variables."
26
- });
27
- if (!onError) throw error;
28
- return onError(event, error);
24
+ return handleMissingConfiguration(event, "github", ["clientId", "clientSecret"], onError);
29
25
  }
26
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
30
27
  if (!query.code) {
31
28
  config.scope = config.scope || [];
32
29
  if (config.emailRequired && !config.scope.includes("user:email")) {
33
30
  config.scope.push("user:email");
34
31
  }
35
- const redirectURL = config.redirectURL || getRequestURL(event).href;
36
32
  return sendRedirect(
37
33
  event,
38
34
  withQuery(config.authorizationURL, {
@@ -43,25 +39,17 @@ export function oauthGitHubEventHandler({ config, onSuccess, onError }) {
43
39
  })
44
40
  );
45
41
  }
46
- const tokens = await $fetch(
47
- config.tokenURL,
48
- {
49
- method: "POST",
50
- body: {
51
- client_id: config.clientId,
52
- client_secret: config.clientSecret,
53
- code: query.code
54
- }
42
+ const tokens = await requestAccessToken(config.tokenURL, {
43
+ body: {
44
+ grant_type: "authorization_code",
45
+ client_id: config.clientId,
46
+ client_secret: config.clientSecret,
47
+ redirect_uri: redirectURL,
48
+ code: query.code
55
49
  }
56
- );
50
+ });
57
51
  if (tokens.error) {
58
- const error = createError({
59
- statusCode: 401,
60
- message: `GitHub login failed: ${tokens.error || "Unknown error"}`,
61
- data: tokens
62
- });
63
- if (!onError) throw error;
64
- return onError(event, error);
52
+ return handleAccessTokenErrorResponse(event, "github", tokens, onError);
65
53
  }
66
54
  const accessToken = tokens.access_token;
67
55
  const user = await $fetch("https://api.github.com/user", {
@@ -1,12 +1,7 @@
1
- import {
2
- eventHandler,
3
- createError,
4
- getQuery,
5
- getRequestURL,
6
- sendRedirect
7
- } from "h3";
8
- import { withQuery, parsePath } from "ufo";
1
+ import { eventHandler, getQuery, sendRedirect } from "h3";
2
+ import { withQuery } from "ufo";
9
3
  import { defu } from "defu";
4
+ import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
10
5
  import { useRuntimeConfig } from "#imports";
11
6
  export function oauthGoogleEventHandler({
12
7
  config,
@@ -20,17 +15,12 @@ export function oauthGoogleEventHandler({
20
15
  userURL: "https://www.googleapis.com/oauth2/v3/userinfo",
21
16
  authorizationParams: {}
22
17
  });
23
- const { code } = getQuery(event);
18
+ const query = getQuery(event);
24
19
  if (!config.clientId) {
25
- const error = createError({
26
- statusCode: 500,
27
- message: "Missing NUXT_OAUTH_GOOGLE_CLIENT_ID env variables."
28
- });
29
- if (!onError) throw error;
30
- return onError(event, error);
20
+ return handleMissingConfiguration(event, "google", ["clientId"], onError);
31
21
  }
32
- const redirectURL = config.redirectURL || getRequestURL(event).href;
33
- if (!code) {
22
+ const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
23
+ if (!query.code) {
34
24
  config.scope = config.scope || ["email", "profile"];
35
25
  return sendRedirect(
36
26
  event,
@@ -43,27 +33,17 @@ export function oauthGoogleEventHandler({
43
33
  })
44
34
  );
45
35
  }
46
- const body = {
47
- grant_type: "authorization_code",
48
- redirect_uri: parsePath(redirectURL).pathname,
49
- client_id: config.clientId,
50
- client_secret: config.clientSecret,
51
- code
52
- };
53
- const tokens = await $fetch(config.tokenURL, {
54
- method: "POST",
55
- body
56
- }).catch((error) => {
57
- return { error };
36
+ const tokens = await requestAccessToken(config.tokenURL, {
37
+ body: {
38
+ grant_type: "authorization_code",
39
+ code: query.code,
40
+ client_id: config.clientId,
41
+ client_secret: config.clientSecret,
42
+ redirect_uri: redirectURL
43
+ }
58
44
  });
59
45
  if (tokens.error) {
60
- const error = createError({
61
- statusCode: 401,
62
- message: `Google login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
63
- data: tokens
64
- });
65
- if (!onError) throw error;
66
- return onError(event, error);
46
+ return handleAccessTokenErrorResponse(event, "google", tokens, onError);
67
47
  }
68
48
  const accessToken = tokens.access_token;
69
49
  const user = await $fetch(