Adding sessions API.

This commit is contained in:
mike12345567 2021-07-07 23:29:19 +01:00
parent 278d984006
commit cc67e2caa6
8 changed files with 80 additions and 19 deletions

View File

@ -68,7 +68,7 @@ module.exports = (noAuthPatterns = [], opts) => {
clearCookie(ctx, Cookies.Auth) clearCookie(ctx, Cookies.Auth)
} else { } else {
// make sure we denote that the session is still in use // 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"] const apiKey = ctx.request.headers["x-budibase-api-key"]

View File

@ -62,14 +62,12 @@ async function authenticate(token, tokenSecret, profile, done) {
// authenticate // authenticate
const sessionId = newid() const sessionId = newid()
const payload = { await createASession(dbUser._id, sessionId)
dbUser.token = jwt.sign({
userId: dbUser._id, userId: dbUser._id,
sessionId, sessionId,
} }, env.JWT_SECRET)
await createASession(dbUser._id, sessionId, payload)
dbUser.sessionId = sessionId
dbUser.token = jwt.sign(payload, env.JWT_SECRET)
return done(null, dbUser) return done(null, dbUser)
} }

View File

@ -34,14 +34,12 @@ exports.authenticate = async function (email, password, done) {
// authenticate // authenticate
if (await compare(password, dbUser.password)) { if (await compare(password, dbUser.password)) {
const sessionId = newid() const sessionId = newid()
const payload = { await createASession(dbUser._id, sessionId)
dbUser.token = jwt.sign({
userId: dbUser._id, userId: dbUser._id,
sessionId, sessionId,
} }, env.JWT_SECRET)
await createASession(dbUser._id, sessionId, payload)
dbUser.sessionId = sessionId
dbUser.token = jwt.sign(payload, env.JWT_SECRET)
// Remove users password in payload // Remove users password in payload
delete dbUser.password delete dbUser.password

View File

@ -4,16 +4,23 @@ const EXPIRY_SECONDS = 86400
async function getSessionsForUser(userId) { async function getSessionsForUser(userId) {
const client = await redis.getSessionClient() 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) { function makeSessionID(userId, sessionId) {
return `${userId}/${sessionId}` return `${userId}/${sessionId}`
} }
exports.createASession = async (userId, sessionId, token) => { exports.createASession = async (userId, sessionId) => {
const client = await redis.getSessionClient() 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) => { exports.invalidateSessions = async (userId, sessionId = null) => {
@ -31,9 +38,11 @@ exports.invalidateSessions = async (userId, sessionId = null) => {
await Promise.all(promises) await Promise.all(promises)
} }
exports.updateSessionTTL = async (userId, sessionId) => { exports.updateSessionTTL = async session => {
const client = await redis.getSessionClient() 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) => { exports.endSession = async (userId, sessionId) => {
@ -41,6 +50,8 @@ exports.endSession = async (userId, sessionId) => {
await client.delete(makeSessionID(userId, sessionId)) await client.delete(makeSessionID(userId, sessionId))
} }
exports.getUserSessions = getSessionsForUser
exports.getSession = async (userId, sessionId) => { exports.getSession = async (userId, sessionId) => {
try { try {
const client = await redis.getSessionClient() const client = await redis.getSessionClient()
@ -53,5 +64,6 @@ exports.getSession = async (userId, sessionId) => {
exports.getAllSessions = async () => { exports.getAllSessions = async () => {
const client = await redis.getSessionClient() const client = await redis.getSessionClient()
return client.scan() const sessions = await client.scan()
return sessions.map(session => session.value)
} }

View File

@ -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."
}
}

View File

@ -122,13 +122,16 @@ exports.removeAppRole = async ctx => {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
const users = await allUsers() const users = await allUsers()
const bulk = [] const bulk = []
const cacheInvalidations = []
for (let user of users) { for (let user of users) {
if (user.roles[appId]) { if (user.roles[appId]) {
cacheInvalidations.push(userCache.invalidateUser(user._id))
delete user.roles[appId] delete user.roles[appId]
bulk.push(user) bulk.push(user)
} }
} }
await db.bulkDocs(bulk) await db.bulkDocs(bulk)
await Promise.all(cacheInvalidations)
ctx.body = { ctx.body = {
message: "App role removed from all users", message: "App role removed from all users",
} }
@ -158,6 +161,7 @@ exports.updateSelf = async ctx => {
...user, ...user,
...ctx.request.body, ...ctx.request.body,
}) })
await userCache.invalidateUser(user._id)
ctx.body = { ctx.body = {
_id: response.id, _id: response.id,
_rev: response.rev, _rev: response.rev,

View File

@ -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

View File

@ -5,6 +5,7 @@ const templateRoutes = require("./admin/templates")
const emailRoutes = require("./admin/email") const emailRoutes = require("./admin/email")
const authRoutes = require("./admin/auth") const authRoutes = require("./admin/auth")
const roleRoutes = require("./admin/roles") const roleRoutes = require("./admin/roles")
const sessionRoutes = require("./admin/sessions")
const appRoutes = require("./app") const appRoutes = require("./app")
exports.routes = [ exports.routes = [
@ -15,5 +16,6 @@ exports.routes = [
appRoutes, appRoutes,
templateRoutes, templateRoutes,
emailRoutes, emailRoutes,
sessionRoutes,
roleRoutes, roleRoutes,
] ]