nuxt-auth-utils 0.0.5 → 0.0.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.
package/README.md CHANGED
@@ -142,16 +142,19 @@ export default defineNuxtConfig({
142
142
  ```
143
143
 
144
144
  It can also be set using environment variables:
145
+
145
146
  - `NUXT_OAUTH_<PROVIDER>_CLIENT_ID`
146
147
  - `NUXT_OAUTH_<PROVIDER>_CLIENT_SECRET`
147
148
 
148
149
  #### Supported OAuth Providers
149
150
 
151
+ - Auth0
152
+ - Discord
150
153
  - GitHub
151
- - Spotify
152
154
  - Google
155
+ - Spotify
153
156
  - Twitch
154
- - Auth0
157
+ - Battle.net
155
158
 
156
159
  You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
157
160
 
@@ -182,7 +185,6 @@ export default oauth.githubEventHandler({
182
185
 
183
186
  Make sure to set the callback URL in your OAuth app settings as `<your-domain>/auth/github`.
184
187
 
185
-
186
188
  ## Development
187
189
 
188
190
  ```bash
package/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.0.5"
4
+ "version": "0.0.6"
5
5
  }
package/dist/module.mjs CHANGED
@@ -76,6 +76,14 @@ const module = defineNuxtModule({
76
76
  clientSecret: "",
77
77
  domain: ""
78
78
  });
79
+ runtimeConfig.oauth.discord = defu(runtimeConfig.oauth.discord, {
80
+ clientId: "",
81
+ clientSecret: ""
82
+ });
83
+ runtimeConfig.oauth.battledotnet = defu(runtimeConfig.oauth.battledotnet, {
84
+ clientId: "",
85
+ clientSecret: ""
86
+ });
79
87
  }
80
88
  });
81
89
 
