diff --git a/ospabhost/backend/.env b/ospabhost/backend/.env index 1043b7c..067460e 100644 --- a/ospabhost/backend/.env +++ b/ospabhost/backend/.env @@ -45,9 +45,10 @@ GITHUB_CLIENT_SECRET=623db1b4285637d328689857f3fc8ae19d84b7f1 YANDEX_CLIENT_ID=d8a889ea467f4d699d1854ac7a4f9b48 YANDEX_CLIENT_SECRET=e599f43f50274344b3bd9a007692c36b - +VK_CLIENT_ID=your_vk_client_id_here +VK_CLIENT_SECRET=your_vk_client_secret_here # OAuth Callback URL -OAUTH_CALLBACK_URL=https://ospab.host:5000/api/auth +OAUTH_CALLBACK_URL=https://api.ospab.host/api/auth # Session Secret SESSION_SECRET="yf2F&Y0qf&ZUxXTWIzu2tw@#6VvBa2ujDiXuv5QoxjkN%&mNqo2PRO*I*d8PlDdKb$!$n3FcXzjjgHz4Zk!W%S2Zowe6uEkuO9lO!O@axYk^TwC7$$s2r$fq#Cg^!OuE" diff --git a/ospabhost/backend/src/modules/auth/oauth.routes.ts b/ospabhost/backend/src/modules/auth/oauth.routes.ts index 5f00ccb..fafb6d8 100644 --- a/ospabhost/backend/src/modules/auth/oauth.routes.ts +++ b/ospabhost/backend/src/modules/auth/oauth.routes.ts @@ -51,4 +51,17 @@ router.get( } ); +// VKontakte OAuth +router.get('/vkontakte', passport.authenticate('vkontakte', { scope: ['email'] })); + +router.get( + '/vkontakte/callback', + passport.authenticate('vkontakte', { session: false, failureRedirect: `${FRONTEND_URL}/login?error=auth_failed` }), + (req: Request, res: Response) => { + const user = req.user as AuthenticatedUser; + const token = jwt.sign({ id: user.id }, JWT_SECRET, { expiresIn: '24h' }); + res.redirect(`${FRONTEND_URL}/login?token=${token}`); + } +); + export default router; diff --git a/ospabhost/backend/src/modules/auth/passport.config.ts b/ospabhost/backend/src/modules/auth/passport.config.ts index 5d610ae..e697fd4 100644 --- a/ospabhost/backend/src/modules/auth/passport.config.ts +++ b/ospabhost/backend/src/modules/auth/passport.config.ts @@ -5,6 +5,7 @@ import passport from 'passport'; import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; import { Strategy as GitHubStrategy } from 'passport-github'; import { Strategy as YandexStrategy } from 'passport-yandex'; +import { Strategy as VKontakteStrategy } from 'passport-vkontakte'; import { prisma } from '../../prisma/client'; const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_key'; const OAUTH_CALLBACK_URL = process.env.OAUTH_CALLBACK_URL || 'https://api.ospab.host/api/auth'; @@ -122,6 +123,42 @@ if (process.env.YANDEX_CLIENT_ID && process.env.YANDEX_CLIENT_SECRET) { ); } +// VKontakte OAuth +if (process.env.VK_CLIENT_ID && process.env.VK_CLIENT_SECRET) { + passport.use( + new VKontakteStrategy( + { + clientID: process.env.VK_CLIENT_ID, + clientSecret: process.env.VK_CLIENT_SECRET, + callbackURL: `${OAUTH_CALLBACK_URL}/vkontakte/callback`, + profileFields: ['email', 'city', 'bdate'] + }, + async (accessToken: string, refreshToken: string, params: any, profile: any, done: any) => { + try { + // VK возвращает email в params, а не в profile + const email = params.email || profile.emails?.[0]?.value; + + if (!email) { + throw new Error('Email не предоставлен ВКонтакте. Убедитесь, что вы разрешили доступ к email.'); + } + + const oauthProfile: OAuthProfile = { + id: profile.id, + displayName: profile.displayName || `${profile.name?.givenName || ''} ${profile.name?.familyName || ''}`.trim(), + emails: [{ value: email }], + provider: 'vkontakte' + }; + + const user = await findOrCreateUser(oauthProfile); + return done(null, user); + } catch (error) { + return done(error as Error); + } + } + ) + ); +} + passport.serializeUser((user: any, done) => { done(null, user.id); diff --git a/ospabhost/frontend/public/vk.svg b/ospabhost/frontend/public/vk.svg new file mode 100644 index 0000000..c43b0b7 --- /dev/null +++ b/ospabhost/frontend/public/vk.svg @@ -0,0 +1,4 @@ + diff --git a/ospabhost/frontend/src/pages/login.tsx b/ospabhost/frontend/src/pages/login.tsx index 6e856fc..5148de3 100644 --- a/ospabhost/frontend/src/pages/login.tsx +++ b/ospabhost/frontend/src/pages/login.tsx @@ -201,7 +201,7 @@ const LoginPage = () => { -