Skip to content

Commit

Permalink
feat: added oauth battle.net (#11)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
4 people authored Nov 13, 2023
1 parent ba78a8b commit b6cdec5
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export default defineNuxtConfig({
```

It can also be set using environment variables:

- `NUXT_OAUTH_<PROVIDER>_CLIENT_ID`
- `NUXT_OAUTH_<PROVIDER>_CLIENT_SECRET`

Expand All @@ -153,6 +154,7 @@ It can also be set using environment variables:
- Google
- Spotify
- Twitch
- Battle.net

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

Expand Down Expand Up @@ -183,7 +185,6 @@ export default oauth.githubEventHandler({

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


## Development

```bash
Expand Down
3 changes: 3 additions & 0 deletions playground/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ NUXT_OAUTH_AUTH0_DOMAIN=
# Discord
NUXT_OAUTH_DISCORD_CLIENT_ID=
NUXT_OAUTH_DISCORD_CLIENT_SECRET=
# Battle.net OAuth
NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID=
NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET=
10 changes: 10 additions & 0 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ const { loggedIn, session, clear } = useUserSession()
>
Login with Discord
</UButton>
<UButton
v-if="!loggedIn || !session.user.auth0"
to="/auth/battledotnet"
icon="i-simple-icons-battledotnet"
external
color="gray"
size="xs"
>
Login with Battle.net
</UButton>
<UButton
v-if="loggedIn"
color="gray"
Expand Down
1 change: 1 addition & 0 deletions playground/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ declare module '#auth-utils' {
twitch?: any
auth0?: any
discord?: any
battledotnet?: any
}
loggedInAt: number
}
Expand Down
12 changes: 12 additions & 0 deletions playground/server/routes/auth/battledotnet.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default oauth.battledotnetEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
battledotnet: user,
},
loggedInAt: Date.now()
})

return sendRedirect(event, '/')
}
})
5 changes: 5 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,10 @@ export default defineNuxtModule<ModuleOptions>({
clientId: '',
clientSecret: ''
})
// Battle.net OAuth
runtimeConfig.oauth.battledotnet = defu(runtimeConfig.oauth.battledotnet, {
clientId: '',
clientSecret: ''
})
}
})
167 changes: 167 additions & 0 deletions src/runtime/server/lib/oauth/battledotnet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import type { H3Event, H3Error } from 'h3'
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
import { ofetch } from 'ofetch'
import { withQuery, parsePath } from 'ufo'
import { defu } from 'defu'
import { useRuntimeConfig } from '#imports'
import { randomUUID } from 'crypto'

export interface OAuthBattledotnetConfig {
/**
* Battle.net OAuth Client ID
* @default process.env.NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID
*/
clientId?: string
/**
* Battle.net OAuth Client Secret
* @default process.env.NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET
*/
clientSecret?: string
/**
* Battle.net OAuth Scope
* @default []
* @see https://develop.battle.net/documentation/guides/using-oauth
* @example ['openid', 'wow.profile', 'sc2.profile', 'd3.profile']
*/
scope?: string[]
/**
* Battle.net OAuth Region
* @default EU
* @see https://develop.battle.net/documentation/guides/using-oauth
* @example EU (possible values: US, EU, APAC)
*/
region?: string
/**
* Battle.net OAuth Authorization URL
* @default 'https://oauth.battle.net/authorize'
*/
authorizationURL?: string
/**
* Battle.net OAuth Token URL
* @default 'https://oauth.battle.net/token'
*/
tokenURL?: string
}

interface OAuthConfig {
config?: OAuthBattledotnetConfig
onSuccess: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
onError?: (event: H3Event, error: H3Error) => Promise<void> | void
}

export function battledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig) {
return eventHandler(async (event: H3Event) => {

// @ts-ignore
config = defu(config, useRuntimeConfig(event).oauth?.battledotnet, {
authorizationURL: 'https://oauth.battle.net/authorize',
tokenURL: 'https://oauth.battle.net/token'
}) as OAuthBattledotnetConfig

const query = getQuery(event)
const { code } = query

if (query.error) {
const error = createError({
statusCode: 401,
message: `Battle.net login failed: ${query.error || 'Unknown error'}`,
data: query
})
if (!onError) throw error
return onError(event, error)
}

if (!config.clientId || !config.clientSecret) {
const error = createError({
statusCode: 500,
message: 'Missing NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID or NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET env variables.'
})
if (!onError) throw error
return onError(event, error)
}

if (!code) {
config.scope = config.scope || ['openid']
config.region = config.region || 'EU'

if (config.region === 'CN') {
config.authorizationURL = 'https://oauth.battlenet.com.cn/authorize'
config.tokenURL = 'https://oauth.battlenet.com.cn/token'
}

// Redirect to Battle.net Oauth page
const redirectUrl = getRequestURL(event).href
return sendRedirect(
event,
withQuery(config.authorizationURL as string, {
client_id: config.clientId,
redirect_uri: redirectUrl,
scope: config.scope.join(' '),
state: randomUUID(), // Todo: handle PKCE flow
response_type: 'code',
})
)
}

const redirectUrl = getRequestURL(event).href
config.scope = config.scope || []
if (!config.scope.includes('openid')) {
config.scope.push('openid')
}

const authCode = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64')

const tokens: any = await $fetch(
config.tokenURL as string,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${authCode}`,
},
params: {
code,
grant_type: 'authorization_code',
scope: config.scope.join(' '),
redirect_uri: parsePath(redirectUrl).pathname,
}
}
).catch((error) => {
return { error }
})

if (tokens.error) {
const error = createError({
statusCode: 401,
message: `Battle.net login failed: ${tokens.error || 'Unknown error'}`,
data: tokens
})
if (!onError) throw error
return onError(event, error)
}

const accessToken = tokens.access_token

const user: any = await ofetch('https://oauth.battle.net/userinfo', {
headers: {
'User-Agent': `Battledotnet-OAuth-${config.clientId}`,
Authorization: `Bearer ${accessToken}`
}
})

if (!user) {
const error = createError({
statusCode: 500,
message: 'Could not get Battle.net user',
data: tokens
})
if (!onError) throw error
return onError(event, error)
}

return onSuccess(event, {
user,
tokens,
})
})
}
4 changes: 3 additions & 1 deletion src/runtime/server/utils/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { spotifyEventHandler } from '../lib/oauth/spotify'
import { twitchEventHandler } from '../lib/oauth/twitch'
import { auth0EventHandler } from '../lib/oauth/auth0'
import { discordEventHandler } from '../lib/oauth/discord'
import { battledotnetEventHandler } from '../lib/oauth/battledotnet'

export const oauth = {
githubEventHandler,
spotifyEventHandler,
googleEventHandler,
twitchEventHandler,
auth0EventHandler,
discordEventHandler
discordEventHandler,
battledotnetEventHandler
}

0 comments on commit b6cdec5

Please sign in to comment.