Skip to content

Commit 6581f12

Browse files
blumgartatinuxautofix-ci[bot]Barbapapazes
authored
feat: add vk provider
* 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 <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Estéban <[email protected]>
1 parent 408b580 commit 6581f12

File tree

8 files changed

+192
-2
lines changed

8 files changed

+192
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
1515
## Features
1616

1717
- [Hybrid Rendering](#hybrid-rendering) support (SSR / CSR / SWR / Prerendering)
18-
- [15+ OAuth Providers](#supported-oauth-providers)
18+
- [20+ OAuth Providers](#supported-oauth-providers)
1919
- [Vue composable](#vue-composable)
2020
- [Server utils](#server-utils)
2121
- [`<AuthState>` component](#authstate-component)
@@ -209,6 +209,7 @@ It can also be set using environment variables:
209209
- Steam
210210
- TikTok
211211
- Twitch
212+
- VK
212213
- X (Twitter)
213214
- XSUAA
214215
- Yandex

playground/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ NUXT_OAUTH_X_CLIENT_SECRET=
5959
NUXT_OAUTH_XSUAA_CLIENT_ID=
6060
NUXT_OAUTH_XSUAA_CLIENT_SECRET=
6161
NUXT_OAUTH_XSUAA_DOMAIN=
62+
# VK
63+
NUXT_OAUTH_VK_CLIENT_ID=
64+
NUXT_OAUTH_VK_CLIENT_SECRET=
6265
# Yandex
6366
NUXT_OAUTH_YANDEX_CLIENT_ID=
6467
NUXT_OAUTH_YANDEX_CLIENT_SECRET=

playground/app.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ const providers = computed(() =>
138138
disabled: Boolean(user.value?.xsuaa),
139139
icon: 'i-simple-icons-sap',
140140
},
141+
{
142+
label: user.value?.vk || 'VK',
143+
to: '/auth/vk',
144+
disabled: Boolean(user.value?.vk),
145+
icon: 'i-simple-icons-vk',
146+
},
141147
{
142148
label: user.value?.yandex || 'Yandex',
143149
to: '/auth/yandex',

playground/auth.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ declare module '#auth-utils' {
1919
steam?: string
2020
x?: string
2121
xsuaa?: string
22+
vk?: string
2223
yandex?: string
2324
tiktok?: string
2425
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default oauthVKEventHandler({
2+
async onSuccess(event, { user }) {
3+
await setUserSession(event, {
4+
user: {
5+
vk: user.user.email,
6+
},
7+
loggedInAt: Date.now(),
8+
})
9+
10+
return sendRedirect(event, '/')
11+
},
12+
})

src/module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ export default defineNuxtModule<ModuleOptions>({
202202
domain: '',
203203
redirectURL: '',
204204
})
205+
// VK OAuth
206+
runtimeConfig.oauth.vk = defu(runtimeConfig.oauth.vk, {
207+
clientId: '',
208+
clientSecret: '',
209+
redirectURL: '',
210+
})
205211
// Yandex OAuth
206212
runtimeConfig.oauth.yandex = defu(runtimeConfig.oauth.yandex, {
207213
clientId: '',

src/runtime/server/lib/oauth/vk.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import crypto from 'node:crypto'
2+
import type { H3Event } from 'h3'
3+
import { eventHandler, getQuery, sendRedirect } from 'h3'
4+
import { withQuery } from 'ufo'
5+
import { defu } from 'defu'
6+
import {
7+
handleMissingConfiguration,
8+
handleAccessTokenErrorResponse,
9+
getOAuthRedirectURL,
10+
requestAccessToken,
11+
type RequestAccessTokenBody,
12+
} from '../utils'
13+
import { useRuntimeConfig, createError } from '#imports'
14+
import type { OAuthConfig } from '#auth-utils'
15+
16+
export interface OAuthVKConfig {
17+
/**
18+
* VK OAuth Client ID
19+
* @default process.env.NUXT_OAUTH_VK_CLIENT_ID
20+
*/
21+
clientId?: string
22+
23+
/**
24+
* VK OAuth Client Secret
25+
* @default process.env.NUXT_OAUTH_VK_CLIENT_SECRET
26+
*/
27+
clientSecret?: string
28+
29+
/**
30+
* VK OAuth Scope
31+
* @default []
32+
* @see https://id.vk.com/about/business/go/docs/en/vkid/latest/vk-id/connection/api-integration/api-description#App-access-to-user-data
33+
* @example ["email", "phone"]
34+
*/
35+
scope?: string[]
36+
37+
/**
38+
* Require email from user, adds the ['login:email'] scope if not present
39+
* @default false
40+
*/
41+
emailRequired?: boolean
42+
43+
/**
44+
* VK OAuth Authorization URL
45+
* @default 'https://id.vk.com/authorize'
46+
*/
47+
authorizationURL?: string
48+
49+
/**
50+
* VK OAuth Token URL
51+
* @default 'https://id.vk.com/oauth2/auth'
52+
*/
53+
tokenURL?: string
54+
55+
/**
56+
* VK OAuth User URL
57+
* @default 'https://id.vk.com/oauth2/user_info'
58+
*/
59+
userURL?: string
60+
61+
/**
62+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
63+
* @default process.env.NUXT_OAUTH_VK_REDIRECT_URL or current URL
64+
*/
65+
redirectURL?: string
66+
}
67+
68+
export function oauthVKEventHandler({
69+
config,
70+
onSuccess,
71+
onError,
72+
}: OAuthConfig<OAuthVKConfig>) {
73+
return eventHandler(async (event: H3Event) => {
74+
config = defu(config, useRuntimeConfig(event).oauth?.vk, {
75+
authorizationURL: 'https://id.vk.com/authorize',
76+
tokenURL: 'https://id.vk.com/oauth2/auth',
77+
userURL: 'https://id.vk.com/oauth2/user_info',
78+
}) as OAuthVKConfig
79+
80+
const query = getQuery<{ code?: string, device_id?: string }>(event)
81+
82+
if (!config.clientId || !config.clientSecret) {
83+
return handleMissingConfiguration(
84+
event,
85+
'vk',
86+
['clientId', 'clientSecret'],
87+
onError,
88+
)
89+
}
90+
91+
const codeVerifier = 'verify'
92+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event)
93+
94+
if (!query.code) {
95+
config.scope = config.scope || []
96+
if (config.emailRequired && !config.scope.includes('email')) {
97+
config.scope.push('email')
98+
}
99+
100+
// Redirect to VK Oauth page
101+
return sendRedirect(
102+
event,
103+
withQuery(config.authorizationURL as string, {
104+
response_type: 'code',
105+
client_id: config.clientId,
106+
code_challenge: crypto.createHash('sha256').update(codeVerifier).digest('base64url'),
107+
code_challenge_method: 's256',
108+
state: crypto.randomUUID(),
109+
redirect_uri: redirectURL,
110+
scope: config.scope.join(' '),
111+
}),
112+
)
113+
}
114+
115+
interface VKRequestAccessTokenBody extends RequestAccessTokenBody {
116+
code_verifier?: string
117+
}
118+
119+
const tokens = await requestAccessToken(config.tokenURL as string, {
120+
body: {
121+
grant_type: 'authorization_code',
122+
code: query.code as string,
123+
code_verifier: codeVerifier,
124+
client_id: config.clientId,
125+
device_id: query.device_id,
126+
redirect_uri: redirectURL,
127+
} as VKRequestAccessTokenBody,
128+
})
129+
130+
if (tokens.error) {
131+
return handleAccessTokenErrorResponse(event, 'vk', tokens, onError)
132+
}
133+
134+
const accessToken = tokens.access_token
135+
136+
// TODO: improve typing
137+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
138+
const user: any = await $fetch(config.userURL as string, {
139+
method: 'POST',
140+
body: {
141+
access_token: accessToken,
142+
client_id: config.clientId,
143+
},
144+
})
145+
146+
if (!user) {
147+
const error = createError({
148+
statusCode: 500,
149+
message: 'Could not get VK user',
150+
data: tokens,
151+
})
152+
if (!onError) throw error
153+
return onError(event, error)
154+
}
155+
156+
return onSuccess(event, {
157+
tokens,
158+
user,
159+
})
160+
})
161+
}

src/runtime/types/oauth-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { H3Event, H3Error } from 'h3'
22

3-
export type OAuthProvider = 'auth0' | 'battledotnet' | 'cognito' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linkedin' | 'microsoft' | 'paypal' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'x' | 'xsuaa' | 'yandex' | (string & {})
3+
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 & {})
44

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

0 commit comments

Comments
 (0)