Skip to content

Commit aa54fce

Browse files
committed
fix: improve session handling and user verification flow
- Add proper session saving in login endpoint with explicit save calls - Update user verification flow to set proper role and activation state - Standardize API endpoint naming for user operations - Add verifiedAt timestamp to track when users complete verification - Clean up console logs and improve logging messages - Fix session userId storage to consistently use string format The main changes focus on ensuring sessions are properly saved during login and standardizing how we handle user verification and activation states.
1 parent 01a8018 commit aa54fce

File tree

6 files changed

+84
-27
lines changed

6 files changed

+84
-27
lines changed

src/auth/localStrategy.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
updateById,
99
findByVerificationToken,
1010
refreshVerificationToken,
11+
completeEmailVerification,
1112
} = require('../domains/user/service');
1213
const { AppError } = require('../libraries/error-handling/AppError');
1314
const { sendVerificationEmail } = require('../libraries/email/emailService');
@@ -84,6 +85,9 @@ const registerUser = async ({ email, password }) => {
8485
isAdmin: false,
8586
verificationToken,
8687
verificationTokenExpiry,
88+
isDeactivated: false, // we activate the user after email verification
89+
role: 'Visitor',
90+
roleId: null,
8791
};
8892

8993
// Create the user
@@ -119,12 +123,7 @@ const verifyEmail = async (token) => {
119123
}
120124

121125
// Update user as verified
122-
await updateById(user._id, {
123-
isVerified: true,
124-
verificationToken: null,
125-
verificationTokenExpiry: null,
126-
updatedAt: new Date()
127-
});
126+
await completeEmailVerification(user._id);
128127

