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 +5 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +8 -0
- package/dist/runtime/server/lib/oauth/battledotnet.d.ts +47 -0
- package/dist/runtime/server/lib/oauth/battledotnet.mjs +110 -0
- package/dist/runtime/server/lib/oauth/discord.d.ts +51 -0
- package/dist/runtime/server/lib/oauth/discord.mjs +85 -0
- package/dist/runtime/server/utils/oauth.d.ts +4 -0
- package/dist/runtime/server/utils/oauth.mjs +5 -1
- package/dist/types.d.mts +3 -2
- package/dist/types.d.ts +3 -2
- package/package.json +6 -6
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
|
-
-
|
|
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
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.
|
|
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.
|
|
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.
|
|
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.
|
|
47
|
+
"@types/node": "^20.9.0",
|
|
48
48
|
"changelogen": "^0.5.5",
|
|
49
|
-
"eslint": "^8.
|
|
49
|
+
"eslint": "^8.53.0",
|
|
50
50
|
"nuxt": "^3.8.1",
|
|
51
51
|
"typescript": "^5.2.2",
|
|
52
|
-
"vitest": "^0.
|
|
52
|
+
"vitest": "^0.34.6",
|
|
53
53
|
"vue-tsc": "^1.8.22"
|
|
54
54
|
}
|
|
55
55
|
}
|