Skip to content

Commit

Permalink
feat: add vk provider
Browse files Browse the repository at this point in the history
* feat: add yandex oauth

* chore: linting

* update: change FormData to URLSearchParams & add config.emailRequired

* up

* [autofix.ci] apply automated fixes

* chore(release): v0.2.0

* style: add lint script

* style: add lint script

* ci: update lint fix command

* [autofix.ci] apply automated fixes

* feat: add gitlab provider

* [autofix.ci] apply automated fixes

* update Supported OAuth Providers in readme

* feat: add vk provider

* [autofix.ci] apply automated fixes

* up

---------

Co-authored-by: Sébastien Chopin <seb@nuxt.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Estéban <esteban.soubiran@insa-cvl.fr>
  • Loading branch information
4 people committed Sep 11, 2024
1 parent 408b580 commit 6581f12
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
## Features

- [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
- [15+ OAuth Providers](#supported-oauth-providers)
- [20+ OAuth Providers](#supported-oauth-providers)
- [Vue composable](#vue-composable)
- [Server utils](#server-utils)
- [`<AuthState>` component](#authstate-component)
Expand Down Expand Up @@ -209,6 +209,7 @@ It can also be set using environment variables:
- Steam
- TikTok
- Twitch
- VK
- X (Twitter)
- XSUAA
- Yandex
Expand Down
3 changes: 3 additions & 0 deletions playground/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ NUXT_OAUTH_X_CLIENT_SECRET=
NUXT_OAUTH_XSUAA_CLIENT_ID=
NUXT_OAUTH_XSUAA_CLIENT_SECRET=
NUXT_OAUTH_XSUAA_DOMAIN=
# VK
NUXT_OAUTH_VK_CLIENT_ID=
NUXT_OAUTH_VK_CLIENT_SECRET=
# Yandex
NUXT_OAUTH_YANDEX_CLIENT_ID=
NUXT_OAUTH_YANDEX_CLIENT_SECRET=
Expand Down
6 changes: 6 additions & 0 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ const providers = computed(() =>
disabled: Boolean(user.value?.xsuaa),
icon: 'i-simple-icons-sap',
},
{
label: user.value?.vk || 'VK',
to: '/auth/vk',
disabled: Boolean(user.value?.vk),
icon: 'i-simple-icons-vk',
},
{
label: user.value?.yandex || 'Yandex',
to: '/auth/yandex',
Expand Down
1 change: 1 addition & 0 deletions playground/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ declare module '#auth-utils' {
steam?: string
x?: string
xsuaa?: string
vk?: string
yandex?: string
tiktok?: string
}
Expand Down
12 changes: 12 additions & 0 deletions playground/server/routes/auth/vk.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default oauthVKEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
vk: user.user.email,
},
loggedInAt: Date.now(),
})

return sendRedirect(event, '/')
},
})
6 changes: 6 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ export default defineNuxtModule<ModuleOptions>({
domain: '',
redirectURL: '',
})
// VK OAuth
runtimeConfig.oauth.vk = defu(runtimeConfig.oauth.vk, {
clientId: '',
clientSecret: '',
redirectURL: '',
})
// Yandex OAuth
runtimeConfig.oauth.yandex = defu(runtimeConfig.oauth.yandex, {
clientId: '',
Expand Down
161 changes: 161 additions & 0 deletions src/runtime/server/lib/oauth/vk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import crypto from 'node:crypto'
import type { H3Event } from 'h3'
import { eventHandler, getQuery, sendRedirect } from 'h3'
import { withQuery } from 'ufo'
import { defu } from 'defu'
import {
handleMissingConfiguration,
handleAccessTokenErrorResponse,
getOAuthRedirectURL,
requestAccessToken,
type RequestAccessTokenBody,
} from '../utils'
import { useRuntimeConfig, createError } from '#imports'
import type { OAuthConfig } from '#auth-utils'

export interface OAuthVKConfig {
/**
* VK OAuth Client ID
* @default process.env.NUXT_OAUTH_VK_CLIENT_ID
*/
clientId?: string

/**
* VK OAuth Client Secret
* @default process.env.NUXT_OAUTH_VK_CLIENT_SECRET
*/
clientSecret?: string

/**
* VK OAuth Scope
* @default []
* @see https://id.vk.com/about/business/go/docs/en/vkid/latest/vk-id/connection/api-integration/api-description#App-access-to-user-data
* @example ["email", "phone"]
*/
scope?: string[]

/**
* Require email from user, adds the ['login:email'] scope if not present
* @default false
*/
emailRequired?: boolean

/**
* VK OAuth Authorization URL
* @default 'https://id.vk.com/authorize'
*/
authorizationURL?: string

/**
* VK OAuth Token URL
* @default 'https://id.vk.com/oauth2/auth'
*/
tokenURL?: string

/**
* VK OAuth User URL
* @default 'https://id.vk.com/oauth2/user_info'
*/
userURL?: string

/**
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
* @default process.env.NUXT_OAUTH_VK_REDIRECT_URL or current URL
*/
redirectURL?: string
}

export function oauthVKEventHandler({
config,
onSuccess,
onError,
}: OAuthConfig<OAuthVKConfig>) {
return eventHandler(async (event: H3Event) => {
config = defu(config, useRuntimeConfig(event).oauth?.vk, {
authorizationURL: 'https://id.vk.com/authorize',
tokenURL: 'https://id.vk.com/oauth2/auth',
userURL: 'https://id.vk.com/oauth2/user_info',
}) as OAuthVKConfig

const query = getQuery<{ code?: string, device_id?: string }>(event)

if (!config.clientId || !config.clientSecret) {
return handleMissingConfiguration(
event,
'vk',
['clientId', 'clientSecret'],
onError,
)
}

const codeVerifier = 'verify'
const redirectURL = config.redirectURL || getOAuthRedirectURL(event)

if (!query.code) {
config.scope = config.scope || []
if (config.emailRequired && !config.scope.includes('email')) {
config.scope.push('email')
}

// Redirect to VK Oauth page
return sendRedirect(
event,
withQuery(config.authorizationURL as string, {
response_type: 'code',
client_id: config.clientId,
code_challenge: crypto.createHash('sha256').update(codeVerifier).digest('base64url'),
code_challenge_method: 's256',
state: crypto.randomUUID(),
redirect_uri: redirectURL,
scope: config.scope.join(' '),
}),
)
}

interface VKRequestAccessTokenBody extends RequestAccessTokenBody {
code_verifier?: string
}

const tokens = await requestAccessToken(config.tokenURL as string, {
body: {
grant_type: 'authorization_code',
code: query.code as string,
code_verifier: codeVerifier,
client_id: config.clientId,
device_id: query.device_id,
redirect_uri: redirectURL,
} as VKRequestAccessTokenBody,
})

if (tokens.error) {
return handleAccessTokenErrorResponse(event, 'vk', tokens, onError)
}

const accessToken = tokens.access_token

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await $fetch(config.userURL as string, {
method: 'POST',
body: {
access_token: accessToken,
client_id: config.clientId,
},
})

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

return onSuccess(event, {
tokens,
user,
})
})
}
2 changes: 1 addition & 1 deletion src/runtime/types/oauth-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { H3Event, H3Error } from 'h3'

export type OAuthProvider = 'auth0' | 'battledotnet' | 'cognito' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linkedin' | 'microsoft' | 'paypal' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'x' | 'xsuaa' | 'yandex' | (string & {})
export type OAuthProvider = 'auth0' | 'battledotnet' | 'cognito' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linkedin' | 'microsoft' | 'paypal' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'vk' | 'x' | 'xsuaa' | 'yandex' | (string & {})

export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void

Expand Down

0 comments on commit 6581f12

Please sign in to comment.