Some work towards implementing the current app cookie, removing some old dead code and re-working some of the different middlewares involved.
This commit is contained in:
parent
0e583eb185
commit
eaad867780
|
@ -22,6 +22,10 @@ exports.generateUserID = email => {
|
|||
return `${DocumentTypes.USER}${SEPARATOR}${email}`
|
||||
}
|
||||
|
||||
exports.getEmailFromUserID = userId => {
|
||||
return userId.split(`${DocumentTypes.USER}${SEPARATOR}`)[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
|
|
|
@ -7,7 +7,7 @@ const { StaticDatabases } = require("./db/utils")
|
|||
const { jwt, local, google, authenticated } = require("./middleware")
|
||||
const { Cookies, UserStatus } = require("./constants")
|
||||
const { hash, compare } = require("./hashing")
|
||||
const { getAppId, setCookie } = require("./utils")
|
||||
const { getAppId, setCookie, getCookie } = require("./utils")
|
||||
const {
|
||||
generateUserID,
|
||||
getUserParams,
|
||||
|
@ -45,5 +45,6 @@ module.exports = {
|
|||
compare,
|
||||
getAppId,
|
||||
setCookie,
|
||||
getCookie,
|
||||
authenticated,
|
||||
}
|
||||
|
|
|
@ -1,57 +1,21 @@
|
|||
const CouchDB = require("../db")
|
||||
const { Cookies } = require("../constants")
|
||||
const { getAppId, setCookie, getCookie } = require("../utils")
|
||||
const { StaticDatabases } = require("../db/utils")
|
||||
|
||||
async function setCurrentAppContext(ctx) {
|
||||
let role = "PUBLIC"
|
||||
|
||||
// Current app cookie
|
||||
let appId = getAppId(ctx)
|
||||
if (!appId) {
|
||||
ctx.user = {
|
||||
role,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
console.log("THE APP ID", appId)
|
||||
|
||||
const currentAppCookie = getCookie(ctx, Cookies.CurrentApp, { decrypt: true })
|
||||
const appIdChanged = appId && currentAppCookie.appId !== appId
|
||||
if (appIdChanged) {
|
||||
try {
|
||||
// get roles for user from global DB
|
||||
const db = new CouchDB(StaticDatabases.USER)
|
||||
const user = await db.get(ctx.user)
|
||||
role = user.roles[appId]
|
||||
} catch (err) {
|
||||
// no user exists
|
||||
}
|
||||
} else if (currentAppCookie.appId) {
|
||||
appId = currentAppCookie.appId
|
||||
}
|
||||
setCookie(ctx, { appId, role }, Cookies.CurrentApp, { encrypt: true })
|
||||
return appId
|
||||
}
|
||||
const { getCookie } = require("../utils")
|
||||
const { getEmailFromUserID } = require("../db/utils")
|
||||
|
||||
module.exports = async (ctx, next) => {
|
||||
try {
|
||||
// check the actual user is authenticated first
|
||||
const authCookie = getCookie(ctx, Cookies.Auth, { decrypt: true })
|
||||
const authCookie = getCookie(ctx, Cookies.Auth)
|
||||
|
||||
if (authCookie) {
|
||||
ctx.isAuthenticated = true
|
||||
ctx.user = authCookie._id
|
||||
ctx.user = authCookie
|
||||
// make sure email is correct from ID
|
||||
ctx.user.email = getEmailFromUserID(authCookie._id)
|
||||
}
|
||||
|
||||
ctx.appId = await setCurrentAppContext(ctx)
|
||||
|
||||
console.log("CONTEXT", ctx)
|
||||
|
||||
await next()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
ctx.throw(err.status || 403, err.text)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
const { Cookies } = require("../../constants")
|
||||
|
||||
exports.options = {
|
||||
jwtFromRequest: function(ctx) {
|
||||
return ctx.cookies.get(Cookies.Auth)
|
||||
},
|
||||
secretOrKey: process.env.JWT_SECRET,
|
||||
}
|
||||
|
||||
|
|
|
@ -45,17 +45,15 @@ exports.getAppId = ctx => {
|
|||
* Get a cookie from context, and decrypt if necessary.
|
||||
* @param {object} ctx The request which is to be manipulated.
|
||||
* @param {string} name The name of the cookie to get.
|
||||
* @param {object} options options .
|
||||
*/
|
||||
exports.getCookie = (ctx, value, options = {}) => {
|
||||
const cookie = ctx.cookies.get(value)
|
||||
exports.getCookie = (ctx, name) => {
|
||||
const cookie = ctx.cookies.get(name)
|
||||
|
||||
if (!cookie) return
|
||||
if (!cookie) {
|
||||
return cookie
|
||||
}
|
||||
|
||||
if (!options.decrypt) return cookie
|
||||
|
||||
const payload = jwt.verify(cookie, process.env.JWT_SECRET)
|
||||
return payload
|
||||
return jwt.verify(cookie, options.secretOrKey)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,11 +69,9 @@ exports.setCookie = (ctx, value, name = "builder") => {
|
|||
if (!value) {
|
||||
ctx.cookies.set(name)
|
||||
} else {
|
||||
if (options.encrypt) {
|
||||
value = jwt.sign(value, process.env.JWT_SECRET, {
|
||||
expiresIn: "1 day",
|
||||
})
|
||||
}
|
||||
value = jwt.sign(value, options.secretOrKey, {
|
||||
expiresIn: "1 day",
|
||||
})
|
||||
ctx.cookies.set(name, value, {
|
||||
expires,
|
||||
path: "/",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const Router = require("@koa/router")
|
||||
const { authenticated } = require("@budibase/auth")
|
||||
const currentApp = require("../middleware/currentapp")
|
||||
const compress = require("koa-compress")
|
||||
const zlib = require("zlib")
|
||||
const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
|
||||
|
@ -31,6 +32,7 @@ router
|
|||
.use("/health", ctx => (ctx.status = 200))
|
||||
.use("/version", ctx => (ctx.body = pkg.version))
|
||||
.use(authenticated)
|
||||
.use(currentApp)
|
||||
|
||||
// error handling middleware
|
||||
router.use(async (ctx, next) => {
|
||||
|
|
|
@ -1,52 +1,37 @@
|
|||
const {
|
||||
BUILTIN_ROLE_IDS,
|
||||
getUserPermissions,
|
||||
} = require("../utilities/security/roles")
|
||||
const { getUserPermissions } = require("../utilities/security/roles")
|
||||
const {
|
||||
PermissionTypes,
|
||||
doesHaveResourcePermission,
|
||||
doesHaveBasePermission,
|
||||
} = require("../utilities/security/permissions")
|
||||
const env = require("../environment")
|
||||
const { isAPIKeyValid } = require("../utilities/security/apikey")
|
||||
const { AuthTypes } = require("../constants")
|
||||
|
||||
const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER]
|
||||
|
||||
function hasResource(ctx) {
|
||||
return ctx.resourceId != null
|
||||
}
|
||||
|
||||
module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
||||
if (env.isProd() && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) {
|
||||
// api key header passed by external webhook
|
||||
if (await isAPIKeyValid(ctx.headers["x-api-key"])) {
|
||||
ctx.auth = {
|
||||
authenticated: AuthTypes.EXTERNAL,
|
||||
apiKey: ctx.headers["x-api-key"],
|
||||
}
|
||||
ctx.user = {
|
||||
appId: ctx.headers["x-instanceid"],
|
||||
}
|
||||
return next()
|
||||
}
|
||||
const WEBHOOK_ENDPOINTS = new RegExp(
|
||||
["webhooks/trigger", "webhooks/schema"].join("|")
|
||||
)
|
||||
|
||||
return ctx.throw(403, "API key invalid")
|
||||
module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
||||
// webhooks don't need authentication, each webhook unique
|
||||
if (WEBHOOK_ENDPOINTS.test(ctx.request.url)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
if (!ctx.user) {
|
||||
return ctx.throw(403, "No user info found")
|
||||
}
|
||||
|
||||
const role = ctx.user.role
|
||||
const isAdmin = ADMIN_ROLES.includes(role._id)
|
||||
const isAuthed = ctx.isAuthenticated
|
||||
|
||||
const { basePermissions, permissions } = await getUserPermissions(
|
||||
ctx.appId,
|
||||
role._id
|
||||
ctx.roleId
|
||||
)
|
||||
|
||||
// TODO: need to determine if the user has permission to build here, global cookie
|
||||
|
||||
// this may need to change in the future, right now only admins
|
||||
// can have access to builder features, this is hard coded into
|
||||
// our rules
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
const { getAppId, setCookie, getCookie, Cookies } = require("@budibase/auth")
|
||||
const { getGlobalUsers } = require("../utilities/workerRequests")
|
||||
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles")
|
||||
|
||||
function CurrentAppCookie(appId, roleId) {
|
||||
this.appId = appId
|
||||
this.roleId = roleId
|
||||
}
|
||||
|
||||
function finish(ctx, next, { appId, roleId, cookie = false }) {
|
||||
if (appId) {
|
||||
ctx.appId = appId
|
||||
}
|
||||
if (roleId) {
|
||||
ctx.roleId = roleId
|
||||
}
|
||||
if (cookie && appId) {
|
||||
setCookie(ctx, new CurrentAppCookie(appId, roleId))
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
module.exports = async (ctx, next) => {
|
||||
// try to get the appID from the request
|
||||
const requestAppId = getAppId(ctx)
|
||||
// get app cookie if it exists
|
||||
const appCookie = getCookie(ctx, Cookies.CurrentApp)
|
||||
if (!appCookie && !requestAppId) {
|
||||
return next()
|
||||
}
|
||||
|
||||
let updateCookie = false,
|
||||
appId,
|
||||
roleId
|
||||
if (!ctx.user) {
|
||||
// not logged in, try to set a cookie for public apps
|
||||
updateCookie = true
|
||||
appId = requestAppId
|
||||
roleId = BUILTIN_ROLE_IDS.PUBLIC
|
||||
} else if (
|
||||
requestAppId != null &&
|
||||
(appCookie == null || requestAppId === appCookie.appId)
|
||||
) {
|
||||
const globalUser = await getGlobalUsers(ctx, requestAppId, ctx.user.email)
|
||||
updateCookie = true
|
||||
appId = requestAppId
|
||||
roleId = globalUser.roles[requestAppId] || BUILTIN_ROLE_IDS.PUBLIC
|
||||
} else if (requestAppId == null && appCookie != null) {
|
||||
appId = appCookie.appId
|
||||
roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC
|
||||
}
|
||||
return finish(ctx, next, {
|
||||
appId: appId,
|
||||
roleId: roleId,
|
||||
cookie: updateCookie,
|
||||
})
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
const authorizedMiddleware = require("../authorized")
|
||||
const env = require("../../environment")
|
||||
const apiKey = require("../../utilities/security/apikey")
|
||||
const { AuthTypes } = require("../../constants")
|
||||
const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions")
|
||||
jest.mock("../../environment", () => ({
|
||||
|
@ -12,7 +11,6 @@ jest.mock("../../environment", () => ({
|
|||
}
|
||||
})
|
||||
)
|
||||
jest.mock("../../utilities/security/apikey")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(role) {
|
||||
|
@ -92,29 +90,6 @@ describe("Authorization middleware", () => {
|
|||
"x-instanceid": "instance123",
|
||||
})
|
||||
})
|
||||
|
||||
it("passes to next() if api key is valid", async () => {
|
||||
apiKey.isAPIKeyValid.mockResolvedValueOnce(true)
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
expect(config.ctx.auth).toEqual({
|
||||
authenticated: AuthTypes.EXTERNAL,
|
||||
apiKey: config.ctx.headers["x-api-key"],
|
||||
})
|
||||
expect(config.ctx.user).toEqual({
|
||||
appId: config.ctx.headers["x-instanceid"],
|
||||
})
|
||||
})
|
||||
|
||||
it("throws if api key is invalid", async () => {
|
||||
apiKey.isAPIKeyValid.mockResolvedValueOnce(false)
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "API key invalid")
|
||||
})
|
||||
})
|
||||
|
||||
describe("non-webhook call", () => {
|
||||
|
|
|
@ -49,12 +49,11 @@ exports.getAppId = ctx => {
|
|||
|
||||
/**
|
||||
* Get the name of the cookie which is to be updated/retrieved
|
||||
* @param {string|undefined|null} name OPTIONAL can specify the specific app if previewing etc
|
||||
* @returns {string} The name of the token trying to find
|
||||
* @param {string} name The name/type of cookie.
|
||||
* @returns {string} The full name of the cookie to retrieve/update.
|
||||
*/
|
||||
exports.getCookieName = (name = "builder") => {
|
||||
let environment = env.isProd() ? "cloud" : "local"
|
||||
return `budibase:${name}:${environment}`
|
||||
exports.getCookieName = name => {
|
||||
return `budibase:${name}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
const { apiKeyTable } = require("../../db/dynamoClient")
|
||||
const env = require("../../environment")
|
||||
|
||||
/**
|
||||
* This file purely exists so that we can centralise all logic pertaining to API keys, as their usage differs
|
||||
* in our Cloud environment versus self hosted.
|
||||
*/
|
||||
|
||||
exports.isAPIKeyValid = async apiKeyId => {
|
||||
if (!env.SELF_HOSTED) {
|
||||
let apiKeyInfo = await apiKeyTable.get({
|
||||
primary: apiKeyId,
|
||||
})
|
||||
return apiKeyInfo != null
|
||||
} else {
|
||||
// if the api key supplied is correct then return structure similar
|
||||
return apiKeyId === env.HOSTING_KEY
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue