Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add types #140

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion playground/server/routes/auth/discord.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export default oauthDiscordEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
discord: user.username,
discord: user.nickname,
},
loggedInAt: Date.now(),
})
Expand Down
2 changes: 1 addition & 1 deletion playground/server/routes/auth/github.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export default oauthGitHubEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
github: user.login,
github: user.nickname,
},
loggedInAt: Date.now(),
})
Expand Down
2 changes: 1 addition & 1 deletion playground/server/routes/auth/steam.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export default oauthSteamEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
steam: user.steamid,
steam: user.id as string,
},
loggedInAt: Date.now(),
})
Expand Down
65 changes: 47 additions & 18 deletions src/runtime/server/lib/oauth/auth0.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import type { H3Event } from 'h3'
import type { H3Event, EventHandler } from 'h3'
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
import { withQuery, parsePath } from 'ufo'
import { defu } from 'defu'
import { useRuntimeConfig } from '#imports'
import type { OAuthConfig } from '#auth-utils'
import type { OAuthAccessTokenError, OAuthAccessTokenSuccess, OAuthConfig, OAuthToken, OAuthUser } from '#auth-utils'

/**
* Auth0 User
*
* @see https://auth0.com/docs/api/authentication#user-profile
*/
type Auth0User = {
email: string
email_verified: boolean
name: string
picture: string
sub: string
updated_at: string
}

