diff --git a/packages/backend-core/src/db/views.ts b/packages/backend-core/src/db/views.ts index 7f5ef29a0a..f0980ad217 100644 --- a/packages/backend-core/src/db/views.ts +++ b/packages/backend-core/src/db/views.ts @@ -190,6 +190,10 @@ export const createPlatformUserView = async () => { if (doc.tenantId) { emit(doc._id.toLowerCase(), doc._id) } + + if (doc.ssoId) { + emit(doc.ssoId, doc._id) + } }` await createPlatformView(viewJs, ViewName.PLATFORM_USERS_LOWERCASE) } diff --git a/packages/backend-core/src/platform/users.ts b/packages/backend-core/src/platform/users.ts index c65a7e0ec4..684dd40052 100644 --- a/packages/backend-core/src/platform/users.ts +++ b/packages/backend-core/src/platform/users.ts @@ -4,7 +4,7 @@ import env from "../environment" import { PlatformUser, PlatformUserByEmail, - PlatformUserById, + PlatformUserById, PlatformUserBySsoId, User, } from "@budibase/types" @@ -45,6 +45,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. */ @@ -64,11 +78,17 @@ async function addUserDoc(emailOrId: string, newDocFn: () => PlatformUser) { } } -export async function addUser(tenantId: string, userId: string, email: string) { - await Promise.all([ +export async function addUser(tenantId: string, userId: string, email: string, ssoId?: string) { + const promises = [ addUserDoc(userId, () => newUserIdDoc(userId, tenantId)), addUserDoc(email, () => newUserEmailDoc(userId, email, tenantId)), - ]) + ] + + if (ssoId) { + promises.push(addUserDoc(ssoId, () => newUserSsoIdDoc(ssoId, email, userId, tenantId))) + } + + await Promise.all(promises) } // DELETE diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index c288540f35..b39fbb5d61 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -278,7 +278,7 @@ export class UserDB { builtUser._rev = response.rev 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 Promise.all(groupPromises) diff --git a/packages/types/src/api/account/accounts.ts b/packages/types/src/api/account/accounts.ts index bb3419c5d1..1be506e14e 100644 --- a/packages/types/src/api/account/accounts.ts +++ b/packages/types/src/api/account/accounts.ts @@ -1,4 +1,4 @@ -import { Account } from "../../documents" +import { Account, AccountSSOProvider } from "../../documents" import { Hosting } from "../../sdk" export interface CreateAccountRequest { @@ -11,6 +11,8 @@ export interface CreateAccountRequest { tenantName?: string name?: string password: string + provider?: AccountSSOProvider + thirdPartyProfile: object } export interface SearchAccountsRequest { diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts index 5321aa7e08..c22ed3572b 100644 --- a/packages/types/src/documents/account/account.ts +++ b/packages/types/src/documents/account/account.ts @@ -20,6 +20,11 @@ export interface CreatePassswordAccount extends CreateAccount { password: string } +export interface CreateVerifiableSSOAccount extends CreateAccount { + provider?: AccountSSOProvider + thirdPartyProfile?: any +} + export const isCreatePasswordAccount = ( account: CreateAccount ): account is CreatePassswordAccount => account.authType === AuthType.PASSWORD @@ -50,6 +55,8 @@ export interface Account extends CreateAccount { licenseKeyActivatedAt?: number licenseRequestedAt?: number licenseOverrides?: LicenseOverrides + provider?: AccountSSOProvider + providerType?: AccountSSOProviderType quotaUsage?: QuotaUsage offlineLicenseToken?: string } diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index 2ce714801d..9769661cd5 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -55,6 +55,7 @@ export interface User extends Document { userGroups?: string[] onboardedAt?: string scimInfo?: { isSync: true } & Record + ssoId?: string } export enum UserStatus { diff --git a/packages/types/src/documents/platform/users.ts b/packages/types/src/documents/platform/users.ts index 46cc44b31d..42754b5e5a 100644 --- a/packages/types/src/documents/platform/users.ts +++ b/packages/types/src/documents/platform/users.ts @@ -15,4 +15,13 @@ export interface PlatformUserById extends Document { 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 diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 6d3a13aa4e..8968ebdcd0 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -95,7 +95,8 @@ const parseBooleanParam = (param: any) => { export const adminUser = async ( ctx: Ctx ) => { - const { email, password, tenantId } = ctx.request.body + // @ts-ignore + const { email, password, tenantId, ssoId } = ctx.request.body if (await platform.tenants.exists(tenantId)) { ctx.throw(403, "Organisation already exists.") @@ -136,6 +137,8 @@ export const adminUser = async ( global: true, }, tenantId, + // @ts-ignore + ssoId, } try { // always bust checklist beforehand, if an error occurs but can proceed, don't get diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index 47e76c17be..a57f7834ac 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -14,6 +14,7 @@ function buildAdminInitValidation() { email: Joi.string().required(), password: Joi.string(), tenantId: Joi.string().required(), + ssoId: Joi.string(), }) .required() .unknown(false)