Added "verified" status to session

refs ENG-1622

Currently unused by the API, this session variable will be used to
confirm whether the user has authenticated their session with an email
OTP. The verified status is not removed on logout, so sessions are now
retained instead of being destroyed.
This commit is contained in:
Sam Lord 2024-10-08 15:15:27 +01:00 committed by Kevin Ansfield
parent 698e42433c
commit 8f7c81ac84
4 changed files with 81 additions and 22 deletions

View File

@ -61,8 +61,8 @@ const controller = {
});
},
delete() {
return Promise.resolve(function destroySessionMw(req, res, next) {
auth.session.destroySession(req, res, next);
return Promise.resolve(function logoutSessionMw(req, res, next) {
auth.session.logout(req, res, next);
});
}
};

View File

@ -8,9 +8,9 @@ function SessionMiddleware({sessionService}) {
}
}
async function destroySession(req, res, next) {
async function logout(req, res, next) {
try {
await sessionService.destroyCurrentSession(req);
await sessionService.removeUserForSession(req, res);
res.sendStatus(204);
} catch (err) {
next(err);
@ -33,7 +33,7 @@ function SessionMiddleware({sessionService}) {
return {
createSession: createSession,
destroySession: destroySession,
logout: logout,
authenticate: authenticate
};
}

View File

@ -1,6 +1,5 @@
const {
BadRequestError,
InternalServerError
BadRequestError
} = require('@tryghost/errors');
/**
@ -15,6 +14,7 @@ const {
* @prop {string} origin
* @prop {string} user_agent
* @prop {string} ip
* @prop {boolean} verified
*/
/**
@ -25,8 +25,9 @@ const {
/**
* @typedef {object} SessionService
* @prop {(req: Req, res: Res) => Promise<User | null>} getUserForSession
* @prop {(req: Req, res: Res) => Promise<void>} destroyCurrentSession
* @prop {(req: Req, res: Res) => Promise<void>} removeUserForSession
* @prop {(req: Req, res: Res, user: User) => Promise<void>} createSessionForUser
* @prop {(req: Req, res: Res) => Promise<void>} verifySession
*/
/**
@ -86,22 +87,26 @@ module.exports = function createSessionService({getSession, findUserById, getOri
}
/**
* destroyCurrentSession
* verifySession
*
* @param {Req} req
* @param {Res} res
*/
async function verifySession(req, res) {
const session = await getSession(req, res);
session.verified = true;
}
/**
* removeUserForSession
*
* @param {Req} req
* @param {Res} res
* @returns {Promise<void>}
*/
async function destroyCurrentSession(req, res) {
async function removeUserForSession(req, res) {
const session = await getSession(req, res);
return new Promise((resolve, reject) => {
session.destroy((err) => {
if (err) {
return reject(new InternalServerError({err}));
}
resolve();
});
});
session.user_id = undefined;
}
/**
@ -139,7 +144,7 @@ module.exports = function createSessionService({getSession, findUserById, getOri
return {
getUserForSession,
createSessionForUser,
destroyCurrentSession
removeUserForSession,
verifySession
};
};

View File

@ -49,8 +49,11 @@ describe('SessionService', function () {
const expectedUser = await findUserById.returnValues[0];
should.equal(actualUser, expectedUser);
await sessionService.destroyCurrentSession(req, res);
should.ok(req.session.destroy.calledOnce);
await sessionService.removeUserForSession(req, res);
should.equal(req.session.user_id, undefined);
const removedUser = await sessionService.getUserForSession(req, res);
should.equal(removedUser, null);
});
it('Throws an error when the csrf verification fails', async function () {
@ -131,4 +134,55 @@ describe('SessionService', function () {
await sessionService.getUserForSession(req, res).should.be.fulfilled();
});
it('Can verify a user session', async function () {
const getSession = async (req) => {
if (req.session) {
return req.session;
}
req.session = {
destroy: sinon.spy(cb => cb())
};
return req.session;
};
const findUserById = sinon.spy(async ({id}) => ({id}));
const getOriginOfRequest = sinon.stub().returns('origin');
const sessionService = SessionService({
getSession,
findUserById,
getOriginOfRequest
});
const req = Object.create(express.request, {
ip: {
value: '0.0.0.0'
},
headers: {
value: {
cookie: 'thing'
}
},
get: {
value: () => 'Fake'
}
});
const res = Object.create(express.response);
const user = {id: 'egg'};
await sessionService.createSessionForUser(req, res, user);
should.equal(req.session.user_id, 'egg');
should.equal(req.session.verified, undefined);
await sessionService.verifySession(req, res);
should.equal(req.session.verified, true);
await sessionService.removeUserForSession(req, res);
should.equal(req.session.user_id, undefined);
should.equal(req.session.verified, true);
await sessionService.createSessionForUser(req, res, user);
should.equal(req.session.user_id, 'egg');
should.equal(req.session.verified, true);
});
});