export interface OAuthAuth0Config {
/**
Expand Down Expand Up @@ -64,7 +78,7 @@ export interface OAuthAuth0Config {
redirectURL?: string
}

export function oauthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config>) {
export function oauthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthAuth0Config, Auth0User>): EventHandler {
return eventHandler(async (event: H3Event) => {
config = defu(config, useRuntimeConfig(event).oauth?.auth0, {
authorizationParams: {},
Expand Down Expand Up @@ -104,9 +118,7 @@ export function oauthAuth0EventHandler({ config, onSuccess, onError }: OAuthConf
)
}

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tokens: any = await $fetch(
const tokens = await $fetch<unknown>(
tokenURL as string,
{
method: 'POST',
Expand All @@ -121,33 +133,50 @@ export function oauthAuth0EventHandler({ config, onSuccess, onError }: OAuthConf
code,
},
},
).catch((error) => {
return { error }
})
if (tokens.error) {
)

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

const tokenType = tokens.token_type
const accessToken = tokens.access_token
const tokenType = (tokens as OAuthAccessTokenSuccess).token_type
const accessToken = (tokens as OAuthAccessTokenSuccess).access_token

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await $fetch(`https://${config.domain}/userinfo`, {
const user = await $fetch<Auth0User>(`https://${config.domain}/userinfo`, {
headers: {
Authorization: `${tokenType} ${accessToken}`,
},
})

return onSuccess(event, {
tokens,
user,
user: normalizeAuth0User(user),
tokens: normalizeAuth0Tokens(tokens as OAuthAccessTokenSuccess),
})
})
}

function normalizeAuth0User(user: Auth0User): OAuthUser<Auth0User> {
return {
id: user.sub,
nickname: user.name,
name: user.name,
email: user.email,
avatar: user.picture,
raw: user,
}
}

function normalizeAuth0Tokens(tokens: OAuthAccessTokenSuccess): OAuthToken {
return {
token: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
approvedScopes: tokens.scope?.split(' ') || [],
}
}
52 changes: 37 additions & 15 deletions src/runtime/server/lib/oauth/battledotnet.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { randomUUID } from 'node:crypto'
import type { H3Event } from 'h3'
import type { H3Event, EventHandler } from 'h3'
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
import { withQuery, parsePath } from 'ufo'
import { defu } from 'defu'
import { useRuntimeConfig } from '#imports'
import type { OAuthConfig } from '#auth-utils'
import type { OAuthConfig, OAuthToken, OAuthUser, OAuthAccessTokenSuccess, OAuthAccessTokenError } from '#auth-utils'

/**
* Battle.net User
*
* Unable to find documentation on the user object
*/
type BattledotnetUser = {
sub: string
id: number
battletag: string
}

export interface OAuthBattledotnetConfig {
/**
Expand Down Expand Up @@ -53,7 +64,7 @@ export interface OAuthBattledotnetConfig {
redirectURL?: string
}

export function oauthBattledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBattledotnetConfig>) {
export function oauthBattledotnetEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBattledotnetConfig, BattledotnetUser>): EventHandler {
return eventHandler(async (event: H3Event) => {
config = defu(config, useRuntimeConfig(event).oauth?.battledotnet, {
authorizationURL: 'https://oauth.battle.net/authorize',
Expand Down Expand Up @@ -114,9 +125,7 @@ export function oauthBattledotnetEventHandler({ config, onSuccess, onError }: OA

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

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tokens: any = await $fetch(
const tokens = await $fetch<unknown>(
config.tokenURL as string,
{
method: 'POST',
Expand All @@ -135,21 +144,19 @@ export function oauthBattledotnetEventHandler({ config, onSuccess, onError }: OA
return { error }
})

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

const accessToken = tokens.access_token
const accessToken = (tokens as OAuthAccessTokenSuccess).access_token

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await $fetch('https://oauth.battle.net/userinfo', {
const user = await $fetch<BattledotnetUser>('https://oauth.battle.net/userinfo', {
headers: {
'User-Agent': `Battledotnet-OAuth-${config.clientId}`,
'Authorization': `Bearer ${accessToken}`,
Expand All @@ -167,8 +174,23 @@ export function oauthBattledotnetEventHandler({ config, onSuccess, onError }: OA
}

return onSuccess(event, {
user,
tokens,
user: normalizeBattledotnetUser(user),
tokens: normalizeBattledotnetToken(tokens as OAuthAccessTokenSuccess),
})
})
}

function normalizeBattledotnetUser(user: BattledotnetUser): OAuthUser<BattledotnetUser> {
return {
id: user.id,
raw: user,
}
}

function normalizeBattledotnetToken(tokens: OAuthAccessTokenSuccess): OAuthToken {
return {
token: tokens.access_token,
expiresIn: tokens.expires_in,
approvedScopes: tokens.scope?.split(','),
}
}
65 changes: 49 additions & 16 deletions src/runtime/server/lib/oauth/cognito.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import type { H3Event } from 'h3'
import type { H3Event, EventHandler } from 'h3'
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
import { withQuery, parsePath } from 'ufo'
import { defu } from 'defu'
import { useRuntimeConfig } from '#imports'
import type { OAuthConfig } from '#auth-utils'
import type { OAuthConfig, OAuthToken, OAuthUser, OAuthAccessTokenSuccess, OAuthAccessTokenError } from '#auth-utils'

/**
* AWS Cognito User
*
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/userinfo-endpoint.html
*/
type CognitoUser = {
sub: string
email_verified: boolean
email: string
username: string
name: string
picture: string
phone_number_verified: boolean
phone_number: string
}

export interface OAuthCognitoConfig {
/**
Expand Down Expand Up @@ -43,7 +59,7 @@ export interface OAuthCognitoConfig {
redirectURL?: string
}

export function oauthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig>) {
export function oauthCognitoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthCognitoConfig, CognitoUser>): EventHandler {
return eventHandler(async (event: H3Event) => {
config = defu(config, useRuntimeConfig(event).oauth?.cognito, {
authorizationParams: {},
Expand Down Expand Up @@ -78,9 +94,7 @@ export function oauthCognitoEventHandler({ config, onSuccess, onError }: OAuthCo
)
}

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tokens: any = await $fetch(
const tokens = await $fetch<unknown>(
tokenURL as string,
{
method: 'POST',
Expand All @@ -93,29 +107,48 @@ export function oauthCognitoEventHandler({ config, onSuccess, onError }: OAuthCo
return { error }
})

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

const tokenType = tokens.token_type
const accessToken = tokens.access_token
// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await $fetch(`https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/userInfo`, {
const tokenType = (tokens as OAuthAccessTokenSuccess).token_type
const accessToken = (tokens as OAuthAccessTokenSuccess).access_token

const user = await $fetch<CognitoUser>(`https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/userInfo`, {
headers: {
Authorization: `${tokenType} ${accessToken}`,
},
})

return onSuccess(event, {
tokens,
user,
user: normalizeCognitoUser(user),
tokens: normalizeCognitoToken(tokens as OAuthAccessTokenSuccess),
})
})
}

function normalizeCognitoUser(user: CognitoUser): OAuthUser<CognitoUser> {
return {
id: user.sub,
email: user.email,
nickname: user.username,
name: user.name,
avatar: user.picture,
raw: user,
}
}

function normalizeCognitoToken(tokens: OAuthAccessTokenSuccess): OAuthToken {
return {
token: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
approvedScopes: tokens.scope?.split(' '),
}
}
Loading