1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00

Server: Expire sessions after 12 hours

This commit is contained in:
Laurent Cozic 2021-10-26 12:34:22 +01:00
parent 134bc91e20
commit 0ada1dfb46
5 changed files with 73 additions and 1 deletions

View File

@ -0,0 +1,46 @@
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models } from '../utils/testing/testUtils';
import { defaultSessionTtl } from './SessionModel';
describe('SessionModel', function() {
beforeAll(async () => {
await beforeAllDb('SessionModel');
});
afterAll(async () => {
await afterAllTests();
});
beforeEach(async () => {
await beforeEachDb();
});
test('should delete expired sessions', async function() {
jest.useFakeTimers('modern');
const t0 = new Date('2020-01-01T00:00:00').getTime();
jest.setSystemTime(t0);
const { user, password } = await createUserAndSession(1);
await models().session().authenticate(user.email, password);
jest.setSystemTime(new Date(t0 + defaultSessionTtl + 10));
const lastSession = await models().session().authenticate(user.email, password);
expect(await models().session().count()).toBe(3);
await models().session().deleteExpiredSessions();
expect(await models().session().count()).toBe(1);
expect((await models().session().all())[0].id).toBe(lastSession.id);
await models().session().authenticate(user.email, password);
await models().session().deleteExpiredSessions();
expect(await models().session().count()).toBe(2);
jest.useRealTimers();
});
});

View File

@ -2,6 +2,9 @@ import BaseModel from './BaseModel';
import { User, Session, Uuid } from '../services/database/types';
import uuidgen from '../utils/uuidgen';
import { ErrorForbidden } from '../utils/errors';
import { Hour } from '../utils/time';
export const defaultSessionTtl = 12 * Hour;
export default class SessionModel extends BaseModel<Session> {
@ -40,4 +43,9 @@ export default class SessionModel extends BaseModel<Session> {
await query.delete();
}
public async deleteExpiredSessions() {
const cutOffTime = Date.now() - defaultSessionTtl;
await this.db(this.tableName).where('created_time', '<', cutOffTime).delete();
}
}

View File

@ -12,6 +12,7 @@ export enum TaskId {
HandleOversizedAccounts = 3,
HandleBetaUserEmails = 4,
HandleFailedPaymentSubscriptions = 5,
DeleteExpiredSessions = 6,
}
export enum RunType {

View File

@ -5,6 +5,7 @@ import Router from './Router';
import { AppContext, HttpMethod, RouteType } from './types';
import { URL } from 'url';
import { csrfCheck } from './csrf';
import { contextSessionId } from './requestUtils';
const { ltrimSlashes, rtrimSlashes } = require('@joplin/lib/path-utils');
@ -200,7 +201,16 @@ export async function execRequest(routes: Routers, ctx: AppContext) {
// This is a generic catch-all for all private end points - if we
// couldn't get a valid session, we exit now. Individual end points
// might have additional permission checks depending on the action.
if (!isPublicRoute && !ctx.joplin.owner) throw new ErrorForbidden();
if (!isPublicRoute && !ctx.joplin.owner) {
if (contextSessionId(ctx, false)) {
// If we have a session but not a user it means the session was
// invalid or has expired, so display a special message, since this
// is also going to be displayed on the website.
throw new ErrorForbidden('Your session has expired. Please login again.');
} else {
throw new ErrorForbidden();
}
}
await csrfCheck(ctx, isPublicRoute);
disabledAccountCheck(match, ctx.joplin.owner);

View File

@ -29,6 +29,13 @@ export default function(env: Env, models: Models, config: Config): TaskService {
schedule: '0 */2 30 * *',
run: (models: Models) => models.user().handleOversizedAccounts(),
},
{
id: TaskId.DeleteExpiredSessions,
description: 'Delete expired sessions',
schedule: '0 */6 * * *',
run: (models: Models) => models.session().deleteExpiredSessions(),
},
];
if (config.isJoplinCloud) {