Merge branch 'master' of github.com:Budibase/budibase into fix/lockdown-admin
This commit is contained in:
commit
348c61a8c5
|
@ -12,15 +12,7 @@
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let url = ""
|
export let url = ""
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let name = "John Doe"
|
export let initials = "JD"
|
||||||
|
|
||||||
function getInitials(name) {
|
|
||||||
let parts = name.split(" ")
|
|
||||||
if (parts.length > 0) {
|
|
||||||
return parts.map(name => name[0]).join("")
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if url}
|
{#if url}
|
||||||
|
@ -38,7 +30,7 @@
|
||||||
size
|
size
|
||||||
)}); font-size: calc(var({sizes.get(size)}) / 2)"
|
)}); font-size: calc(var({sizes.get(size)}) / 2)"
|
||||||
>
|
>
|
||||||
{getInitials(name)}
|
{initials || ""}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -52,5 +44,6 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
on:blur
|
on:blur
|
||||||
on:focus
|
on:focus
|
||||||
on:input
|
on:input
|
||||||
|
on:keyup
|
||||||
on:blur={onBlur}
|
on:blur={onBlur}
|
||||||
on:focus={onFocus}
|
on:focus={onFocus}
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
|
|
|
@ -34,5 +34,6 @@
|
||||||
on:input
|
on:input
|
||||||
on:blur
|
on:blur
|
||||||
on:focus
|
on:focus
|
||||||
|
on:keyup
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
const updatePassword = async () => {
|
const updatePassword = async () => {
|
||||||
try {
|
try {
|
||||||
await auth.updateSelf({ ...$auth.user, password })
|
await auth.updateSelf({ password })
|
||||||
notifications.success("Password changed successfully")
|
notifications.success("Password changed successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Failed to update password")
|
notifications.error("Failed to update password")
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
const updateInfo = async () => {
|
const updateInfo = async () => {
|
||||||
try {
|
try {
|
||||||
await auth.updateSelf({ ...$auth.user, ...$values })
|
await auth.updateSelf($values)
|
||||||
notifications.success("Information updated successfully")
|
notifications.success("Information updated successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Failed to update information")
|
notifications.error("Failed to update information")
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { admin, organisation } from "stores/portal"
|
import { admin, organisation } from "stores/portal"
|
||||||
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
|
|
||||||
let adminUser = {}
|
let adminUser = {}
|
||||||
|
let error
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
try {
|
try {
|
||||||
|
@ -42,13 +44,11 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Input label="Email" bind:value={adminUser.email} />
|
<Input label="Email" bind:value={adminUser.email} />
|
||||||
<Input
|
<PasswordRepeatInput bind:password={adminUser.password} bind:error />
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
bind:value={adminUser.password}
|
|
||||||
/>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
<Button cta on:click={save}>Create super admin user</Button>
|
<Button cta disabled={error} on:click={save}>
|
||||||
|
Create super admin user
|
||||||
|
</Button>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<div slot="control" class="avatar">
|
<div slot="control" class="avatar">
|
||||||
<Avatar size="M" name="John Doe" />
|
<Avatar size="M" initials={$auth.initials} />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="XL" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
||||||
|
|
|
@ -1,24 +1,26 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
import GoogleLogo from "/assets/google-logo.png"
|
import GoogleLogo from "/assets/google-logo.png"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
|
let show = false
|
||||||
|
|
||||||
|
$: show = $admin.checklist?.oauth
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton>
|
{#if show}
|
||||||
<a target="_blank" href="/api/admin/auth/google">
|
<ActionButton>
|
||||||
<div class="inner">
|
<a target="_blank" href="/api/admin/auth/google">
|
||||||
<img src={GoogleLogo} alt="google icon" />
|
<div class="inner">
|
||||||
<p>Sign in with Google</p>
|
<img src={GoogleLogo} alt="google icon" />
|
||||||
</div>
|
<p>Sign in with Google</p>
|
||||||
</a>
|
</div>
|
||||||
</ActionButton>
|
</a>
|
||||||
|
</ActionButton>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.outer {
|
|
||||||
border: 1px solid #494949;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--background-alt);
|
|
||||||
}
|
|
||||||
.inner {
|
.inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -38,8 +38,13 @@
|
||||||
notifications.error("Invalid credentials")
|
notifications.error("Invalid credentials")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleKeydown(evt) {
|
||||||
|
if (evt.key === "Enter") login()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeydown} />
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
try {
|
try {
|
||||||
if (forceResetPassword) {
|
if (forceResetPassword) {
|
||||||
await auth.updateSelf({
|
await auth.updateSelf({
|
||||||
...$auth.user,
|
|
||||||
password,
|
password,
|
||||||
forceResetPassword: false,
|
forceResetPassword: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
Avatar,
|
Avatar,
|
||||||
Search,
|
|
||||||
Layout,
|
Layout,
|
||||||
SideNavigation as Navigation,
|
SideNavigation as Navigation,
|
||||||
SideNavigationItem as Item,
|
SideNavigationItem as Item,
|
||||||
|
@ -77,7 +76,7 @@
|
||||||
<div />
|
<div />
|
||||||
<ActionMenu align="right">
|
<ActionMenu align="right">
|
||||||
<div slot="control" class="avatar">
|
<div slot="control" class="avatar">
|
||||||
<Avatar size="M" name="John Doe" />
|
<Avatar size="M" initials={$auth.initials} />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="XL" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
|
||||||
|
|
|
@ -41,8 +41,8 @@
|
||||||
const enrichedApps = apps.map(app => ({
|
const enrichedApps = apps.map(app => ({
|
||||||
...app,
|
...app,
|
||||||
deployed: app.status === AppStatus.DEPLOYED,
|
deployed: app.status === AppStatus.DEPLOYED,
|
||||||
lockedYou: app.lockedBy?.email === user.email,
|
lockedYou: app.lockedBy && app.lockedBy.email === user?.email,
|
||||||
lockedOther: app.lockedBy && app.lockedBy.email !== user.email,
|
lockedOther: app.lockedBy && app.lockedBy.email !== user?.email,
|
||||||
}))
|
}))
|
||||||
if (sortBy === "status") {
|
if (sortBy === "status") {
|
||||||
return enrichedApps.sort((a, b) => {
|
return enrichedApps.sort((a, b) => {
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
Button,
|
Button,
|
||||||
Heading,
|
Heading,
|
||||||
Divider,
|
Divider,
|
||||||
Page,
|
|
||||||
Label,
|
Label,
|
||||||
notifications,
|
notifications,
|
||||||
Layout,
|
Layout,
|
||||||
|
@ -23,6 +22,13 @@
|
||||||
const ConfigFields = {
|
const ConfigFields = {
|
||||||
Google: ["clientID", "clientSecret", "callbackURL"],
|
Google: ["clientID", "clientSecret", "callbackURL"],
|
||||||
}
|
}
|
||||||
|
const ConfigLabels = {
|
||||||
|
Google: {
|
||||||
|
clientID: "Client ID",
|
||||||
|
clientSecret: "Client secret",
|
||||||
|
callbackURL: "Callback URL",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let google
|
let google
|
||||||
|
|
||||||
|
@ -85,7 +91,7 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
{#each ConfigFields.Google as field}
|
{#each ConfigFields.Google as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">{field}</Label>
|
<Label size="L">{ConfigLabels.Google[field]}</Label>
|
||||||
<Input bind:value={google.config[field]} />
|
<Input bind:value={google.config[field]} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
<Heading>User: {$userFetch?.data?.email}</Heading>
|
<Heading>User: {$userFetch?.data?.email}</Heading>
|
||||||
<Body>
|
<Body>
|
||||||
Change user settings and update their app roles. Also contains the ability
|
Change user settings and update their app roles. Also contains the ability
|
||||||
to delete the user as well as force reset their password..
|
to delete the user as well as force reset their password.
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
<!-- don't let a user remove the privileges that let them be here -->
|
<!-- don't let a user remove the privileges that let them be here -->
|
||||||
{#if userId !== $auth.user._id}
|
{#if userId !== $auth.user._id}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label size="L">Development access?</Label>
|
<Label size="L">Development access</Label>
|
||||||
<Toggle
|
<Toggle
|
||||||
text=""
|
text=""
|
||||||
value={$userFetch?.data?.builder?.global}
|
value={$userFetch?.data?.builder?.global}
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label size="L">Administration access?</Label>
|
<Label size="L">Administration access</Label>
|
||||||
<Toggle
|
<Toggle
|
||||||
text=""
|
text=""
|
||||||
value={$userFetch?.data?.admin?.global}
|
value={$userFetch?.data?.admin?.global}
|
||||||
|
|
|
@ -9,13 +9,26 @@
|
||||||
$: leftover = roles.length - tags.length
|
$: leftover = roles.length - tags.length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tags>
|
<div class="tag-renderer">
|
||||||
{#each tags as tag}
|
<Tags>
|
||||||
<Tag disabled>
|
{#each tags as tag}
|
||||||
{tag}
|
<Tag>
|
||||||
</Tag>
|
{tag}
|
||||||
{/each}
|
</Tag>
|
||||||
{#if leftover}
|
{/each}
|
||||||
<Tag>+{leftover} more</Tag>
|
{#if leftover}
|
||||||
{/if}
|
<Tag>+{leftover} more</Tag>
|
||||||
</Tags>
|
{/if}
|
||||||
|
</Tags>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tag-renderer :global(.spectrum-Tags-item:hover) {
|
||||||
|
color: var(--spectrum-alias-label-text-color);
|
||||||
|
border-color: var(--spectrum-alias-border-color-darker-default);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tag-renderer :global(.spectrum-Tags-itemLabel) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -53,9 +53,8 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Heading>Users</Heading>
|
<Heading>Users</Heading>
|
||||||
<Body>
|
<Body>
|
||||||
Users are the common denominator in Budibase. Each user is assigned to a
|
Each user is assigned to a group that contains apps and permissions. In
|
||||||
group that contains apps and permissions. In this section, you can add
|
this section, you can add users, or edit and delete an existing user.
|
||||||
users, or edit and delete an existing user.
|
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
|
|
|
@ -1,25 +1,42 @@
|
||||||
import { writable } from "svelte/store"
|
import { derived, writable, get } from "svelte/store"
|
||||||
import api from "../../builderStore/api"
|
import api from "../../builderStore/api"
|
||||||
|
|
||||||
export function createAuthStore() {
|
export function createAuthStore() {
|
||||||
const store = writable({ user: null })
|
const user = writable(null)
|
||||||
|
const store = derived(user, $user => {
|
||||||
|
let initials = null
|
||||||
|
if ($user) {
|
||||||
|
if ($user.firstName) {
|
||||||
|
initials = $user.firstName[0]
|
||||||
|
if ($user.lastName) {
|
||||||
|
initials += $user.lastName[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
initials = $user.email[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
user: $user,
|
||||||
|
initials,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
checkAuth: async () => {
|
checkAuth: async () => {
|
||||||
const response = await api.get("/api/admin/users/self")
|
const response = await api.get("/api/admin/users/self")
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
store.update(state => ({ ...state, user: null }))
|
user.set(null)
|
||||||
} else {
|
} else {
|
||||||
const user = await response.json()
|
const json = await response.json()
|
||||||
store.update(state => ({ ...state, user }))
|
user.set(json)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
login: async creds => {
|
login: async creds => {
|
||||||
const response = await api.post(`/api/admin/auth`, creds)
|
const response = await api.post(`/api/admin/auth`, creds)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
store.update(state => ({ ...state, user: json.user }))
|
user.set(json.user)
|
||||||
} else {
|
} else {
|
||||||
throw "Invalid credentials"
|
throw "Invalid credentials"
|
||||||
}
|
}
|
||||||
|
@ -31,12 +48,13 @@ export function createAuthStore() {
|
||||||
throw "Unable to create logout"
|
throw "Unable to create logout"
|
||||||
}
|
}
|
||||||
await response.json()
|
await response.json()
|
||||||
store.update(state => ({ ...state, user: null }))
|
user.set(null)
|
||||||
},
|
},
|
||||||
updateSelf: async user => {
|
updateSelf: async fields => {
|
||||||
const response = await api.post("/api/admin/users/self", user)
|
const newUser = { ...get(user), ...fields }
|
||||||
|
const response = await api.post("/api/admin/users/self", newUser)
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
store.update(state => ({ ...state, user: { ...state.user, ...user } }))
|
user.set(newUser)
|
||||||
} else {
|
} else {
|
||||||
throw "Unable to update user details"
|
throw "Unable to update user details"
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,11 @@ exports.configChecklist = async function (ctx) {
|
||||||
type: Configs.SMTP,
|
type: Configs.SMTP,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// They have set up Google Auth
|
||||||
|
const oauthConfig = await getScopedFullConfig(db, {
|
||||||
|
type: Configs.GOOGLE,
|
||||||
|
})
|
||||||
|
|
||||||
// They have set up an admin user
|
// They have set up an admin user
|
||||||
const users = await db.allDocs(
|
const users = await db.allDocs(
|
||||||
getGlobalUserParams(null, {
|
getGlobalUserParams(null, {
|
||||||
|
@ -180,6 +185,7 @@ exports.configChecklist = async function (ctx) {
|
||||||
apps: appDbNames.length,
|
apps: appDbNames.length,
|
||||||
smtp: !!smtpConfig,
|
smtp: !!smtpConfig,
|
||||||
adminUser,
|
adminUser,
|
||||||
|
oauth: !!oauthConfig,
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(err.status, err)
|
ctx.throw(err.status, err)
|
||||||
|
|
Loading…
Reference in New Issue