129128
return { message: 'Email verified successfully' };
130129
} catch (error) {

src/domains/user/api.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const routes = () => {
6262
);
6363

6464
router.get(
65-
'/detail/:id',
65+
'/:id/detail',
6666
logRequest({}),
6767
validateRequest({ schema: idSchema, isParam: true }),
6868
async (req, res, next) => {
@@ -107,8 +107,9 @@ const routes = () => {
107107
return result;
108108
};
109109

110-
router.delete(
111-
'/remove/:id',
110+
// deactivateUser
111+
router.put(
112+
'/:id/deactivate',
112113
logRequest({}),
113114
isAuthorized,
114115
validateRequest({ schema: idSchema, isParam: true }),
@@ -131,8 +132,8 @@ const routes = () => {
131132
);
132133

133134
// activateUser
134-
router.post(
135-
'/activate/:id',
135+
router.put(
136+
'/:id/activate',
136137
logRequest({}),
137138
isAuthorized,
138139
validateRequest({ schema: idSchema, isParam: true }),
@@ -150,7 +151,7 @@ const routes = () => {
150151

151152
// update user's role only
152153
router.put(
153-
'/update-role/:id',
154+
'/:id/update-role',
154155
logRequest({}),
155156
isAuthorized,
156157
validateRequest({ schema: updateUserRoleSchema, isParam: false }),

src/domains/user/schema.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ const schema = new mongoose.Schema({
9999
verificationEmailSentAt: {
100100
type: Date,
101101
},
102+
verifiedAt: {
103+
type: Date,
104+
},
102105

103106
// Auth and status flags
104107
isDemo: {
@@ -145,7 +148,7 @@ const schema = new mongoose.Schema({
145148
role: {
146149
type: String,
147150
required: true,
148-
default: 'visitor',
151+
default: 'Visitor',
149152
},
150153
roleId: {
151154
type: mongoose.Schema.Types.ObjectId,

src/domains/user/service.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,42 @@ const refreshVerificationToken = async (email) => {
298298
}
299299
};
300300

301+
const completeEmailVerification = async (userId) => {
302+
try {
303+
const defaultRole = await Role.findOne({ name: 'Visitor' });
304+
if (!defaultRole) {
305+
throw new AppError('role-not-found', 'Default user role not found', 500);
306+
}
307+
308+
const updateData = {
309+
$unset: {
310+
verificationToken: 1,
311+
verificationTokenExpiry: 1,
312+
isDemo: 1
313+
},
314+
$set: {
315+
isVerified: true,
316+
verifiedAt: new Date(),
317+
role: 'Visitor',
318+
roleId: defaultRole._id,
319+
updatedAt: new Date(),
320+
isDeactivated: false
321+
}
322+
};
323+
324+
const user = await Model.findByIdAndUpdate(userId, updateData, { new: true });
325+
if (!user) {
326+
throw new AppError('user-not-found', 'User not found', 404);
327+
}
328+
329+
logger.info('completeEmailVerification(): User verification completed', { userId });
330+
return user;
331+
} catch (error) {
332+
logger.error('completeEmailVerification(): Failed to complete verification', error);
333+
throw error instanceof AppError ? error : new AppError('verification-failed', error.message, 400);
334+
}
335+
};
336+
301337
module.exports = {
302338
create,
303339
search,
@@ -315,4 +351,5 @@ module.exports = {
315351
findByVerificationToken,
316352
refreshVerificationToken,
317353
updateUserRole,
354+
completeEmailVerification,
318355
};

src/libraries/email/emailService.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ sgMail.setApiKey(config.SENDGRID_API_KEY);
99

1010
// Debug mode configuration
1111
const isDebugMode = process.env.EMAIL_DEBUG === 'true';
12-
console.log('isDebugMode', isDebugMode, 'process.env.NODE_ENV', process.env.NODE_ENV, 'process.env.EMAIL_DEBUG', process.env.EMAIL_DEBUG);
1312
const debugDir = path.join(__dirname, 'debug');
1413

1514
// Ensure debug directory exists

src/server.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ const handleAuthCallback = (strategy) => {
6565
`${config.CLIENT_HOST}/login?error=failed-to-authenticate`
6666
);
6767
}
68-
69-
req.session.userId = trimmedUser._id;
68+
logger.info('saving session for user', { user: trimmedUser });
69+
req.session.userId = trimmedUser._id.toString();
7070
req.session.sessionId = req.sessionID;
7171
req.session.save((err) => {
7272
if (err) {
@@ -132,12 +132,10 @@ const createExpressApp = () => {
132132
// Update serialization
133133
passport.serializeUser(async function (user, done) {
134134
const trimmedUser = createTrimmedUser(user);
135-
console.log('serializeUser', trimmedUser);
136135
done(null, trimmedUser);
137136
});
138137

139138
passport.deserializeUser(function (trimmedUser, done) {
140-
console.log('deserializeUser', trimmedUser);
141139
done(null, trimmedUser);
142140
});
143141

@@ -217,9 +215,9 @@ const createExpressApp = () => {
217215
res.json(result);
218216
} catch (err) {
219217
if (err instanceof AppError) {
220-
return res.status(err.statusCode || 400).json({
218+
return res.status(err.statusCode || 400).json({
221219
message: err.message,
222-
code: err.name
220+
code: err.name
223221
});
224222
}
225223
next(err);
@@ -248,9 +246,28 @@ const createExpressApp = () => {
248246
}
249247

250248
const trimmedUser = createTrimmedUser(user);
251-
return res.json({
252-
message: 'Login successful',
253-
user: trimmedUser,
249+
// Save session data
250+
req.session.userId = trimmedUser._id.toString();
251+
req.session.sessionId = req.sessionID;
252+
253+
logger.info('saving session for user', { user: trimmedUser });
254+
255+
// Explicitly save the session
256+
req.session.save((err) => {
257+
if (err) {
258+
logger.error('Failed to save session', err);
259+
return next(err);
260+
}
261+
262+
logger.info('Session saved successfully', {
263+
sessionId: req.sessionID,
264+
userId: trimmedUser._id
265+
});
266+
267+
return res.json({
268+
message: 'Login successful',
269+
user: trimmedUser,
270+
});
254271
});
255272
});
256273
})(req, res, next);
@@ -259,7 +276,8 @@ const createExpressApp = () => {
259276
expressApp.get('/api/logout', async (req, res, next) => {
260277
const username = req.user?.username;
261278
const userId = req.user?._id;
262-
279+
console.log('req.session', req.session);
280+
console.log('req.session.userId', req.session.userId);
263281
req.logout(async function (err) {
264282
// Passport.js logout function
265283
if (err) {
@@ -299,7 +317,7 @@ const createExpressApp = () => {
299317
expressApp.post('/api/resend-verification', async (req, res, next) => {
300318
try {
301319
const { email } = req.body;
302-
320+
logger.info('resend-verification', { email });
303321
if (!email) {
304322
return res.status(400).json({ message: 'Email is required' });
305323
}
@@ -308,9 +326,9 @@ const createExpressApp = () => {
308326
res.json(result);
309327
} catch (err) {
310328
if (err instanceof AppError) {
311-
return res.status(err.statusCode || 400).json({
329+
return res.status(err.statusCode || 400).json({
312330
message: err.message,
313-
code: err.name
331+
code: err.name
314332
});
315333
}
316334
next(err);

0 commit comments

Comments
 (0)