From 851bcf98161b969c53eb195d375f70f8b30387df Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 27 Mar 2019 10:36:15 +0100 Subject: [PATCH] feat(xo-server/api): close connection when session expires (#4071) See xoa-support#1389 --- packages/xo-server/src/api/session.js | 12 ++++++-- packages/xo-server/src/index.js | 9 ++++-- .../xo-server/src/xo-mixins/authentication.js | 30 +++++++++++++++---- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/xo-server/src/api/session.js b/packages/xo-server/src/api/session.js index c9f952767..5f7425735 100644 --- a/packages/xo-server/src/api/session.js +++ b/packages/xo-server/src/api/session.js @@ -5,8 +5,16 @@ import { getUserPublicProperties } from '../utils' // =================================================================== export async function signIn(credentials) { - const user = await this.authenticateUser(credentials) - this.session.set('user_id', user.id) + const { session } = this + + const { user, expiration } = await this.authenticateUser(credentials) + session.set('user_id', user.id) + + if (expiration === undefined) { + session.unset('expiration') + } else { + session.set('expiration', expiration) + } return getUserPublicProperties(user) } diff --git a/packages/xo-server/src/index.js b/packages/xo-server/src/index.js index 071502cc4..f5794fa29 100644 --- a/packages/xo-server/src/index.js +++ b/packages/xo-server/src/index.js @@ -251,7 +251,7 @@ async function setUpPassport(express, xo, { authentication: authCfg }) { xo.registerPassportStrategy( new LocalStrategy(async (username, password, done) => { try { - const user = await xo.authenticateUser({ username, password }) + const { user } = await xo.authenticateUser({ username, password }) done(null, user) } catch (error) { done(null, false, { message: error.message }) @@ -518,6 +518,11 @@ const setUpApi = (webServer, xo, config) => { // Connect the WebSocket to the JSON-RPC server. socket.on('message', message => { + const expiration = connection.get('expiration', undefined) + if (expiration !== undefined && expiration < Date.now()) { + return void connection.close() + } + jsonRpc.write(message) }) @@ -565,7 +570,7 @@ const setUpConsoleProxy = (webServer, xo) => { { const { token } = parseCookies(req.headers.cookie) - const user = await xo.authenticateUser({ token }) + const { user } = await xo.authenticateUser({ token }) if (!(await xo.hasPermissions(user.id, [[id, 'operate']]))) { throw invalidCredentials() } diff --git a/packages/xo-server/src/xo-mixins/authentication.js b/packages/xo-server/src/xo-mixins/authentication.js index 31e7c871a..8cd1321dd 100644 --- a/packages/xo-server/src/xo-mixins/authentication.js +++ b/packages/xo-server/src/xo-mixins/authentication.js @@ -37,7 +37,7 @@ export default class { const user = await xo.getUserByName(username, true) if (user && (await xo.checkUserPassword(user.id, password))) { - return user.id + return { userId: user.id } } }) @@ -48,7 +48,8 @@ export default class { } try { - return (await xo.getAuthenticationToken(tokenId)).user_id + const token = await xo.getAuthenticationToken(tokenId) + return { expiration: token.expiration, userId: token.user_id } } catch (error) {} }) @@ -88,6 +89,10 @@ export default class { // A provider can return: // - `undefined`/`null` if the user could not be authenticated // - the identifier of the authenticated user + // - an object containing: + // - `userId` + // - optionally `expiration` to indicate when the session is no longer + // valid // - an object with a property `username` containing the name // of the authenticated user const result = await provider(credentials) @@ -97,9 +102,20 @@ export default class { continue } - return result.username - ? await this._xo.registerUser(undefined, result.username) - : await this._xo.getUser(result) + if (typeof result === 'string') { + return { + user: await this._getUser(result), + } + } + + const { userId, username, expiration } = result + + return { + user: await (userId !== undefined + ? this._xo.getUser(userId) + : this._xo.registerUser(undefined, username)), + expiration, + } } catch (error) { // DEPRECATED: Authentication providers may just throw `null` // to indicate they could not authenticate the user without @@ -109,7 +125,9 @@ export default class { } } - async authenticateUser(credentials) { + async authenticateUser( + credentials + ): Promise<{| user: Object, expiration?: number |}> { // don't even attempt to authenticate with empty password const { password } = credentials if (password === '') {