diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index 944f3ee9d9..4223e7e395 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -42,8 +42,9 @@ module.exports = ( internal = false if (authCookie) { let error = null - const sessionId = authCookie.sessionId, - userId = authCookie.userId + const sessionId = authCookie.sessionId + const userId = authCookie.userId + const session = await getSession(userId, sessionId) if (!session) { error = "No session found" diff --git a/packages/auth/src/security/sessions.js b/packages/auth/src/security/sessions.js index 83ca9d9bcd..93c2d0a9ca 100644 --- a/packages/auth/src/security/sessions.js +++ b/packages/auth/src/security/sessions.js @@ -24,17 +24,24 @@ exports.createASession = async (userId, session) => { await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS) } -exports.invalidateSessions = async (userId, sessionId = null) => { +exports.invalidateSessions = async (userId, sessionIds = null) => { let sessions = [] - if (sessionId) { - sessions.push({ key: makeSessionID(userId, sessionId) }) - } else { + + // If no sessionIds, get all the sessions for the user + if (!sessionIds) { sessions = await getSessionsForUser(userId) sessions.forEach( session => (session.key = makeSessionID(session.userId, session.sessionId)) ) + } else { + // use the passed array of sessionIds + sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds] + sessions = sessions.map(sessionId => ({ + key: makeSessionID(userId, sessionId), + })) } + const client = await redis.getSessionClient() const promises = [] for (let session of sessions) { diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index f509a626c1..823fd06322 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -7,7 +7,7 @@ const { const jwt = require("jsonwebtoken") const { options } = require("./middleware/passport/jwt") const { createUserEmailView } = require("./db/views") -const { Headers, UserStatus } = require("./constants") +const { Headers, UserStatus, Cookies } = require("./constants") const { getGlobalDB, updateTenantId, @@ -19,6 +19,7 @@ const accounts = require("./cloud/accounts") const { hash } = require("./hashing") const userCache = require("./cache/user") const env = require("./environment") +const { getUserSessions, invalidateSessions } = require("./security/sessions") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -235,3 +236,28 @@ exports.saveUser = async ( } } } + +/** + * Logs a user out from budibase. Re-used across account portal and builder. + */ +exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => { + if (!ctx) throw new Error("Koa context must be supplied to logout.") + + const currentSession = this.getCookie(ctx, Cookies.Auth) + let sessions = await getUserSessions(userId) + + if (keepActiveSession) { + sessions = sessions.filter( + session => session.sessionId !== currentSession.sessionId + ) + } else { + // clear cookies + this.clearCookie(ctx, Cookies.Auth) + this.clearCookie(ctx, Cookies.CurrentApp) + } + + await invalidateSessions( + userId, + sessions.map(({ sessionId }) => sessionId) + ) +} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 011c9bee43..cd437bcad2 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -32,6 +32,7 @@ const FORMULA_TYPE = FIELDS.FORMULA.type const LINK_TYPE = FIELDS.LINK.type const dispatch = createEventDispatcher() + const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] const { hide } = getContext(Context.Modal) let fieldDefinitions = cloneDeep(FIELDS) @@ -66,7 +67,11 @@ (field.type === LINK_TYPE && !field.tableId) || Object.keys($tables.draft?.schema ?? {}).some( key => key !== originalName && key === field.name - ) + ) || + columnNameInvalid + $: columnNameInvalid = PROHIBITED_COLUMN_NAMES.some( + name => field.name === name + ) // used to select what different options can be displayed for column type $: canBeSearched = @@ -200,6 +205,9 @@ label="Name" bind:value={field.name} disabled={uneditable || (linkEditDisabled && field.type === LINK_TYPE)} + error={columnNameInvalid + ? `${PROHIBITED_COLUMN_NAMES.join(", ")} are not allowed as column names` + : ""} />