Skip to content

Commit f157e4a

Browse files
feat-krishnaacharyaa#429: Added Signin with Google button (krishnaacharyaa#454)
2 parents b1e47b1 + 35b7810 commit f157e4a

File tree

13 files changed

+245
-43
lines changed

13 files changed

+245
-43
lines changed

Diff for: backend/.env.sample

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ PORT=8080
22
MONGODB_URI="mongodb://127.0.0.1/wanderlust"
33
REDIS_URL="redis://127.0.0.1:6379"
44
FRONTEND_URL=http://localhost:5173
5+
BACKEND_URL=http://localhost:8080
56
ACCESS_COOKIE_MAXAGE=120000
67
ACCESS_TOKEN_EXPIRES_IN='120s'
78
REFRESH_COOKIE_MAXAGE=120000
89
REFRESH_TOKEN_EXPIRES_IN='120s'
910
JWT_SECRET=70dd8b38486eee723ce2505f6db06f1ee503fde5eb06fc04687191a0ed665f3f98776902d2c89f6b993b1c579a87fedaf584c693a106f7cbf16e8b4e67e9d6df
10-
NODE_ENV=Development
11+
NODE_ENV=Development
12+
GOOGLE_CLIENT_ID=
13+
GOOGLE_CLIENT_SECRET=

Diff for: backend/app.js

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import authRouter from './routes/auth.js';
77
import postsRouter from './routes/posts.js';
88
import userRouter from './routes/user.js';
99
import errorMiddleware from './middlewares/error-middleware.js';
10+
import passport from './config/passport.js';
11+
import session from 'express-session';
1012

1113
const app = express();
1214

@@ -21,6 +23,9 @@ app.use(express.json());
2123
app.use(express.urlencoded({ extended: true }));
2224
app.use(cookieParser());
2325
app.use(compression());
26+
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));
27+
app.use(passport.initialize());
28+
app.use(passport.session());
2429

2530
// API route
2631
app.use('/api/posts', postsRouter);

Diff for: backend/config/passport.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import passport from 'passport';
2+
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
3+
import User from '../models/user.js';
4+
5+
passport.use(
6+
new GoogleStrategy(
7+
{
8+
clientID: process.env.GOOGLE_CLIENT_ID,
9+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
10+
callbackURL: `${process.env.BACKEND_URL}/api/auth/google/callback`,
11+
},
12+
async (accessToken, refreshToken, profile, done) => {
13+
try {
14+
let user = await User.findOne({ googleId: profile.id });
15+
16+
if (!user) {
17+
const email = profile.emails && profile.emails[0] ? profile.emails[0].value : '';
18+
let fullName = profile.displayName || '';
19+
if (fullName.length > 15) {
20+
fullName = fullName.slice(0, 15); // Ensure fullName is less than 15 characters
21+
}
22+
const userName = email.split('@')[0] || fullName.replace(/\s+/g, '').toLowerCase();
23+
24+
user = new User({
25+
googleId: profile.id,
26+
email,
27+
fullName,
28+
userName,
29+
avatar: profile.photos && profile.photos[0] ? profile.photos[0].value : '',
30+
});
31+
32+
await user.save();
33+
}
34+
35+
done(null, user);
36+
} catch (err) {
37+
done(err, null);
38+
}
39+
}
40+
)
41+
);
42+
43+
passport.serializeUser((user, done) => done(null, user.id));
44+
45+
passport.deserializeUser(async (id, done) => {
46+
try {
47+
const user = await User.findById(id);
48+
done(null, user);
49+
} catch (err) {
50+
done(err, null);
51+
}
52+
});
53+
54+
export default passport;

Diff for: backend/controllers/auth-controller.js

+34-12
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const signInWithEmailOrUsername = asyncHandler(async (req, res) => {
115115
});
116116

