backmerge from master
This commit is contained in:
commit
65af2ed7c2
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.10.12-alpha.26",
|
"version": "2.10.15",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -18,7 +18,7 @@ export enum ViewName {
|
||||||
ROUTING = "screen_routes",
|
ROUTING = "screen_routes",
|
||||||
AUTOMATION_LOGS = "automation_logs",
|
AUTOMATION_LOGS = "automation_logs",
|
||||||
ACCOUNT_BY_EMAIL = "account_by_email",
|
ACCOUNT_BY_EMAIL = "account_by_email",
|
||||||
PLATFORM_USERS_LOWERCASE = "platform_users_lowercase",
|
PLATFORM_USERS_LOWERCASE = "platform_users_lowercase_2",
|
||||||
USER_BY_GROUP = "user_by_group",
|
USER_BY_GROUP = "user_by_group",
|
||||||
APP_BACKUP_BY_TRIGGER = "by_trigger",
|
APP_BACKUP_BY_TRIGGER = "by_trigger",
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,6 +190,10 @@ export const createPlatformUserView = async () => {
|
||||||
if (doc.tenantId) {
|
if (doc.tenantId) {
|
||||||
emit(doc._id.toLowerCase(), doc._id)
|
emit(doc._id.toLowerCase(), doc._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (doc.ssoId) {
|
||||||
|
emit(doc.ssoId, doc._id)
|
||||||
|
}
|
||||||
}`
|
}`
|
||||||
await createPlatformView(viewJs, ViewName.PLATFORM_USERS_LOWERCASE)
|
await createPlatformView(viewJs, ViewName.PLATFORM_USERS_LOWERCASE)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
PlatformUser,
|
PlatformUser,
|
||||||
PlatformUserByEmail,
|
PlatformUserByEmail,
|
||||||
PlatformUserById,
|
PlatformUserById,
|
||||||
|
PlatformUserBySsoId,
|
||||||
User,
|
User,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
@ -45,6 +46,20 @@ function newUserEmailDoc(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newUserSsoIdDoc(
|
||||||
|
ssoId: string,
|
||||||
|
email: string,
|
||||||
|
userId: string,
|
||||||
|
tenantId: string
|
||||||
|
): PlatformUserBySsoId {
|
||||||
|
return {
|
||||||
|
_id: ssoId,
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
tenantId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new user id or email doc if it doesn't exist.
|
* Add a new user id or email doc if it doesn't exist.
|
||||||
*/
|
*/
|
||||||
|
@ -64,11 +79,24 @@ async function addUserDoc(emailOrId: string, newDocFn: () => PlatformUser) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addUser(tenantId: string, userId: string, email: string) {
|
export async function addUser(
|
||||||
await Promise.all([
|
tenantId: string,
|
||||||
|
userId: string,
|
||||||
|
email: string,
|
||||||
|
ssoId?: string
|
||||||
|
) {
|
||||||
|
const promises = [
|
||||||
addUserDoc(userId, () => newUserIdDoc(userId, tenantId)),
|
addUserDoc(userId, () => newUserIdDoc(userId, tenantId)),
|
||||||
addUserDoc(email, () => newUserEmailDoc(userId, email, tenantId)),
|
addUserDoc(email, () => newUserEmailDoc(userId, email, tenantId)),
|
||||||
])
|
]
|
||||||
|
|
||||||
|
if (ssoId) {
|
||||||
|
promises.push(
|
||||||
|
addUserDoc(ssoId, () => newUserSsoIdDoc(ssoId, email, userId, tenantId))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
|
|
|
@ -278,7 +278,12 @@ export class UserDB {
|
||||||
builtUser._rev = response.rev
|
builtUser._rev = response.rev
|
||||||
|
|
||||||
await eventHelpers.handleSaveEvents(builtUser, dbUser)
|
await eventHelpers.handleSaveEvents(builtUser, dbUser)
|
||||||
await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
|
await platform.users.addUser(
|
||||||
|
tenantId,
|
||||||
|
builtUser._id!,
|
||||||
|
builtUser.email,
|
||||||
|
builtUser.ssoId
|
||||||
|
)
|
||||||
await cache.user.invalidateUser(response.id)
|
await cache.user.invalidateUser(response.id)
|
||||||
|
|
||||||
await Promise.all(groupPromises)
|
await Promise.all(groupPromises)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { generator, uuid, quotas } from "."
|
import { generator, quotas, uuid } from "."
|
||||||
import { generateGlobalUserID } from "../../../../src/docIds"
|
import { generateGlobalUserID } from "../../../../src/docIds"
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
|
@ -6,10 +6,11 @@ import {
|
||||||
AccountSSOProviderType,
|
AccountSSOProviderType,
|
||||||
AuthType,
|
AuthType,
|
||||||
CloudAccount,
|
CloudAccount,
|
||||||
Hosting,
|
|
||||||
SSOAccount,
|
|
||||||
CreateAccount,
|
CreateAccount,
|
||||||
CreatePassswordAccount,
|
CreatePassswordAccount,
|
||||||
|
CreateVerifiableSSOAccount,
|
||||||
|
Hosting,
|
||||||
|
SSOAccount,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sample from "lodash/sample"
|
import sample from "lodash/sample"
|
||||||
|
|
||||||
|
@ -68,6 +69,23 @@ export function ssoAccount(account: Account = cloudAccount()): SSOAccount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function verifiableSsoAccount(
|
||||||
|
account: Account = cloudAccount()
|
||||||
|
): SSOAccount {
|
||||||
|
return {
|
||||||
|
...account,
|
||||||
|
authType: AuthType.SSO,
|
||||||
|
oauth2: {
|
||||||
|
accessToken: generator.string(),
|
||||||
|
refreshToken: generator.string(),
|
||||||
|
},
|
||||||
|
pictureUrl: generator.url(),
|
||||||
|
provider: AccountSSOProvider.MICROSOFT,
|
||||||
|
providerType: AccountSSOProviderType.MICROSOFT,
|
||||||
|
thirdPartyProfile: { id: "abc123" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const cloudCreateAccount: CreatePassswordAccount = {
|
export const cloudCreateAccount: CreatePassswordAccount = {
|
||||||
email: "cloud@budibase.com",
|
email: "cloud@budibase.com",
|
||||||
tenantId: "cloud",
|
tenantId: "cloud",
|
||||||
|
@ -91,6 +109,19 @@ export const cloudSSOCreateAccount: CreateAccount = {
|
||||||
profession: "Software Engineer",
|
profession: "Software Engineer",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const cloudVerifiableSSOCreateAccount: CreateVerifiableSSOAccount = {
|
||||||
|
email: "cloud-sso@budibase.com",
|
||||||
|
tenantId: "cloud-sso",
|
||||||
|
hosting: Hosting.CLOUD,
|
||||||
|
authType: AuthType.SSO,
|
||||||
|
tenantName: "cloudsso",
|
||||||
|
name: "Budi Armstrong",
|
||||||
|
size: "10+",
|
||||||
|
profession: "Software Engineer",
|
||||||
|
provider: AccountSSOProvider.MICROSOFT,
|
||||||
|
thirdPartyProfile: { id: "abc123" },
|
||||||
|
}
|
||||||
|
|
||||||
export const selfCreateAccount: CreatePassswordAccount = {
|
export const selfCreateAccount: CreatePassswordAccount = {
|
||||||
email: "self@budibase.com",
|
email: "self@budibase.com",
|
||||||
tenantId: "self",
|
tenantId: "self",
|
||||||
|
|
|
@ -33,6 +33,8 @@ const generateTableBlock = datasource => {
|
||||||
showTitleButton: true,
|
showTitleButton: true,
|
||||||
titleButtonText: "Create row",
|
titleButtonText: "Create row",
|
||||||
titleButtonClickBehaviour: "new",
|
titleButtonClickBehaviour: "new",
|
||||||
|
sidePanelSaveLabel: "Save",
|
||||||
|
sidePanelDeleteLabel: "Delete",
|
||||||
})
|
})
|
||||||
.instanceName(`${datasource.label} - Table block`)
|
.instanceName(`${datasource.label} - Table block`)
|
||||||
return tableBlock
|
return tableBlock
|
||||||
|
|
|
@ -47,28 +47,29 @@
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 16px;
|
--gap: 16px;
|
||||||
|
grid-gap: var(--gap);
|
||||||
}
|
}
|
||||||
.mainSidebar {
|
.mainSidebar {
|
||||||
grid-template-columns: 3fr 1fr;
|
grid-template-columns:
|
||||||
|
calc((100% - var(--gap)) / 4 * 3) /* 75% */
|
||||||
|
calc((100% - var(--gap)) / 4); /* 25% */
|
||||||
}
|
}
|
||||||
.sidebarMain {
|
.sidebarMain {
|
||||||
grid-template-columns: 1fr 3fr;
|
grid-template-columns:
|
||||||
}
|
calc((100% - var(--gap)) / 4) /* 25% */
|
||||||
.oneColumn {
|
calc((100% - var(--gap)) / 4 * 3); /* 75% */
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
.twoColumns {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
.threeColumns {
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
}
|
}
|
||||||
|
.oneColumn,
|
||||||
.columns-1 {
|
.columns-1 {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
.twoColumns,
|
||||||
.columns-2 {
|
.columns-2 {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: repeat(2, calc((100% - var(--gap)) / 2));
|
||||||
|
}
|
||||||
|
.threeColumns {
|
||||||
|
grid-template-columns: repeat(3, calc((100% - var(--gap)) / 3));
|
||||||
}
|
}
|
||||||
.placeholder {
|
.placeholder {
|
||||||
border: 2px dashed var(--spectrum-global-color-gray-600);
|
border: 2px dashed var(--spectrum-global-color-gray-600);
|
||||||
|
|
|
@ -45,8 +45,21 @@
|
||||||
let enrichedSearchColumns
|
let enrichedSearchColumns
|
||||||
let schemaLoaded = false
|
let schemaLoaded = false
|
||||||
|
|
||||||
// Accommodate old config to ensure delete button does not reappear
|
$: deleteLabel = setDeleteLabel(sidePanelDeleteLabel, sidePanelShowDelete)
|
||||||
$: deleteLabel = sidePanelShowDelete === false ? "" : sidePanelDeleteLabel
|
|
||||||
|
const setDeleteLabel = sidePanelDeleteLabel => {
|
||||||
|
// Accommodate old config to ensure delete button does not reappear
|
||||||
|
let labelText = sidePanelShowDelete === false ? "" : sidePanelDeleteLabel
|
||||||
|
|
||||||
|
// Empty text is considered hidden.
|
||||||
|
if (labelText?.trim() === "") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to "Delete" if the value is unset
|
||||||
|
return labelText || "Delete"
|
||||||
|
}
|
||||||
|
|
||||||
$: isDSPlus = dataSource?.type === "table" || dataSource?.type === "viewV2"
|
$: isDSPlus = dataSource?.type === "table" || dataSource?.type === "viewV2"
|
||||||
$: fetchSchema(dataSource)
|
$: fetchSchema(dataSource)
|
||||||
$: enrichSearchColumns(searchColumns, schema).then(
|
$: enrichSearchColumns(searchColumns, schema).then(
|
||||||
|
@ -249,7 +262,7 @@
|
||||||
props={{
|
props={{
|
||||||
dataSource,
|
dataSource,
|
||||||
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
|
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
|
||||||
deleteButtonLabel: deleteLabel, //respect config
|
deleteButtonLabel: deleteLabel,
|
||||||
actionType: "Update",
|
actionType: "Update",
|
||||||
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||||
fields: sidePanelFields || normalFields,
|
fields: sidePanelFields || normalFields,
|
||||||
|
|
|
@ -1567,8 +1567,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"email",
|
"email"
|
||||||
"roles"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"userOutput": {
|
"userOutput": {
|
||||||
|
@ -1639,7 +1638,6 @@
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"email",
|
"email",
|
||||||
"roles",
|
|
||||||
"_id"
|
"_id"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1718,7 +1716,6 @@
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"email",
|
"email",
|
||||||
"roles",
|
|
||||||
"_id"
|
"_id"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1337,7 +1337,6 @@ components:
|
||||||
role ID, e.g. ADMIN.
|
role ID, e.g. ADMIN.
|
||||||
required:
|
required:
|
||||||
- email
|
- email
|
||||||
- roles
|
|
||||||
userOutput:
|
userOutput:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -1398,7 +1397,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- email
|
- email
|
||||||
- roles
|
|
||||||
- _id
|
- _id
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
|
@ -1464,7 +1462,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- email
|
- email
|
||||||
- roles
|
|
||||||
- _id
|
- _id
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
|
|
|
@ -92,7 +92,7 @@ const userSchema = object(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ required: ["email", "roles"] }
|
{ required: ["email"] }
|
||||||
)
|
)
|
||||||
|
|
||||||
const userOutputSchema = {
|
const userOutputSchema = {
|
||||||
|
|
|
@ -15,10 +15,15 @@ function user(body: any): User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapUser(ctx: any): { data: User } {
|
function mapUser(ctx: any) {
|
||||||
return {
|
const body: { data: User; message?: string } = {
|
||||||
data: user(ctx.body),
|
data: user(ctx.body),
|
||||||
}
|
}
|
||||||
|
if (ctx.extra?.message) {
|
||||||
|
body.message = ctx.extra.message
|
||||||
|
delete ctx.extra
|
||||||
|
}
|
||||||
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapUsers(ctx: any): { data: User[] } {
|
function mapUsers(ctx: any): { data: User[] } {
|
||||||
|
|
|
@ -10,6 +10,32 @@ import { search as stringSearch } from "./utils"
|
||||||
import { UserCtx, User } from "@budibase/types"
|
import { UserCtx, User } from "@budibase/types"
|
||||||
import { Next } from "koa"
|
import { Next } from "koa"
|
||||||
import { sdk } from "@budibase/pro"
|
import { sdk } from "@budibase/pro"
|
||||||
|
import { isEqual, cloneDeep } from "lodash"
|
||||||
|
|
||||||
|
function rolesRemoved(base: User, ctx: UserCtx) {
|
||||||
|
return (
|
||||||
|
!isEqual(base.builder, ctx.request.body.builder) ||
|
||||||
|
!isEqual(base.admin, ctx.request.body.admin) ||
|
||||||
|
!isEqual(base.roles, ctx.request.body.roles)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NO_ROLES_MSG =
|
||||||
|
"Roles/admin/builder can only be set on business/enterprise licenses - input ignored."
|
||||||
|
|
||||||
|
async function createUpdateResponse(ctx: UserCtx, user?: User) {
|
||||||
|
const base = cloneDeep(ctx.request.body)
|
||||||
|
ctx = await sdk.publicApi.users.roleCheck(ctx, user)
|
||||||
|
// check the ctx before any updates to it
|
||||||
|
const removed = rolesRemoved(base, ctx)
|
||||||
|
ctx = publicApiUserFix(ctx)
|
||||||
|
const response = await saveGlobalUser(ctx)
|
||||||
|
ctx.body = await getUser(ctx, response._id)
|
||||||
|
if (removed) {
|
||||||
|
ctx.extra = { message: NO_ROLES_MSG }
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
function isLoggedInUser(ctx: UserCtx, user: User) {
|
function isLoggedInUser(ctx: UserCtx, user: User) {
|
||||||
const loggedInId = ctx.user?._id
|
const loggedInId = ctx.user?._id
|
||||||
|
@ -35,9 +61,7 @@ export async function search(ctx: UserCtx, next: Next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(ctx: UserCtx, next: Next) {
|
export async function create(ctx: UserCtx, next: Next) {
|
||||||
ctx = publicApiUserFix(await sdk.publicApi.users.roleCheck(ctx))
|
await createUpdateResponse(ctx)
|
||||||
const response = await saveGlobalUser(ctx)
|
|
||||||
ctx.body = await getUser(ctx, response._id)
|
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +76,7 @@ export async function update(ctx: UserCtx, next: Next) {
|
||||||
...ctx.request.body,
|
...ctx.request.body,
|
||||||
_rev: user._rev,
|
_rev: user._rev,
|
||||||
}
|
}
|
||||||
ctx = publicApiUserFix(await sdk.publicApi.users.roleCheck(ctx, user))
|
await createUpdateResponse(ctx, user)
|
||||||
const response = await saveGlobalUser(ctx)
|
|
||||||
ctx.body = await getUser(ctx, response._id)
|
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ describe("no user role update in free", () => {
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.body.data.roles["app_a"]).toBeUndefined()
|
expect(res.body.data.roles["app_a"]).toBeUndefined()
|
||||||
|
expect(res.body.message).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not allow 'admin' to be updated", async () => {
|
it("should not allow 'admin' to be updated", async () => {
|
||||||
|
@ -77,6 +78,7 @@ describe("no user role update in free", () => {
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.body.data.admin).toBeUndefined()
|
expect(res.body.data.admin).toBeUndefined()
|
||||||
|
expect(res.body.message).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not allow 'builder' to be updated", async () => {
|
it("should not allow 'builder' to be updated", async () => {
|
||||||
|
@ -86,6 +88,7 @@ describe("no user role update in free", () => {
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.body.data.builder).toBeUndefined()
|
expect(res.body.data.builder).toBeUndefined()
|
||||||
|
expect(res.body.message).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -102,6 +105,7 @@ describe("no user role update in business", () => {
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.body.data.roles["app_a"]).toBe("BASIC")
|
expect(res.body.data.roles["app_a"]).toBe("BASIC")
|
||||||
|
expect(res.body.message).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow 'admin' to be updated", async () => {
|
it("should allow 'admin' to be updated", async () => {
|
||||||
|
@ -112,6 +116,7 @@ describe("no user role update in business", () => {
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.body.data.admin.global).toBe(true)
|
expect(res.body.data.admin.global).toBe(true)
|
||||||
|
expect(res.body.message).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow 'builder' to be updated", async () => {
|
it("should allow 'builder' to be updated", async () => {
|
||||||
|
@ -122,5 +127,6 @@ describe("no user role update in business", () => {
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.body.data.builder.global).toBe(true)
|
expect(res.body.data.builder.global).toBe(true)
|
||||||
|
expect(res.body.message).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -613,7 +613,7 @@ export interface components {
|
||||||
global?: boolean;
|
global?: boolean;
|
||||||
};
|
};
|
||||||
/** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */
|
/** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */
|
||||||
roles: { [key: string]: string };
|
roles?: { [key: string]: string };
|
||||||
};
|
};
|
||||||
userOutput: {
|
userOutput: {
|
||||||
data: {
|
data: {
|
||||||
|
@ -643,7 +643,7 @@ export interface components {
|
||||||
global?: boolean;
|
global?: boolean;
|
||||||
};
|
};
|
||||||
/** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */
|
/** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */
|
||||||
roles: { [key: string]: string };
|
roles?: { [key: string]: string };
|
||||||
/** @description The ID of the user. */
|
/** @description The ID of the user. */
|
||||||
_id: string;
|
_id: string;
|
||||||
};
|
};
|
||||||
|
@ -676,7 +676,7 @@ export interface components {
|
||||||
global?: boolean;
|
global?: boolean;
|
||||||
};
|
};
|
||||||
/** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */
|
/** @description Contains the roles of the user per app (assuming they are not a builder user). This field can only be set on a business or enterprise license. */
|
||||||
roles: { [key: string]: string };
|
roles?: { [key: string]: string };
|
||||||
/** @description The ID of the user. */
|
/** @description The ID of the user. */
|
||||||
_id: string;
|
_id: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
|
@ -36,5 +36,8 @@ export function publicApiUserFix(ctx: UserCtx) {
|
||||||
if (!ctx.request.body._id && ctx.params.userId) {
|
if (!ctx.request.body._id && ctx.params.userId) {
|
||||||
ctx.request.body._id = ctx.params.userId
|
ctx.request.body._id = ctx.params.userId
|
||||||
}
|
}
|
||||||
|
if (!ctx.request.body.roles) {
|
||||||
|
ctx.request.body.roles = {}
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Account } from "../../documents"
|
import { Account, AccountSSOProvider } from "../../documents"
|
||||||
import { Hosting } from "../../sdk"
|
import { Hosting } from "../../sdk"
|
||||||
|
|
||||||
export interface CreateAccountRequest {
|
export interface CreateAccountRequest {
|
||||||
|
@ -11,6 +11,8 @@ export interface CreateAccountRequest {
|
||||||
tenantName?: string
|
tenantName?: string
|
||||||
name?: string
|
name?: string
|
||||||
password: string
|
password: string
|
||||||
|
provider?: AccountSSOProvider
|
||||||
|
thirdPartyProfile: object
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchAccountsRequest {
|
export interface SearchAccountsRequest {
|
||||||
|
|
|
@ -61,6 +61,7 @@ export interface CreateAdminUserRequest {
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
tenantId: string
|
tenantId: string
|
||||||
|
ssoId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateAdminUserResponse {
|
export interface CreateAdminUserResponse {
|
||||||
|
|
|
@ -20,6 +20,11 @@ export interface CreatePassswordAccount extends CreateAccount {
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateVerifiableSSOAccount extends CreateAccount {
|
||||||
|
provider?: AccountSSOProvider
|
||||||
|
thirdPartyProfile?: any
|
||||||
|
}
|
||||||
|
|
||||||
export const isCreatePasswordAccount = (
|
export const isCreatePasswordAccount = (
|
||||||
account: CreateAccount
|
account: CreateAccount
|
||||||
): account is CreatePassswordAccount => account.authType === AuthType.PASSWORD
|
): account is CreatePassswordAccount => account.authType === AuthType.PASSWORD
|
||||||
|
@ -50,6 +55,8 @@ export interface Account extends CreateAccount {
|
||||||
licenseKeyActivatedAt?: number
|
licenseKeyActivatedAt?: number
|
||||||
licenseRequestedAt?: number
|
licenseRequestedAt?: number
|
||||||
licenseOverrides?: LicenseOverrides
|
licenseOverrides?: LicenseOverrides
|
||||||
|
provider?: AccountSSOProvider
|
||||||
|
providerType?: AccountSSOProviderType
|
||||||
quotaUsage?: QuotaUsage
|
quotaUsage?: QuotaUsage
|
||||||
offlineLicenseToken?: string
|
offlineLicenseToken?: string
|
||||||
}
|
}
|
||||||
|
@ -87,6 +94,13 @@ export enum AccountSSOProvider {
|
||||||
MICROSOFT = "microsoft",
|
MICROSOFT = "microsoft",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const verifiableSSOProviders: AccountSSOProvider[] = [
|
||||||
|
AccountSSOProvider.MICROSOFT,
|
||||||
|
]
|
||||||
|
export function isVerifiableSSOProvider(provider: AccountSSOProvider): boolean {
|
||||||
|
return verifiableSSOProviders.includes(provider)
|
||||||
|
}
|
||||||
|
|
||||||
export interface AccountSSO {
|
export interface AccountSSO {
|
||||||
provider: AccountSSOProvider
|
provider: AccountSSOProvider
|
||||||
providerType: AccountSSOProviderType
|
providerType: AccountSSOProviderType
|
||||||
|
|
|
@ -55,6 +55,7 @@ export interface User extends Document {
|
||||||
userGroups?: string[]
|
userGroups?: string[]
|
||||||
onboardedAt?: string
|
onboardedAt?: string
|
||||||
scimInfo?: { isSync: true } & Record<string, any>
|
scimInfo?: { isSync: true } & Record<string, any>
|
||||||
|
ssoId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserStatus {
|
export enum UserStatus {
|
||||||
|
|
|
@ -15,4 +15,16 @@ export interface PlatformUserById extends Document {
|
||||||
tenantId: string
|
tenantId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlatformUser = PlatformUserByEmail | PlatformUserById
|
/**
|
||||||
|
* doc id is a unique SSO provider ID for the user
|
||||||
|
*/
|
||||||
|
export interface PlatformUserBySsoId extends Document {
|
||||||
|
tenantId: string
|
||||||
|
userId: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlatformUser =
|
||||||
|
| PlatformUserByEmail
|
||||||
|
| PlatformUserById
|
||||||
|
| PlatformUserBySsoId
|
||||||
|
|
|
@ -95,7 +95,7 @@ const parseBooleanParam = (param: any) => {
|
||||||
export const adminUser = async (
|
export const adminUser = async (
|
||||||
ctx: Ctx<CreateAdminUserRequest, CreateAdminUserResponse>
|
ctx: Ctx<CreateAdminUserRequest, CreateAdminUserResponse>
|
||||||
) => {
|
) => {
|
||||||
const { email, password, tenantId } = ctx.request.body
|
const { email, password, tenantId, ssoId } = ctx.request.body
|
||||||
|
|
||||||
if (await platform.tenants.exists(tenantId)) {
|
if (await platform.tenants.exists(tenantId)) {
|
||||||
ctx.throw(403, "Organisation already exists.")
|
ctx.throw(403, "Organisation already exists.")
|
||||||
|
@ -136,6 +136,7 @@ export const adminUser = async (
|
||||||
global: true,
|
global: true,
|
||||||
},
|
},
|
||||||
tenantId,
|
tenantId,
|
||||||
|
ssoId,
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
||||||
|
|
|
@ -14,6 +14,7 @@ function buildAdminInitValidation() {
|
||||||
email: Joi.string().required(),
|
email: Joi.string().required(),
|
||||||
password: Joi.string(),
|
password: Joi.string(),
|
||||||
tenantId: Joi.string().required(),
|
tenantId: Joi.string().required(),
|
||||||
|
ssoId: Joi.string(),
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
.unknown(false)
|
.unknown(false)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../packages/server/specs/openapi.json
|
|
|
@ -1 +0,0 @@
|
||||||
../packages/server/specs/openapi.yaml
|
|
Loading…
Reference in New Issue