@@ -0,0 +1,47 @@
1
+ import type { H3Event, H3Error } from 'h3';
2
+ export interface OAuthBattledotnetConfig {
3
+ /**
4
+ * Battle.net OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Battle.net OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Battle.net OAuth Scope
15
+ * @default []
16
+ * @see https://develop.battle.net/documentation/guides/using-oauth
17
+ * @example ['openid', 'wow.profile', 'sc2.profile', 'd3.profile']
18
+ */
19
+ scope?: string[];
20
+ /**
21
+ * Battle.net OAuth Region
22
+ * @default EU
23
+ * @see https://develop.battle.net/documentation/guides/using-oauth
24
+ * @example EU (possible values: US, EU, APAC)
25
+ */
26
+ region?: string;
27
+ /**
28
+ * Battle.net OAuth Authorization URL
29
+ * @default 'https://oauth.battle.net/authorize'
30
+ */
31
+ authorizationURL?: string;
32
+ /**
33
+ * Battle.net OAuth Token URL
34
+ * @default 'https://oauth.battle.net/token'
35
+ */
36
+ tokenURL?: string;
37
+ }
38
+ interface OAuthConfig {
39
+ config?: OAuthBattledotnetConfig;
40
+ onSuccess: (event: H3Event, result: {
41
+ user: any;
42
+ tokens: any;
43
+ }) => Promise<void> | void;
44
+ onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
45
+ }
46
+ export declare function battledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
47
+ export {};
@@ -0,0 +1,110 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { ofetch } from "ofetch";
3
+ import { withQuery, parsePath } from "ufo";
4
+ import { defu } from "defu";
5
+ import { useRuntimeConfig } from "#imports";
6
+ import { randomUUID } from "crypto";
7
+ export function battledotnetEventHandler({ config, onSuccess, onError }) {
8
+ return eventHandler(async (event) => {
9
+ config = defu(config, useRuntimeConfig(event).oauth?.battledotnet, {
10
+ authorizationURL: "https://oauth.battle.net/authorize",
11
+ tokenURL: "https://oauth.battle.net/token"
12
+ });
13
+ const query = getQuery(event);
14
+ const { code } = query;
15
+ if (query.error) {
16
+ const error = createError({
17
+ statusCode: 401,
18
+ message: `Battle.net login failed: ${query.error || "Unknown error"}`,
19
+ data: query
20
+ });
21
+ if (!onError)
22
+ throw error;
23
+ return onError(event, error);
24
+ }
25
+ if (!config.clientId || !config.clientSecret) {
26
+ const error = createError({
27
+ statusCode: 500,
28
+ message: "Missing NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID or NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET env variables."
29
+ });
30
+ if (!onError)
31
+ throw error;
32
+ return onError(event, error);
33
+ }
34
+ if (!code) {
35
+ config.scope = config.scope || ["openid"];
36
+ config.region = config.region || "EU";
37
+ if (config.region === "CN") {
38
+ config.authorizationURL = "https://oauth.battlenet.com.cn/authorize";
39
+ config.tokenURL = "https://oauth.battlenet.com.cn/token";
40
+ }
41
+ const redirectUrl2 = getRequestURL(event).href;
42
+ return sendRedirect(
43
+ event,
44
+ withQuery(config.authorizationURL, {
45
+ client_id: config.clientId,
46
+ redirect_uri: redirectUrl2,
47
+ scope: config.scope.join(" "),
48
+ state: randomUUID(),
49
+ // Todo: handle PKCE flow
50
+ response_type: "code"
51
+ })
52
+ );
53
+ }
54
+ const redirectUrl = getRequestURL(event).href;
55
+ config.scope = config.scope || [];
56
+ if (!config.scope.includes("openid")) {
57
+ config.scope.push("openid");
58
+ }
59
+ const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
60
+ const tokens = await $fetch(
61
+ config.tokenURL,
62
+ {
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/x-www-form-urlencoded",
66
+ Authorization: `Basic ${authCode}`
67
+ },
68
+ params: {
69
+ code,
70
+ grant_type: "authorization_code",
71
+ scope: config.scope.join(" "),
72
+ redirect_uri: parsePath(redirectUrl).pathname
73
+ }
74
+ }
75
+ ).catch((error) => {
76
+ return { error };
77
+ });
78
+ if (tokens.error) {
79
+ const error = createError({
80
+ statusCode: 401,
81
+ message: `Battle.net login failed: ${tokens.error || "Unknown error"}`,
82
+ data: tokens
83
+ });
84
+ if (!onError)
85
+ throw error;
86
+ return onError(event, error);
87
+ }
88
+ const accessToken = tokens.access_token;
89
+ const user = await ofetch("https://oauth.battle.net/userinfo", {
90
+ headers: {
91
+ "User-Agent": `Battledotnet-OAuth-${config.clientId}`,
92
+ Authorization: `Bearer ${accessToken}`
93
+ }
94
+ });
95
+ if (!user) {
96
+ const error = createError({
97
+ statusCode: 500,
98
+ message: "Could not get Battle.net user",
99
+ data: tokens
100
+ });
101
+ if (!onError)
102
+ throw error;
103
+ return onError(event, error);
104
+ }
105
+ return onSuccess(event, {
106
+ user,
107
+ tokens
108
+ });
109
+ });
110
+ }
@@ -0,0 +1,51 @@
1
+ import type { H3Event, H3Error } from 'h3';
2
+ export interface OAuthDiscordConfig {
3
+ /**
4
+ * Discord OAuth Client ID
5
+ * @default process.env.NUXT_OAUTH_DISCORD_CLIENT_ID
6
+ */
7
+ clientId?: string;
8
+ /**
9
+ * Discord OAuth Client Secret
10
+ * @default process.env.NUXT_OAUTH_DISCORD_CLIENT_SECRET
11
+ */
12
+ clientSecret?: string;
13
+ /**
14
+ * Discord OAuth Scope
15
+ * @default []
16
+ * @see https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
17
+ * @example ['identify', 'email']
18
+ * Without the identify scope the user will not be returned.
19
+ */
20
+ scope?: string[];
21
+ /**
22
+ * Require email from user, adds the ['email'] scope if not present.
23
+ * @default false
24
+ */
25
+ emailRequired?: boolean;
26
+ /**
27
+ * Require profile from user, adds the ['identify'] scope if not present.
28
+ * @default true
29
+ */
30
+ profileRequired?: boolean;
31
+ /**
32
+ * Discord OAuth Authorization URL
33
+ * @default 'https://discord.com/oauth2/authorize'
34
+ */
35
+ authorizationURL?: string;
36
+ /**
37
+ * Discord OAuth Token URL
38
+ * @default 'https://discord.com/api/oauth2/token'
39
+ */
40
+ tokenURL?: string;
41
+ }
42
+ interface OAuthConfig {
43
+ config?: OAuthDiscordConfig;
44
+ onSuccess: (event: H3Event, result: {
45
+ user: any;
46
+ tokens: any;
47
+ }) => Promise<void> | void;
48
+ onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
49
+ }
50
+ export declare function discordEventHandler({ config, onSuccess, onError }: OAuthConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
51
+ export {};
@@ -0,0 +1,85 @@
1
+ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
2
+ import { withQuery, parseURL, stringifyParsedURL } from "ufo";
3
+ import { ofetch } from "ofetch";
4
+ import { defu } from "defu";
5
+ import { useRuntimeConfig } from "#imports";
6
+ export function discordEventHandler({ config, onSuccess, onError }) {
7
+ return eventHandler(async (event) => {
8
+ config = defu(config, useRuntimeConfig(event).oauth?.discord, {
9
+ authorizationURL: "https://discord.com/oauth2/authorize",
10
+ tokenURL: "https://discord.com/api/oauth2/token",
11
+ profileRequired: true
12
+ });
13
+ const { code } = getQuery(event);
14
+ 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)
20
+ throw error;
21
+ return onError(event, error);
22
+ }
23
+ const redirectUrl = getRequestURL(event).href;
24
+ if (!code) {
25
+ config.scope = config.scope || [];
26
+ if (config.emailRequired && !config.scope.includes("email")) {
27
+ config.scope.push("email");
28
+ }
29
+ if (config.profileRequired && !config.scope.includes("identify")) {
30
+ config.scope.push("identify");
31
+ }
32
+ return sendRedirect(
33
+ event,
34
+ withQuery(config.authorizationURL, {
35
+ response_type: "code",
36
+ client_id: config.clientId,
37
+ redirect_uri: redirectUrl,
38
+ scope: config.scope.join(" ")
39
+ })
40
+ );
41
+ }
42
+ const parsedRedirectUrl = parseURL(redirectUrl);
43
+ parsedRedirectUrl.search = "";
44
+ const tokens = await ofetch(
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()
58
+ }
59
+ ).catch((error) => {
60
+ return { error };
61
+ });
62
+ if (tokens.error) {
63
+ console.log(tokens);
64
+ const error = createError({
65
+ statusCode: 401,
66
+ message: `Discord login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
67
+ data: tokens
68
+ });
69
+ if (!onError)
70
+ throw error;
71
+ return onError(event, error);
72
+ }
73
+ const accessToken = tokens.access_token;
74
+ const user = await ofetch("https://discord.com/api/users/@me", {
75
+ headers: {
76
+ "user-agent": "Nuxt Auth Utils",
77
+ Authorization: `Bearer ${accessToken}`
78
+ }
79
+ });
80
+ return onSuccess(event, {
81
+ tokens,
82
+ user
83
+ });
84
+ });
85
+ }
@@ -3,10 +3,14 @@ import { googleEventHandler } from '../lib/oauth/google';
3
3
  import { spotifyEventHandler } from '../lib/oauth/spotify';
4
4
  import { twitchEventHandler } from '../lib/oauth/twitch';
5
5
  import { auth0EventHandler } from '../lib/oauth/auth0';
6
+ import { discordEventHandler } from '../lib/oauth/discord';
7
+ import { battledotnetEventHandler } from '../lib/oauth/battledotnet';
6
8
  export declare const oauth: {
7
9
  githubEventHandler: typeof githubEventHandler;
8
10
  spotifyEventHandler: typeof spotifyEventHandler;
9
11
  googleEventHandler: typeof googleEventHandler;
10
12
  twitchEventHandler: typeof twitchEventHandler;
11
13
  auth0EventHandler: typeof auth0EventHandler;
14
+ discordEventHandler: typeof discordEventHandler;
15
+ battledotnetEventHandler: typeof battledotnetEventHandler;
12
16
  };
@@ -3,10 +3,14 @@ import { googleEventHandler } from "../lib/oauth/google.mjs";
3
3
  import { spotifyEventHandler } from "../lib/oauth/spotify.mjs";
4
4
  import { twitchEventHandler } from "../lib/oauth/twitch.mjs";
5
5
  import { auth0EventHandler } from "../lib/oauth/auth0.mjs";
6
+ import { discordEventHandler } from "../lib/oauth/discord.mjs";
7
+ import { battledotnetEventHandler } from "../lib/oauth/battledotnet.mjs";
6
8
  export const oauth = {
7
9
  githubEventHandler,
8
10
  spotifyEventHandler,
9
11
  googleEventHandler,
10
12
  twitchEventHandler,
11
- auth0EventHandler
13
+ auth0EventHandler,
14
+ discordEventHandler,
15
+ battledotnetEventHandler
12
16
  };
package/dist/types.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
- import { ModuleOptions } from './module'
2
+ import type { ModuleOptions } from './module'
3
+
3
4
 
4
5
  declare module '@nuxt/schema' {
5
6
  interface NuxtConfig { ['auth']?: Partial<ModuleOptions> }
@@ -12,4 +13,4 @@ declare module 'nuxt/schema' {
12
13
  }
13
14
 
14
15
 
15
- export { ModuleOptions, default } from './module'
16
+ export type { ModuleOptions, default } from './module'
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
- import { ModuleOptions } from './module'
2
+ import type { ModuleOptions } from './module'
3
+
3
4
 
4
5
  declare module '@nuxt/schema' {
5
6
  interface NuxtConfig { ['auth']?: Partial<ModuleOptions> }
@@ -12,4 +13,4 @@ declare module 'nuxt/schema' {
12
13
  }
13
14
 
14
15
 
15
- export { ModuleOptions, default } from './module'
16
+ export type { ModuleOptions, default } from './module'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auth-utils",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Minimalist Auth module for Nuxt with SSR",
5
5
  "repository": "Atinux/nuxt-auth-utils",
6
6
  "license": "MIT",
@@ -36,20 +36,20 @@
36
36
  "ohash": "^1.1.3"
37
37
  },
38
38
  "devDependencies": {
39
- "@iconify-json/simple-icons": "^1.1.76",
39
+ "@iconify-json/simple-icons": "^1.1.78",
40
40
  "@nuxt/devtools": "latest",
41
41
  "@nuxt/eslint-config": "^0.2.0",
42
- "@nuxt/module-builder": "^0.5.2",
42
+ "@nuxt/module-builder": "^0.5.4",
43
43
  "@nuxt/schema": "^3.8.1",
44
44
  "@nuxt/test-utils": "^3.8.1",
45
45
  "@nuxt/ui": "^2.10.0",
46
46
  "@nuxt/ui-pro": "^0.4.2",
47
- "@types/node": "^20.8.9",
47
+ "@types/node": "^20.9.0",
48
48
  "changelogen": "^0.5.5",
49
- "eslint": "^8.52.0",
49
+ "eslint": "^8.53.0",
50
50
  "nuxt": "^3.8.1",
51
51
  "typescript": "^5.2.2",
52
- "vitest": "^0.33.0",
52
+ "vitest": "^0.34.6",
53
53
  "vue-tsc": "^1.8.22"
54
54
  }
55
55
  }