diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index b928d809f9..043987fcc4 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -6,6 +6,7 @@ exports.StaticDatabases = { const DocumentTypes = { USER: "us", + APP: "app", } exports.DocumentTypes = DocumentTypes @@ -13,6 +14,8 @@ exports.DocumentTypes = DocumentTypes const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" +exports.SEPARATOR = SEPARATOR + /** * 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. diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index 5966da483e..af2c7d5575 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -11,11 +11,11 @@ module.exports = async (ctx, next) => { ctx.isAuthenticated = true ctx.user = authCookie // make sure email is correct from ID - ctx.user.email = getEmailFromUserID(authCookie._id) + ctx.user.email = getEmailFromUserID(authCookie.userId) } await next() } catch (err) { - ctx.throw(err.status || 403, err.text) + ctx.throw(err.status || 403, err) } } diff --git a/packages/auth/src/middleware/passport/jwt.js b/packages/auth/src/middleware/passport/jwt.js index 1d6a4e04e0..06071f77e8 100644 --- a/packages/auth/src/middleware/passport/jwt.js +++ b/packages/auth/src/middleware/passport/jwt.js @@ -1,5 +1,10 @@ +const { Cookies } = require("../../constants") + exports.options = { secretOrKey: process.env.JWT_SECRET, + jwtFromRequest: function(ctx) { + return ctx.cookies.get(Cookies.Auth) + }, } exports.authenticate = async function(jwt, done) { diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js index 379ec58dbb..5a2221499a 100644 --- a/packages/auth/src/middleware/passport/local.js +++ b/packages/auth/src/middleware/passport/local.js @@ -38,7 +38,7 @@ exports.authenticate = async function(username, password, done) { // authenticate if (await compare(password, dbUser.password)) { const payload = { - _id: dbUser._id, + userId: dbUser._id, } const token = jwt.sign(payload, process.env.JWT_SECRET, { diff --git a/packages/builder/src/components/login/LoginForm.svelte b/packages/builder/src/components/login/LoginForm.svelte index 5265d2e6d6..57ba75934c 100644 --- a/packages/builder/src/components/login/LoginForm.svelte +++ b/packages/builder/src/components/login/LoginForm.svelte @@ -12,7 +12,11 @@ username, password, }) - notifier.success("Logged in successfully.") + if (json.success) { + notifier.success("Logged in successfully.") + } else { + notifier.danger("Invalid credentials") + } } catch (err) { console.error(err) notifier.danger(`Error logging in: ${err}`) diff --git a/packages/builder/src/stores/backend/auth.js b/packages/builder/src/stores/backend/auth.js index e0a9496b94..d0a92237b7 100644 --- a/packages/builder/src/stores/backend/auth.js +++ b/packages/builder/src/stores/backend/auth.js @@ -4,13 +4,17 @@ import api from "../../builderStore/api" async function checkAuth() { const response = await api.get("/api/self") const user = await response.json() - if (json) return json + if (response.status === 200) return user + + return null } export function createAuthStore() { const { subscribe, set } = writable({}) - checkAuth().then(user => set({ user })) + checkAuth() + .then(user => set({ user })) + .catch(err => set({ user: null })) return { subscribe, @@ -21,6 +25,7 @@ export function createAuthStore() { localStorage.setItem("auth:user", JSON.stringify(json.user)) set({ user: json.user }) } + return json }, logout: async () => { const response = await api.post(`/api/auth/logout`) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 042474c5b6..fd1d7a6688 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -145,7 +145,7 @@ exports.fetchAppPackage = async function(ctx) { layouts, 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) { @@ -184,7 +184,7 @@ exports.create = async function(ctx) { await createApp(appId) } - await setBuilderToken(ctx, appId, version) + // await setBuilderToken(ctx, appId, version) ctx.status = 200 ctx.body = newApplication ctx.message = `Application ${ctx.request.body.name} created successfully` diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index 0cc8668687..f62afdf185 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -7,7 +7,7 @@ const { generateUserMetadataID } = require("../../db/utils") const { setCookie } = require("../../utilities") const { outputProcessing } = require("../../utilities/rowProcessor") const { InternalTables } = require("../../db/utils") -const { UserStatus } = require("@budibase/auth") +const { UserStatus, StaticDatabases } = require("@budibase/auth") const { getFullUser } = require("../../utilities/users") const INVALID_ERR = "Invalid Credentials" @@ -73,10 +73,19 @@ exports.authenticate = async ctx => { exports.fetchSelf = async ctx => { const { userId, appId } = ctx.user /* istanbul ignore next */ - if (!userId || !appId) { + if (!userId) { ctx.body = {} 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 user = await getFullUser({ ctx, userId: userId }) const userTable = await db.get(InternalTables.USER_METADATA) diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index a1edd4643a..dd60ee5985 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -9,7 +9,6 @@ const { processString } = require("@budibase/string-templates") const { budibaseTempDir } = require("../../../utilities/budibaseDir") const { getDeployedApps } = require("../../../utilities/builder/hosting") const CouchDB = require("../../../db") -const setBuilderToken = require("../../../utilities/builder/setBuilderToken") const { loadHandlebarsFile, NODE_MODULES_PATH, @@ -35,9 +34,9 @@ const COMP_LIB_BASE_APP_VERSION = "0.2.5" exports.serveBuilder = async function(ctx) { let builderPath = resolve(TOP_LEVEL_PATH, "builder") - if (ctx.file === "index.html") { - // await setBuilderToken(ctx) - } + // if (ctx.file === "index.html") { + // // await setBuilderToken(ctx) + // } await send(ctx, ctx.file, { root: builderPath }) } diff --git a/packages/server/src/api/routes/auth.js b/packages/server/src/api/routes/auth.js index 954130370b..b07627c29e 100644 --- a/packages/server/src/api/routes/auth.js +++ b/packages/server/src/api/routes/auth.js @@ -4,7 +4,6 @@ const controller = require("../controllers/auth") const router = Router() // TODO: needs removed -router.post("/api/authenticate", controller.authenticate) router.get("/api/self", controller.fetchSelf) module.exports = router diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index fe08c923f9..de614f3dce 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -2,11 +2,6 @@ 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 @@ -15,7 +10,7 @@ function finish(ctx, next, { appId, roleId, cookie = false }) { ctx.roleId = roleId } if (cookie && appId) { - setCookie(ctx, new CurrentAppCookie(appId, roleId)) + setCookie(ctx, { appId, roleId }, Cookies.CurrentApp) } return next() } diff --git a/packages/server/src/middleware/tests/authenticated.spec.js b/packages/server/src/middleware/tests/authenticated.spec.js new file mode 100644 index 0000000000..5e13206498 --- /dev/null +++ b/packages/server/src/middleware/tests/authenticated.spec.js @@ -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") + }) +}) \ No newline at end of file diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js index c8fc54cd12..663a962582 100644 --- a/packages/server/src/utilities/builder/setBuilderToken.js +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -22,7 +22,7 @@ module.exports = async (ctx, appId, version) => { // set the builder token // 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 // let allDbNames = await CouchDB.allDbs() // allDbNames.map(dbName => { diff --git a/packages/server/src/utilities/users.js b/packages/server/src/utilities/users.js index b41a0da9c7..f8246e4e91 100644 --- a/packages/server/src/utilities/users.js +++ b/packages/server/src/utilities/users.js @@ -1,9 +1,9 @@ -const CouchDB = require("../../db") +const CouchDB = require("../db") const { generateUserMetadataID, getEmailFromUserMetadataID, } = require("../db/utils") -const { getGlobalUsers } = require("../../utilities/workerRequests") +const { getGlobalUsers } = require("../utilities/workerRequests") exports.getFullUser = async ({ ctx, email, userId }) => { if (!email) { diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index b081762e13..65d013c935 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -11,7 +11,7 @@ function getAppRole(appId, user) { if (!user.roleId) { user.roleId = BUILTIN_ROLE_IDS.PUBLIC } - delete user.roles + // delete user.roles return user }