Add VKontakte OAuth provider support
This commit is contained in:
@@ -45,9 +45,10 @@ GITHUB_CLIENT_SECRET=623db1b4285637d328689857f3fc8ae19d84b7f1
|
|||||||
|
|
||||||
YANDEX_CLIENT_ID=d8a889ea467f4d699d1854ac7a4f9b48
|
YANDEX_CLIENT_ID=d8a889ea467f4d699d1854ac7a4f9b48
|
||||||
YANDEX_CLIENT_SECRET=e599f43f50274344b3bd9a007692c36b
|
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
|
||||||
OAUTH_CALLBACK_URL=https://ospab.host:5000/api/auth
|
OAUTH_CALLBACK_URL=https://api.ospab.host/api/auth
|
||||||
|
|
||||||
# Session Secret
|
# Session Secret
|
||||||
SESSION_SECRET="yf2F&Y0qf&ZUxXTWIzu2tw@#6VvBa2ujDiXuv5QoxjkN%&mNqo2PRO*I*d8PlDdKb$!$n3FcXzjjgHz4Zk!W%S2Zowe6uEkuO9lO!O@axYk^TwC7$$s2r$fq#Cg^!OuE"
|
SESSION_SECRET="yf2F&Y0qf&ZUxXTWIzu2tw@#6VvBa2ujDiXuv5QoxjkN%&mNqo2PRO*I*d8PlDdKb$!$n3FcXzjjgHz4Zk!W%S2Zowe6uEkuO9lO!O@axYk^TwC7$$s2r$fq#Cg^!OuE"
|
||||||
|
|||||||
@@ -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;
|
export default router;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import passport from 'passport';
|
|||||||
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
||||||
import { Strategy as GitHubStrategy } from 'passport-github';
|
import { Strategy as GitHubStrategy } from 'passport-github';
|
||||||
import { Strategy as YandexStrategy } from 'passport-yandex';
|
import { Strategy as YandexStrategy } from 'passport-yandex';
|
||||||
|
import { Strategy as VKontakteStrategy } from 'passport-vkontakte';
|
||||||
import { prisma } from '../../prisma/client';
|
import { prisma } from '../../prisma/client';
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_key';
|
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';
|
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) => {
|
passport.serializeUser((user: any, done) => {
|
||||||
done(null, user.id);
|
done(null, user.id);
|
||||||
|
|||||||
4
ospabhost/frontend/public/vk.svg
Normal file
4
ospabhost/frontend/public/vk.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="24" height="24" rx="5" fill="#0077FF"/>
|
||||||
|
<path d="M12.7627 17.3516H11.5762C11.5762 17.3516 7.65625 17.5547 5.48438 15.1406C3.14062 12.5391 5.71875 6.28906 5.71875 6.28906C5.71875 6.28906 6.67188 6.26562 7.60938 6.75781C8.54688 7.25 9.28125 8.92188 9.28125 8.92188C9.28125 8.92188 10.4844 11.4609 11.9531 11.9766C12.5391 12.1953 13.2266 11.8359 13.2266 10.1328C13.2266 9.14062 13.3203 7.47656 12.6719 6.76562C12.0234 6.05469 11.0156 6.0625 11.0156 6.0625C11.0156 6.0625 11.4531 5.40625 12.3047 5.17969C13.1562 4.95312 14.7422 5.00781 15.6484 5.1875C16.5547 5.36719 16.9922 5.8125 17.1484 5.96094C17.3047 6.10938 17.5469 6.52344 17.5938 7.36719C17.6406 8.21094 17.5703 10.0547 17.2812 10.8984C16.9922 11.7422 16.0859 12.2344 16.8047 12.9297C17.5234 13.625 18.2891 14.4766 19.0547 15.5547C19.8203 16.6328 20.2188 17.3516 20.2188 17.3516C20.2188 17.3516 20.5 17.9453 19.9141 17.9609C19.3281 17.9766 18.0234 17.9766 18.0234 17.9766C18.0234 17.9766 17.2578 18.0703 16.4609 17.3281C15.6641 16.5859 14.9375 15.5 14.0625 14.6094C13.1875 13.7188 12.7891 13.7734 12.5938 14.1016C12.3984 14.4297 12.4141 15.2031 12.4297 16.0781C12.4453 16.9531 12.7627 17.3516 12.7627 17.3516Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -201,7 +201,7 @@ const LoginPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 grid grid-cols-3 gap-3">
|
<div className="mt-6 grid grid-cols-4 gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleOAuthLogin('google')}
|
onClick={() => handleOAuthLogin('google')}
|
||||||
@@ -229,6 +229,14 @@ const LoginPage = () => {
|
|||||||
<img src="/yandex.png" alt="" className="h-6 w-6" />
|
<img src="/yandex.png" alt="" className="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleOAuthLogin('vkontakte')}
|
||||||
|
className="flex items-center justify-center h-12 rounded-full border border-gray-300 bg-white shadow-sm hover:bg-gray-50 transition"
|
||||||
|
aria-label={locale === 'en' ? 'Sign in with VK' : 'Войти через VK'}
|
||||||
|
>
|
||||||
|
<img src="/vk.svg" alt="VK" className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ const RegisterPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 grid grid-cols-3 gap-3">
|
<div className="mt-6 grid grid-cols-4 gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleOAuthLogin('google')}
|
onClick={() => handleOAuthLogin('google')}
|
||||||
@@ -315,6 +315,15 @@ const RegisterPage = () => {
|
|||||||
>
|
>
|
||||||
<img src="/yandex.png" alt="" className="h-6 w-6" />
|
<img src="/yandex.png" alt="" className="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleOAuthLogin('vkontakte')}
|
||||||
|
className="flex items-center justify-center h-12 rounded-full border border-gray-300 bg-white shadow-sm hover:bg-gray-50 transition"
|
||||||
|
aria-label={locale === 'en' ? 'Sign up with VK' : 'Регистрация через VK'}
|
||||||
|
>
|
||||||
|
<img src="/vk.svg" alt="VK" className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user