Merge remote-tracking branch 'origin/v3-ui' into feature/automation-branching-ux
This commit is contained in:
commit
d1c21d7aeb
|
@ -27,7 +27,7 @@ export function doInUserContext(user: User, ctx: Ctx, task: any) {
|
||||||
hostInfo: {
|
hostInfo: {
|
||||||
ipAddress: ctx.request.ip,
|
ipAddress: ctx.request.ip,
|
||||||
// filled in by koa-useragent package
|
// filled in by koa-useragent package
|
||||||
userAgent: ctx.userAgent._agent.source,
|
userAgent: ctx.userAgent.source,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return doInIdentityContext(userContext, task)
|
return doInIdentityContext(userContext, task)
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
import { Cookie, Header } from "../constants"
|
import { Cookie, Header } from "../constants"
|
||||||
import {
|
import {
|
||||||
getCookie,
|
|
||||||
clearCookie,
|
clearCookie,
|
||||||
openJwt,
|
getCookie,
|
||||||
isValidInternalAPIKey,
|
isValidInternalAPIKey,
|
||||||
|
openJwt,
|
||||||
} from "../utils"
|
} from "../utils"
|
||||||
import { getUser } from "../cache/user"
|
import { getUser } from "../cache/user"
|
||||||
import { getSession, updateSessionTTL } from "../security/sessions"
|
import { getSession, updateSessionTTL } from "../security/sessions"
|
||||||
import { buildMatcherRegex, matches } from "./matchers"
|
import { buildMatcherRegex, matches } from "./matchers"
|
||||||
import { SEPARATOR, queryGlobalView, ViewName } from "../db"
|
import { queryGlobalView, SEPARATOR, ViewName } from "../db"
|
||||||
import { getGlobalDB, doInTenant } from "../context"
|
import { doInTenant, getGlobalDB } from "../context"
|
||||||
import { decrypt } from "../security/encryption"
|
import { decrypt } from "../security/encryption"
|
||||||
import * as identity from "../context/identity"
|
import * as identity from "../context/identity"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { Ctx, EndpointMatcher, SessionCookie, User } from "@budibase/types"
|
import {
|
||||||
import { InvalidAPIKeyError, ErrorCode } from "../errors"
|
Ctx,
|
||||||
|
EndpointMatcher,
|
||||||
|
LoginMethod,
|
||||||
|
SessionCookie,
|
||||||
|
User,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { ErrorCode, InvalidAPIKeyError } from "../errors"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
|
|
||||||
const ONE_MINUTE = env.SESSION_UPDATE_PERIOD
|
const ONE_MINUTE = env.SESSION_UPDATE_PERIOD
|
||||||
|
@ -26,16 +32,18 @@ interface FinaliseOpts {
|
||||||
internal?: boolean
|
internal?: boolean
|
||||||
publicEndpoint?: boolean
|
publicEndpoint?: boolean
|
||||||
version?: string
|
version?: string
|
||||||
user?: any
|
user?: User | { tenantId: string }
|
||||||
|
loginMethod?: LoginMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeMinusOneMinute() {
|
function timeMinusOneMinute() {
|
||||||
return new Date(Date.now() - ONE_MINUTE).toISOString()
|
return new Date(Date.now() - ONE_MINUTE).toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalise(ctx: any, opts: FinaliseOpts = {}) {
|
function finalise(ctx: Ctx, opts: FinaliseOpts = {}) {
|
||||||
ctx.publicEndpoint = opts.publicEndpoint || false
|
ctx.publicEndpoint = opts.publicEndpoint || false
|
||||||
ctx.isAuthenticated = opts.authenticated || false
|
ctx.isAuthenticated = opts.authenticated || false
|
||||||
|
ctx.loginMethod = opts.loginMethod
|
||||||
ctx.user = opts.user
|
ctx.user = opts.user
|
||||||
ctx.internal = opts.internal || false
|
ctx.internal = opts.internal || false
|
||||||
ctx.version = opts.version
|
ctx.version = opts.version
|
||||||
|
@ -120,9 +128,10 @@ export default function (
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenantId = ctx.request.headers[Header.TENANT_ID]
|
const tenantId = ctx.request.headers[Header.TENANT_ID]
|
||||||
let authenticated = false,
|
let authenticated: boolean = false,
|
||||||
user = null,
|
user: User | { tenantId: string } | undefined = undefined,
|
||||||
internal = false
|
internal: boolean = false,
|
||||||
|
loginMethod: LoginMethod | undefined = undefined
|
||||||
if (authCookie && !apiKey) {
|
if (authCookie && !apiKey) {
|
||||||
const sessionId = authCookie.sessionId
|
const sessionId = authCookie.sessionId
|
||||||
const userId = authCookie.userId
|
const userId = authCookie.userId
|
||||||
|
@ -146,6 +155,7 @@ export default function (
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
user.csrfToken = session.csrfToken
|
user.csrfToken = session.csrfToken
|
||||||
|
loginMethod = LoginMethod.COOKIE
|
||||||
|
|
||||||
if (session?.lastAccessedAt < timeMinusOneMinute()) {
|
if (session?.lastAccessedAt < timeMinusOneMinute()) {
|
||||||
// make sure we denote that the session is still in use
|
// make sure we denote that the session is still in use
|
||||||
|
@ -170,17 +180,16 @@ export default function (
|
||||||
apiKey,
|
apiKey,
|
||||||
populateUser
|
populateUser
|
||||||
)
|
)
|
||||||
if (valid && foundUser) {
|
if (valid) {
|
||||||
authenticated = true
|
authenticated = true
|
||||||
|
loginMethod = LoginMethod.API_KEY
|
||||||
user = foundUser
|
user = foundUser
|
||||||
} else if (valid) {
|
internal = !foundUser
|
||||||
authenticated = true
|
|
||||||
internal = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!user && tenantId) {
|
if (!user && tenantId) {
|
||||||
user = { tenantId }
|
user = { tenantId }
|
||||||
} else if (user) {
|
} else if (user && "password" in user) {
|
||||||
delete user.password
|
delete user.password
|
||||||
}
|
}
|
||||||
// be explicit
|
// be explicit
|
||||||
|
@ -204,7 +213,14 @@ export default function (
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAuthenticated is a function, so use a variable to be able to check authed state
|
// isAuthenticated is a function, so use a variable to be able to check authed state
|
||||||
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
finalise(ctx, {
|
||||||
|
authenticated,
|
||||||
|
user,
|
||||||
|
internal,
|
||||||
|
version,
|
||||||
|
publicEndpoint,
|
||||||
|
loginMethod,
|
||||||
|
})
|
||||||
|
|
||||||
if (isUser(user)) {
|
if (isUser(user)) {
|
||||||
return identity.doInUserContext(user, ctx, next)
|
return identity.doInUserContext(user, ctx, next)
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import { themeStore } from "stores/portal"
|
import { themeStore } from "stores/portal"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { ThemeOptions } from "@budibase/shared-core"
|
||||||
|
|
||||||
const modalContext = getContext(Context.Modal)
|
const modalContext = getContext(Context.Modal)
|
||||||
const commands = [
|
const commands = [
|
||||||
|
@ -141,13 +141,13 @@
|
||||||
icon: "ShareAndroid",
|
icon: "ShareAndroid",
|
||||||
action: () => $goto(`./automation/${automation._id}`),
|
action: () => $goto(`./automation/${automation._id}`),
|
||||||
})) ?? []),
|
})) ?? []),
|
||||||
...Constants.Themes.map(theme => ({
|
...ThemeOptions.map(themeMeta => ({
|
||||||
type: "Change Builder Theme",
|
type: "Change Builder Theme",
|
||||||
name: theme.name,
|
name: themeMeta.name,
|
||||||
icon: "ColorPalette",
|
icon: "ColorPalette",
|
||||||
action: () =>
|
action: () =>
|
||||||
themeStore.update(state => {
|
themeStore.update(state => {
|
||||||
state.theme = theme.class
|
state.theme = themeMeta.id
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { Label } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { themeStore } from "stores/portal"
|
import { themeStore } from "stores/portal"
|
||||||
|
import { Theme } from "@budibase/types"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -116,7 +117,7 @@
|
||||||
readOnly,
|
readOnly,
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: true,
|
||||||
autoCloseTags: true,
|
autoCloseTags: true,
|
||||||
theme: $themeStore.theme.includes("light") ? THEMES.LIGHT : THEMES.DARK,
|
theme: $themeStore.theme === Theme.LIGHT ? THEMES.LIGHT : THEMES.DARK,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tab)
|
if (!tab)
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Select } from "@budibase/bbui"
|
import { ModalContent, Select } from "@budibase/bbui"
|
||||||
import { themeStore } from "stores/portal"
|
import { themeStore } from "stores/portal"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { ThemeOptions } from "@budibase/shared-core"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent title="Theme">
|
<ModalContent title="Theme">
|
||||||
<Select
|
<Select
|
||||||
options={Constants.Themes}
|
options={ThemeOptions}
|
||||||
bind:value={$themeStore.theme}
|
bind:value={$themeStore.theme}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={x => x.name}
|
getOptionLabel={x => x.name}
|
||||||
getOptionValue={x => x.class}
|
getOptionValue={x => x.id}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import { themeStore, appStore } from "stores/builder"
|
import { themeStore, appStore } from "stores/builder"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { ThemeOptions, getThemeClassNames } from "@budibase/shared-core"
|
||||||
|
|
||||||
const onChangeTheme = async theme => {
|
const onChangeTheme = async theme => {
|
||||||
try {
|
try {
|
||||||
await themeStore.save(`spectrum--${theme}`, $appStore.appId)
|
await themeStore.save(theme, $appStore.appId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error updating theme")
|
notifications.error("Error updating theme")
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,14 @@
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#each Constants.Themes as theme}
|
{#each ThemeOptions as themeMeta}
|
||||||
<div
|
<div
|
||||||
class="theme"
|
class="theme"
|
||||||
class:selected={`spectrum--${theme.class}` === $themeStore.theme}
|
class:selected={themeMeta.id === $themeStore.theme}
|
||||||
on:click={() => onChangeTheme(theme.class)}
|
on:click={() => onChangeTheme(themeMeta.id)}
|
||||||
>
|
>
|
||||||
<div
|
<div class="color {getThemeClassNames(themeMeta.id)}" />
|
||||||
style="background: {theme.preview}"
|
{themeMeta.name}
|
||||||
class="color spectrum--{theme.class}"
|
|
||||||
/>
|
|
||||||
{theme.name}
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { findComponent, findComponentPath } from "helpers/components"
|
import { findComponent, findComponentPath } from "helpers/components"
|
||||||
import { isActive, goto } from "@roxi/routify"
|
import { isActive, goto } from "@roxi/routify"
|
||||||
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||||
|
import { getThemeClassNames, ThemeClassPrefix } from "@budibase/shared-core"
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
let layout
|
let layout
|
||||||
|
@ -47,7 +48,9 @@
|
||||||
layout,
|
layout,
|
||||||
screen,
|
screen,
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
theme: $themeStore.theme,
|
theme: $appStore.clientFeatures.unifiedThemes
|
||||||
|
? $themeStore.theme
|
||||||
|
: `${ThemeClassPrefix}${$themeStore.theme}`,
|
||||||
customTheme: $themeStore.customTheme,
|
customTheme: $themeStore.customTheme,
|
||||||
previewDevice: $previewStore.previewDevice,
|
previewDevice: $previewStore.previewDevice,
|
||||||
messagePassing: $appStore.clientFeatures.messagePassing,
|
messagePassing: $appStore.clientFeatures.messagePassing,
|
||||||
|
@ -257,7 +260,7 @@
|
||||||
class:mobile={$previewStore.previewDevice === "mobile"}
|
class:mobile={$previewStore.previewDevice === "mobile"}
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class={`loading ${$themeStore.baseTheme} ${$themeStore.theme}`}>
|
<div class={`loading ${getThemeClassNames($themeStore.theme)}`}>
|
||||||
<ClientAppSkeleton
|
<ClientAppSkeleton
|
||||||
sideNav={$navigationStore?.navigation === "Left"}
|
sideNav={$navigationStore?.navigation === "Left"}
|
||||||
hideFooter
|
hideFooter
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
TooltipPosition,
|
TooltipPosition,
|
||||||
TooltipType,
|
TooltipType,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk, getThemeClassNames } from "@budibase/shared-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import ErrorSVG from "./ErrorSVG.svelte"
|
import ErrorSVG from "./ErrorSVG.svelte"
|
||||||
import { getBaseTheme, ClientAppSkeleton } from "@budibase/frontend-core"
|
import { ClientAppSkeleton } from "@budibase/frontend-core"
|
||||||
import { contextMenuStore } from "stores/builder"
|
import { contextMenuStore } from "stores/builder"
|
||||||
|
|
||||||
$: app = $enrichedApps.find(app => app.appId === $params.appId)
|
$: app = $enrichedApps.find(app => app.appId === $params.appId)
|
||||||
|
@ -163,9 +163,7 @@
|
||||||
class:hide={!loading || !app?.features?.skeletonLoader}
|
class:hide={!loading || !app?.features?.skeletonLoader}
|
||||||
class="loading"
|
class="loading"
|
||||||
>
|
>
|
||||||
<div
|
<div class="loadingThemeWrapper {getThemeClassNames(app.theme)}">
|
||||||
class={`loadingThemeWrapper ${getBaseTheme(app.theme)} ${app.theme}`}
|
|
||||||
>
|
|
||||||
<ClientAppSkeleton
|
<ClientAppSkeleton
|
||||||
noAnimation
|
noAnimation
|
||||||
hideDevTools={app?.status === "published"}
|
hideDevTools={app?.status === "published"}
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { getBaseTheme } from "@budibase/frontend-core"
|
import { ensureValidTheme, DefaultAppTheme } from "@budibase/shared-core"
|
||||||
|
|
||||||
const INITIAL_THEMES_STATE = {
|
export const createThemeStore = () => {
|
||||||
theme: "",
|
|
||||||
customTheme: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const themes = () => {
|
|
||||||
const store = writable({
|
const store = writable({
|
||||||
...INITIAL_THEMES_STATE,
|
theme: DefaultAppTheme,
|
||||||
|
customTheme: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const syncAppTheme = app => {
|
const syncAppTheme = app => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const theme = app.theme || "spectrum--light"
|
const theme = ensureValidTheme(app.theme, DefaultAppTheme)
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
theme,
|
theme,
|
||||||
baseTheme: getBaseTheme(theme),
|
|
||||||
customTheme: app.customTheme,
|
customTheme: app.customTheme,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -51,7 +46,7 @@ export const themes = () => {
|
||||||
const { theme, customTheme } = metadata
|
const { theme, customTheme } = metadata
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
theme,
|
theme: ensureValidTheme(theme, DefaultAppTheme),
|
||||||
customTheme,
|
customTheme,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -66,4 +61,4 @@ export const themes = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const themeStore = themes()
|
export const themeStore = createThemeStore()
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
import { Constants, createLocalStorageStore } from "@budibase/frontend-core"
|
import { createLocalStorageStore } from "@budibase/frontend-core"
|
||||||
|
import { derived } from "svelte/store"
|
||||||
|
import {
|
||||||
|
DefaultBuilderTheme,
|
||||||
|
ensureValidTheme,
|
||||||
|
getThemeClassNames,
|
||||||
|
ThemeOptions,
|
||||||
|
ThemeClassPrefix,
|
||||||
|
} from "@budibase/shared-core"
|
||||||
|
|
||||||
export const getThemeStore = () => {
|
export const getThemeStore = () => {
|
||||||
const themeElement = document.documentElement
|
const themeElement = document.documentElement
|
||||||
|
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
theme: "darkest",
|
theme: DefaultBuilderTheme,
|
||||||
}
|
}
|
||||||
const store = createLocalStorageStore("bb-theme", initialValue)
|
const store = createLocalStorageStore("bb-theme", initialValue)
|
||||||
|
const derivedStore = derived(store, $store => ({
|
||||||
|
...$store,
|
||||||
|
theme: ensureValidTheme($store.theme, DefaultBuilderTheme),
|
||||||
|
}))
|
||||||
|
|
||||||
// Update theme class when store changes
|
// Update theme class when store changes
|
||||||
store.subscribe(state => {
|
derivedStore.subscribe(({ theme }) => {
|
||||||
// Handle any old local storage values - this can be removed after the update
|
const classNames = getThemeClassNames(theme).split(" ")
|
||||||
if (state.darkMode !== undefined) {
|
ThemeOptions.forEach(option => {
|
||||||
store.set(initialValue)
|
const className = `${ThemeClassPrefix}${option.id}`
|
||||||
return
|
themeElement.classList.toggle(className, classNames.includes(className))
|
||||||
}
|
|
||||||
|
|
||||||
// Update global class names to use the new theme and remove others
|
|
||||||
Constants.Themes.forEach(option => {
|
|
||||||
themeElement.classList.toggle(
|
|
||||||
`spectrum--${option.class}`,
|
|
||||||
option.class === state.theme
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add base theme if required
|
|
||||||
const selectedTheme = Constants.Themes.find(x => x.class === state.theme)
|
|
||||||
if (selectedTheme?.base) {
|
|
||||||
themeElement.classList.add(`spectrum--${selectedTheme.base}`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return store
|
return {
|
||||||
|
...store,
|
||||||
|
subscribe: derivedStore.subscribe,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ?? confusion
|
|
||||||
export const themeStore = getThemeStore()
|
export const themeStore = getThemeStore()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"features": {
|
"features": {
|
||||||
"spectrumThemes": true,
|
"spectrumThemes": true,
|
||||||
|
"unifiedTheme": true,
|
||||||
"intelligentLoading": true,
|
"intelligentLoading": true,
|
||||||
"deviceAwareness": true,
|
"deviceAwareness": true,
|
||||||
"state": true,
|
"state": true,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { Layout, Heading, Body } from "@budibase/bbui"
|
import { Layout, Heading, Body } from "@budibase/bbui"
|
||||||
import ErrorSVG from "@budibase/frontend-core/assets/error.svg"
|
import ErrorSVG from "@budibase/frontend-core/assets/error.svg"
|
||||||
import { Constants, CookieUtils } from "@budibase/frontend-core"
|
import { Constants, CookieUtils } from "@budibase/frontend-core"
|
||||||
|
import { getThemeClassNames } from "@budibase/shared-core"
|
||||||
import Component from "./Component.svelte"
|
import Component from "./Component.svelte"
|
||||||
import SDK from "sdk"
|
import SDK from "sdk"
|
||||||
import {
|
import {
|
||||||
|
@ -154,7 +155,7 @@
|
||||||
id="spectrum-root"
|
id="spectrum-root"
|
||||||
lang="en"
|
lang="en"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
|
class="spectrum spectrum--medium {getThemeClassNames($themeStore.theme)}"
|
||||||
class:builder={$builderStore.inBuilder}
|
class:builder={$builderStore.inBuilder}
|
||||||
class:show={fontsLoaded && dataLoaded}
|
class:show={fontsLoaded && dataLoaded}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import { getBaseTheme } from "@budibase/frontend-core"
|
import { ensureValidTheme, DefaultAppTheme } from "@budibase/shared-core"
|
||||||
|
|
||||||
// This is the good old acorn bug where having the word "g l o b a l" makes it
|
// This is the good old acorn bug where having the word "g l o b a l" makes it
|
||||||
// think that this is not ES6 compatible and starts throwing errors when using
|
// think that this is not ES6 compatible and starts throwing errors when using
|
||||||
// optional chaining. Piss off acorn.
|
// optional chaining. Piss off acorn.
|
||||||
const defaultTheme = "spectrum--light"
|
|
||||||
const defaultCustomTheme = {
|
const defaultCustomTheme = {
|
||||||
primaryColor: "var(--spectrum-glo" + "bal-color-blue-600)",
|
primaryColor: "var(--spectrum-glo" + "bal-color-blue-600)",
|
||||||
primaryColorHover: "var(--spectrum-glo" + "bal-color-blue-500)",
|
primaryColorHover: "var(--spectrum-glo" + "bal-color-blue-500)",
|
||||||
|
@ -27,7 +26,7 @@ const createThemeStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure theme is set
|
// Ensure theme is set
|
||||||
theme = theme || defaultTheme
|
theme = ensureValidTheme(theme, DefaultAppTheme)
|
||||||
|
|
||||||
// Delete and nullish keys from the custom theme
|
// Delete and nullish keys from the custom theme
|
||||||
if (customTheme) {
|
if (customTheme) {
|
||||||
|
@ -52,7 +51,6 @@ const createThemeStore = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme,
|
theme,
|
||||||
baseTheme: getBaseTheme(theme),
|
|
||||||
customTheme,
|
customTheme,
|
||||||
customThemeCss,
|
customThemeCss,
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,35 +108,6 @@ export const Roles = {
|
||||||
CREATOR: "CREATOR",
|
CREATOR: "CREATOR",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Themes = [
|
|
||||||
{
|
|
||||||
class: "lightest",
|
|
||||||
name: "Lightest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
class: "light",
|
|
||||||
name: "Light",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
class: "dark",
|
|
||||||
name: "Dark",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
class: "darkest",
|
|
||||||
name: "Darkest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
class: "nord",
|
|
||||||
name: "Nord",
|
|
||||||
base: "darkest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
class: "midnight",
|
|
||||||
name: "Midnight",
|
|
||||||
base: "darkest",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const EventPublishType = {
|
export const EventPublishType = {
|
||||||
ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened",
|
ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened",
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ export * as SchemaUtils from "./schema"
|
||||||
export { memo, derivedMemo } from "./memo"
|
export { memo, derivedMemo } from "./memo"
|
||||||
export { createWebsocket } from "./websocket"
|
export { createWebsocket } from "./websocket"
|
||||||
export * from "./download"
|
export * from "./download"
|
||||||
export * from "./theme"
|
|
||||||
export * from "./settings"
|
export * from "./settings"
|
||||||
export * from "./relatedColumns"
|
export * from "./relatedColumns"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { Themes } from "../constants.js"
|
|
||||||
|
|
||||||
export const getBaseTheme = theme => {
|
|
||||||
if (!theme) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
let base = Themes.find(x => `spectrum--${x.class}` === theme)?.base || ""
|
|
||||||
if (base) {
|
|
||||||
base = `spectrum--${base}`
|
|
||||||
}
|
|
||||||
return base
|
|
||||||
}
|
|
|
@ -20,19 +20,15 @@ const options = {
|
||||||
{
|
{
|
||||||
url: "https://budibase.app/api/public/v1",
|
url: "https://budibase.app/api/public/v1",
|
||||||
description: "Budibase Cloud API",
|
description: "Budibase Cloud API",
|
||||||
},
|
|
||||||
{
|
|
||||||
url: "{protocol}://{hostname}/api/public/v1",
|
|
||||||
description: "Budibase self hosted API",
|
|
||||||
variables: {
|
variables: {
|
||||||
protocol: {
|
apiKey: {
|
||||||
default: "http",
|
default: "<user API key>",
|
||||||
description:
|
description: "The API key of the user to assume for API call.",
|
||||||
"Whether HTTP or HTTPS should be used to communicate with your Budibase instance.",
|
|
||||||
},
|
},
|
||||||
hostname: {
|
appId: {
|
||||||
default: "localhost:10000",
|
default: "<App ID>",
|
||||||
description: "The URL of your Budibase instance.",
|
description:
|
||||||
|
"The ID of the app the calls will be executed within the context of, this should start with app_ (production) or app_dev (development).",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,19 +8,15 @@
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "https://budibase.app/api/public/v1",
|
"url": "https://budibase.app/api/public/v1",
|
||||||
"description": "Budibase Cloud API"
|
"description": "Budibase Cloud API",
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "{protocol}://{hostname}/api/public/v1",
|
|
||||||
"description": "Budibase self hosted API",
|
|
||||||
"variables": {
|
"variables": {
|
||||||
"protocol": {
|
"apiKey": {
|
||||||
"default": "http",
|
"default": "<user API key>",
|
||||||
"description": "Whether HTTP or HTTPS should be used to communicate with your Budibase instance."
|
"description": "The API key of the user to assume for API call."
|
||||||
},
|
},
|
||||||
"hostname": {
|
"appId": {
|
||||||
"default": "localhost:10000",
|
"default": "<App ID>",
|
||||||
"description": "The URL of your Budibase instance."
|
"description": "The ID of the app the calls will be executed within the context of, this should start with app_ (production) or app_dev (development)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +47,7 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "The ID of the app which this request is targeting.",
|
"description": "The ID of the app which this request is targeting.",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
"default": "{{ appId }}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -60,6 +57,7 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "The ID of the app which this request is targeting.",
|
"description": "The ID of the app which this request is targeting.",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
"default": "{{ appId }}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,16 +6,14 @@ info:
|
||||||
servers:
|
servers:
|
||||||
- url: https://budibase.app/api/public/v1
|
- url: https://budibase.app/api/public/v1
|
||||||
description: Budibase Cloud API
|
description: Budibase Cloud API
|
||||||
- url: "{protocol}://{hostname}/api/public/v1"
|
|
||||||
description: Budibase self hosted API
|
|
||||||
variables:
|
variables:
|
||||||
protocol:
|
apiKey:
|
||||||
default: http
|
default: <user API key>
|
||||||
description: Whether HTTP or HTTPS should be used to communicate with your
|
description: The API key of the user to assume for API call.
|
||||||
Budibase instance.
|
appId:
|
||||||
hostname:
|
default: <App ID>
|
||||||
default: localhost:10000
|
description: The ID of the app the calls will be executed within the context of,
|
||||||
description: The URL of your Budibase instance.
|
this should start with app_ (production) or app_dev (development).
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
tableId:
|
tableId:
|
||||||
|
@ -38,6 +36,7 @@ components:
|
||||||
required: true
|
required: true
|
||||||
description: The ID of the app which this request is targeting.
|
description: The ID of the app which this request is targeting.
|
||||||
schema:
|
schema:
|
||||||
|
default: "{{ appId }}"
|
||||||
type: string
|
type: string
|
||||||
appIdUrl:
|
appIdUrl:
|
||||||
in: path
|
in: path
|
||||||
|
@ -45,6 +44,7 @@ components:
|
||||||
required: true
|
required: true
|
||||||
description: The ID of the app which this request is targeting.
|
description: The ID of the app which this request is targeting.
|
||||||
schema:
|
schema:
|
||||||
|
default: "{{ appId }}"
|
||||||
type: string
|
type: string
|
||||||
queryId:
|
queryId:
|
||||||
in: path
|
in: path
|
||||||
|
|
|
@ -24,6 +24,7 @@ export const appId = {
|
||||||
required: true,
|
required: true,
|
||||||
description: "The ID of the app which this request is targeting.",
|
description: "The ID of the app which this request is targeting.",
|
||||||
schema: {
|
schema: {
|
||||||
|
default: "{{ appId }}",
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -34,6 +35,7 @@ export const appIdUrl = {
|
||||||
required: true,
|
required: true,
|
||||||
description: "The ID of the app which this request is targeting.",
|
description: "The ID of the app which this request is targeting.",
|
||||||
schema: {
|
schema: {
|
||||||
|
default: "{{ appId }}",
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ const tableSchema = {
|
||||||
},
|
},
|
||||||
formulaType: {
|
formulaType: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: Object.values(FormulaType),
|
enum: [FormulaType.STATIC, FormulaType.DYNAMIC],
|
||||||
description:
|
description:
|
||||||
"Defines whether this is a static or dynamic formula.",
|
"Defines whether this is a static or dynamic formula.",
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,7 +63,7 @@ import {
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
import { builderSocket } from "../../websockets"
|
import { builderSocket } from "../../websockets"
|
||||||
import { sdk as sharedCoreSDK } from "@budibase/shared-core"
|
import { DefaultAppTheme, sdk as sharedCoreSDK } from "@budibase/shared-core"
|
||||||
import * as appMigrations from "../../appMigrations"
|
import * as appMigrations from "../../appMigrations"
|
||||||
|
|
||||||
// utility function, need to do away with this
|
// utility function, need to do away with this
|
||||||
|
@ -302,7 +302,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
navBackground: "var(--spectrum-global-color-gray-100)",
|
navBackground: "var(--spectrum-global-color-gray-100)",
|
||||||
links: [],
|
links: [],
|
||||||
},
|
},
|
||||||
theme: "spectrum--light",
|
theme: DefaultAppTheme,
|
||||||
customTheme: {
|
customTheme: {
|
||||||
buttonBorderRadius: "16px",
|
buttonBorderRadius: "16px",
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { User, Table, SearchFilters, Row } from "@budibase/types"
|
||||||
|
import { HttpMethod, MakeRequestResponse, generateMakeRequest } from "./utils"
|
||||||
|
import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
|
||||||
|
import { Expectations } from "../../../../tests/utilities/api/base"
|
||||||
|
|
||||||
|
type RequestOpts = { internal?: boolean; appId?: string }
|
||||||
|
|
||||||
|
export interface PublicAPIExpectations {
|
||||||
|
status?: number
|
||||||
|
body?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PublicAPIRequest {
|
||||||
|
private makeRequest: MakeRequestResponse
|
||||||
|
private appId: string | undefined
|
||||||
|
|
||||||
|
tables: PublicTableAPI
|
||||||
|
rows: PublicRowAPI
|
||||||
|
apiKey: string
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
apiKey: string,
|
||||||
|
makeRequest: MakeRequestResponse,
|
||||||
|
appId?: string
|
||||||
|
) {
|
||||||
|
this.apiKey = apiKey
|
||||||
|
this.makeRequest = makeRequest
|
||||||
|
this.appId = appId
|
||||||
|
this.tables = new PublicTableAPI(this)
|
||||||
|
this.rows = new PublicRowAPI(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async init(config: TestConfiguration, user: User, opts?: RequestOpts) {
|
||||||
|
const apiKey = await config.generateApiKey(user._id)
|
||||||
|
const makeRequest = generateMakeRequest(apiKey, opts)
|
||||||
|
return new this(apiKey, makeRequest, opts?.appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts(opts: RequestOpts) {
|
||||||
|
if (opts.appId) {
|
||||||
|
this.appId = opts.appId
|
||||||
|
}
|
||||||
|
this.makeRequest = generateMakeRequest(this.apiKey, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(
|
||||||
|
method: HttpMethod,
|
||||||
|
endpoint: string,
|
||||||
|
body?: any,
|
||||||
|
expectations?: PublicAPIExpectations
|
||||||
|
) {
|
||||||
|
if (!this.makeRequest) {
|
||||||
|
throw new Error("Init has not been called")
|
||||||
|
}
|
||||||
|
const res = await this.makeRequest(method, endpoint, body, this.appId)
|
||||||
|
if (expectations?.status) {
|
||||||
|
expect(res.status).toEqual(expectations.status)
|
||||||
|
}
|
||||||
|
if (expectations?.body) {
|
||||||
|
expect(res.body).toEqual(expectations?.body)
|
||||||
|
}
|
||||||
|
return res.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PublicTableAPI {
|
||||||
|
request: PublicAPIRequest
|
||||||
|
|
||||||
|
constructor(request: PublicAPIRequest) {
|
||||||
|
this.request = request
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
table: Table,
|
||||||
|
expectations?: PublicAPIExpectations
|
||||||
|
): Promise<{ data: Table }> {
|
||||||
|
return this.request.send("post", "/tables", table, expectations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PublicRowAPI {
|
||||||
|
request: PublicAPIRequest
|
||||||
|
|
||||||
|
constructor(request: PublicAPIRequest) {
|
||||||
|
this.request = request
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(
|
||||||
|
tableId: string,
|
||||||
|
query: SearchFilters,
|
||||||
|
expectations?: PublicAPIExpectations
|
||||||
|
): Promise<{ data: Row[] }> {
|
||||||
|
return this.request.send(
|
||||||
|
"post",
|
||||||
|
`/tables/${tableId}/rows/search`,
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
expectations
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
const setup = require("../../tests/utilities")
|
import * as setup from "../../tests/utilities"
|
||||||
|
|
||||||
describe("/metrics", () => {
|
describe("/metrics", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
|
@ -0,0 +1,71 @@
|
||||||
|
import * as setup from "../../tests/utilities"
|
||||||
|
import { roles } from "@budibase/backend-core"
|
||||||
|
import { basicTable } from "../../../../tests/utilities/structures"
|
||||||
|
import { Table, User } from "@budibase/types"
|
||||||
|
import { PublicAPIRequest } from "./Request"
|
||||||
|
|
||||||
|
describe("check public API security", () => {
|
||||||
|
const config = setup.getConfig()
|
||||||
|
let builderRequest: PublicAPIRequest,
|
||||||
|
appUserRequest: PublicAPIRequest,
|
||||||
|
table: Table,
|
||||||
|
appUser: User
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
const builderUser = await config.globalUser()
|
||||||
|
appUser = await config.globalUser({
|
||||||
|
builder: { global: false },
|
||||||
|
roles: {
|
||||||
|
[config.getProdAppId()]: roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
builderRequest = await PublicAPIRequest.init(config, builderUser)
|
||||||
|
appUserRequest = await PublicAPIRequest.init(config, appUser)
|
||||||
|
table = (await builderRequest.tables.create(basicTable())).data
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should allow with builder API key", async () => {
|
||||||
|
const res = await builderRequest.rows.search(
|
||||||
|
table._id!,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(res.data.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should 403 when from browser, but API key", async () => {
|
||||||
|
await appUserRequest.rows.search(
|
||||||
|
table._id!,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
status: 403,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should re-direct when using cookie", async () => {
|
||||||
|
const headers = await config.login({
|
||||||
|
userId: appUser._id!,
|
||||||
|
builder: false,
|
||||||
|
prodApp: false,
|
||||||
|
})
|
||||||
|
await config.withHeaders(
|
||||||
|
{
|
||||||
|
...headers,
|
||||||
|
"User-Agent": config.browserUserAgent(),
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
await config.api.row.search(
|
||||||
|
table._id!,
|
||||||
|
{ query: {} },
|
||||||
|
{
|
||||||
|
status: 302,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -21,17 +21,19 @@ export type MakeRequestWithFormDataResponse = (
|
||||||
function base(
|
function base(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
intAppId: string | null,
|
opts?: {
|
||||||
isInternal: boolean
|
intAppId?: string
|
||||||
|
internal?: boolean
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const extraHeaders: any = {
|
const extraHeaders: any = {
|
||||||
"x-budibase-api-key": apiKey,
|
"x-budibase-api-key": apiKey,
|
||||||
}
|
}
|
||||||
if (intAppId) {
|
if (opts?.intAppId) {
|
||||||
extraHeaders["x-budibase-app-id"] = intAppId
|
extraHeaders["x-budibase-app-id"] = opts.intAppId
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = isInternal
|
const url = opts?.internal
|
||||||
? endpoint
|
? endpoint
|
||||||
: checkSlashesInUrl(`/api/public/v1/${endpoint}`)
|
: checkSlashesInUrl(`/api/public/v1/${endpoint}`)
|
||||||
return { headers: extraHeaders, url }
|
return { headers: extraHeaders, url }
|
||||||
|
@ -39,7 +41,7 @@ function base(
|
||||||
|
|
||||||
export function generateMakeRequest(
|
export function generateMakeRequest(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
isInternal = false
|
opts?: { internal?: boolean }
|
||||||
): MakeRequestResponse {
|
): MakeRequestResponse {
|
||||||
const request = setup.getRequest()!
|
const request = setup.getRequest()!
|
||||||
const config = setup.getConfig()!
|
const config = setup.getConfig()!
|
||||||
|
@ -47,9 +49,12 @@ export function generateMakeRequest(
|
||||||
method: HttpMethod,
|
method: HttpMethod,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
body?: any,
|
body?: any,
|
||||||
intAppId: string | null = config.getAppId()
|
intAppId: string | undefined = config.getAppId()
|
||||||
) => {
|
) => {
|
||||||
const { headers, url } = base(apiKey, endpoint, intAppId, isInternal)
|
const { headers, url } = base(apiKey, endpoint, { ...opts, intAppId })
|
||||||
|
if (body && typeof body !== "string") {
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
}
|
||||||
const req = request[method](url).set(config.defaultHeaders(headers))
|
const req = request[method](url).set(config.defaultHeaders(headers))
|
||||||
if (body) {
|
if (body) {
|
||||||
req.send(body)
|
req.send(body)
|
||||||
|
@ -62,7 +67,7 @@ export function generateMakeRequest(
|
||||||
|
|
||||||
export function generateMakeRequestWithFormData(
|
export function generateMakeRequestWithFormData(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
isInternal = false
|
opts?: { internal?: boolean; browser?: boolean }
|
||||||
): MakeRequestWithFormDataResponse {
|
): MakeRequestWithFormDataResponse {
|
||||||
const request = setup.getRequest()!
|
const request = setup.getRequest()!
|
||||||
const config = setup.getConfig()!
|
const config = setup.getConfig()!
|
||||||
|
@ -70,9 +75,9 @@ export function generateMakeRequestWithFormData(
|
||||||
method: HttpMethod,
|
method: HttpMethod,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
fields: Record<string, string | { path: string }>,
|
fields: Record<string, string | { path: string }>,
|
||||||
intAppId: string | null = config.getAppId()
|
intAppId: string | undefined = config.getAppId()
|
||||||
) => {
|
) => {
|
||||||
const { headers, url } = base(apiKey, endpoint, intAppId, isInternal)
|
const { headers, url } = base(apiKey, endpoint, { ...opts, intAppId })
|
||||||
const req = request[method](url).set(config.defaultHeaders(headers))
|
const req = request[method](url).set(config.defaultHeaders(headers))
|
||||||
for (let [field, value] of Object.entries(fields)) {
|
for (let [field, value] of Object.entries(fields)) {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
const setup = require("./utilities")
|
import * as setup from "./utilities"
|
||||||
const { basicScreen, powerScreen } = setup.structures
|
import { checkBuilderEndpoint, runInProd } from "./utilities/TestFunctions"
|
||||||
const { checkBuilderEndpoint, runInProd } = require("./utilities/TestFunctions")
|
import { roles } from "@budibase/backend-core"
|
||||||
const { roles } = require("@budibase/backend-core")
|
import { Screen } from "@budibase/types"
|
||||||
const { BUILTIN_ROLE_IDS } = roles
|
|
||||||
|
|
||||||
|
const { BUILTIN_ROLE_IDS } = roles
|
||||||
|
const { basicScreen, powerScreen } = setup.structures
|
||||||
const route = "/test"
|
const route = "/test"
|
||||||
|
|
||||||
// there are checks which are disabled in test env,
|
// there are checks which are disabled in test env,
|
||||||
|
@ -12,7 +13,7 @@ const route = "/test"
|
||||||
describe("/routing", () => {
|
describe("/routing", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
let basic, power
|
let basic: Screen, power: Screen
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
@ -25,26 +26,40 @@ describe("/routing", () => {
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
it("prevents a public user from accessing development app", async () => {
|
it("prevents a public user from accessing development app", async () => {
|
||||||
await runInProd(() => {
|
await config.withHeaders(
|
||||||
return request
|
{
|
||||||
.get(`/api/routing/client`)
|
"User-Agent": config.browserUserAgent(),
|
||||||
.set(config.publicHeaders({ prodApp: false }))
|
},
|
||||||
.expect(302)
|
async () => {
|
||||||
})
|
await runInProd(() => {
|
||||||
|
return request
|
||||||
|
.get(`/api/routing/client`)
|
||||||
|
.set(config.publicHeaders({ prodApp: false }))
|
||||||
|
.expect(302)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("prevents a non builder from accessing development app", async () => {
|
it("prevents a non builder from accessing development app", async () => {
|
||||||
await runInProd(async () => {
|
await config.withHeaders(
|
||||||
return request
|
{
|
||||||
.get(`/api/routing/client`)
|
"User-Agent": config.browserUserAgent(),
|
||||||
.set(
|
},
|
||||||
await config.roleHeaders({
|
async () => {
|
||||||
roleId: BUILTIN_ROLE_IDS.BASIC,
|
await runInProd(async () => {
|
||||||
prodApp: false,
|
return request
|
||||||
})
|
.get(`/api/routing/client`)
|
||||||
)
|
.set(
|
||||||
.expect(302)
|
await config.roleHeaders({
|
||||||
})
|
roleId: BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
prodApp: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.expect(302)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
it("returns the correct routing for basic user", async () => {
|
it("returns the correct routing for basic user", async () => {
|
||||||
const res = await request
|
const res = await request
|
|
@ -1,5 +1,9 @@
|
||||||
export const getThemeVariables = (theme: string) => {
|
import { ensureValidTheme } from "@budibase/shared-core"
|
||||||
if (theme === "spectrum--lightest") {
|
import { Theme } from "@budibase/types"
|
||||||
|
|
||||||
|
export const getThemeVariables = (theme: Theme) => {
|
||||||
|
theme = ensureValidTheme(theme, Theme.LIGHT)
|
||||||
|
if (theme === Theme.LIGHTEST) {
|
||||||
return `
|
return `
|
||||||
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||||
--spectrum-global-color-gray-200: rgb(244, 244, 244);
|
--spectrum-global-color-gray-200: rgb(244, 244, 244);
|
||||||
|
@ -7,16 +11,15 @@ export const getThemeVariables = (theme: string) => {
|
||||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
if (theme === "spectrum--light") {
|
if (theme === Theme.LIGHT) {
|
||||||
return `
|
return `
|
||||||
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
--spectrum-global-color-gray-50: rgb(255, 255, 255);
|
||||||
--spectrum-global-color-gray-200: rgb(234, 234, 234);
|
--spectrum-global-color-gray-200: rgb(234, 234, 234);
|
||||||
--spectrum-global-color-gray-300: rgb(225, 225, 225);
|
--spectrum-global-color-gray-300: rgb(225, 225, 225);
|
||||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-50);
|
||||||
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
if (theme === "spectrum--dark") {
|
if (theme === Theme.DARK) {
|
||||||
return `
|
return `
|
||||||
--spectrum-global-color-gray-100: rgb(50, 50, 50);
|
--spectrum-global-color-gray-100: rgb(50, 50, 50);
|
||||||
--spectrum-global-color-gray-200: rgb(62, 62, 62);
|
--spectrum-global-color-gray-200: rgb(62, 62, 62);
|
||||||
|
@ -24,7 +27,7 @@ export const getThemeVariables = (theme: string) => {
|
||||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
if (theme === "spectrum--darkest") {
|
if (theme === Theme.DARKEST) {
|
||||||
return `
|
return `
|
||||||
--spectrum-global-color-gray-100: rgb(30, 30, 30);
|
--spectrum-global-color-gray-100: rgb(30, 30, 30);
|
||||||
--spectrum-global-color-gray-200: rgb(44, 44, 44);
|
--spectrum-global-color-gray-200: rgb(44, 44, 44);
|
||||||
|
@ -32,7 +35,7 @@ export const getThemeVariables = (theme: string) => {
|
||||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
if (theme === "spectrum--nord") {
|
if (theme === Theme.NORD) {
|
||||||
return `
|
return `
|
||||||
--spectrum-global-color-gray-100: #3b4252;
|
--spectrum-global-color-gray-100: #3b4252;
|
||||||
|
|
||||||
|
@ -41,7 +44,7 @@ export const getThemeVariables = (theme: string) => {
|
||||||
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
--spectrum-alias-background-color-primary: var(--spectrum-global-color-gray-100);
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
if (theme === "spectrum--midnight") {
|
if (theme === Theme.MIDNIGHT) {
|
||||||
return `
|
return `
|
||||||
--hue: 220;
|
--hue: 220;
|
||||||
--sat: 10%;
|
--sat: 10%;
|
||||||
|
|
|
@ -277,11 +277,14 @@ export interface components {
|
||||||
| "link"
|
| "link"
|
||||||
| "formula"
|
| "formula"
|
||||||
| "auto"
|
| "auto"
|
||||||
|
| "ai"
|
||||||
| "json"
|
| "json"
|
||||||
| "internal"
|
| "internal"
|
||||||
| "barcodeqr"
|
| "barcodeqr"
|
||||||
|
| "signature_single"
|
||||||
| "bigint"
|
| "bigint"
|
||||||
| "bb_reference";
|
| "bb_reference"
|
||||||
|
| "bb_reference_single";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
|
@ -386,11 +389,14 @@ export interface components {
|
||||||
| "link"
|
| "link"
|
||||||
| "formula"
|
| "formula"
|
||||||
| "auto"
|
| "auto"
|
||||||
|
| "ai"
|
||||||
| "json"
|
| "json"
|
||||||
| "internal"
|
| "internal"
|
||||||
| "barcodeqr"
|
| "barcodeqr"
|
||||||
|
| "signature_single"
|
||||||
| "bigint"
|
| "bigint"
|
||||||
| "bb_reference";
|
| "bb_reference"
|
||||||
|
| "bb_reference_single";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
|
@ -497,11 +503,14 @@ export interface components {
|
||||||
| "link"
|
| "link"
|
||||||
| "formula"
|
| "formula"
|
||||||
| "auto"
|
| "auto"
|
||||||
|
| "ai"
|
||||||
| "json"
|
| "json"
|
||||||
| "internal"
|
| "internal"
|
||||||
| "barcodeqr"
|
| "barcodeqr"
|
||||||
|
| "signature_single"
|
||||||
| "bigint"
|
| "bigint"
|
||||||
| "bb_reference";
|
| "bb_reference"
|
||||||
|
| "bb_reference_single";
|
||||||
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
/** @description A constraint can be applied to the column which will be validated against when a row is saved. */
|
||||||
constraints?: {
|
constraints?: {
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { generateUserMetadataID, isDevAppID } from "../db/utils"
|
import { generateUserMetadataID, isDevAppID } from "../db/utils"
|
||||||
import { getCachedSelf } from "../utilities/global"
|
import { getCachedSelf } from "../utilities/global"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { isWebhookEndpoint } from "./utils"
|
import { isWebhookEndpoint, isBrowser, isApiKey } from "./utils"
|
||||||
import { UserCtx, ContextUser } from "@budibase/types"
|
import { UserCtx, ContextUser } from "@budibase/types"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export default async (ctx: UserCtx, next: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deny access to application preview
|
// deny access to application preview
|
||||||
if (!env.isTest()) {
|
if (isBrowser(ctx) && !isApiKey(ctx)) {
|
||||||
if (
|
if (
|
||||||
isDevAppID(requestAppId) &&
|
isDevAppID(requestAppId) &&
|
||||||
!isWebhookEndpoint(ctx) &&
|
!isWebhookEndpoint(ctx) &&
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
require("../../db").init()
|
import * as db from "../../db"
|
||||||
|
|
||||||
|
db.init()
|
||||||
mockAuthWithNoCookie()
|
mockAuthWithNoCookie()
|
||||||
mockWorker()
|
mockWorker()
|
||||||
mockUserGroups()
|
mockUserGroups()
|
||||||
|
@ -45,7 +47,7 @@ function mockAuthWithNoCookie() {
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
user: {
|
user: {
|
||||||
getUser: async id => {
|
getUser: async () => {
|
||||||
return {
|
return {
|
||||||
_id: "us_uuid1",
|
_id: "us_uuid1",
|
||||||
}
|
}
|
||||||
|
@ -82,7 +84,7 @@ function mockAuthWithCookie() {
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
user: {
|
user: {
|
||||||
getUser: async id => {
|
getUser: async () => {
|
||||||
return {
|
return {
|
||||||
_id: "us_uuid1",
|
_id: "us_uuid1",
|
||||||
}
|
}
|
||||||
|
@ -94,6 +96,10 @@ function mockAuthWithCookie() {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
|
next: jest.MockedFunction<any>
|
||||||
|
throw: jest.MockedFunction<any>
|
||||||
|
ctx: any
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.next = jest.fn()
|
this.next = jest.fn()
|
||||||
this.throw = jest.fn()
|
this.throw = jest.fn()
|
||||||
|
@ -130,7 +136,7 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Current app middleware", () => {
|
describe("Current app middleware", () => {
|
||||||
let config
|
let config: TestConfiguration
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config = new TestConfiguration()
|
config = new TestConfiguration()
|
||||||
|
@ -192,7 +198,7 @@ describe("Current app middleware", () => {
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
user: {
|
user: {
|
||||||
getUser: async id => {
|
getUser: async () => {
|
||||||
return {
|
return {
|
||||||
_id: "us_uuid1",
|
_id: "us_uuid1",
|
||||||
}
|
}
|
|
@ -1,9 +1,18 @@
|
||||||
import { BBContext } from "@budibase/types"
|
import { LoginMethod, UserCtx } from "@budibase/types"
|
||||||
|
|
||||||
const WEBHOOK_ENDPOINTS = new RegExp(
|
const WEBHOOK_ENDPOINTS = new RegExp(
|
||||||
["webhooks/trigger", "webhooks/schema"].join("|")
|
["webhooks/trigger", "webhooks/schema"].join("|")
|
||||||
)
|
)
|
||||||
|
|
||||||
export function isWebhookEndpoint(ctx: BBContext) {
|
export function isWebhookEndpoint(ctx: UserCtx) {
|
||||||
return WEBHOOK_ENDPOINTS.test(ctx.request.url)
|
return WEBHOOK_ENDPOINTS.test(ctx.request.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBrowser(ctx: UserCtx) {
|
||||||
|
const browser = ctx.userAgent?.browser
|
||||||
|
return browser && browser !== "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isApiKey(ctx: UserCtx) {
|
||||||
|
return ctx.loginMethod === LoginMethod.API_KEY
|
||||||
|
}
|
||||||
|
|
|
@ -423,6 +423,7 @@ export default class TestConfiguration {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
Cookie: [`${constants.Cookie.Auth}=${authToken}`],
|
Cookie: [`${constants.Cookie.Auth}=${authToken}`],
|
||||||
[constants.Header.APP_ID]: appId,
|
[constants.Header.APP_ID]: appId,
|
||||||
|
...this.temporaryHeaders,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -527,6 +528,10 @@ export default class TestConfiguration {
|
||||||
return this.login({ userId: email, roleId, builder, prodApp })
|
return this.login({ userId: email, roleId, builder, prodApp })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
browserUserAgent() {
|
||||||
|
return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
|
||||||
|
}
|
||||||
|
|
||||||
// TENANCY
|
// TENANCY
|
||||||
|
|
||||||
tenantHost() {
|
tenantHost() {
|
||||||
|
|
|
@ -3,6 +3,7 @@ export * from "./api"
|
||||||
export * from "./fields"
|
export * from "./fields"
|
||||||
export * from "./rows"
|
export * from "./rows"
|
||||||
export * from "./colors"
|
export * from "./colors"
|
||||||
|
export * from "./themes"
|
||||||
|
|
||||||
export const OperatorOptions = {
|
export const OperatorOptions = {
|
||||||
Equals: {
|
Equals: {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { ThemeMeta, Theme } from "@budibase/types"
|
||||||
|
|
||||||
|
export const ThemeClassPrefix = "spectrum--"
|
||||||
|
export const DefaultBuilderTheme = Theme.DARKEST
|
||||||
|
export const DefaultAppTheme = Theme.LIGHT
|
||||||
|
|
||||||
|
// Currently available theme options for builder and apps
|
||||||
|
export const ThemeOptions: ThemeMeta[] = [
|
||||||
|
{
|
||||||
|
id: Theme.LIGHT,
|
||||||
|
name: "Light",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We call this dark for simplicity, but we want to use the spectrum darkest style
|
||||||
|
id: Theme.DARKEST,
|
||||||
|
name: "Dark",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: Theme.NORD,
|
||||||
|
name: "Nord",
|
||||||
|
base: Theme.DARKEST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: Theme.MIDNIGHT,
|
||||||
|
name: "Midnight",
|
||||||
|
base: Theme.DARKEST,
|
||||||
|
},
|
||||||
|
]
|
|
@ -4,3 +4,4 @@ export * as helpers from "./helpers"
|
||||||
export * as utils from "./utils"
|
export * as utils from "./utils"
|
||||||
export * as sdk from "./sdk"
|
export * as sdk from "./sdk"
|
||||||
export * from "./table"
|
export * from "./table"
|
||||||
|
export * from "./themes"
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { getThemeClassNames, ensureValidTheme } from "../themes"
|
||||||
|
import { Theme } from "@budibase/types"
|
||||||
|
|
||||||
|
describe("theme class names", () => {
|
||||||
|
it("generates class names for a theme without base theme", () => {
|
||||||
|
expect(getThemeClassNames(Theme.LIGHT)).toStrictEqual("spectrum--light")
|
||||||
|
})
|
||||||
|
it("generates class names for a theme with base theme", () => {
|
||||||
|
expect(getThemeClassNames(Theme.NORD)).toStrictEqual(
|
||||||
|
"spectrum--darkest spectrum--nord"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("theme validity checking", () => {
|
||||||
|
it("handles no theme", () => {
|
||||||
|
expect(ensureValidTheme(undefined)).toStrictEqual(Theme.DARKEST)
|
||||||
|
})
|
||||||
|
it("allows specifiying a fallback", () => {
|
||||||
|
expect(ensureValidTheme(undefined, Theme.NORD)).toStrictEqual(Theme.NORD)
|
||||||
|
})
|
||||||
|
it("migrates lightest to light", () => {
|
||||||
|
expect(ensureValidTheme(Theme.LIGHTEST)).toStrictEqual(Theme.LIGHT)
|
||||||
|
})
|
||||||
|
it("migrates dark to darkest", () => {
|
||||||
|
expect(ensureValidTheme(Theme.DARK)).toStrictEqual(Theme.DARKEST)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { ThemeOptions, ThemeClassPrefix } from "./constants/themes"
|
||||||
|
import { Theme } from "@budibase/types"
|
||||||
|
|
||||||
|
// Gets the CSS class names for the specified theme
|
||||||
|
export const getThemeClassNames = (theme: Theme): string => {
|
||||||
|
theme = ensureValidTheme(theme)
|
||||||
|
let classNames = `${ThemeClassPrefix}${theme}`
|
||||||
|
|
||||||
|
// Prefix with base class if required
|
||||||
|
const base = ThemeOptions.find(x => x.id === theme)?.base
|
||||||
|
if (base) {
|
||||||
|
classNames = `${ThemeClassPrefix}${base} ${classNames}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return classNames
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures a theme value is a valid option
|
||||||
|
export const ensureValidTheme = (
|
||||||
|
theme?: Theme,
|
||||||
|
fallback: Theme = Theme.DARKEST
|
||||||
|
): Theme => {
|
||||||
|
if (!theme) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we aren't using the spectrum prefix
|
||||||
|
if (theme.startsWith(ThemeClassPrefix)) {
|
||||||
|
theme = theme.split(ThemeClassPrefix)[1] as Theme
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we aren't using a deprecated theme, and migrate
|
||||||
|
// to the nearest valid theme if we are
|
||||||
|
if (!ThemeOptions.some(x => x.id === theme)) {
|
||||||
|
if (theme === Theme.LIGHTEST) {
|
||||||
|
return Theme.LIGHT
|
||||||
|
} else if (theme === Theme.DARK) {
|
||||||
|
return Theme.DARKEST
|
||||||
|
} else {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return theme
|
||||||
|
}
|
|
@ -19,7 +19,8 @@
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/redlock": "4.0.7",
|
"@types/redlock": "4.0.7",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"typescript": "5.5.2"
|
"typescript": "5.5.2",
|
||||||
|
"koa-useragent": "^4.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scim-patch": "^0.8.1"
|
"scim-patch": "^0.8.1"
|
||||||
|
|
|
@ -17,3 +17,4 @@ export * from "./component"
|
||||||
export * from "./sqlite"
|
export * from "./sqlite"
|
||||||
export * from "./snippet"
|
export * from "./snippet"
|
||||||
export * from "./rowAction"
|
export * from "./rowAction"
|
||||||
|
export * from "./theme"
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
export enum Theme {
|
||||||
|
LIGHTEST = "lightest",
|
||||||
|
LIGHT = "light",
|
||||||
|
DARK = "dark",
|
||||||
|
DARKEST = "darkest",
|
||||||
|
NORD = "nord",
|
||||||
|
MIDNIGHT = "midnight",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ThemeMeta = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
base?: Theme
|
||||||
|
}
|
|
@ -12,6 +12,12 @@ import {
|
||||||
import { FeatureFlag, License } from "../sdk"
|
import { FeatureFlag, License } from "../sdk"
|
||||||
import { Files } from "formidable"
|
import { Files } from "formidable"
|
||||||
import { EventType } from "../core"
|
import { EventType } from "../core"
|
||||||
|
import { UserAgentContext } from "koa-useragent"
|
||||||
|
|
||||||
|
export enum LoginMethod {
|
||||||
|
API_KEY = "api_key",
|
||||||
|
COOKIE = "cookie",
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContextUser extends Omit<User, "roles"> {
|
export interface ContextUser extends Omit<User, "roles"> {
|
||||||
globalId?: string
|
globalId?: string
|
||||||
|
@ -41,6 +47,7 @@ export interface BBRequest<RequestBody> extends Request {
|
||||||
export interface Ctx<RequestBody = any, ResponseBody = any> extends Context {
|
export interface Ctx<RequestBody = any, ResponseBody = any> extends Context {
|
||||||
request: BBRequest<RequestBody>
|
request: BBRequest<RequestBody>
|
||||||
body: ResponseBody
|
body: ResponseBody
|
||||||
|
userAgent: UserAgentContext["userAgent"]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,6 +58,7 @@ export interface UserCtx<RequestBody = any, ResponseBody = any>
|
||||||
user: ContextUser
|
user: ContextUser
|
||||||
roleId?: string
|
roleId?: string
|
||||||
eventEmitter?: ContextEmitter
|
eventEmitter?: ContextEmitter
|
||||||
|
loginMethod?: LoginMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue