Merge branch 'feature/global-user-management' of github.com:Budibase/budibase into feature/global-user-management

This commit is contained in:
mike12345567 2021-04-13 13:35:21 +01:00
commit 81d7d1f4c6
15 changed files with 168 additions and 25 deletions

View File

@ -6,6 +6,7 @@ exports.StaticDatabases = {
const DocumentTypes = { const DocumentTypes = {
USER: "us", USER: "us",
APP: "app",
} }
exports.DocumentTypes = DocumentTypes exports.DocumentTypes = DocumentTypes
@ -13,6 +14,8 @@ exports.DocumentTypes = DocumentTypes
const UNICODE_MAX = "\ufff0" const UNICODE_MAX = "\ufff0"
const SEPARATOR = "_" const SEPARATOR = "_"
exports.SEPARATOR = SEPARATOR
/** /**
* Generates a new user ID based on the passed in email. * Generates a new user ID based on the passed in email.
* @param {string} email The email which the ID is going to be built up of. * @param {string} email The email which the ID is going to be built up of.

View File

@ -11,11 +11,11 @@ module.exports = async (ctx, next) => {
ctx.isAuthenticated = true ctx.isAuthenticated = true
ctx.user = authCookie ctx.user = authCookie
// make sure email is correct from ID // make sure email is correct from ID
ctx.user.email = getEmailFromUserID(authCookie._id) ctx.user.email = getEmailFromUserID(authCookie.userId)
} }
await next() await next()
} catch (err) { } catch (err) {
ctx.throw(err.status || 403, err.text) ctx.throw(err.status || 403, err)
} }
} }

View File

@ -1,5 +1,10 @@
const { Cookies } = require("../../constants")
exports.options = { exports.options = {
secretOrKey: process.env.JWT_SECRET, secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: function(ctx) {
return ctx.cookies.get(Cookies.Auth)
},
} }
exports.authenticate = async function(jwt, done) { exports.authenticate = async function(jwt, done) {

View File

@ -38,7 +38,7 @@ exports.authenticate = async function(username, password, done) {
// authenticate // authenticate
if (await compare(password, dbUser.password)) { if (await compare(password, dbUser.password)) {
const payload = { const payload = {
_id: dbUser._id, userId: dbUser._id,
} }
const token = jwt.sign(payload, process.env.JWT_SECRET, { const token = jwt.sign(payload, process.env.JWT_SECRET, {

View File

@ -12,7 +12,11 @@
username, username,
password, password,
}) })
notifier.success("Logged in successfully.") if (json.success) {
notifier.success("Logged in successfully.")
} else {
notifier.danger("Invalid credentials")
}
} catch (err) { } catch (err) {
console.error(err) console.error(err)
notifier.danger(`Error logging in: ${err}`) notifier.danger(`Error logging in: ${err}`)

View File

@ -4,13 +4,17 @@ import api from "../../builderStore/api"
async function checkAuth() { async function checkAuth() {
const response = await api.get("/api/self") const response = await api.get("/api/self")
const user = await response.json() const user = await response.json()
if (json) return json if (response.status === 200) return user
return null
} }
export function createAuthStore() { export function createAuthStore() {
const { subscribe, set } = writable({}) const { subscribe, set } = writable({})
checkAuth().then(user => set({ user })) checkAuth()
.then(user => set({ user }))
.catch(err => set({ user: null }))
return { return {
subscribe, subscribe,
@ -21,6 +25,7 @@ export function createAuthStore() {
localStorage.setItem("auth:user", JSON.stringify(json.user)) localStorage.setItem("auth:user", JSON.stringify(json.user))
set({ user: json.user }) set({ user: json.user })
} }
return json
}, },
logout: async () => { logout: async () => {
const response = await api.post(`/api/auth/logout`) const response = await api.post(`/api/auth/logout`)

View File

@ -145,7 +145,7 @@ exports.fetchAppPackage = async function(ctx) {
layouts, layouts,
clientLibPath: clientLibraryPath(ctx.params.appId), clientLibPath: clientLibraryPath(ctx.params.appId),
} }
await setBuilderToken(ctx, ctx.params.appId, application.version) // await setBuilderToken(ctx, ctx.params.appId, application.version)
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
@ -184,7 +184,7 @@ exports.create = async function(ctx) {
await createApp(appId) await createApp(appId)
} }
await setBuilderToken(ctx, appId, version) // await setBuilderToken(ctx, appId, version)
ctx.status = 200 ctx.status = 200
ctx.body = newApplication ctx.body = newApplication
ctx.message = `Application ${ctx.request.body.name} created successfully` ctx.message = `Application ${ctx.request.body.name} created successfully`

View File

@ -7,7 +7,7 @@ const { generateUserMetadataID } = require("../../db/utils")
const { setCookie } = require("../../utilities") const { setCookie } = require("../../utilities")
const { outputProcessing } = require("../../utilities/rowProcessor") const { outputProcessing } = require("../../utilities/rowProcessor")
const { InternalTables } = require("../../db/utils") const { InternalTables } = require("../../db/utils")
const { UserStatus } = require("@budibase/auth") const { UserStatus, StaticDatabases } = require("@budibase/auth")
const { getFullUser } = require("../../utilities/users") const { getFullUser } = require("../../utilities/users")
const INVALID_ERR = "Invalid Credentials" const INVALID_ERR = "Invalid Credentials"
@ -73,10 +73,19 @@ exports.authenticate = async ctx => {
exports.fetchSelf = async ctx => { exports.fetchSelf = async ctx => {
const { userId, appId } = ctx.user const { userId, appId } = ctx.user
/* istanbul ignore next */ /* istanbul ignore next */
if (!userId || !appId) { if (!userId) {
ctx.body = {} ctx.body = {}
return return
} }
if (!appId) {
const db = new CouchDB(StaticDatabases.USER.name)
const user = await db.get(userId)
delete user.password
ctx.body = { user }
return
}
const db = new CouchDB(appId) const db = new CouchDB(appId)
const user = await getFullUser({ ctx, userId: userId }) const user = await getFullUser({ ctx, userId: userId })
const userTable = await db.get(InternalTables.USER_METADATA) const userTable = await db.get(InternalTables.USER_METADATA)

View File

@ -9,7 +9,6 @@ const { processString } = require("@budibase/string-templates")
const { budibaseTempDir } = require("../../../utilities/budibaseDir") const { budibaseTempDir } = require("../../../utilities/budibaseDir")
const { getDeployedApps } = require("../../../utilities/builder/hosting") const { getDeployedApps } = require("../../../utilities/builder/hosting")
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
const { const {
loadHandlebarsFile, loadHandlebarsFile,
NODE_MODULES_PATH, NODE_MODULES_PATH,
@ -35,9 +34,9 @@ const COMP_LIB_BASE_APP_VERSION = "0.2.5"
exports.serveBuilder = async function(ctx) { exports.serveBuilder = async function(ctx) {
let builderPath = resolve(TOP_LEVEL_PATH, "builder") let builderPath = resolve(TOP_LEVEL_PATH, "builder")
if (ctx.file === "index.html") { // if (ctx.file === "index.html") {
// await setBuilderToken(ctx) // // await setBuilderToken(ctx)
} // }
await send(ctx, ctx.file, { root: builderPath }) await send(ctx, ctx.file, { root: builderPath })
} }

View File

@ -4,7 +4,6 @@ const controller = require("../controllers/auth")
const router = Router() const router = Router()
// TODO: needs removed // TODO: needs removed
router.post("/api/authenticate", controller.authenticate)
router.get("/api/self", controller.fetchSelf) router.get("/api/self", controller.fetchSelf)
module.exports = router module.exports = router

View File

@ -2,11 +2,6 @@ const { getAppId, setCookie, getCookie, Cookies } = require("@budibase/auth")
const { getGlobalUsers } = require("../utilities/workerRequests") const { getGlobalUsers } = require("../utilities/workerRequests")
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles") 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 }) { function finish(ctx, next, { appId, roleId, cookie = false }) {
if (appId) { if (appId) {
ctx.appId = appId ctx.appId = appId
@ -15,7 +10,7 @@ function finish(ctx, next, { appId, roleId, cookie = false }) {
ctx.roleId = roleId ctx.roleId = roleId
} }
if (cookie && appId) { if (cookie && appId) {
setCookie(ctx, new CurrentAppCookie(appId, roleId)) setCookie(ctx, { appId, roleId }, Cookies.CurrentApp)
} }
return next() return next()
} }

View File

@ -0,0 +1,124 @@
const { AuthTypes } = require("../../constants")
const authenticatedMiddleware = require("../authenticated")
const jwt = require("jsonwebtoken")
jest.mock("jsonwebtoken")
class TestConfiguration {
constructor(middleware) {
this.middleware = authenticatedMiddleware
this.ctx = {
config: {},
auth: {},
cookies: {
set: jest.fn(),
get: jest.fn(),
},
headers: {},
params: {},
path: "",
request: {
headers: {},
},
throw: jest.fn(),
}
this.next = jest.fn()
}
setHeaders(headers) {
this.ctx.headers = headers
}
executeMiddleware() {
return this.middleware(this.ctx, this.next)
}
afterEach() {
jest.resetAllMocks()
}
}
describe("Authenticated middleware", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
afterEach(() => {
config.afterEach()
})
it("calls next() when on the builder path", async () => {
config.ctx.path = "/builder"
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("sets a new cookie when the current cookie does not match the app id from context", async () => {
const appId = "app_123"
config.setHeaders({
"x-budibase-app-id": appId,
})
config.ctx.cookies.get.mockImplementation(() => "cookieAppId")
await config.executeMiddleware()
expect(config.ctx.cookies.set).toHaveBeenCalledWith(
"budibase:currentapp",
appId,
expect.any(Object)
)
})
it("sets the correct BUILDER auth type information when the x-budibase-type header is not 'client'", async () => {
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local")
jwt.verify.mockImplementationOnce(() => ({
apiKey: "1234",
roleId: "BUILDER",
}))
await config.executeMiddleware()
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.BUILDER)
expect(config.ctx.user).toMatchSnapshot()
})
it("sets the correct APP auth type information when the user is not in the builder", async () => {
config.setHeaders({
"x-budibase-type": "client",
})
config.ctx.cookies.get.mockImplementation(() => `budibase:app:local`)
jwt.verify.mockImplementationOnce(() => ({
apiKey: "1234",
roleId: "ADMIN",
}))
await config.executeMiddleware()
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.APP)
expect(config.ctx.user).toMatchSnapshot()
})
it("marks the user as unauthenticated when a token cannot be determined from the users cookie", async () => {
config.executeMiddleware()
expect(config.ctx.auth.authenticated).toBe(false)
expect(config.ctx.user.role).toEqual({
_id: "PUBLIC",
name: "Public",
permissionId: "public",
})
})
it("clears the cookie when there is an error authenticating in the builder", async () => {
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local")
jwt.verify.mockImplementationOnce(() => {
throw new Error()
})
await config.executeMiddleware()
expect(config.ctx.cookies.set).toBeCalledWith("budibase:builder:local")
})
})

View File

@ -22,7 +22,7 @@ module.exports = async (ctx, appId, version) => {
// set the builder token // set the builder token
// setCookie(ctx, token, "builder") // setCookie(ctx, token, "builder")
setCookie(ctx, appId, "currentapp") // setCookie(ctx, appId, "currentapp")
// need to clear all app tokens or else unable to use the app in the builder // need to clear all app tokens or else unable to use the app in the builder
// let allDbNames = await CouchDB.allDbs() // let allDbNames = await CouchDB.allDbs()
// allDbNames.map(dbName => { // allDbNames.map(dbName => {

View File

@ -1,9 +1,9 @@
const CouchDB = require("../../db") const CouchDB = require("../db")
const { const {
generateUserMetadataID, generateUserMetadataID,
getEmailFromUserMetadataID, getEmailFromUserMetadataID,
} = require("../db/utils") } = require("../db/utils")
const { getGlobalUsers } = require("../../utilities/workerRequests") const { getGlobalUsers } = require("../utilities/workerRequests")
exports.getFullUser = async ({ ctx, email, userId }) => { exports.getFullUser = async ({ ctx, email, userId }) => {
if (!email) { if (!email) {

View File

@ -11,7 +11,7 @@ function getAppRole(appId, user) {
if (!user.roleId) { if (!user.roleId) {
user.roleId = BUILTIN_ROLE_IDS.PUBLIC user.roleId = BUILTIN_ROLE_IDS.PUBLIC
} }
delete user.roles // delete user.roles
return user return user
} }