logout button
This commit is contained in:
commit
509a04d914
|
@ -7,7 +7,7 @@ const { StaticDatabases } = require("./db/utils")
|
||||||
const { jwt, local, google, authenticated } = require("./middleware")
|
const { jwt, local, google, authenticated } = require("./middleware")
|
||||||
const { Cookies, UserStatus } = require("./constants")
|
const { Cookies, UserStatus } = require("./constants")
|
||||||
const { hash, compare } = require("./hashing")
|
const { hash, compare } = require("./hashing")
|
||||||
const { getAppId, setCookie, getCookie } = require("./utils")
|
const { getAppId, setCookie, getCookie, clearCookie } = require("./utils")
|
||||||
const {
|
const {
|
||||||
generateUserID,
|
generateUserID,
|
||||||
getUserParams,
|
getUserParams,
|
||||||
|
@ -46,5 +46,6 @@ module.exports = {
|
||||||
getAppId,
|
getAppId,
|
||||||
setCookie,
|
setCookie,
|
||||||
getCookie,
|
getCookie,
|
||||||
|
clearCookie,
|
||||||
authenticated,
|
authenticated,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ import { cloneDeep } from "lodash/fp"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { findComponent, findComponentPath } from "./storeUtils"
|
import { findComponent, findComponentPath } from "./storeUtils"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import {
|
import { tables as tablesStore, queries as queriesStores } from "stores/backend"
|
||||||
tables as tablesStore,
|
|
||||||
queries as queriesStores,
|
|
||||||
} from "stores/backend"
|
|
||||||
import { makePropSafe } from "@budibase/string-templates"
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
import { TableNames } from "../constants"
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export { LoginForm } from "./LoginForm.svelte"
|
export { LoginForm } from "./LoginForm.svelte"
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script>
|
||||||
|
import { TextButton as Button, Modal } from "@budibase/bbui"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button text on:click={auth.logout}>
|
||||||
|
<i class="ri-logout-box-line" />
|
||||||
|
<p>Logout</p>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div i {
|
||||||
|
font-size: 26px;
|
||||||
|
color: var(--grey-7);
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div p {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0 0 0 12px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -11,9 +11,7 @@
|
||||||
|
|
||||||
{#if $datasources.list.length === 0}
|
{#if $datasources.list.length === 0}
|
||||||
<i>Connect your first datasource to start building.</i>
|
<i>Connect your first datasource to start building.</i>
|
||||||
{:else}
|
{:else}<i>Select a datasource to edit</i>{/if}
|
||||||
<i>Select a datasource to edit</i>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
i {
|
i {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Home as Link } from "@budibase/bbui"
|
import { Home as Link, Button } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
AppsIcon,
|
AppsIcon,
|
||||||
HostingIcon,
|
HostingIcon,
|
||||||
|
@ -9,6 +9,7 @@
|
||||||
} from "components/common/Icons"
|
} from "components/common/Icons"
|
||||||
import LoginForm from "components/login/LoginForm.svelte"
|
import LoginForm from "components/login/LoginForm.svelte"
|
||||||
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte"
|
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte"
|
||||||
|
import LogoutButton from "components/start/LogoutButton.svelte"
|
||||||
import Logo from "/assets/budibase-logo.svg"
|
import Logo from "/assets/budibase-logo.svg"
|
||||||
import { auth } from "stores/backend"
|
import { auth } from "stores/backend"
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-bottom">
|
<div class="nav-bottom">
|
||||||
<BuilderSettingsButton />
|
<BuilderSettingsButton />
|
||||||
|
<LogoutButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function createAuthStore() {
|
||||||
return json
|
return json
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
const response = await api.post(`/api/auth/logout`)
|
const response = await api.post(`/api/admin/auth/logout`)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
set({ user: null })
|
set({ user: null })
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,12 +32,15 @@ module.exports = async (ctx, next) => {
|
||||||
updateCookie = true
|
updateCookie = true
|
||||||
appId = requestAppId
|
appId = requestAppId
|
||||||
roleId = BUILTIN_ROLE_IDS.PUBLIC
|
roleId = BUILTIN_ROLE_IDS.PUBLIC
|
||||||
} else if (requestAppId != null) {
|
} else if (
|
||||||
|
requestAppId != null &&
|
||||||
|
(appCookie == null || requestAppId !== appCookie.appId)
|
||||||
|
) {
|
||||||
const globalUser = await getGlobalUsers(ctx, requestAppId, ctx.user.email)
|
const globalUser = await getGlobalUsers(ctx, requestAppId, ctx.user.email)
|
||||||
updateCookie = true
|
updateCookie = true
|
||||||
appId = requestAppId
|
appId = requestAppId
|
||||||
roleId = globalUser.roles[requestAppId] || BUILTIN_ROLE_IDS.PUBLIC
|
roleId = globalUser.roles[requestAppId] || BUILTIN_ROLE_IDS.PUBLIC
|
||||||
} else if (requestAppId == null && appCookie != null) {
|
} else if (appCookie != null) {
|
||||||
appId = appCookie.appId
|
appId = appCookie.appId
|
||||||
roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC
|
roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Authenticated middleware sets the correct APP auth type information when the user is not in the builder 1`] = `
|
|
||||||
Object {
|
|
||||||
"apiKey": "1234",
|
|
||||||
"role": Role {
|
|
||||||
"_id": "ADMIN",
|
|
||||||
"inherits": "POWER",
|
|
||||||
"name": "Admin",
|
|
||||||
"permissionId": "admin",
|
|
||||||
},
|
|
||||||
"roleId": "ADMIN",
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Authenticated middleware sets the correct BUILDER auth type information when the x-budibase-type header is not 'client' 1`] = `
|
|
||||||
Object {
|
|
||||||
"apiKey": "1234",
|
|
||||||
"role": Role {
|
|
||||||
"_id": "BUILDER",
|
|
||||||
"name": "Builder",
|
|
||||||
"permissionId": "admin",
|
|
||||||
},
|
|
||||||
"roleId": "BUILDER",
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,124 +0,0 @@
|
||||||
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")
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,6 +1,5 @@
|
||||||
const authorizedMiddleware = require("../authorized")
|
const authorizedMiddleware = require("../authorized")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { AuthTypes } = require("../../constants")
|
|
||||||
const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions")
|
const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions")
|
||||||
jest.mock("../../environment", () => ({
|
jest.mock("../../environment", () => ({
|
||||||
prod: false,
|
prod: false,
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
mockAuthWithNoCookie()
|
||||||
|
mockWorker()
|
||||||
|
|
||||||
|
function mockWorker() {
|
||||||
|
jest.mock("../../utilities/workerRequests", () => ({
|
||||||
|
getGlobalUsers: () => {
|
||||||
|
return {
|
||||||
|
email: "test@test.com",
|
||||||
|
roles: {
|
||||||
|
"app_test": "BASIC",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockReset() {
|
||||||
|
jest.resetModules()
|
||||||
|
mockWorker()
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockAuthWithNoCookie() {
|
||||||
|
jest.resetModules()
|
||||||
|
mockWorker()
|
||||||
|
jest.mock("@budibase/auth", () => ({
|
||||||
|
getAppId: jest.fn(),
|
||||||
|
setCookie: jest.fn(),
|
||||||
|
getCookie: jest.fn(),
|
||||||
|
Cookies: {},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockAuthWithCookie() {
|
||||||
|
jest.resetModules()
|
||||||
|
mockWorker()
|
||||||
|
jest.mock("@budibase/auth", () => ({
|
||||||
|
getAppId: () => {
|
||||||
|
return "app_test"
|
||||||
|
},
|
||||||
|
setCookie: jest.fn(),
|
||||||
|
getCookie: () => ({ appId: "app_different", roleId: "PUBLIC" }),
|
||||||
|
Cookies: {
|
||||||
|
Auth: "auth",
|
||||||
|
CurrentApp: "currentapp",
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestConfiguration {
|
||||||
|
constructor() {
|
||||||
|
this.next = jest.fn()
|
||||||
|
this.throw = jest.fn()
|
||||||
|
|
||||||
|
this.ctx = {
|
||||||
|
next: this.next,
|
||||||
|
throw: this.throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUser() {
|
||||||
|
this.ctx.user = {
|
||||||
|
email: "test@test.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeMiddleware() {
|
||||||
|
// import as late as possible for mocks
|
||||||
|
const currentAppMiddleware = require("../currentapp")
|
||||||
|
return currentAppMiddleware(this.ctx, this.next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Current app middleware", () => {
|
||||||
|
let config
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config = new TestConfiguration()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("test having no cookies or app ID", () => {
|
||||||
|
it("should be able to proceed with nothing setup", async () => {
|
||||||
|
await config.executeMiddleware()
|
||||||
|
expect(config.next).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("check get public for app when not logged in", () => {
|
||||||
|
it("should be able to proceed with no login, but cookies configured", async () => {
|
||||||
|
mockAuthWithCookie()
|
||||||
|
await config.executeMiddleware()
|
||||||
|
expect(config.ctx.roleId).toEqual("PUBLIC")
|
||||||
|
expect(config.ctx.appId).toEqual("app_test")
|
||||||
|
expect(config.next).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("check functionality when logged in", () => {
|
||||||
|
async function checkExpected(setCookie) {
|
||||||
|
config.setUser()
|
||||||
|
await config.executeMiddleware()
|
||||||
|
const cookieFn = require("@budibase/auth").setCookie
|
||||||
|
if (setCookie) {
|
||||||
|
expect(cookieFn).toHaveBeenCalled()
|
||||||
|
} else {
|
||||||
|
expect(cookieFn).not.toHaveBeenCalled()
|
||||||
|
}
|
||||||
|
expect(config.ctx.roleId).toEqual("BASIC")
|
||||||
|
expect(config.ctx.appId).toEqual("app_test")
|
||||||
|
expect(config.next).toHaveBeenCalled()
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should be able to setup an app token when cookie not setup", async () => {
|
||||||
|
mockAuthWithCookie()
|
||||||
|
await checkExpected(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should perform correct when no cookie exists", async () => {
|
||||||
|
mockReset()
|
||||||
|
jest.mock("@budibase/auth", () => ({
|
||||||
|
getAppId: () => {
|
||||||
|
return "app_test"
|
||||||
|
},
|
||||||
|
setCookie: jest.fn(),
|
||||||
|
getCookie: jest.fn(),
|
||||||
|
Cookies: {},
|
||||||
|
}))
|
||||||
|
await checkExpected(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("lastly check what occurs when cookie doesn't need updated", async () => {
|
||||||
|
mockReset()
|
||||||
|
jest.mock("@budibase/auth", () => ({
|
||||||
|
getAppId: () => {
|
||||||
|
return "app_test"
|
||||||
|
},
|
||||||
|
setCookie: jest.fn(),
|
||||||
|
getCookie: () => ({ appId: "app_test", roleId: "BASIC" }),
|
||||||
|
Cookies: {},
|
||||||
|
}))
|
||||||
|
await checkExpected(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
const { passport, Cookies } = require("@budibase/auth")
|
const { passport, Cookies, clearCookie } = require("@budibase/auth")
|
||||||
|
|
||||||
exports.authenticate = async (ctx, next) => {
|
exports.authenticate = async (ctx, next) => {
|
||||||
return passport.authenticate("local", async (err, user) => {
|
return passport.authenticate("local", async (err, user) => {
|
||||||
|
@ -27,6 +27,15 @@ exports.authenticate = async (ctx, next) => {
|
||||||
})(ctx, next)
|
})(ctx, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.logout = async ctx => {
|
||||||
|
clearCookie(Cookies.Auth)
|
||||||
|
ctx.body = { success: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.googleAuth = async (ctx, next) => {
|
||||||
|
// return passport.authenticate("google")
|
||||||
|
}
|
||||||
|
|
||||||
exports.googleAuth = async (ctx, next) => {
|
exports.googleAuth = async (ctx, next) => {
|
||||||
// return passport.authenticate("google")
|
// return passport.authenticate("google")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.post("/api/admin/auth", authController.authenticate)
|
.post("/api/admin/auth", authController.authenticate)
|
||||||
|
.post("/api/admin/auth/logout", authController.logout)
|
||||||
.get("/api/auth/google", passport.authenticate("google"))
|
.get("/api/auth/google", passport.authenticate("google"))
|
||||||
.get(
|
.get(
|
||||||
"/api/auth/google/callback",
|
"/api/auth/google/callback",
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
const env = require("../environment")
|
|
||||||
|
|
||||||
module.exports = async (ctx, next) => {
|
|
||||||
const selfHostKey =
|
|
||||||
ctx.request.headers["x-budibase-auth"] || ctx.request.body.selfHostKey
|
|
||||||
if (!selfHostKey || env.SELF_HOST_KEY !== selfHostKey) {
|
|
||||||
ctx.throw(401, "Request unauthorised")
|
|
||||||
} else {
|
|
||||||
await next()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue