Merge branch 'develop' of github.com:Budibase/budibase into feature/cloud-export
This commit is contained in:
commit
1f127939af
|
@ -37,5 +37,5 @@ dependencies:
|
|||
condition: services.couchdb.enabled
|
||||
- name: ingress-nginx
|
||||
version: 3.35.0
|
||||
repository: https://github.com/kubernetes/ingress-nginx
|
||||
repository: https://kubernetes.github.io/ingress-nginx
|
||||
condition: services.ingress.nginx
|
||||
|
|
|
@ -94,6 +94,8 @@ spec:
|
|||
value: {{ .Values.globals.sentryDSN }}
|
||||
- name: WORKER_URL
|
||||
value: worker-service:{{ .Values.services.worker.port }}
|
||||
- name: COOKIE_DOMAIN
|
||||
value: {{ .Values.globals.cookieDomain | quote }}
|
||||
image: budibase/apps
|
||||
imagePullPolicy: Always
|
||||
name: bbapps
|
||||
|
|
|
@ -89,6 +89,8 @@ spec:
|
|||
value: {{ .Values.globals.selfHosted | quote }}
|
||||
- name: ACCOUNT_PORTAL_URL
|
||||
value: {{ .Values.globals.accountPortalUrl | quote }}
|
||||
- name: COOKIE_DOMAIN
|
||||
value: {{ .Values.globals.cookieDomain | quote }}
|
||||
image: budibase/worker
|
||||
imagePullPolicy: Always
|
||||
name: bbworker
|
||||
|
|
|
@ -90,6 +90,7 @@ globals:
|
|||
logLevel: info
|
||||
selfHosted: 1
|
||||
accountPortalUrL: ""
|
||||
cookieDomain: ""
|
||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||
|
||||
# if createSecrets is set to false, you can hard-code your secrets here
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = require("./src/cloud/accounts")
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/auth",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"description": "Authentication middlewares for budibase builder and apps",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -22,6 +22,7 @@ module.exports = {
|
|||
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
||||
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
|
||||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||
isTest,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
|
|
|
@ -10,15 +10,10 @@ function finalise(
|
|||
{ authenticated, user, internal, version, publicEndpoint } = {}
|
||||
) {
|
||||
ctx.publicEndpoint = publicEndpoint || false
|
||||
console.log("Temp Auth Middleware: public endoint", ctx.publicEndpoint)
|
||||
ctx.isAuthenticated = authenticated || false
|
||||
console.log("Temp Auth Middleware: isAuthenticated", ctx.isAuthenticated)
|
||||
ctx.user = user
|
||||
console.log("Temp Auth Middleware: user", ctx.user)
|
||||
ctx.internal = internal || false
|
||||
console.log("Temp Auth Middleware: internal", ctx.internal)
|
||||
ctx.version = version
|
||||
console.log("Temp Auth Middleware: version", ctx.version)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,50 +27,40 @@ module.exports = (
|
|||
) => {
|
||||
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
||||
return async (ctx, next) => {
|
||||
console.log("Temp Auth Middleware: Start auth middleware")
|
||||
let publicEndpoint = false
|
||||
const version = ctx.request.headers[Headers.API_VER]
|
||||
// the path is not authenticated
|
||||
const found = matches(ctx, noAuthOptions)
|
||||
if (found) {
|
||||
console.log("Temp Auth Middleware: Public endpoint found")
|
||||
publicEndpoint = true
|
||||
}
|
||||
try {
|
||||
console.log("Temp Auth Middleware: Parsing cookie")
|
||||
// check the actual user is authenticated first
|
||||
const authCookie = getCookie(ctx, Cookies.Auth)
|
||||
let authenticated = false,
|
||||
user = null,
|
||||
internal = false
|
||||
if (authCookie) {
|
||||
console.log("Temp Auth Middleware: Auth cookie found")
|
||||
let error = null
|
||||
const sessionId = authCookie.sessionId,
|
||||
userId = authCookie.userId
|
||||
console.log("Temp Auth Middleware: Getting session")
|
||||
const session = await getSession(userId, sessionId)
|
||||
if (!session) {
|
||||
error = "No session found"
|
||||
} else {
|
||||
try {
|
||||
console.log("Temp Auth Middleware: Getting user")
|
||||
if (opts && opts.populateUser) {
|
||||
console.log("Temp Auth Middleware: Populate user function found")
|
||||
user = await getUser(
|
||||
userId,
|
||||
session.tenantId,
|
||||
opts.populateUser(ctx)
|
||||
)
|
||||
} else {
|
||||
console.log("Temp Auth Middleware: Getting user from DB")
|
||||
user = await getUser(userId, session.tenantId)
|
||||
}
|
||||
delete user.password
|
||||
console.log("Temp Auth Middleware: User is authenticated")
|
||||
authenticated = true
|
||||
} catch (err) {
|
||||
console.log("Temp Auth Middleware: Holy shit there was an error")
|
||||
error = err
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +69,6 @@ module.exports = (
|
|||
// remove the cookie as the user does not exist anymore
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
} else {
|
||||
console.log("Temp Auth Middleware: No error")
|
||||
// make sure we denote that the session is still in use
|
||||
await updateSessionTTL(session)
|
||||
}
|
||||
|
@ -103,23 +87,14 @@ module.exports = (
|
|||
if (authenticated !== true) {
|
||||
authenticated = false
|
||||
}
|
||||
console.log("Temp Auth Middleware: Auth status", {
|
||||
authenticated,
|
||||
user,
|
||||
internal,
|
||||
version,
|
||||
publicEndpoint,
|
||||
})
|
||||
// isAuthenticated is a function, so use a variable to be able to check authed state
|
||||
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
||||
return next()
|
||||
} catch (err) {
|
||||
console.log("Temp Auth Middleware: Error:", err)
|
||||
// allow configuring for public access
|
||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
||||
finalise(ctx, { authenticated: false, version, publicEndpoint })
|
||||
} else {
|
||||
console.log("Temp Auth Middleware: Throwing error status", err.status)
|
||||
ctx.throw(err.status || 403, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,11 @@ exports.setTenantId = (
|
|||
// processed later in the chain
|
||||
tenantId = user.tenantId || header || tenantId
|
||||
|
||||
// Set the tenantId from the subdomain
|
||||
if (!tenantId) {
|
||||
tenantId = ctx.subdomains && ctx.subdomains[0]
|
||||
}
|
||||
|
||||
if (!tenantId && !allowNoTenant) {
|
||||
ctx.throw(403, "Tenant id not set")
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ const { options } = require("./middleware/passport/jwt")
|
|||
const { createUserEmailView } = require("./db/views")
|
||||
const { Headers } = require("./constants")
|
||||
const { getGlobalDB } = require("./tenancy")
|
||||
const environment = require("./environment")
|
||||
|
||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||
|
||||
|
@ -70,12 +71,19 @@ exports.setCookie = (ctx, value, name = "builder") => {
|
|||
ctx.cookies.set(name)
|
||||
} else {
|
||||
value = jwt.sign(value, options.secretOrKey)
|
||||
ctx.cookies.set(name, value, {
|
||||
|
||||
const config = {
|
||||
maxAge: Number.MAX_SAFE_INTEGER,
|
||||
path: "/",
|
||||
httpOnly: false,
|
||||
overwrite: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (environment.COOKIE_DOMAIN) {
|
||||
config.domain = environment.COOKIE_DOMAIN
|
||||
}
|
||||
|
||||
ctx.cookies.set(name, value, config)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"license": "AGPL-3.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -65,10 +65,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.143-alpha.1",
|
||||
"@budibase/client": "^0.9.143-alpha.1",
|
||||
"@budibase/bbui": "^0.9.144-alpha.0",
|
||||
"@budibase/client": "^0.9.144-alpha.0",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/string-templates": "^0.9.143-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.144-alpha.0",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -3,8 +3,6 @@ import PosthogClient from "./PosthogClient"
|
|||
import IntercomClient from "./IntercomClient"
|
||||
import SentryClient from "./SentryClient"
|
||||
import { Events } from "./constants"
|
||||
import { auth } from "stores/portal"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
const posthog = new PosthogClient(
|
||||
process.env.POSTHOG_TOKEN,
|
||||
|
@ -19,27 +17,13 @@ class AnalyticsHub {
|
|||
}
|
||||
|
||||
async activate() {
|
||||
// Setting the analytics env var off in the backend overrides org/tenant settings
|
||||
const analyticsStatus = await api.get("/api/analytics")
|
||||
const json = await analyticsStatus.json()
|
||||
|
||||
// Multitenancy disabled on the backend
|
||||
// Analytics disabled
|
||||
if (!json.enabled) return
|
||||
|
||||
const tenantId = get(auth).tenantId
|
||||
|
||||
if (tenantId) {
|
||||
const res = await api.get(
|
||||
`/api/global/configs/public?tenantId=${tenantId}`
|
||||
)
|
||||
const orgJson = await res.json()
|
||||
|
||||
// analytics opted out for the tenant
|
||||
if (orgJson.config?.analytics === false) return
|
||||
}
|
||||
|
||||
this.clients.forEach(client => client.init())
|
||||
this.enabled = true
|
||||
}
|
||||
|
||||
identify(id, metadata) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
$: actionProviders = getActionProviderComponents(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId,
|
||||
"RefreshDataProvider"
|
||||
"RefreshDatasource"
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
/>
|
||||
{:else if ["string", "longform", "number"].includes(filter.type)}
|
||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||
{:else if filter.type === "options" || "array"}
|
||||
{:else if ["options", "array"].includes(filter.type)}
|
||||
<Combobox
|
||||
disabled={filter.noValue}
|
||||
options={getFieldOptions(filter.field)}
|
||||
|
|
|
@ -12,7 +12,12 @@
|
|||
}
|
||||
|
||||
// redirect to account portal for authentication in the cloud
|
||||
if (!$auth.user && $admin.cloud && $admin.accountPortalUrl) {
|
||||
if (
|
||||
!$auth.user &&
|
||||
$admin.cloud &&
|
||||
$admin.accountPortalUrl &&
|
||||
!$admin?.checklist?.sso?.checked
|
||||
) {
|
||||
window.location.href = $admin.accountPortalUrl
|
||||
}
|
||||
})
|
||||
|
|
|
@ -7,13 +7,11 @@
|
|||
Divider,
|
||||
Label,
|
||||
Input,
|
||||
Toggle,
|
||||
Dropzone,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { auth, organisation } from "stores/portal"
|
||||
import { auth, organisation, admin } from "stores/portal"
|
||||
import { post } from "builderStore/api"
|
||||
import analytics from "analytics"
|
||||
import { writable } from "svelte/store"
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
|
@ -25,7 +23,6 @@
|
|||
}
|
||||
|
||||
const values = writable({
|
||||
analytics: analytics.enabled,
|
||||
company: $organisation.company,
|
||||
platformUrl: $organisation.platformUrl,
|
||||
logo: $organisation.logoUrl
|
||||
|
@ -57,7 +54,6 @@
|
|||
const config = {
|
||||
company: $values.company ?? "",
|
||||
platformUrl: $values.platformUrl ?? "",
|
||||
analytics: $values.analytics,
|
||||
}
|
||||
// remove logo if required
|
||||
if (!$values.logo) {
|
||||
|
@ -112,34 +108,22 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Heading size="S">Platform</Heading>
|
||||
<Body size="S">Here you can set up general platform settings.</Body>
|
||||
</Layout>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<Label size="L">Platform URL</Label>
|
||||
<Input thin bind:value={$values.platformUrl} />
|
||||
</div>
|
||||
</div>
|
||||
<Divider size="S" />
|
||||
<Layout gap="S" noPadding>
|
||||
{#if !$admin.cloud}
|
||||
<Divider size="S" />
|
||||
<Layout gap="XS" noPadding>
|
||||
<Heading size="S">Analytics</Heading>
|
||||
<Body size="S">
|
||||
If you would like to send analytics that help us make Budibase better,
|
||||
please let us know below.
|
||||
</Body>
|
||||
<Heading size="S">Platform</Heading>
|
||||
<Body size="S">Here you can set up general platform settings.</Body>
|
||||
</Layout>
|
||||
<Toggle
|
||||
text="Send Analytics to Budibase"
|
||||
bind:value={$values.analytics}
|
||||
/>
|
||||
<div>
|
||||
<Button disabled={loading} on:click={saveConfig} cta>Save</Button>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<Label size="L">Platform URL</Label>
|
||||
<Input thin bind:value={$values.platformUrl} />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
{/if}
|
||||
<div>
|
||||
<Button disabled={loading} on:click={saveConfig} cta>Save</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -54,15 +54,13 @@ export function createAuthStore() {
|
|||
if (user) {
|
||||
analytics.activate().then(() => {
|
||||
analytics.identify(user._id, user)
|
||||
if (user.size === "100+" || user.size === "10000+") {
|
||||
analytics.showChat({
|
||||
email: user.email,
|
||||
created_at: user.createdAt || Date.now(),
|
||||
name: user.name,
|
||||
user_id: user._id,
|
||||
tenant: user.tenantId,
|
||||
})
|
||||
}
|
||||
analytics.showChat({
|
||||
email: user.email,
|
||||
created_at: user.createdAt || Date.now(),
|
||||
name: user.name,
|
||||
user_id: user._id,
|
||||
tenant: user.tenantId,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.143-alpha.1",
|
||||
"@budibase/bbui": "^0.9.144-alpha.0",
|
||||
"@budibase/standard-components": "^0.9.139",
|
||||
"@budibase/string-templates": "^0.9.143-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.144-alpha.0",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -25,7 +25,9 @@
|
|||
"lint:fix": "yarn run format && yarn run lint",
|
||||
"initialise": "node scripts/initialise.js",
|
||||
"multi:enable": "node scripts/multiTenancy.js enable",
|
||||
"multi:disable": "node scripts/multiTenancy.js disable"
|
||||
"multi:disable": "node scripts/multiTenancy.js disable",
|
||||
"selfhost:enable": "node scripts/selfhost.js enable",
|
||||
"selfhost:disable": "node scripts/selfhost.js disable"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
|
@ -62,9 +64,9 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.143-alpha.1",
|
||||
"@budibase/client": "^0.9.143-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.143-alpha.1",
|
||||
"@budibase/auth": "^0.9.144-alpha.0",
|
||||
"@budibase/client": "^0.9.144-alpha.0",
|
||||
"@budibase/string-templates": "^0.9.144-alpha.0",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
|
|
|
@ -126,7 +126,7 @@ module PostgresModule {
|
|||
private readonly config: PostgresConfig
|
||||
|
||||
COLUMNS_SQL =
|
||||
"select * from information_schema.columns where table_schema = 'public'"
|
||||
"select * from information_schema.columns where not table_schema = 'information_schema' and not table_schema = 'pg_catalog'"
|
||||
|
||||
PRIMARY_KEYS_SQL = `
|
||||
select tc.table_schema, tc.table_name, kc.column_name as primary_key
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.143-alpha.1",
|
||||
"version": "0.9.144-alpha.0",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -25,8 +25,8 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.143-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.143-alpha.1",
|
||||
"@budibase/auth": "^0.9.144-alpha.0",
|
||||
"@budibase/string-templates": "^0.9.144-alpha.0",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@techpass/passport-openidconnect": "^0.3.0",
|
||||
"aws-sdk": "^2.811.0",
|
||||
|
|
|
@ -11,6 +11,7 @@ const { sendEmail } = require("../../../utilities/email")
|
|||
const { user: userCache } = require("@budibase/auth/cache")
|
||||
const { invalidateSessions } = require("@budibase/auth/sessions")
|
||||
const CouchDB = require("../../../db")
|
||||
const accounts = require("@budibase/auth/accounts")
|
||||
const {
|
||||
getGlobalDB,
|
||||
getTenantId,
|
||||
|
@ -49,10 +50,27 @@ async function saveUser(
|
|||
// make sure another user isn't using the same email
|
||||
let dbUser
|
||||
if (email) {
|
||||
// check budibase users inside the tenant
|
||||
dbUser = await getGlobalUserByEmail(email)
|
||||
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
|
||||
throw "Email address already in use."
|
||||
}
|
||||
|
||||
// check budibase users in other tenants
|
||||
if (env.MULTI_TENANCY) {
|
||||
dbUser = await getTenantUser(email)
|
||||
if (dbUser != null) {
|
||||
throw "Email address already in use."
|
||||
}
|
||||
}
|
||||
|
||||
// check root account users in account portal
|
||||
if (!env.SELF_HOSTED) {
|
||||
const account = await accounts.getAccount(email)
|
||||
if (account) {
|
||||
throw "Email address already in use."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dbUser = await db.get(_id)
|
||||
}
|
||||
|
@ -267,13 +285,22 @@ exports.find = async ctx => {
|
|||
ctx.body = user
|
||||
}
|
||||
|
||||
exports.tenantUserLookup = async ctx => {
|
||||
const id = ctx.params.id
|
||||
// lookup, could be email or userId, either will return a doc
|
||||
// lookup, could be email or userId, either will return a doc
|
||||
const getTenantUser = async identifier => {
|
||||
const db = new CouchDB(PLATFORM_INFO_DB)
|
||||
try {
|
||||
ctx.body = await db.get(id)
|
||||
return await db.get(identifier)
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
exports.tenantUserLookup = async ctx => {
|
||||
const id = ctx.params.id
|
||||
const user = await getTenantUser(id)
|
||||
if (user) {
|
||||
ctx.body = user
|
||||
} else {
|
||||
ctx.throw(400, "No tenant user found.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: #3869D4;
|
||||
color: #3869D4 !important;
|
||||
}
|
||||
|
||||
a img {
|
||||
|
@ -115,8 +115,8 @@
|
|||
border-bottom: 10px solid #3869D4;
|
||||
border-left: 18px solid #3869D4;
|
||||
display: inline-block;
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
color: #FFF !important;
|
||||
text-decoration: none !important;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||
-webkit-text-size-adjust: none;
|
||||
|
|
Loading…
Reference in New Issue