From b847f85d91420ce43b232c657f41f15f41ebc535 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 6 Jul 2021 18:10:04 +0100 Subject: [PATCH 1/7] WIP - first version of user sessions. --- packages/auth/cache.js | 3 + packages/auth/sessions.js | 1 + packages/auth/src/cache/user.js | 22 +++++++ packages/auth/src/index.js | 4 ++ packages/auth/src/middleware/authenticated.js | 30 +++++++--- .../auth/src/middleware/passport/google.js | 12 ++-- .../auth/src/middleware/passport/local.js | 10 +++- packages/auth/src/redis/authRedis.js | 28 +++++++++ packages/auth/src/redis/index.js | 15 +++-- packages/auth/src/redis/utils.js | 4 ++ packages/auth/src/security/sessions.js | 57 +++++++++++++++++++ packages/auth/src/utils.js | 11 +--- packages/server/src/middleware/currentapp.js | 24 ++------ packages/server/src/utilities/global.js | 16 +++++- .../worker/src/api/controllers/admin/auth.js | 7 ++- .../worker/src/api/controllers/admin/users.js | 5 ++ packages/worker/src/utilities/redis.js | 2 +- 17 files changed, 197 insertions(+), 54 deletions(-) create mode 100644 packages/auth/cache.js create mode 100644 packages/auth/sessions.js create mode 100644 packages/auth/src/cache/user.js create mode 100644 packages/auth/src/redis/authRedis.js create mode 100644 packages/auth/src/security/sessions.js diff --git a/packages/auth/cache.js b/packages/auth/cache.js new file mode 100644 index 0000000000..48563a16f3 --- /dev/null +++ b/packages/auth/cache.js @@ -0,0 +1,3 @@ +module.exports = { + user: require("./src/cache/user"), +} diff --git a/packages/auth/sessions.js b/packages/auth/sessions.js new file mode 100644 index 0000000000..f823773797 --- /dev/null +++ b/packages/auth/sessions.js @@ -0,0 +1 @@ +module.exports = require("./src/security/sessions") \ No newline at end of file diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js new file mode 100644 index 0000000000..aac9f68a3c --- /dev/null +++ b/packages/auth/src/cache/user.js @@ -0,0 +1,22 @@ +const { getDB } = require("../db") +const { StaticDatabases } = require("../db/utils") +const redis = require("../redis/authRedis") + +const EXPIRY_SECONDS = 3600 + + +exports.getUser = async userId => { + const client = await redis.getUserClient() + // try cache + let user = await client.get(userId) + if (!user) { + user = await getDB(StaticDatabases.GLOBAL.name).get(userId) + client.store(userId, user, EXPIRY_SECONDS) + } + return user +} + +exports.invalidateUser = async userId => { + const client = await redis.getUserClient() + await client.delete(userId) +} diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index 9582d6ffd6..ff604b5a3a 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -4,6 +4,7 @@ const JwtStrategy = require("passport-jwt").Strategy const { StaticDatabases } = require("./db/utils") const { jwt, local, authenticated, google, auditLog } = require("./middleware") const { setDB, getDB } = require("./db") +const userCache = require("./cache/user") // Strategies passport.use(new LocalStrategy(local.options, local.authenticate)) @@ -47,6 +48,9 @@ module.exports = { jwt: require("jsonwebtoken"), auditLog, }, + cache: { + user: userCache, + }, StaticDatabases, constants: require("./constants"), } diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index 64494f709d..fd65afa744 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -1,7 +1,7 @@ const { Cookies } = require("../constants") -const database = require("../db") const { getCookie, clearCookie } = require("../utils") -const { StaticDatabases } = require("../db/utils") +const { getUser } = require("../cache/user") +const { getSession, updateSessionTTL } = require("../security/sessions") const env = require("../environment") const PARAM_REGEX = /\/:(.*?)\//g @@ -48,14 +48,26 @@ module.exports = (noAuthPatterns = [], opts) => { user = null, internal = false if (authCookie) { - try { - const db = database.getDB(StaticDatabases.GLOBAL.name) - user = await db.get(authCookie.userId) - delete user.password - authenticated = true - } catch (err) { - // remove the cookie as the use does not exist anymore + let error = null + const sessionId = authCookie.sessionId, userId = authCookie.userId + const session = await getSession(userId, sessionId) + if (!session) { + error = "No session found" + } else { + try { + const user = await getUser(userId) + delete user.password + authenticated = true + } catch (err) { + error = err + } + } + if (error) { + // remove the cookie as the user does not exist anymore clearCookie(ctx, Cookies.Auth) + } else { + // make sure we denote that the session is still in use + await updateSessionTTL(userId, sessionId) } } const apiKey = ctx.request.headers["x-budibase-api-key"] diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index f84da95e73..c3ef155f1b 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -7,6 +7,8 @@ const { generateGlobalUserID, ViewNames, } = require("../../db/utils") +const { newid } = require("../../hashing") +const { createASession } = require("../../security/sessions") async function authenticate(token, tokenSecret, profile, done) { // Check the user exists in the instance DB by email @@ -59,15 +61,15 @@ async function authenticate(token, tokenSecret, profile, done) { } // authenticate + const sessionId = newid() const payload = { userId: dbUser._id, - builder: dbUser.builder, - email: dbUser.email, + sessionId, } + await createASession(dbUser._id, sessionId, payload) + dbUser.sessionId = sessionId - dbUser.token = jwt.sign(payload, env.JWT_SECRET, { - expiresIn: "1 day", - }) + dbUser.token = jwt.sign(payload, env.JWT_SECRET) return done(null, dbUser) } diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 0f5cb82606..5ede7f91a1 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -3,6 +3,8 @@ const { UserStatus } = require("../../constants") const { compare } = require("../../hashing") const env = require("../../environment") const { getGlobalUserByEmail } = require("../../utils") +const { newid } = require("../../hashing") +const { createASession } = require("../../security/sessions") const INVALID_ERR = "Invalid Credentials" @@ -31,13 +33,15 @@ exports.authenticate = async function (email, password, done) { // authenticate if (await compare(password, dbUser.password)) { + const sessionId = newid() const payload = { userId: dbUser._id, + sessionId, } + await createASession(dbUser._id, sessionId, payload) + dbUser.sessionId = sessionId - dbUser.token = jwt.sign(payload, env.JWT_SECRET, { - expiresIn: "1 day", - }) + dbUser.token = jwt.sign(payload, env.JWT_SECRET) // Remove users password in payload delete dbUser.password diff --git a/packages/auth/src/redis/authRedis.js b/packages/auth/src/redis/authRedis.js new file mode 100644 index 0000000000..b7054e7b7c --- /dev/null +++ b/packages/auth/src/redis/authRedis.js @@ -0,0 +1,28 @@ +const { Client, utils } = require("./index") + +let userClient, sessionClient + +async function init() { + userClient = await new Client(utils.Databases.USER_CACHE).init() + sessionClient = await new Client(utils.Databases.SESSIONS).init() +} + +process.on("exit", async () => { + if (userClient) await userClient.finish() + if (sessionClient) await sessionClient.finish() +}) + +module.exports = { + getUserClient: async () => { + if (!userClient) { + await init() + } + return userClient + }, + getSessionClient: async () => { + if (!sessionClient) { + await init() + } + return sessionClient + }, +} diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index e20255bfd3..2480b531e7 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -1,7 +1,7 @@ const env = require("../environment") // ioredis mock is all in memory const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") -const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") +const { addDbPrefix, removeDbPrefix, getRedisOptions, SEPARATOR } = require("./utils") const RETRY_PERIOD_MS = 2000 const STARTUP_TIMEOUT_MS = 5000 @@ -143,14 +143,15 @@ class RedisWrapper { CLIENT.disconnect() } - async scan() { + async scan(key = "") { const db = this._db + key = `${db}${SEPARATOR}${key}` let stream if (CLUSTERED) { let node = CLIENT.nodes("master") - stream = node[0].scanStream({ match: db + "-*", count: 100 }) + stream = node[0].scanStream({ match: key + "*", count: 100 }) } else { - stream = CLIENT.scanStream({ match: db + "-*", count: 100 }) + stream = CLIENT.scanStream({ match: key + "*", count: 100 }) } return promisifyStream(stream) } @@ -182,6 +183,12 @@ class RedisWrapper { } } + async setExpiry(key, expirySeconds) { + const db = this._db + const prefixedKey = addDbPrefix(db, key) + await CLIENT.expire(prefixedKey, expirySeconds) + } + async delete(key) { const db = this._db await CLIENT.del(addDbPrefix(db, key)) diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js index 23702353d8..415dcbf463 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/auth/src/redis/utils.js @@ -11,8 +11,12 @@ exports.Databases = { INVITATIONS: "invitation", DEV_LOCKS: "devLocks", DEBOUNCE: "debounce", + SESSIONS: "session", + USER_CACHE: "users", } +exports.SEPARATOR = SEPARATOR + exports.getRedisOptions = (clustered = false) => { const [host, port] = REDIS_URL.split(":") const opts = { diff --git a/packages/auth/src/security/sessions.js b/packages/auth/src/security/sessions.js new file mode 100644 index 0000000000..83af6c723c --- /dev/null +++ b/packages/auth/src/security/sessions.js @@ -0,0 +1,57 @@ +const redis = require("../redis/authRedis") + +const EXPIRY_SECONDS = 86400 + +async function getSessionsForUser(userId) { + const client = await redis.getSessionClient() + return client.scan(userId) +} + +function makeSessionID(userId, sessionId) { + return `${userId}/${sessionId}` +} + +exports.createASession = async (userId, sessionId, token) => { + const client = await redis.getSessionClient() + await client.store(makeSessionID(userId, sessionId), token, EXPIRY_SECONDS) +} + +exports.invalidateSessions = async (userId, sessionId = null) => { + let sessions = [] + if (sessionId) { + sessions.push({ key: makeSessionID(userId, sessionId) }) + } else { + sessions = await getSessionsForUser(userId) + } + const client = await redis.getSessionClient() + const promises = [] + for (let session of sessions) { + promises.push(client.delete(session.key)) + } + await Promise.all(promises) +} + +exports.updateSessionTTL = async (userId, sessionId) => { + const client = await redis.getSessionClient() + await client.setExpiry(makeSessionID(userId, sessionId), EXPIRY_SECONDS) +} + +exports.endSession = async (userId, sessionId) => { + const client = await redis.getSessionClient() + await client.delete(makeSessionID(userId, sessionId)) +} + +exports.getSession = async (userId, sessionId) => { + try { + const client = await redis.getSessionClient() + return client.get(makeSessionID(userId, sessionId)) + } catch (err) { + // if can't get session don't error, just don't return anything + return null + } +} + +exports.getAllSessions = async () => { + const client = await redis.getSessionClient() + return client.scan() +} diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 278ad07174..8bd635e2e3 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -64,23 +64,18 @@ exports.getCookie = (ctx, name) => { } /** - * Store a cookie for the request, has a hardcoded expiry. + * Store a cookie for the request - it will not expire. * @param {object} ctx The request which is to be manipulated. * @param {string} name The name of the cookie to set. * @param {string|object} value The value of cookie which will be set. */ exports.setCookie = (ctx, value, name = "builder") => { - const expires = new Date() - expires.setDate(expires.getDate() + 1) - if (!value) { ctx.cookies.set(name) } else { - value = jwt.sign(value, options.secretOrKey, { - expiresIn: "1 day", - }) + value = jwt.sign(value, options.secretOrKey) ctx.cookies.set(name, value, { - expires, + maxAge: Number.MAX_SAFE_INTEGER, path: "/", httpOnly: false, overwrite: true, diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 683b7f8ef3..82d8c1af72 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -6,6 +6,7 @@ const { getGlobalSelf } = require("../utilities/workerRequests") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { generateUserMetadataID } = require("../db/utils") const { dbExists } = require("@budibase/auth/db") +const { getCachedSelf } = require("../utilities/global") const CouchDB = require("../db") module.exports = async (ctx, next) => { @@ -26,29 +27,16 @@ module.exports = async (ctx, next) => { } } - let updateCookie = false, - appId, - roleId = BUILTIN_ROLE_IDS.PUBLIC + let appId, roleId = BUILTIN_ROLE_IDS.PUBLIC if (!ctx.user) { // not logged in, try to set a cookie for public apps - updateCookie = true appId = requestAppId - } else if ( - requestAppId != null && - (appCookie == null || - requestAppId !== appCookie.appId || - appCookie.roleId === BUILTIN_ROLE_IDS.PUBLIC || - !appCookie.roleId) - ) { + } else if (requestAppId != null) { // Different App ID means cookie needs reset, or if the same public user has logged in - const globalUser = await getGlobalSelf(ctx, requestAppId) - updateCookie = true + const globalUser = await getCachedSelf(ctx, requestAppId) appId = requestAppId // retrieving global user gets the right role roleId = globalUser.roleId || BUILTIN_ROLE_IDS.PUBLIC - } else if (appCookie != null) { - appId = appCookie.appId - roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC } // nothing more to do if (!appId) { @@ -68,8 +56,8 @@ module.exports = async (ctx, next) => { role: await getRole(appId, roleId), } } - if (updateCookie) { - setCookie(ctx, { appId, roleId }, Cookies.CurrentApp) + if (requestAppId !== appId) { + setCookie(ctx, { appId }, Cookies.CurrentApp) } return next() } diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index 17ce066551..8c4bb78455 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -7,6 +7,7 @@ const { const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { getDeployedAppID } = require("@budibase/auth/db") const { getGlobalUserParams } = require("@budibase/auth/db") +const { user: userCache } = require("@budibase/auth/cache") exports.updateAppRole = (appId, user) => { if (!user.roles) { @@ -25,15 +26,24 @@ exports.updateAppRole = (appId, user) => { return user } -exports.getGlobalUser = async (appId, userId) => { - const db = CouchDB(StaticDatabases.GLOBAL.name) - let user = await db.get(getGlobalIDFromUserMetadataID(userId)) +function processUser(appId, user) { if (user) { delete user.password } return exports.updateAppRole(appId, user) } +exports.getCachedSelf = async (ctx, appId) => { + const user = await userCache.getUser(ctx.user._id) + return processUser(appId, user) +} + +exports.getGlobalUser = async (appId, userId) => { + const db = CouchDB(StaticDatabases.GLOBAL.name) + let user = await db.get(getGlobalIDFromUserMetadataID(userId)) + return processUser(appId, user) +} + exports.getGlobalUsers = async (appId = null, users = null) => { const db = CouchDB(StaticDatabases.GLOBAL.name) let globalUsers diff --git a/packages/worker/src/api/controllers/admin/auth.js b/packages/worker/src/api/controllers/admin/auth.js index 5304ac85d1..0c2405b728 100644 --- a/packages/worker/src/api/controllers/admin/auth.js +++ b/packages/worker/src/api/controllers/admin/auth.js @@ -10,7 +10,7 @@ const { checkResetPasswordCode } = require("../../../utilities/redis") const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name -function authInternal(ctx, user, err = null) { +async function authInternal(ctx, user, err = null) { if (err) { return ctx.throw(403, "Unauthorized") } @@ -22,6 +22,7 @@ function authInternal(ctx, user, err = null) { return ctx.throw(403, "Unauthorized") } + // just store the user ID ctx.cookies.set(Cookies.Auth, user.token, { expires, path: "/", @@ -32,7 +33,7 @@ function authInternal(ctx, user, err = null) { exports.authenticate = async (ctx, next) => { return passport.authenticate("local", async (err, user) => { - authInternal(ctx, user, err) + await authInternal(ctx, user, err) delete user.token @@ -123,7 +124,7 @@ exports.googleAuth = async (ctx, next) => { strategy, { successRedirect: "/", failureRedirect: "/error" }, async (err, user) => { - authInternal(ctx, user, err) + await authInternal(ctx, user, err) ctx.redirect("/") } diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index dbe34d7ded..5b299d9906 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -5,6 +5,8 @@ const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils const { UserStatus, EmailTemplatePurpose } = require("../../../constants") const { checkInviteCode } = require("../../../utilities/redis") const { sendEmail } = require("../../../utilities/email") +const { user: userCache } = require("@budibase/auth/cache") +const { invalidateSessions } = require("@budibase/auth/sessions") const GLOBAL_DB = StaticDatabases.GLOBAL.name @@ -62,6 +64,7 @@ exports.save = async ctx => { password: hashedPassword, ...user, }) + await userCache.invalidateUser(response.id) ctx.body = { _id: response.id, _rev: response.rev, @@ -107,6 +110,8 @@ exports.destroy = async ctx => { const db = new CouchDB(GLOBAL_DB) const dbUser = await db.get(ctx.params.id) await db.remove(dbUser._id, dbUser._rev) + await userCache.invalidateUser(dbUser._id) + await invalidateSessions(dbUser._id) ctx.body = { message: `User ${ctx.params.id} deleted.`, } diff --git a/packages/worker/src/utilities/redis.js b/packages/worker/src/utilities/redis.js index 0701e500dd..6e55795de1 100644 --- a/packages/worker/src/utilities/redis.js +++ b/packages/worker/src/utilities/redis.js @@ -44,7 +44,7 @@ async function getACode(db, code, deleteCode = true) { exports.init = async () => { pwResetClient = await new Client(utils.Databases.PW_RESETS).init() - invitationClient = await new Client(utils.Databases.PW_RESETS).init() + invitationClient = await new Client(utils.Databases.INVITATIONS).init() } /** From 4acd094c7457c19a0658b6183ba0ed9ba2652244 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Jul 2021 17:12:13 +0100 Subject: [PATCH 2/7] Making it possible to create an internal table from the plus symbol. --- .../TableIntegrationMenu/index.svelte | 9 ++++++--- .../modals/CreateDatasourceModal.svelte | 19 +++++++++++++++++-- .../modals/CreateTableModal.svelte | 10 +++++++--- .../app/[application]/data/_layout.svelte | 1 - 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte index 7e11a4c3ff..6f3d0082cc 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte @@ -5,14 +5,17 @@ import ICONS from "../icons" export let integration = {} - let integrations = [] + const INTERNAL = "BUDIBASE" async function fetchIntegrations() { const response = await api.get("/api/integrations") const json = await response.json() - integrations = json + integrations = { + [INTERNAL]: { datasource: {} }, + ...json, + } return json } @@ -21,7 +24,7 @@ // build the schema const schema = {} - for (let key in selected.datasource) { + for (let key of Object.keys(selected.datasource)) { schema[key] = selected.datasource[key].default } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte index d8c29ed250..9cdd893230 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte @@ -2,15 +2,21 @@ import { goto } from "@roxi/routify" import { datasources } from "stores/backend" import { notifications } from "@budibase/bbui" - import { Input, Label, ModalContent } from "@budibase/bbui" + import { Input, Label, ModalContent, Modal, Context } from "@budibase/bbui" import TableIntegrationMenu from "../TableIntegrationMenu/index.svelte" + import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte" import analytics from "analytics" + import { getContext } from "svelte" - let error = "" + const modalContext = getContext(Context.Modal) + let tableModal let name + let error = "" let integration + $: checkOpenModal(integration && integration.type === "BUDIBASE") + function checkValid(evt) { const datasourceName = evt.target.value if ( @@ -22,6 +28,12 @@ error = "" } + function checkOpenModal(isInternal) { + if (isInternal) { + tableModal.show() + } + } + async function saveDatasource() { const { type, plus, ...config } = integration @@ -40,6 +52,9 @@ } + + + - import { goto } from "@roxi/routify" + import { goto, url } from "@roxi/routify" import { store } from "builderStore" import { tables } from "stores/backend" import { notifications } from "@budibase/bbui" @@ -27,7 +27,7 @@ $: tableNames = $tables.list.map(table => table.name) - let name + export let name let dataImport let error = "" let createAutoscreens = true @@ -91,7 +91,11 @@ } // Navigate to new table - $goto(`../../table/${table._id}`) + const currentUrl = $url() + const path = currentUrl.endsWith("data") + ? `./table/${table._id}` + : `../../table/${table._id}` + $goto(path) } diff --git a/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte index 6ce765d424..5202bd45f2 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte @@ -7,7 +7,6 @@ let selected = "Sources" let modal - $: isExternal = $params.selectedDatasource && $params.selectedDatasource !== BUDIBASE_INTERNAL_DB From 7f0fc5f89914b54fb87e95287920cfed6f4f9f95 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Jul 2021 17:15:53 +0100 Subject: [PATCH 3/7] Fixing session issues after testing a bit. --- packages/auth/sessions.js | 2 +- packages/auth/src/cache/user.js | 1 - packages/auth/src/middleware/authenticated.js | 5 +++-- packages/auth/src/redis/authRedis.js | 3 ++- packages/auth/src/redis/index.js | 7 ++++++- packages/server/src/middleware/currentapp.js | 4 ++-- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/auth/sessions.js b/packages/auth/sessions.js index f823773797..c07efa2380 100644 --- a/packages/auth/sessions.js +++ b/packages/auth/sessions.js @@ -1 +1 @@ -module.exports = require("./src/security/sessions") \ No newline at end of file +module.exports = require("./src/security/sessions") diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index aac9f68a3c..46202cbfe9 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -4,7 +4,6 @@ const redis = require("../redis/authRedis") const EXPIRY_SECONDS = 3600 - exports.getUser = async userId => { const client = await redis.getUserClient() // try cache diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index fd65afa744..9afbb33489 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -49,13 +49,14 @@ module.exports = (noAuthPatterns = [], opts) => { internal = false if (authCookie) { let error = null - const sessionId = authCookie.sessionId, userId = authCookie.userId + const sessionId = authCookie.sessionId, + userId = authCookie.userId const session = await getSession(userId, sessionId) if (!session) { error = "No session found" } else { try { - const user = await getUser(userId) + user = await getUser(userId) delete user.password authenticated = true } catch (err) { diff --git a/packages/auth/src/redis/authRedis.js b/packages/auth/src/redis/authRedis.js index b7054e7b7c..decce6763b 100644 --- a/packages/auth/src/redis/authRedis.js +++ b/packages/auth/src/redis/authRedis.js @@ -1,4 +1,5 @@ -const { Client, utils } = require("./index") +const Client = require("./index") +const utils = require("./utils") let userClient, sessionClient diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 2480b531e7..4f2b5288ea 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -1,7 +1,12 @@ const env = require("../environment") // ioredis mock is all in memory const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") -const { addDbPrefix, removeDbPrefix, getRedisOptions, SEPARATOR } = require("./utils") +const { + addDbPrefix, + removeDbPrefix, + getRedisOptions, + SEPARATOR, +} = require("./utils") const RETRY_PERIOD_MS = 2000 const STARTUP_TIMEOUT_MS = 5000 diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 82d8c1af72..f599fa2bf7 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -2,7 +2,6 @@ const { getAppId, setCookie, getCookie, clearCookie } = require("@budibase/auth").utils const { Cookies } = require("@budibase/auth").constants const { getRole } = require("@budibase/auth/roles") -const { getGlobalSelf } = require("../utilities/workerRequests") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { generateUserMetadataID } = require("../db/utils") const { dbExists } = require("@budibase/auth/db") @@ -27,7 +26,8 @@ module.exports = async (ctx, next) => { } } - let appId, roleId = BUILTIN_ROLE_IDS.PUBLIC + let appId, + roleId = BUILTIN_ROLE_IDS.PUBLIC if (!ctx.user) { // not logged in, try to set a cookie for public apps appId = requestAppId From 524957cb4de8f75ec4b11df61190c1f2f4cdda65 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Jul 2021 17:46:39 +0100 Subject: [PATCH 4/7] Changing budibase -> internal/csv in datasource menu. --- .../DatasourceNavigator/TableIntegrationMenu/index.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte index 6f3d0082cc..79ebdb1020 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte @@ -13,7 +13,7 @@ const json = await response.json() integrations = { - [INTERNAL]: { datasource: {} }, + [INTERNAL]: { datasource: {}, name: "INTERNAL/CSV" }, ...json, } return json @@ -42,7 +42,7 @@
- {#each Object.keys(integrations) as integrationType} + {#each Object.entries(integrations) as [integrationType, schema]}
- {integrationType} + {schema.name || integrationType}
{/each}
From 4916ff7eb84c4aa6c66f9dac590aac0d41955744 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Jul 2021 23:29:19 +0100 Subject: [PATCH 5/7] Adding sessions API. --- packages/auth/src/middleware/authenticated.js | 2 +- .../auth/src/middleware/passport/google.js | 10 +++--- .../auth/src/middleware/passport/local.js | 10 +++--- packages/auth/src/security/sessions.js | 24 ++++++++++---- .../src/api/controllers/admin/sessions.js | 33 +++++++++++++++++++ .../worker/src/api/controllers/admin/users.js | 4 +++ .../worker/src/api/routes/admin/sessions.js | 14 ++++++++ packages/worker/src/api/routes/index.js | 2 ++ 8 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 packages/worker/src/api/controllers/admin/sessions.js create mode 100644 packages/worker/src/api/routes/admin/sessions.js diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index 9afbb33489..db1fdfacd9 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -68,7 +68,7 @@ module.exports = (noAuthPatterns = [], opts) => { clearCookie(ctx, Cookies.Auth) } else { // make sure we denote that the session is still in use - await updateSessionTTL(userId, sessionId) + await updateSessionTTL(session) } } const apiKey = ctx.request.headers["x-budibase-api-key"] diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index c3ef155f1b..ab3ae0bb1a 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -62,14 +62,12 @@ async function authenticate(token, tokenSecret, profile, done) { // authenticate const sessionId = newid() - const payload = { + await createASession(dbUser._id, sessionId) + + dbUser.token = jwt.sign({ userId: dbUser._id, sessionId, - } - await createASession(dbUser._id, sessionId, payload) - dbUser.sessionId = sessionId - - dbUser.token = jwt.sign(payload, env.JWT_SECRET) + }, env.JWT_SECRET) return done(null, dbUser) } diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 5ede7f91a1..a3968f9da6 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -34,14 +34,12 @@ exports.authenticate = async function (email, password, done) { // authenticate if (await compare(password, dbUser.password)) { const sessionId = newid() - const payload = { + await createASession(dbUser._id, sessionId) + + dbUser.token = jwt.sign({ userId: dbUser._id, sessionId, - } - await createASession(dbUser._id, sessionId, payload) - dbUser.sessionId = sessionId - - dbUser.token = jwt.sign(payload, env.JWT_SECRET) + }, env.JWT_SECRET) // Remove users password in payload delete dbUser.password diff --git a/packages/auth/src/security/sessions.js b/packages/auth/src/security/sessions.js index 83af6c723c..353b53871a 100644 --- a/packages/auth/src/security/sessions.js +++ b/packages/auth/src/security/sessions.js @@ -4,16 +4,23 @@ const EXPIRY_SECONDS = 86400 async function getSessionsForUser(userId) { const client = await redis.getSessionClient() - return client.scan(userId) + const sessions = await client.scan(userId) + return sessions.map(session => session.value) } function makeSessionID(userId, sessionId) { return `${userId}/${sessionId}` } -exports.createASession = async (userId, sessionId, token) => { +exports.createASession = async (userId, sessionId) => { const client = await redis.getSessionClient() - await client.store(makeSessionID(userId, sessionId), token, EXPIRY_SECONDS) + const session = { + createdAt: (new Date()).toISOString(), + lastAccessedAt: (new Date()).toISOString(), + sessionId, + userId, + } + await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS) } exports.invalidateSessions = async (userId, sessionId = null) => { @@ -31,9 +38,11 @@ exports.invalidateSessions = async (userId, sessionId = null) => { await Promise.all(promises) } -exports.updateSessionTTL = async (userId, sessionId) => { +exports.updateSessionTTL = async session => { const client = await redis.getSessionClient() - await client.setExpiry(makeSessionID(userId, sessionId), EXPIRY_SECONDS) + const key = makeSessionID(session.userId, session.sessionId) + session.lastAccessedAt = (new Date()).toISOString() + await client.store(key, session, EXPIRY_SECONDS) } exports.endSession = async (userId, sessionId) => { @@ -41,6 +50,8 @@ exports.endSession = async (userId, sessionId) => { await client.delete(makeSessionID(userId, sessionId)) } +exports.getUserSessions = getSessionsForUser + exports.getSession = async (userId, sessionId) => { try { const client = await redis.getSessionClient() @@ -53,5 +64,6 @@ exports.getSession = async (userId, sessionId) => { exports.getAllSessions = async () => { const client = await redis.getSessionClient() - return client.scan() + const sessions = await client.scan() + return sessions.map(session => session.value) } diff --git a/packages/worker/src/api/controllers/admin/sessions.js b/packages/worker/src/api/controllers/admin/sessions.js new file mode 100644 index 0000000000..ce6ccf47e2 --- /dev/null +++ b/packages/worker/src/api/controllers/admin/sessions.js @@ -0,0 +1,33 @@ +const { getAllSessions, getUserSessions, invalidateSessions } = require("@budibase/auth/sessions") + +exports.fetch = async ctx => { + ctx.body = await getAllSessions() +} + +exports.find = async ctx => { + const { userId } = ctx.params + const sessions = await getUserSessions(userId) + ctx.body = sessions.map(session => session.value) +} + +exports.invalidateUser = async ctx => { + const { userId } = ctx.params + await invalidateSessions(userId) + ctx.body = { + message: "User sessions invalidated" + } +} + +exports.selfSessions = async ctx => { + const userId = ctx.user._id + ctx.body = await getUserSessions(userId) +} + +exports.invalidateSession = async ctx => { + const userId = ctx.user._id + const { sessionId } = ctx.params + await invalidateSessions(userId, sessionId) + ctx.body = { + message: "Session invalidated successfully." + } +} diff --git a/packages/worker/src/api/controllers/admin/users.js b/packages/worker/src/api/controllers/admin/users.js index 5b299d9906..f524379266 100644 --- a/packages/worker/src/api/controllers/admin/users.js +++ b/packages/worker/src/api/controllers/admin/users.js @@ -122,13 +122,16 @@ exports.removeAppRole = async ctx => { const db = new CouchDB(GLOBAL_DB) const users = await allUsers() const bulk = [] + const cacheInvalidations = [] for (let user of users) { if (user.roles[appId]) { + cacheInvalidations.push(userCache.invalidateUser(user._id)) delete user.roles[appId] bulk.push(user) } } await db.bulkDocs(bulk) + await Promise.all(cacheInvalidations) ctx.body = { message: "App role removed from all users", } @@ -158,6 +161,7 @@ exports.updateSelf = async ctx => { ...user, ...ctx.request.body, }) + await userCache.invalidateUser(user._id) ctx.body = { _id: response.id, _rev: response.rev, diff --git a/packages/worker/src/api/routes/admin/sessions.js b/packages/worker/src/api/routes/admin/sessions.js new file mode 100644 index 0000000000..f7661e2b49 --- /dev/null +++ b/packages/worker/src/api/routes/admin/sessions.js @@ -0,0 +1,14 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/admin/sessions") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router + .get("/api/admin/sessions", adminOnly, controller.fetch) + .get("/api/admin/sessions/self", controller.selfSessions) + .get("/api/admin/sessions/:userId", adminOnly, controller.find) + .delete("/api/admin/sessions/:userId", adminOnly, controller.invalidateUser) + .delete("/api/admin/sessions/self/:sessionId", controller.invalidateSession) + +module.exports = router \ No newline at end of file diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index 8b232f7b7c..21ec324880 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -5,6 +5,7 @@ const templateRoutes = require("./admin/templates") const emailRoutes = require("./admin/email") const authRoutes = require("./admin/auth") const roleRoutes = require("./admin/roles") +const sessionRoutes = require("./admin/sessions") const appRoutes = require("./app") exports.routes = [ @@ -15,5 +16,6 @@ exports.routes = [ appRoutes, templateRoutes, emailRoutes, + sessionRoutes, roleRoutes, ] From 93302cb667821821040a2475b7477586e7182bb7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Jul 2021 23:30:14 +0100 Subject: [PATCH 6/7] Linting. --- packages/auth/src/middleware/passport/google.js | 11 +++++++---- packages/auth/src/middleware/passport/local.js | 11 +++++++---- packages/auth/src/security/sessions.js | 6 +++--- packages/worker/src/api/controllers/admin/sessions.js | 10 +++++++--- packages/worker/src/api/routes/admin/sessions.js | 2 +- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index ab3ae0bb1a..b357eb4903 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -64,10 +64,13 @@ async function authenticate(token, tokenSecret, profile, done) { const sessionId = newid() await createASession(dbUser._id, sessionId) - dbUser.token = jwt.sign({ - userId: dbUser._id, - sessionId, - }, env.JWT_SECRET) + dbUser.token = jwt.sign( + { + userId: dbUser._id, + sessionId, + }, + env.JWT_SECRET + ) return done(null, dbUser) } diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index a3968f9da6..90303cb95f 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -36,10 +36,13 @@ exports.authenticate = async function (email, password, done) { const sessionId = newid() await createASession(dbUser._id, sessionId) - dbUser.token = jwt.sign({ - userId: dbUser._id, - sessionId, - }, env.JWT_SECRET) + dbUser.token = jwt.sign( + { + userId: dbUser._id, + sessionId, + }, + env.JWT_SECRET + ) // Remove users password in payload delete dbUser.password diff --git a/packages/auth/src/security/sessions.js b/packages/auth/src/security/sessions.js index 353b53871a..4051df7123 100644 --- a/packages/auth/src/security/sessions.js +++ b/packages/auth/src/security/sessions.js @@ -15,8 +15,8 @@ function makeSessionID(userId, sessionId) { exports.createASession = async (userId, sessionId) => { const client = await redis.getSessionClient() const session = { - createdAt: (new Date()).toISOString(), - lastAccessedAt: (new Date()).toISOString(), + createdAt: new Date().toISOString(), + lastAccessedAt: new Date().toISOString(), sessionId, userId, } @@ -41,7 +41,7 @@ exports.invalidateSessions = async (userId, sessionId = null) => { exports.updateSessionTTL = async session => { const client = await redis.getSessionClient() const key = makeSessionID(session.userId, session.sessionId) - session.lastAccessedAt = (new Date()).toISOString() + session.lastAccessedAt = new Date().toISOString() await client.store(key, session, EXPIRY_SECONDS) } diff --git a/packages/worker/src/api/controllers/admin/sessions.js b/packages/worker/src/api/controllers/admin/sessions.js index ce6ccf47e2..170e97d690 100644 --- a/packages/worker/src/api/controllers/admin/sessions.js +++ b/packages/worker/src/api/controllers/admin/sessions.js @@ -1,4 +1,8 @@ -const { getAllSessions, getUserSessions, invalidateSessions } = require("@budibase/auth/sessions") +const { + getAllSessions, + getUserSessions, + invalidateSessions, +} = require("@budibase/auth/sessions") exports.fetch = async ctx => { ctx.body = await getAllSessions() @@ -14,7 +18,7 @@ exports.invalidateUser = async ctx => { const { userId } = ctx.params await invalidateSessions(userId) ctx.body = { - message: "User sessions invalidated" + message: "User sessions invalidated", } } @@ -28,6 +32,6 @@ exports.invalidateSession = async ctx => { const { sessionId } = ctx.params await invalidateSessions(userId, sessionId) ctx.body = { - message: "Session invalidated successfully." + message: "Session invalidated successfully.", } } diff --git a/packages/worker/src/api/routes/admin/sessions.js b/packages/worker/src/api/routes/admin/sessions.js index f7661e2b49..9cf5f58f8b 100644 --- a/packages/worker/src/api/routes/admin/sessions.js +++ b/packages/worker/src/api/routes/admin/sessions.js @@ -11,4 +11,4 @@ router .delete("/api/admin/sessions/:userId", adminOnly, controller.invalidateUser) .delete("/api/admin/sessions/self/:sessionId", controller.invalidateSession) -module.exports = router \ No newline at end of file +module.exports = router From b0fb7ae991c13e0f61199db5d5e14cb74967763a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 8 Jul 2021 00:30:55 +0100 Subject: [PATCH 7/7] Fixing test cases. --- .../src/api/routes/tests/routing.spec.js | 1 - packages/server/src/middleware/currentapp.js | 6 ++++- .../src/middleware/tests/currentapp.spec.js | 9 +++++++ .../src/tests/utilities/TestConfiguration.js | 25 +++++++++++-------- .../tests/utilities/TestConfiguration.js | 3 +++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js index 96ec492dbc..81f56a939d 100644 --- a/packages/server/src/api/routes/tests/routing.spec.js +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -2,7 +2,6 @@ const setup = require("./utilities") const { basicScreen } = setup.structures const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") -const workerRequests = require("../../../utilities/workerRequests") const route = "/test" diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 5eab32a804..1953eef63a 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -56,7 +56,11 @@ module.exports = async (ctx, next) => { role: await getRole(appId, roleId), } } - if (requestAppId !== appId) { + if ( + requestAppId !== appId || + appCookie == null || + appCookie.appId !== requestAppId + ) { setCookie(ctx, { appId }, Cookies.CurrentApp) } return next() diff --git a/packages/server/src/middleware/tests/currentapp.spec.js b/packages/server/src/middleware/tests/currentapp.spec.js index 2b4a815542..7bdaa929d1 100644 --- a/packages/server/src/middleware/tests/currentapp.spec.js +++ b/packages/server/src/middleware/tests/currentapp.spec.js @@ -23,6 +23,15 @@ function mockReset() { function mockAuthWithNoCookie() { jest.resetModules() mockWorker() + jest.mock("@budibase/auth/cache", () => ({ + user: { + getUser: () => { + return { + _id: "us_uuid1", + } + }, + }, + })) jest.mock("@budibase/auth", () => ({ utils: { getAppId: jest.fn(), diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index 83786e0155..a69ea35385 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -17,6 +17,8 @@ const { cleanup } = require("../../utilities/fileSystem") const { Cookies } = require("@budibase/auth").constants const { jwt } = require("@budibase/auth").auth const { StaticDatabases } = require("@budibase/auth/db") +const { createASession } = require("@budibase/auth/sessions") +const { user: userCache } = require("@budibase/auth/cache") const CouchDB = require("../../db") const GLOBAL_USER_ID = "us_uuid1" @@ -62,7 +64,7 @@ class TestConfiguration { return request.body } - async globalUser(id = GLOBAL_USER_ID, builder = true) { + async globalUser(id = GLOBAL_USER_ID, builder = true, roles) { const db = new CouchDB(StaticDatabases.GLOBAL.name) let existing try { @@ -73,8 +75,9 @@ class TestConfiguration { const user = { _id: id, ...existing, - roles: {}, + roles: roles || {}, } + await createASession(id, "sessionid") if (builder) { user.builder = { global: true } } @@ -103,6 +106,7 @@ class TestConfiguration { defaultHeaders() { const auth = { userId: GLOBAL_USER_ID, + sessionId: "sessionid", } const app = { roleId: BUILTIN_ROLE_IDS.ADMIN, @@ -138,13 +142,7 @@ class TestConfiguration { roleId = BUILTIN_ROLE_IDS.ADMIN, builder = false, }) { - let user - try { - user = await this.createUser(email, PASSWORD, roleId) - } catch (err) { - // allow errors here - } - return this.login(email, PASSWORD, { roleId, userId: user._id, builder }) + return this.login(email, PASSWORD, { roleId, builder }) } async createApp(appName) { @@ -313,6 +311,7 @@ class TestConfiguration { async createUser(id = null) { const globalId = !id ? `us_${Math.random()}` : `us_${id}` const resp = await this.globalUser(globalId) + await userCache.invalidateUser(globalId) return { ...resp, globalId, @@ -326,14 +325,19 @@ class TestConfiguration { } // make sure the user exists in the global DB if (roleId !== BUILTIN_ROLE_IDS.PUBLIC) { - await this.globalUser(userId, builder) + const appId = `app${this.getAppId().split("app_dev")[1]}` + await this.globalUser(userId, builder, { + [appId]: roleId, + }) } if (!email || !password) { await this.createUser() } + await createASession(userId, "sessionid") // have to fake this const auth = { userId, + sessionId: "sessionid", } const app = { roleId: roleId, @@ -343,6 +347,7 @@ class TestConfiguration { const appToken = jwt.sign(app, env.JWT_SECRET) // returning necessary request headers + await userCache.invalidateUser(userId) return { Accept: "application/json", Cookie: [ diff --git a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js index 746a26c800..c205a45e38 100644 --- a/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/worker/src/api/routes/tests/utilities/TestConfiguration.js @@ -5,6 +5,7 @@ const { jwt } = require("@budibase/auth").auth const { Cookies } = require("@budibase/auth").constants const { Configs, LOGO_URL } = require("../../../../constants") const { getGlobalUserByEmail } = require("@budibase/auth").utils +const { createASession } = require("@budibase/auth/sessions") class TestConfiguration { constructor(openServer = true) { @@ -56,6 +57,7 @@ class TestConfiguration { null, controllers.users.save ) + await createASession("us_uuid1", "sessionid") } } @@ -69,6 +71,7 @@ class TestConfiguration { const user = { _id: "us_uuid1", userId: "us_uuid1", + sessionId: "sessionid", } const authToken = jwt.sign(user, env.JWT_SECRET) return {