117117
//Sign Out
118-
export const signOutUser = asyncHandler(async (req, res) => {
118+
export const signOutUser = asyncHandler(async (req, res, next) => {
119119
await User.findByIdAndUpdate(
120120
req.user?._id,
121121
{
@@ -128,42 +128,64 @@ export const signOutUser = asyncHandler(async (req, res) => {
128128
}
129129
);
130130

131-
res
132-
.status(HTTP_STATUS.OK)
133-
.clearCookie('access_token', cookieOptions)
134-
.clearCookie('refresh_token', cookieOptions)
135-
.json(new ApiResponse(HTTP_STATUS.OK, '', RESPONSE_MESSAGES.USERS.SIGNED_OUT));
136-
});
131+
// Passport.js logout
132+
req.logout((err) => {
133+
if (err) {
134+
return next(new ApiError(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Logout failed'));
135+
}
137136

137+
res
138+
.status(HTTP_STATUS.OK)
139+
.clearCookie('access_token', cookieOptions)
140+
.clearCookie('refresh_token', cookieOptions)
141+
.clearCookie('jwt', cookieOptions)
142+
.json(new ApiResponse(HTTP_STATUS.OK, '', RESPONSE_MESSAGES.USERS.SIGNED_OUT));
143+
});
144+
});
138145
// check user
139146
export const isLoggedIn = asyncHandler(async (req, res) => {
140147
let access_token = req.cookies?.access_token;
141148
let refresh_token = req.cookies?.refresh_token;
142149
const { _id } = req.params;
143150

151+
if (!_id) {
152+
return res
153+
.status(HTTP_STATUS.BAD_REQUEST)
154+
.json(new ApiResponse(HTTP_STATUS.BAD_REQUEST, '', 'User ID is required'));
155+
}
144156
if (access_token) {
145157
try {
146158
await jwt.verify(access_token, JWT_SECRET);
147159
return res
148160
.status(HTTP_STATUS.OK)
149161
.json(new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN));
150162
} catch (error) {
151-
// Access token invalid, proceed to check refresh token
152-
console.log(error);
163+
console.log('Access token verification error:', error.message);
153164
}
154-
} else if (refresh_token) {
165+
}
166+
// If access token is not valid, check the refresh token
167+
if (refresh_token) {
155168
try {
156169
await jwt.verify(refresh_token, JWT_SECRET);
170+
const user = await User.findById(_id);
171+
if (!user) {
172+
return res
173+
.status(HTTP_STATUS.NOT_FOUND)
174+
.json(
175+
new ApiResponse(HTTP_STATUS.NOT_FOUND, '', RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS)
176+
);
177+
}
157178
access_token = await user.generateAccessToken();
158179
return res
159180
.status(HTTP_STATUS.OK)
160181
.cookie('access_token', access_token, cookieOptions)
161182
.json(new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN));
162183
} catch (error) {
163-
// Access token invalid, proceed to check refresh token that is in db
164-
console.log(error);
184+
console.log('Refresh token verification error:', error.message);
165185
}
166186
}
187+
188+
// If neither token is valid, handle accordingly
167189
const user = await User.findById(_id);
168190
if (!user) {
169191
return res

Diff for: backend/middlewares/auth-middleware.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ import { ApiError } from '../utils/api-error.js';
33
import { HTTP_STATUS, RESPONSE_MESSAGES } from '../utils/constants.js';
44
import jwt from 'jsonwebtoken';
55
import { Role } from '../types/role-type.js';
6+
import User from '../models/user.js';
67

78
export const authMiddleware = async (req, res, next) => {
89
const token = req.cookies?.access_token;
910
if (!token) {
1011
return next(new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.USERS.RE_LOGIN));
1112
}
1213

13-
if (token) {
14-
await jwt.verify(token, JWT_SECRET, (error, payload) => {
15-
if (error) {
16-
return new ApiError(HTTP_STATUS.FORBIDDEN, RESPONSE_MESSAGES.USERS.INVALID_TOKEN);
17-
}
18-
req.user = payload;
19-
next();
20-
});
14+
try {
15+
const payload = jwt.verify(token, JWT_SECRET);
16+
req.user = await User.findById(payload.id);
17+
next();
18+
} catch (error) {
19+
console.log('Token verification error:', error.message);
20+
return next(new ApiError(HTTP_STATUS.FORBIDDEN, RESPONSE_MESSAGES.USERS.INVALID_TOKEN));
2121
}
2222
};
2323

Diff for: backend/models/user.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const userSchema = new Schema(
3434
},
3535
password: {
3636
type: String,
37-
required: [true, 'Password is required'],
37+
required: false,
3838
minLength: [8, 'Password must be at least 8 character '],
3939
match: [
4040
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/,
@@ -60,7 +60,13 @@ const userSchema = new Schema(
6060
refreshToken: String,
6161
forgotPasswordToken: String,
6262
forgotPasswordExpiry: Date,
63+
googleId: {
64+
type: String,
65+
unique: true,
66+
required: false,
67+
},
6368
},
69+
6470
{ timestamps: true }
6571
);
6672

Diff for: backend/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
"cors": "^2.8.5",
99
"dotenv": "^16.3.1",
1010
"express": "^4.18.2",
11+
"express-session": "^1.18.0",
1112
"jsonwebtoken": "^9.0.2",
1213
"mongoose": "^8.0.0",
1314
"nodemon": "^3.0.1",
15+
"passport": "^0.7.0",
16+
"passport-google-oauth20": "^2.0.0",
1417
"redis": "^4.6.13"
1518
},
1619
"lint-staged": {

Diff for: backend/routes/auth.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Router } from 'express';
22
import { authMiddleware } from '../middlewares/auth-middleware.js';
3+
import passport from '../config/passport.js';
4+
import jwt from 'jsonwebtoken';
35
import {
46
signUpWithEmail,
57
signInWithEmailOrUsername,
@@ -13,10 +15,38 @@ const router = Router();
1315
router.post('/email-password/signup', signUpWithEmail);
1416
router.post('/email-password/signin', signInWithEmailOrUsername);
1517

18+
// Google-login
19+
router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
20+
21+
router.get(
22+
'/google/callback',
23+
passport.authenticate('google', { failureRedirect: '/' }),
24+
(req, res) => {
25+
const token = jwt.sign({ id: req.user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
26+
res.cookie('access_token', token, {
27+
httpOnly: true,
28+
secure: process.env.NODE_ENV === 'production',
29+
sameSite: 'lax',
30+
});
31+
res.redirect(`${process.env.FRONTEND_URL}/signup?google-callback=true`);
32+
}
33+
);
34+
35+
router.get('/check', authMiddleware, (req, res) => {
36+
const token = req.cookies.access_token;
37+
res.json({
38+
token,
39+
user: {
40+
_id: req.user._id,
41+
role: req.user.role,
42+
},
43+
});
44+
});
45+
1646
//SIGN OUT
1747
router.post('/signout', authMiddleware, signOutUser);
1848

1949
//CHECK USER STATUS
20-
router.get('/check/:_id', isLoggedIn);
50+
router.get('/check/:_id', authMiddleware, isLoggedIn);
2151

2252
export default router;

Diff for: frontend/src/components/require-auth.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function RequireAuth({ allowedRole }: { allowedRole: string[] }) {
88
if (loading) {
99
return (
1010
<>
11-
<Loader/>
11+
<Loader />
1212
</>
1313
); // Render a loading indicator
1414
}

Diff for: frontend/src/hooks/useAuthData.ts

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const useAuthData = (): AuthData => {
3232
loading: false,
3333
});
3434
} catch (error) {
35+
console.error('Error fetching token:', error);
3536
setData({
3637
...data,
3738
token: '',

0 commit comments

Comments
 (0)