Merge pull request #1781 from Budibase/fix/mike-fixes

Fixes and making login/forgot/reset password pages respect logo and company name
This commit is contained in:
Michael Drury 2021-06-21 19:43:05 +01:00 committed by GitHub
commit abc5a6687c
12 changed files with 99 additions and 31 deletions

View File

@ -22,6 +22,12 @@ function buildNoAuthRegex(patterns) {
}) })
} }
function finalise(ctx, { authenticated, user, internal } = {}) {
ctx.isAuthenticated = authenticated || false
ctx.user = user
ctx.internal = internal || false
}
module.exports = (noAuthPatterns = [], opts) => { module.exports = (noAuthPatterns = [], opts) => {
const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : [] const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : []
return async (ctx, next) => { return async (ctx, next) => {
@ -36,35 +42,39 @@ module.exports = (noAuthPatterns = [], opts) => {
return next() return next()
} }
try { try {
const apiKey = ctx.request.headers["x-budibase-api-key"]
// check the actual user is authenticated first // check the actual user is authenticated first
const authCookie = getCookie(ctx, Cookies.Auth) const authCookie = getCookie(ctx, Cookies.Auth)
let authenticated = false,
// this is an internal request, no user made it user = null,
if (apiKey && apiKey === env.INTERNAL_API_KEY) { internal = false
ctx.isAuthenticated = true if (authCookie) {
ctx.internal = true
} else if (authCookie) {
try { try {
const db = database.getDB(StaticDatabases.GLOBAL.name) const db = database.getDB(StaticDatabases.GLOBAL.name)
const user = await db.get(authCookie.userId) user = await db.get(authCookie.userId)
delete user.password delete user.password
ctx.isAuthenticated = true authenticated = true
ctx.user = user
} catch (err) { } catch (err) {
// remove the cookie as the use does not exist anymore // remove the cookie as the use does not exist anymore
clearCookie(ctx, Cookies.Auth) clearCookie(ctx, Cookies.Auth)
} }
} }
// be explicit const apiKey = ctx.request.headers["x-budibase-api-key"]
if (ctx.isAuthenticated !== true) { // this is an internal request, no user made it
ctx.isAuthenticated = false if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) {
authenticated = true
internal = true
} }
// be explicit
if (authenticated !== true) {
authenticated = false
}
// isAuthenticated is a function, so use a variable to be able to check authed state
finalise(ctx, { authenticated, user, internal })
return next() return next()
} catch (err) { } catch (err) {
// allow configuring for public access // allow configuring for public access
if (opts && opts.publicAllowed) { if (opts && opts.publicAllowed) {
ctx.isAuthenticated = false finalise(ctx, { authenticated: false })
} else { } else {
ctx.throw(err.status || 403, err) ctx.throw(err.status || 403, err)
} }

View File

@ -20,6 +20,7 @@ process.env.MINIO_ACCESS_KEY = "budibase"
process.env.MINIO_SECRET_KEY = "budibase" process.env.MINIO_SECRET_KEY = "budibase"
process.env.COUCH_DB_USER = "budibase" process.env.COUCH_DB_USER = "budibase"
process.env.COUCH_DB_PASSWORD = "budibase" process.env.COUCH_DB_PASSWORD = "budibase"
process.env.INTERNAL_API_KEY = "budibase"
// Stop info logs polluting test outputs // Stop info logs polluting test outputs
process.env.LOG_LEVEL = "error" process.env.LOG_LEVEL = "error"

View File

@ -9,6 +9,7 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { organisation, auth } from "stores/portal" import { organisation, auth } from "stores/portal"
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
import { onMount } from "svelte"
let email = "" let email = ""
@ -20,6 +21,10 @@
notifications.error("Unable to send reset password link") notifications.error("Unable to send reset password link")
} }
} }
onMount(async () => {
await organisation.init()
})
</script> </script>
<div class="login"> <div class="login">

View File

