refactor: move session revocation route to write api

v1.18.x
Julian Lam 4 years ago
parent e250c3f1fb
commit f300c933a5

@ -1,7 +1,7 @@
'use strict';
define('forum/account/sessions', ['forum/account/header', 'components'], function (header, components) {
define('forum/account/sessions', ['forum/account/header', 'components', 'api'], function (header, components, api) {
var Sessions = {};
Sessions.init = function () {
@ -17,15 +17,9 @@ define('forum/account/sessions', ['forum/account/header', 'components'], functio
if (uuid) {
// This is done via DELETE because a user shouldn't be able to
// revoke his own session! This is what logout is for
$.ajax({
url: config.relative_path + '/api/user/' + ajaxify.data.userslug + '/session/' + uuid,
method: 'delete',
headers: {
'x-csrf-token': config.csrf_token,
},
}).done(function () {
api.del(`/users/${ajaxify.data.uid}/sessions/${uuid}`, {}).then(() => {
parentEl.remove();
}).fail(function (err) {
}).catch((err) => {
try {
var errorObj = JSON.parse(err.responseText);
if (errorObj.loggedIn === false) {

@ -1,8 +1,5 @@
'use strict';
const util = require('util');
const db = require('../../database');
const user = require('../../user');
const helpers = require('../helpers');
const accountHelpers = require('./helpers');
@ -21,39 +18,3 @@ sessionController.get = async function (req, res, next) {
res.render('account/sessions', userData);
};
const getSessionAsync = util.promisify(function (sid, callback) {
db.sessionStore.get(sid, (err, sessionObj) => callback(err, sessionObj || null));
});
sessionController.revoke = async function (req, res, next) {
if (!req.params.hasOwnProperty('uuid')) {
return next();
}
try {
const uid = await user.getUidByUserslug(req.params.userslug);
if (!uid) {
throw new Error('[[error:no-session-found]]');
}
const sids = await db.getSortedSetRange('uid:' + uid + ':sessions', 0, -1);
let _id;
for (const sid of sids) {
/* eslint-disable no-await-in-loop */
const sessionObj = await getSessionAsync(sid);
if (sessionObj && sessionObj.meta && sessionObj.meta.uuid === req.params.uuid) {
_id = sid;
break;
}
}
if (!_id) {
throw new Error('[[error:no-session-found]]');
}
await user.auth.revokeSession(_id, uid);
} catch (err) {
return res.status(500).send(err.message);
}
res.sendStatus(200);
};

@ -1,5 +1,8 @@
'use strict';
const util = require('util');
const db = require('../../database');
const api = require('../../api');
const user = require('../../user');
const meta = require('../../meta');
@ -120,3 +123,32 @@ Users.deleteToken = async (req, res) => {
helpers.formatApiResponse(404, res);
}
};
const getSessionAsync = util.promisify(function (sid, callback) {
db.sessionStore.get(sid, (err, sessionObj) => callback(err, sessionObj || null));
});
Users.revokeSession = async (req, res) => {
// Only admins or global mods (besides the user themselves) can revoke sessions
if (parseInt(req.params.uid, 10) !== req.uid && !await user.isAdminOrGlobalMod(req.uid)) {
return helpers.formatApiResponse(404, res);
}
const sids = await db.getSortedSetRange('uid:' + req.params.uid + ':sessions', 0, -1);
let _id;
for (const sid of sids) {
/* eslint-disable no-await-in-loop */
const sessionObj = await getSessionAsync(sid);
if (sessionObj && sessionObj.meta && sessionObj.meta.uuid === req.params.uuid) {
_id = sid;
break;
}
}
if (!_id) {
throw new Error('[[error:no-session-found]]');
}
await user.auth.revokeSession(_id, req.params.uid);
helpers.formatApiResponse(200, res);
};

@ -1,5 +1,8 @@
'use strict';
const winston = require('winston');
const nconf = require('nconf');
var helpers = require('./helpers');
var setupPageRoute = helpers.setupPageRoute;
@ -39,7 +42,14 @@ module.exports = function (app, middleware, controllers) {
setupPageRoute(app, '/user/:userslug/consent', middleware, accountMiddlewares, controllers.accounts.consent.get);
setupPageRoute(app, '/user/:userslug/blocks', middleware, accountMiddlewares, controllers.accounts.blocks.getBlocks);
setupPageRoute(app, '/user/:userslug/sessions', middleware, accountMiddlewares, controllers.accounts.sessions.get);
app.delete('/api/user/:userslug/session/:uuid', [middleware.exposeUid, middleware.ensureSelfOrGlobalPrivilege], controllers.accounts.sessions.revoke);
app.delete('/api/user/:userslug/session/:uuid', [middleware.exposeUid], function (req, res, next) {
// TODO: Remove this entire route in v1.16.0
winston.warn('[router] `/api/user/:userslug/session/:uuid` has been deprecated, use `DELETE /api/v3/users/:uid/sessions/:uuid` or `DELETE /api/v3/users/bySlug/:userslug/sessions/:uuid` instead');
if (!res.locals.uid) {
return next();
}
res.redirect(`${nconf.get('relative_path')}/api/v3/users/${res.locals.uid}/sessions/${req.params.uuid}`);
});
setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get);
setupPageRoute(app, '/user/:userslug/chats/:roomid?', middleware, middlewares, controllers.accounts.chats.get);

@ -35,6 +35,8 @@ function authenticatedRoutes() {
setupApiRoute(router, 'post', '/:uid/tokens', [...middlewares, middleware.assert.user], controllers.write.users.generateToken);
setupApiRoute(router, 'delete', '/:uid/tokens/:token', [...middlewares, middleware.assert.user], controllers.write.users.deleteToken);
setupApiRoute(router, 'delete', '/:uid/sessions/:uuid', [...middlewares, middleware.assert.user], controllers.write.users.revokeSession);
// Shorthand route to access user routes by userslug
router.all('/+bySlug/:userslug*?', [], controllers.write.users.redirectBySlug);
}

@ -813,19 +813,19 @@ describe('Controllers', function () {
});
it('should fail if user doesn\'t exist', function (done) {
request.del(nconf.get('url') + '/api/user/doesnotexist/session/1112233', {
request.del(`${nconf.get('url')}/api/v3/users/doesnotexist/sessions/1112233`, {
jar: jar,
headers: {
'x-csrf-token': csrf_token,
},
}, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 403);
assert.equal(res.statusCode, 404);
assert.deepEqual(JSON.parse(body), {
response: {},
status: {
code: 'forbidden',
message: 'You are not authorised to make this call',
code: 'not-found',
message: '[[error:no-user]]',
},
});
done();
@ -839,15 +839,21 @@ describe('Controllers', function () {
db.sessionStore.get(sid, function (err, sessionObj) {
assert.ifError(err);
request.del(nconf.get('url') + '/api/user/revokeme/session/' + sessionObj.meta.uuid, {
request.del(`${nconf.get('url')}/api/v3/users/${uid}/sessions/${sessionObj.meta.uuid}`, {
jar: jar,
headers: {
'x-csrf-token': csrf_token,
},
}, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(body, 'OK');
assert.strictEqual(res.statusCode, 200);
assert.deepStrictEqual(JSON.parse(body), {
status: {
code: 'ok',
message: 'OK',
},
response: {},
});
done();
});
});

Loading…
Cancel
Save