mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-28 10:19:50 +01:00
Session Timeout if "Remember Me" is not checked (#11125)
* fix: convert loginDays and loginSeconds to number inputs * feat: configurable session timeout for when "Remember Me" is not checked closes #11124 * test: addition tests to check loginDays and sessionDuration settings * test: also test loginSeconds override
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"defaultLang": "en-GB",
|
||||
"loginDays": 14,
|
||||
"loginSeconds": 0,
|
||||
"sessionDuration": 0,
|
||||
"loginAttempts": 5,
|
||||
"lockoutDuration": 60,
|
||||
"adminReloginDuration": 60,
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
"session-time-days": "Days",
|
||||
"session-time-seconds": "Seconds",
|
||||
"session-time-help": "These values are used to govern how long a user stays logged in when they check "Remember Me" on login. Note that only one of these values will be used. If there is no <i>seconds</i> value we fall back to <i>days</i>. If there is no <i>days</i> value we default to <i>14 days</i>.",
|
||||
"session-duration": "Session length if \"Remember Me\" is not checked (seconds)",
|
||||
"session-duration-help": "By default — or if set to <code>0</code> — a user will stay logged in for the duration of the session (e.g. however long the browser window/tab remains open). Set this value to explicitly invalidate the session after the specified number of seconds.",
|
||||
"online-cutoff": "Minutes after user is considered inactive",
|
||||
"online-cutoff-help": "If user performs no actions for this duration, they are considered inactive and they do not receive realtime updates.",
|
||||
"registration": "User Registration",
|
||||
|
||||
@@ -294,8 +294,9 @@ function continueLogin(strategy, req, res, next) {
|
||||
req.session.cookie.maxAge = duration;
|
||||
req.session.cookie.expires = new Date(Date.now() + duration);
|
||||
} else {
|
||||
req.session.cookie.maxAge = false;
|
||||
req.session.cookie.expires = false;
|
||||
const duration = meta.config.sessionDuration * 1000;
|
||||
req.session.cookie.maxAge = duration || false;
|
||||
req.session.cookie.expires = duration ? new Date(Date.now() + duration) : false;
|
||||
}
|
||||
|
||||
plugins.hooks.fire('action:login.continue', { req, strategy, userData, error: null });
|
||||
|
||||
@@ -126,13 +126,13 @@
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="loginDays">[[admin/settings/user:session-time-days]]</label>
|
||||
<input id="loginDays" type="text" class="form-control" data-field="loginDays" placeholder="[[admin/settings/user:session-time-days]]" />
|
||||
<input id="loginDays" type="number" min="0" class="form-control" data-field="loginDays" placeholder="[[admin/settings/user:session-time-days]]" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="loginSeconds">[[admin/settings/user:session-time-seconds]]</label>
|
||||
<input id="loginSeconds" type="text" class="form-control" data-field="loginSeconds" placeholder="[[admin/settings/user:session-time-seconds]]" />
|
||||
<input id="loginSeconds" type="number" min="0" step="60" class="form-control" data-field="loginSeconds" placeholder="[[admin/settings/user:session-time-seconds]]" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
@@ -141,6 +141,13 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sessionDuration">[[admin/settings/user:session-duration]]</label>
|
||||
<input id="sessionDuration" type="number" step="60" min="0" class="form-control" data-field="sessionDuration">
|
||||
<p class="help-block">[[admin/settings/user:session-duration-help]]</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="onlineCutoff">[[admin/settings/user:online-cutoff]]</label>
|
||||
<input id="onlineCutoff" type="text" class="form-control" data-field="onlineCutoff">
|
||||
|
||||
@@ -158,22 +158,6 @@ describe('authentication', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should login a user', async () => {
|
||||
const { jar, body: loginBody } = await helpers.loginUser('regular', 'regularpwd');
|
||||
assert(loginBody);
|
||||
const body = await requestAsync({
|
||||
url: `${nconf.get('url')}/api/self`,
|
||||
json: true,
|
||||
jar,
|
||||
});
|
||||
assert(body);
|
||||
assert.equal(body.username, 'regular');
|
||||
assert.equal(body.email, 'regular@nodebb.org');
|
||||
const sessions = await db.getObject(`uid:${regularUid}:sessionUUID:sessionId`);
|
||||
assert(sessions);
|
||||
assert(Object.keys(sessions).length > 0);
|
||||
});
|
||||
|
||||
it('should regenerate the session identifier on successful login', async () => {
|
||||
const matchRegexp = /express\.sid=s%3A(.+?);/;
|
||||
const { hostname, path } = url.parse(nconf.get('url'));
|
||||
@@ -202,6 +186,114 @@ describe('authentication', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
let username;
|
||||
let password;
|
||||
let uid;
|
||||
|
||||
function getCookieExpiry(res) {
|
||||
assert(res.headers['set-cookie']);
|
||||
assert.strictEqual(res.headers['set-cookie'][0].includes('Expires'), true);
|
||||
|
||||
const values = res.headers['set-cookie'][0].split(';');
|
||||
return values.reduce((memo, cur) => {
|
||||
if (!memo) {
|
||||
const [name, value] = cur.split('=');
|
||||
if (name === ' Expires') {
|
||||
memo = new Date(value);
|
||||
}
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, undefined);
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
([username, password] = [utils.generateUUID().slice(0, 10), utils.generateUUID()]);
|
||||
uid = await user.create({ username, password });
|
||||
});
|
||||
|
||||
it('should login a user', async () => {
|
||||
const { jar, body: loginBody } = await helpers.loginUser(username, password);
|
||||
assert(loginBody);
|
||||
const body = await requestAsync({
|
||||
url: `${nconf.get('url')}/api/self`,
|
||||
json: true,
|
||||
jar,
|
||||
});
|
||||
assert(body);
|
||||
assert.equal(body.username, username);
|
||||
const sessions = await db.getObject(`uid:${uid}:sessionUUID:sessionId`);
|
||||
assert(sessions);
|
||||
assert(Object.keys(sessions).length > 0);
|
||||
});
|
||||
|
||||
it('should set a cookie that only lasts for the life of the browser session', async () => {
|
||||
const { res } = await helpers.loginUser(username, password);
|
||||
|
||||
assert(res.headers);
|
||||
assert(res.headers['set-cookie']);
|
||||
assert.strictEqual(res.headers['set-cookie'][0].includes('Expires'), false);
|
||||
});
|
||||
|
||||
it('should set a different expiry if sessionDuration is set', async () => {
|
||||
const _sessionDuration = meta.config.sessionDuration;
|
||||
const days = 1;
|
||||
meta.config.sessionDuration = days * 24 * 60 * 60;
|
||||
|
||||
const { res } = await helpers.loginUser(username, password);
|
||||
|
||||
const expiry = getCookieExpiry(res);
|
||||
const expected = new Date();
|
||||
expected.setUTCDate(expected.getUTCDate() + days);
|
||||
|
||||
assert.strictEqual(expiry.getUTCDate(), expected.getUTCDate());
|
||||
|
||||
meta.config.sessionDuration = _sessionDuration;
|
||||
});
|
||||
|
||||
it('should set a cookie that lasts for x days where x is loginDays setting, if asked to remember', async () => {
|
||||
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
|
||||
|
||||
const expiry = getCookieExpiry(res);
|
||||
const expected = new Date();
|
||||
expected.setUTCDate(expected.getUTCDate() + meta.config.loginDays);
|
||||
|
||||
assert.strictEqual(expiry.getUTCDate(), expected.getUTCDate());
|
||||
});
|
||||
|
||||
it('should set the cookie expiry properly if loginDays setting is changed', async () => {
|
||||
const _loginDays = meta.config.loginDays;
|
||||
meta.config.loginDays = 5;
|
||||
|
||||
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
|
||||
|
||||
const expiry = getCookieExpiry(res);
|
||||
const expected = new Date();
|
||||
expected.setUTCDate(expected.getUTCDate() + meta.config.loginDays);
|
||||
|
||||
assert.strictEqual(expiry.getUTCDate(), expected.getUTCDate());
|
||||
|
||||
meta.config.loginDays = _loginDays;
|
||||
});
|
||||
|
||||
it('should ignore loginDays if loginSeconds is truthy', async () => {
|
||||
const _loginSeconds = meta.config.loginSeconds;
|
||||
meta.config.loginSeconds = 60;
|
||||
|
||||
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
|
||||
|
||||
const expiry = getCookieExpiry(res);
|
||||
const expected = new Date();
|
||||
expected.setUTCSeconds(expected.getUTCSeconds() + meta.config.loginSeconds);
|
||||
|
||||
assert.strictEqual(expiry.getUTCDate(), expected.getUTCDate());
|
||||
assert.strictEqual(expiry.getUTCMinutes(), expected.getUTCMinutes());
|
||||
|
||||
meta.config.loginSeconds = _loginSeconds;
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to login if ip address is invalid', (done) => {
|
||||
const jar = request.jar();
|
||||
request({
|
||||
|
||||
Reference in New Issue
Block a user