@ -10,13 +10,16 @@
notifications, notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import { goto, params } from "@roxi/routify" import { goto, params } from "@roxi/routify"
import { auth } from "stores/portal" import { auth, organisation } from "stores/portal"
import GoogleButton from "./_components/GoogleButton.svelte" import GoogleButton from "./_components/GoogleButton.svelte"
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
import { onMount } from "svelte"
let username = "" let username = ""
let password = "" let password = ""
$: company = $organisation.company || "Budibase"
async function login() { async function login() {
try { try {
await auth.login({ await auth.login({
@ -43,6 +46,10 @@
function handleKeydown(evt) { function handleKeydown(evt) {
if (evt.key === "Enter") login() if (evt.key === "Enter") login()
} }
onMount(async () => {
await organisation.init()
})
</script> </script>
<svelte:window on:keydown={handleKeydown} /> <svelte:window on:keydown={handleKeydown} />
@ -50,8 +57,8 @@
<div class="main"> <div class="main">
<Layout> <Layout>
<Layout noPadding justifyItems="center"> <Layout noPadding justifyItems="center">
<img alt="logo" src={Logo} /> <img alt="logo" src={$organisation.logoUrl || Logo} />
<Heading>Sign in to Budibase</Heading> <Heading>Sign in to {company}</Heading>
</Layout> </Layout>
<GoogleButton /> <GoogleButton />
<Divider noGrid /> <Divider noGrid />
@ -66,7 +73,7 @@
/> />
</Layout> </Layout>
<Layout gap="XS" noPadding> <Layout gap="XS" noPadding>
<Button cta on:click={login}>Sign in to Budibase</Button> <Button cta on:click={login}>Sign in to {company}</Button>
<ActionButton quiet on:click={() => $goto("./forgot")}> <ActionButton quiet on:click={() => $goto("./forgot")}>
Forgot password? Forgot password?
</ActionButton> </ActionButton>

View File

@ -2,8 +2,9 @@
import { Body, Button, Heading, Layout, notifications } from "@budibase/bbui" import { Body, Button, Heading, Layout, notifications } from "@budibase/bbui"
import { goto, params } from "@roxi/routify" import { goto, params } from "@roxi/routify"
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte" import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
import { auth } from "stores/portal" import { auth, organisation } from "stores/portal"
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
import { onMount } from "svelte"
const resetCode = $params["?code"] const resetCode = $params["?code"]
let password, error let password, error
@ -28,13 +29,17 @@
notifications.error("Unable to reset password") notifications.error("Unable to reset password")
} }
} }
onMount(async () => {
await organisation.init()
})
</script> </script>
<div class="login"> <div class="login">
<div class="main"> <div class="main">
<Layout> <Layout>
<Layout noPadding justifyItems="center"> <Layout noPadding justifyItems="center">
<img src={Logo} alt="Organisation logo" /> <img src={$organisation.logoUrl || Logo} alt="Organisation logo" />
</Layout> </Layout>
<Layout gap="XS" noPadding> <Layout gap="XS" noPadding>
<Heading textAlign="center">Reset your password</Heading> <Heading textAlign="center">Reset your password</Heading>

View File

@ -57,11 +57,17 @@
await organisation.init() await organisation.init()
} }
// Update settings const config = {
const res = await organisation.save({
company: $values.company ?? "", company: $values.company ?? "",
platformUrl: $values.platformUrl ?? "", platformUrl: $values.platformUrl ?? "",
}) }
// remove logo if required
if (!$values.logo) {
config.logoUrl = ""
}
// Update settings
const res = await organisation.save(config)
if (res.status === 200) { if (res.status === 200) {
notifications.success("Settings saved successfully") notifications.success("Settings saved successfully")
} else { } else {
@ -98,7 +104,11 @@
<Dropzone <Dropzone
value={[$values.logo]} value={[$values.logo]}
on:change={e => { on:change={e => {
$values.logo = e.detail?.[0] if (!e.detail || e.detail.length === 0) {
$values.logo = null
} else {
$values.logo = e.detail[0]
}
}} }}
/> />
</div> </div>

View File

@ -13,7 +13,7 @@ export function createOrganisationStore() {
const { subscribe, set } = store const { subscribe, set } = store
async function init() { async function init() {
const res = await api.get(`/api/admin/configs/settings`) const res = await api.get(`/api/admin/configs/public`)
const json = await res.json() const json = await res.json()
if (json.status === 400) { if (json.status === 400) {

View File

@ -11,10 +11,14 @@ async function redirect(ctx, method) {
const { devPath } = ctx.params const { devPath } = ctx.params
const response = await fetch( const response = await fetch(
checkSlashesInUrl(`${env.WORKER_URL}/api/admin/${devPath}`), checkSlashesInUrl(`${env.WORKER_URL}/api/admin/${devPath}`),
request(ctx, { request(
ctx,
{
method, method,
body: ctx.request.body, body: ctx.request.body,
}) },
true
)
) )
if (response.status !== 200) { if (response.status !== 200) {
ctx.throw(response.status, response.statusText) ctx.throw(response.status, response.statusText)

View File

@ -90,7 +90,25 @@ exports.find = async function (ctx) {
if (scopedConfig) { if (scopedConfig) {
ctx.body = scopedConfig ctx.body = scopedConfig
} else { } else {
ctx.throw(400, "No configuration exists.") // don't throw an error, there simply is nothing to return
ctx.body = {}
}
} catch (err) {
ctx.throw(err.status, err)
}
}
exports.publicSettings = async function (ctx) {
const db = new CouchDB(GLOBAL_DB)
try {
// Find the config with the most granular scope based on context
const config = await getScopedFullConfig(db, {
type: Configs.SETTINGS,
})
if (!config) {
ctx.body = {}
} else {
ctx.body = config
} }
} catch (err) { } catch (err) {
ctx.throw(err.status, err) ctx.throw(err.status, err)

View File

@ -130,6 +130,9 @@ exports.removeAppRole = async ctx => {
} }
exports.getSelf = async ctx => { exports.getSelf = async ctx => {
if (!ctx.user) {
ctx.throw(403, "User not logged in")
}
ctx.params = { ctx.params = {
id: ctx.user._id, id: ctx.user._id,
} }

View File

@ -37,6 +37,10 @@ const PUBLIC_ENDPOINTS = [
route: "/api/apps", route: "/api/apps",
method: "GET", method: "GET",
}, },
{
route: "/api/admin/configs/public",
method: "GET",
},
] ]
const router = new Router() const router = new Router()

View File

@ -26,7 +26,7 @@ function settingValidation() {
// prettier-ignore // prettier-ignore
return Joi.object({ return Joi.object({
platformUrl: Joi.string().optional(), platformUrl: Joi.string().optional(),
logoUrl: Joi.string().optional(), logoUrl: Joi.string().optional().allow("", null),
docsUrl: Joi.string().optional(), docsUrl: Joi.string().optional(),
company: Joi.string().required(), company: Joi.string().required(),
}).unknown(true) }).unknown(true)
@ -91,6 +91,7 @@ router
buildConfigGetValidation(), buildConfigGetValidation(),
controller.fetch controller.fetch
) )
.get("/api/admin/configs/public", controller.publicSettings)
.get("/api/admin/configs/:type", buildConfigGetValidation(), controller.find) .get("/api/admin/configs/:type", buildConfigGetValidation(), controller.find)
.post( .post(
"/api/admin/configs/upload/:type/:name", "/api/admin/configs/upload/:type/:name",