Skip to content

Commit b6cdec5

Browse files
samulefevrejustserdarautofix-ci[bot]atinux
authored
feat: added oauth battle.net (#11)
* add oauth battle.net * add battle.net to env.example * fix: rm emailRequired and if cn to better place * feat: added discord auth provider (#7) * feat: discord auth provider * Update discord.get.ts Typo Github -> Discord * fix: Update redirectUrl to include server route * refactor: cleanup discord provider * fix: Added discord env vars in example env * feat: Module addition of Discord vars * [autofix.ci] apply automated fixes * refactor: removed domain env usage * chore: update --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Sébastien Chopin <[email protected]> * chore: update --------- Co-authored-by: h+ <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Sébastien Chopin <[email protected]>
1 parent ba78a8b commit b6cdec5

File tree

8 files changed

+203
-2
lines changed

8 files changed

+203
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export default defineNuxtConfig({
142142
```
143143

144144
It can also be set using environment variables:
145+
145146
- `NUXT_OAUTH_<PROVIDER>_CLIENT_ID`
146147
- `NUXT_OAUTH_<PROVIDER>_CLIENT_SECRET`
147148

@@ -153,6 +154,7 @@ It can also be set using environment variables:
153154
- Google
154155
- Spotify
155156
- Twitch
157+
- Battle.net
156158

157159
You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
158160

@@ -183,7 +185,6 @@ export default oauth.githubEventHandler({
183185

184186
Make sure to set the callback URL in your OAuth app settings as `<your-domain>/auth/github`.
185187

186-
187188
## Development
188189

189190
```bash

playground/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ NUXT_OAUTH_AUTH0_DOMAIN=
1818
# Discord
1919
NUXT_OAUTH_DISCORD_CLIENT_ID=
2020
NUXT_OAUTH_DISCORD_CLIENT_SECRET=
21+
# Battle.net OAuth
22+
NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID=
23+
NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET=

playground/app.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ const { loggedIn, session, clear } = useUserSession()
6565
>
6666
Login with Discord
6767
</UButton>
68+
<UButton
69+
v-if="!loggedIn || !session.user.auth0"
70+
to="/auth/battledotnet"
71+
icon="i-simple-icons-battledotnet"
72+
external
73+
color="gray"
74+
size="xs"
75+
>
76+
Login with Battle.net
77+
</UButton>
6878
<UButton
6979
v-if="loggedIn"
7080
color="gray"

playground/auth.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ declare module '#auth-utils' {
77
twitch?: any
88
auth0?: any
99
discord?: any
10+
battledotnet?: any
1011
}
1112
loggedInAt: number
1213
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default oauth.battledotnetEventHandler({
2+
async onSuccess(event, { user }) {
3+
await setUserSession(event, {
4+
user: {
5+
battledotnet: user,
6+
},
7+
loggedInAt: Date.now()
8+
})
9+
10+
return sendRedirect(event, '/')
11+
}
12+
})

src/module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,10 @@ export default defineNuxtModule<ModuleOptions>({
100100
clientId: '',
101101
clientSecret: ''
102102
})
103+
// Battle.net OAuth
104+
runtimeConfig.oauth.battledotnet = defu(runtimeConfig.oauth.battledotnet, {
105+
clientId: '',
106+
clientSecret: ''
107+
})
103108
}
104109
})
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import type { H3Event, H3Error } from 'h3'
2+
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
3+
import { ofetch } from 'ofetch'
4+
import { withQuery, parsePath } from 'ufo'
5+
import { defu } from 'defu'
6+
import { useRuntimeConfig } from '#imports'
7+
import { randomUUID } from 'crypto'
8+
9+
export interface OAuthBattledotnetConfig {
10+
/**
11+
* Battle.net OAuth Client ID
12+
* @default process.env.NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID
13+
*/
14+
clientId?: string
15+
/**
16+
* Battle.net OAuth Client Secret
17+
* @default process.env.NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET
18+
*/
19+
clientSecret?: string
20+
/**
21+
* Battle.net OAuth Scope
22+
* @default []
23+
* @see https://develop.battle.net/documentation/guides/using-oauth
24+
* @example ['openid', 'wow.profile', 'sc2.profile', 'd3.profile']
25+
*/
26+
scope?: string[]
27+
/**
28+
* Battle.net OAuth Region
29+
* @default EU
30+
* @see https://develop.battle.net/documentation/guides/using-oauth
31+
* @example EU (possible values: US, EU, APAC)
32+
*/
33+
region?: string
34+
/**
35+
* Battle.net OAuth Authorization URL
36+
* @default 'https://oauth.battle.net/authorize'
37+
*/
38+
authorizationURL?: string
39+
/**
40+
* Battle.net OAuth Token URL
41+
* @default 'https://oauth.battle.net/token'
42+
*/
43+
tokenURL?: string
44+
}
45+
46+
interface OAuthConfig {
47+
config?: OAuthBattledotnetConfig
48+
onSuccess: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
49+
onError?: (event: H3Event, error: H3Error) => Promise<void> | void
50+
}
51+
52+
export function battledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig) {
53+
return eventHandler(async (event: H3Event) => {
54+
55+
// @ts-ignore
56+
config = defu(config, useRuntimeConfig(event).oauth?.battledotnet, {
57+
authorizationURL: 'https://oauth.battle.net/authorize',
58+
tokenURL: 'https://oauth.battle.net/token'
59+
}) as OAuthBattledotnetConfig
60+
61+
const query = getQuery(event)
62+
const { code } = query
63+
64+
if (query.error) {
65+
const error = createError({
66+
statusCode: 401,
67+
message: `Battle.net login failed: ${query.error || 'Unknown error'}`,
68+
data: query
69+
})
70+
if (!onError) throw error
71+
return onError(event, error)
72+
}
73+
74+
if (!config.clientId || !config.clientSecret) {
75+
const error = createError({
76+
statusCode: 500,
77+
message: 'Missing NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID or NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET env variables.'
78+
})
79+
if (!onError) throw error
80+
return onError(event, error)
81+
}
82+
83+
if (!code) {
84+
config.scope = config.scope || ['openid']
85+
config.region = config.region || 'EU'
86+
87+
if (config.region === 'CN') {
88+
config.authorizationURL = 'https://oauth.battlenet.com.cn/authorize'
89+
config.tokenURL = 'https://oauth.battlenet.com.cn/token'
90+
}
91+
92+
// Redirect to Battle.net Oauth page
93+
const redirectUrl = getRequestURL(event).href
94+
return sendRedirect(
95+
event,
96+
withQuery(config.authorizationURL as string, {
97+
client_id: config.clientId,
98+
redirect_uri: redirectUrl,
99+
scope: config.scope.join(' '),
100+
state: randomUUID(), // Todo: handle PKCE flow
101+
response_type: 'code',
102+
})
103+
)
104+
}
105+
106+
const redirectUrl = getRequestURL(event).href
107+
config.scope = config.scope || []
108+
if (!config.scope.includes('openid')) {
109+
config.scope.push('openid')
110+
}
111+
112+
const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64')
113+
114+
const tokens: any = await $fetch(
115+
config.tokenURL as string,
116+
{
117+
method: 'POST',
118+
headers: {
119+
'Content-Type': 'application/x-www-form-urlencoded',
120+
Authorization: `Basic ${authCode}`,
121+
},
122+
params: {
123+
code,
124+
grant_type: 'authorization_code',
125+
scope: config.scope.join(' '),
126+
redirect_uri: parsePath(redirectUrl).pathname,
127+
}
128+
}
129+
).catch((error) => {
130+
return { error }
131+
})
132+
133+
if (tokens.error) {
134+
const error = createError({
135+
statusCode: 401,
136+
message: `Battle.net login failed: ${tokens.error || 'Unknown error'}`,
137+
data: tokens
138+
})
139+
if (!onError) throw error
140+
return onError(event, error)
141+
}
142+
143+
const accessToken = tokens.access_token
144+
145+
const user: any = await ofetch('https://oauth.battle.net/userinfo', {
146+
headers: {
147+
'User-Agent': `Battledotnet-OAuth-${config.clientId}`,
148+
Authorization: `Bearer ${accessToken}`
149+
}
150+
})
151+
152+
if (!user) {
153+
const error = createError({
154+
statusCode: 500,
155+
message: 'Could not get Battle.net user',
156+
data: tokens
157+
})
158+
if (!onError) throw error
159+
return onError(event, error)
160+
}
161+
162+
return onSuccess(event, {
163+
user,
164+
tokens,
165+
})
166+
})
167+
}

src/runtime/server/utils/oauth.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { spotifyEventHandler } from '../lib/oauth/spotify'
44
import { twitchEventHandler } from '../lib/oauth/twitch'
55
import { auth0EventHandler } from '../lib/oauth/auth0'
66
import { discordEventHandler } from '../lib/oauth/discord'
7+
import { battledotnetEventHandler } from '../lib/oauth/battledotnet'
78

89
export const oauth = {
910
githubEventHandler,
1011
spotifyEventHandler,
1112
googleEventHandler,
1213
twitchEventHandler,
1314
auth0EventHandler,
14-
discordEventHandler
15+
discordEventHandler,
16+
battledotnetEventHandler
1517
}

0 commit comments

Comments
 (0)