Skip to content

Commit

Permalink
add Twitter auth
Browse files Browse the repository at this point in the history
  • Loading branch information
Shubham-Lal committed Nov 10, 2023
1 parent 654388d commit 008dc40
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 51 deletions.
47 changes: 47 additions & 0 deletions api/config/twitterAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const passport = require('passport');
const { Strategy: TwitterStrategy } = require('passport-twitter');
const User = require('../models/userModel.js');
const cloudinary = require('cloudinary');

passport.use(
new TwitterStrategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL: `${process.env.BACKEND_URL}/api/auth/twitter/callback`,
includeEmail: true
},
async (token, tokenSecret, profile, done) => {
try {
let user = await User.findOne({
$or: [{ email: profile.emails[0].value }, { twitterId: profile.id }]
});

if (!user) {
const imageUpload = await cloudinary.v2.uploader.upload(profile.photos[0].value, { folder: "BIT/Account" });
user = await User.create({
googleId: profile.id,
authType: "register",
firstName: profile.displayName,
lastName: "",
email: profile.emails[0].value,
avatar: {
public_id: imageUpload.public_id,
url: imageUpload.secure_url,
}
});

return done(null, user);
}

user = await User.findByIdAndUpdate(user._id, {
authType: "login"
});
return done(null, user);
}
catch (error) {
done(error, false);
}
}
)
);
35 changes: 35 additions & 0 deletions api/controllers/socialController.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,41 @@ exports.googleCallbackController = async function (req, res, next) {
})(req, res, next);
}

exports.twitterCallbackController = async function (req, res, next) {
await passport.authenticate('twitter', { session: false }, async function (err, user, info) {
if (err) {
console.error(err);
return res.redirect(`${process.env.FRONTEND_URL}/auth?error=${encodeURIComponent(err.message)}`);
}
if (!user) {
return res.redirect(`${process.env.FRONTEND_URL}/auth?error=User not found`);
}

const userData = {
firstName: user.firstName,
lastName: user.lastName,
avatar: {
public_id: user.avatar.public_id,
url: user.avatar.url,
}
}

if (user.authType === 'register') {
const registerToken = user.getRegisterToken();
const loginToken = user.getLoginToken();
await user.save();

return res.redirect(`${process.env.FRONTEND_URL}/auth?type=${user.authType}&response=${encodeURIComponent(JSON.stringify(userData))}&registerToken=${registerToken}&loginToken=${loginToken}`);
}
else if (user.authType === 'login') {
const loginToken = user.getLoginToken();
await user.save();

return res.redirect(`${process.env.FRONTEND_URL}/auth?type=${user.authType}&response=${encodeURIComponent(JSON.stringify(userData))}&loginToken=${loginToken}`);
}
})(req, res, next);
}

