Merge branch 'feature/global-user-management' of github.com:Budibase/budibase into feature/global-user-management
This commit is contained in:
commit
81d7d1f4c6
15 changed files with 168 additions and 25 deletions
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -12,7 +12,11 @@
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
|
if (json.success) {
|
||||||
notifier.success("Logged in successfully.")
|
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}`)
|
||||||
|
|
|
@ -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`)
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
124
packages/server/src/middleware/tests/authenticated.spec.js
Normal file
124
packages/server/src/middleware/tests/authenticated.spec.js
Normal 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")
|
||||||
|
})
|
||||||
|
})
|
|
@ -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 => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue