1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-19 21:29:43 +02:00

feat: Automatic logout when session expires
Some checks are pending
Build and push Docker DEV image / build ([self-hosted arm64], linux/arm/v7) (push) Waiting to run
Build and push Docker DEV image / build ([self-hosted arm64], linux/arm64) (push) Waiting to run
Build and push Docker DEV image / build ([self-hosted x64], linux/amd64) (push) Waiting to run
Build and push Docker DEV image / merge (push) Blocked by required conditions
Build and push Docker DEV image / rerun-failed-jobs (push) Blocked by required conditions

Closes #693
This commit is contained in:
Maksim Eltyshev 2024-04-09 15:12:46 +02:00
parent b46fb43e6f
commit 4e2863faa7
15 changed files with 130 additions and 42 deletions

View file

@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({
}, },
}); });
const logout = () => ({ const logout = (invalidateAccessToken) => ({
type: ActionTypes.LOGOUT, type: ActionTypes.LOGOUT,
payload: {}, payload: {
invalidateAccessToken,
},
}); });
logout.invalidateAccessToken = () => ({ logout.invalidateAccessToken = () => ({

View file

@ -38,7 +38,7 @@ const handleCardUpdate = (card) => ({
}, },
}); });
const moveCard = (id, listId, index = 0) => ({ const moveCard = (id, listId, index) => ({
type: EntryActionTypes.CARD_MOVE, type: EntryActionTypes.CARD_MOVE,
payload: { payload: {
id, id,
@ -47,7 +47,7 @@ const moveCard = (id, listId, index = 0) => ({
}, },
}); });
const moveCurrentCard = (listId, index = 0) => ({ const moveCurrentCard = (listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_MOVE, type: EntryActionTypes.CURRENT_CARD_MOVE,
payload: { payload: {
listId, listId,
@ -55,7 +55,7 @@ const moveCurrentCard = (listId, index = 0) => ({
}, },
}); });
const transferCard = (id, boardId, listId, index = 0) => ({ const transferCard = (id, boardId, listId, index) => ({
type: EntryActionTypes.CARD_TRANSFER, type: EntryActionTypes.CARD_TRANSFER,
payload: { payload: {
id, id,
@ -65,7 +65,7 @@ const transferCard = (id, boardId, listId, index = 0) => ({
}, },
}); });
const transferCurrentCard = (boardId, listId, index = 0) => ({ const transferCurrentCard = (boardId, listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_TRANSFER, type: EntryActionTypes.CURRENT_CARD_TRANSFER,
payload: { payload: {
boardId, boardId,

View file

@ -1,8 +1,10 @@
import EntryActionTypes from '../constants/EntryActionTypes'; import EntryActionTypes from '../constants/EntryActionTypes';
const logout = () => ({ const logout = (invalidateAccessToken) => ({
type: EntryActionTypes.LOGOUT, type: EntryActionTypes.LOGOUT,
payload: {}, payload: {
invalidateAccessToken,
},
}); });
export default { export default {

View file

@ -1,8 +1,7 @@
import { call, fork, join, put, select, take } from 'redux-saga/effects'; import { call, fork, join, put, select, take } from 'redux-saga/effects';
import selectors from '../../selectors'; import selectors from '../../selectors';
import actions from '../../actions'; import entryActions from '../../entry-actions';
import { removeAccessToken } from '../../utils/access-token-storage';
import ErrorCodes from '../../constants/ErrorCodes'; import ErrorCodes from '../../constants/ErrorCodes';
let lastRequestTask; let lastRequestTask;
@ -22,8 +21,7 @@ function* queueRequest(method, ...args) {
}); });
} catch (error) { } catch (error) {
if (error.code === ErrorCodes.UNAUTHORIZED) { if (error.code === ErrorCodes.UNAUTHORIZED) {
yield call(removeAccessToken); yield put(entryActions.logout(false));
yield put(actions.logout()); // TODO: next url
yield take(); yield take();
} }

View file

@ -86,7 +86,7 @@ export function* handleCardUpdate(card) {
yield put(actions.handleCardUpdate(card)); yield put(actions.handleCardUpdate(card));
} }
export function* moveCard(id, listId, index) { export function* moveCard(id, listId, index = 0) {
const position = yield select(selectors.selectNextCardPosition, listId, index, id); const position = yield select(selectors.selectNextCardPosition, listId, index, id);
yield call(updateCard, id, { yield call(updateCard, id, {
@ -101,7 +101,7 @@ export function* moveCurrentCard(listId, index) {
yield call(moveCard, cardId, listId, index); yield call(moveCard, cardId, listId, index);
} }
export function* transferCard(id, boardId, listId, index) { export function* transferCard(id, boardId, listId, index = 0) {
const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath); const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);
const position = yield select(selectors.selectNextCardPosition, listId, index, id); const position = yield select(selectors.selectNextCardPosition, listId, index, id);

View file

@ -1,4 +1,4 @@
import { call, put, select, take } from 'redux-saga/effects'; import { call, put, select } from 'redux-saga/effects';
import request from '../request'; import request from '../request';
import requests from '../requests'; import requests from '../requests';
@ -84,8 +84,7 @@ export function* logout(invalidateAccessToken = true) {
} catch (error) {} // eslint-disable-line no-empty } catch (error) {} // eslint-disable-line no-empty
} }
yield put(actions.logout()); yield put(actions.logout()); // TODO: next url
yield take();
} }
export default { export default {

View file

@ -1,10 +1,12 @@
import { call, put, select, take } from 'redux-saga/effects'; import { call, put, select, take } from 'redux-saga/effects';
import { push } from '../../../lib/redux-router'; import { push } from '../../../lib/redux-router';
import { logout } from './core';
import request from '../request'; import request from '../request';
import selectors from '../../../selectors'; import selectors from '../../../selectors';
import actions from '../../../actions'; import actions from '../../../actions';
import api from '../../../api'; import api from '../../../api';
import { getAccessToken } from '../../../utils/access-token-storage';
import ActionTypes from '../../../constants/ActionTypes'; import ActionTypes from '../../../constants/ActionTypes';
import Paths from '../../../constants/Paths'; import Paths from '../../../constants/Paths';
@ -25,6 +27,13 @@ export function* goToCard(cardId) {
} }
export function* handleLocationChange() { export function* handleLocationChange() {
const accessToken = yield call(getAccessToken);
if (!accessToken) {
yield call(logout, false);
return;
}
const pathsMatch = yield select(selectors.selectPathsMatch); const pathsMatch = yield select(selectors.selectPathsMatch);
if (!pathsMatch) { if (!pathsMatch) {

View file

@ -218,6 +218,7 @@ export function* handleUserDelete(user) {
if (user.id === currentUserId) { if (user.id === currentUserId) {
yield call(logout, false); yield call(logout, false);
return;
} }
yield put(actions.handleUserDelete(user)); yield put(actions.handleUserDelete(user));

View file

@ -4,5 +4,9 @@ import services from '../services';
import EntryActionTypes from '../../../constants/EntryActionTypes'; import EntryActionTypes from '../../../constants/EntryActionTypes';
export default function* coreWatchers() { export default function* coreWatchers() {
yield all([takeEvery(EntryActionTypes.LOGOUT, () => services.logout())]); yield all([
takeEvery(EntryActionTypes.LOGOUT, ({ payload: { invalidateAccessToken } }) =>
services.logout(invalidateAccessToken),
),
]);
} }

View file

@ -16,6 +16,10 @@ const createSocketEventsChannel = () =>
emit(entryActions.handleSocketReconnect()); emit(entryActions.handleSocketReconnect());
}; };
const handleLogout = () => {
emit(entryActions.logout(false));
};
const handleUserCreate = api.makeHandleUserCreate(({ item }) => { const handleUserCreate = api.makeHandleUserCreate(({ item }) => {
emit(entryActions.handleUserCreate(item)); emit(entryActions.handleUserCreate(item));
}); });
@ -171,6 +175,8 @@ const createSocketEventsChannel = () =>
socket.on('disconnect', handleDisconnect); socket.on('disconnect', handleDisconnect);
socket.on('reconnect', handleReconnect); socket.on('reconnect', handleReconnect);
socket.on('logout', handleLogout);
socket.on('userCreate', handleUserCreate); socket.on('userCreate', handleUserCreate);
socket.on('userUpdate', handleUserUpdate); socket.on('userUpdate', handleUserUpdate);
socket.on('userDelete', handleUserDelete); socket.on('userDelete', handleUserDelete);
@ -227,6 +233,8 @@ const createSocketEventsChannel = () =>
socket.off('disconnect', handleDisconnect); socket.off('disconnect', handleDisconnect);
socket.off('reconnect', handleReconnect); socket.off('reconnect', handleReconnect);
socket.off('logout', handleLogout);
socket.off('userCreate', handleUserCreate); socket.off('userCreate', handleUserCreate);
socket.off('userUpdate', handleUserUpdate); socket.off('userUpdate', handleUserUpdate);
socket.off('userDelete', handleUserDelete); socket.off('userDelete', handleUserDelete);

View file

@ -9,6 +9,10 @@ module.exports = {
deletedAt: new Date().toISOString(), deletedAt: new Date().toISOString(),
}); });
if (this.req.isSocket) {
sails.sockets.leaveAll(`@accessToken:${accessToken}`);
}
return { return {
item: accessToken, item: accessToken,
}; };

View file

@ -61,6 +61,7 @@ module.exports = function defineCurrentUserHook(sails) {
}); });
if (req.isSocket) { if (req.isSocket) {
sails.sockets.join(req, `@accessToken:${accessToken}`);
sails.sockets.join(req, `@user:${currentUser.id}`); sails.sockets.join(req, `@user:${currentUser.id}`);
} }
} }

View file

@ -1,6 +1,14 @@
const openidClient = require('openid-client'); const openidClient = require('openid-client');
module.exports = function oidcServiceHook(sails) { /**
* oidc hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
module.exports = function defineOidcHook(sails) {
let client = null; let client = null;
return { return {
@ -9,7 +17,12 @@ module.exports = function oidcServiceHook(sails) {
*/ */
async initialize() { async initialize() {
if (sails.config.custom.oidcIssuer) { if (!sails.config.custom.oidcIssuer) {
return;
}
sails.log.info('Initializing custom hook (`oidc`)');
const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer); const issuer = await openidClient.Issuer.discover(sails.config.custom.oidcIssuer);
client = new issuer.Client({ client = new issuer.Client({
@ -18,8 +31,6 @@ module.exports = function oidcServiceHook(sails) {
redirect_uris: [sails.config.custom.oidcRedirectUri], redirect_uris: [sails.config.custom.oidcRedirectUri],
response_types: ['code'], response_types: ['code'],
}); });
sails.log.info('OIDC hook has been loaded successfully');
}
}, },
getClient() { getClient() {

View file

@ -1,6 +1,14 @@
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer');
module.exports = function smtpServiceHook(sails) { /**
* smtp hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
module.exports = function defineSmtpHook(sails) {
let transporter = null; let transporter = null;
return { return {
@ -9,7 +17,12 @@ module.exports = function smtpServiceHook(sails) {
*/ */
async initialize() { async initialize() {
if (sails.config.custom.smtpHost) { if (!sails.config.custom.smtpHost) {
return;
}
sails.log.info('Initializing custom hook (`smtp`)');
transporter = nodemailer.createTransport({ transporter = nodemailer.createTransport({
pool: true, pool: true,
host: sails.config.custom.smtpHost, host: sails.config.custom.smtpHost,
@ -20,8 +33,6 @@ module.exports = function smtpServiceHook(sails) {
pass: sails.config.custom.smtpPassword, pass: sails.config.custom.smtpPassword,
}, },
}); });
sails.log.info('SMTP hook has been loaded successfully');
}
}, },
getTransporter() { getTransporter() {

View file

@ -0,0 +1,38 @@
/**
* watcher hook
*
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions,
* and/or initialization logic.
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
*/
module.exports = function defineWatcherHook(sails) {
const checkSocketConnectionsToLogout = () => {
Object.keys(sails.io.sockets.adapter.rooms).forEach((room) => {
if (!room.startsWith('@accessToken:')) {
return;
}
const accessToken = room.split(':')[1];
try {
sails.helpers.utils.verifyToken(accessToken);
} catch (error) {
sails.sockets.broadcast(room, 'logout');
sails.sockets.leaveAll(room);
}
});
};
return {
/**
* Runs when this Sails app loads/lifts.
*/
async initialize() {
sails.log.info('Initializing custom hook (`watcher`)');
setInterval(checkSocketConnectionsToLogout, 60 * 1000);
},
};
};