exports.linkedinCallbackController = async function (req, res, next) {
await passport.authenticate('linkedin', { session: false }, async function (err, user, info) {
if (err) {
Expand Down
1 change: 1 addition & 0 deletions api/models/userModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const JWT = require('jsonwebtoken');

const UserSchema = mongoose.Schema({
googleId: String,
twitterId: String,
linkedinId: String,
authType: String,
firstName: String,
Expand Down
9 changes: 6 additions & 3 deletions api/routes/socialRoutes.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
const express = require("express");
const passport = require('passport');
require('../config/googleAuth.js');
require('../config/twitterAuth.js');
require('../config/linkedinAuth.js');
const { googleCallbackController, linkedinCallbackController } = require("../controllers/socialController");
const { googleCallbackController, twitterCallbackController, linkedinCallbackController } = require("../controllers/socialController");
const router = express.Router();

router.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
router.get('/auth/google/callback', googleCallbackController);
router.get('/auth/linkedin', passport.authenticate('linkedin', { scope: ['openid', 'profile', 'email'] }));
router.get('/auth/linkedin/callback', linkedinCallbackController);
router.get('/auth/twitter', passport.authenticate('twitter'));
router.get('/auth/twitter/callback', twitterCallbackController);
// router.get('/auth/linkedin', passport.authenticate('linkedin', { scope: ['openid', 'profile', 'email'] }));
// router.get('/auth/linkedin/callback', linkedinCallbackController);

module.exports = router;
1 change: 1 addition & 0 deletions client/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_DEV_GOOGLE_AUTH_URL=http://localhost:5000/api/auth/google
VITE_DEV_TWITTER_AUTH_URL=http://localhost:5000/api/auth/twitter
VITE_DEV_LINKEDIN_AUTH_URL=http://localhost:5000/api/auth/linkedin
3 changes: 3 additions & 0 deletions client/public/twitter-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 65 additions & 48 deletions client/src/pages/Auth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,71 @@ const Auth = () => {
)
}

// Socials authentication component
const SocialAuth = () => {
const isProduction = import.meta.env.MODE === 'production'

const handleGoogleAuth = () => {
localStorage.removeItem('login_token')
const authUrl = isProduction
? 'https://gdsc-bit.vercel.app/api/auth/google'
: import.meta.env.VITE_DEV_GOOGLE_AUTH_URL
window.location.href = authUrl
}

const handleTwitterAuth = () => {
localStorage.removeItem('login_token')
const authUrl = isProduction
? 'https://gdsc-bit.vercel.app/api/auth/twitter'
: import.meta.env.VITE_DEV_TWITTER_AUTH_URL
window.location.href = authUrl
}

const handleLinkedInAuth = () => {
localStorage.removeItem('login_token')
if (isProduction) {
toast.error('Currently under maintainance!')
} else {
const authUrl = isProduction
? 'https://gdsc-bit.vercel.app/api/auth/linkedin'
: import.meta.env.VITE_DEV_LINKEDIN_AUTH_URL
window.location.href = authUrl
}
}

return (
<div className='flex flex-col gap-[0.5rem]'>
<button
onClick={handleGoogleAuth}
className='hover:bg-slate-100 duration-200 border border-[#00000014] py-[0.625rem] px-[1.25rem] flex gap-[1rem] rounded-[0.375rem]'
>
<img src='/google-small.svg' className='w-[1.25rem] h-auto' />
<span className='font-[400] text-[0.8125rem]'>
Continue with Google
</span>
</button>
<button
onClick={handleTwitterAuth}
className='hover:bg-slate-100 duration-200 border border-[#00000014] py-[0.625rem] px-[1.25rem] flex gap-[1rem] rounded-[0.375rem]'
>
<img src='/twitter-small.svg' className='w-[1.25rem] h-auto' />
<span className='font-[400] text-[0.8125rem]'>
Continue with Twitter
</span>
</button>
{/* <button
onClick={handleLinkedInAuth}
className='hover:bg-slate-100 duration-200 border border-[#00000014] py-[0.625rem] px-[1.25rem] flex gap-[1rem] rounded-[0.375rem]'
>
<img src='/linkedin-small.svg' className='w-[1.25rem] h-auto' />
<span className='font-[400] text-[0.8125rem]'>
Continue with LinkedIn
</span>
</button> */}
</div>
)
}

// Email, Password & Reset Password component
const ManualAuth = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -399,54 +464,6 @@ const NewUser = () => {
)
}

// Socials authentication component
const SocialAuth = () => {
const isProduction = import.meta.env.MODE === 'production'

const handleGoogleAuth = () => {
localStorage.removeItem('login_token')
const authUrl = isProduction
? 'https://gdsc-bit.vercel.app/api/auth/google'
: import.meta.env.VITE_DEV_GOOGLE_AUTH_URL
window.location.href = authUrl
}

const handleLinkedInAuth = () => {
localStorage.removeItem('login_token')
if (isProduction) {
toast.error('Currently under maintainance!')
} else {
const authUrl = isProduction
? 'https://gdsc-bit.vercel.app/api/auth/linkedin'
: import.meta.env.VITE_DEV_LINKEDIN_AUTH_URL
window.location.href = authUrl
}
}

return (
<div className='flex flex-col gap-[0.5rem]'>
<button
onClick={handleGoogleAuth}
className='hover:bg-slate-100 duration-200 border border-[#00000014] py-[0.625rem] px-[1.25rem] flex gap-[1rem] rounded-[0.375rem]'
>
<img src='/google-small.svg' className='w-[1.25rem] h-auto' />
<span className='font-[400] text-[0.8125rem]'>
Continue with Google
</span>
</button>
<button
onClick={handleLinkedInAuth}
className='hover:bg-slate-100 duration-200 border border-[#00000014] py-[0.625rem] px-[1.25rem] flex gap-[1rem] rounded-[0.375rem]'
>
<img src='/linkedin-small.svg' className='w-[1.25rem] h-auto' />
<span className='font-[400] text-[0.8125rem]'>
Continue with LinkedIn
</span>
</button>
</div>
)
}

// Forgot password email component
const ForgotPasswordEmail = () => {
const { setAuthType, emailVerifyLoading } = useAuthStore();
Expand Down
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"passport-linkedin-oauth2": "^2.0.0",
"passport-twitter": "^1.0.4",
"path": "^0.12.7"
}
}

0 comments on commit 008dc40

Please sign in to comment.