feat(sso): allow to use OIDC and SAML (#7246)
## What it does ### Backend - [x] Add a mutation to create OIDC and SAML configuration - [x] Add a mutation to delete an SSO config - [x] Add a feature flag to toggle SSO - [x] Add a mutation to activate/deactivate an SSO config - [x] Add a mutation to delete an SSO config - [x] Add strategy to use OIDC or SAML - [ ] Improve error management ### Frontend - [x] Add section "security" in settings - [x] Add page to list SSO configurations - [x] Add page and forms to create OIDC or SAML configuration - [x] Add field to "connect with SSO" in the signin/signup process - [x] Trigger auth when a user switch to a workspace with SSO enable - [x] Add an option on the security page to activate/deactivate the global invitation link - [ ] Add new Icons for SSO Identity Providers (okta, Auth0, Azure, Microsoft) --------- Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -60,6 +60,11 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsSSOEnabled,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsGmailSendEmailScopeEnabled,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddWorkspaceSSOIdentityProvider1727181198403
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddWorkspaceSSOIdentityProvider1727181198403';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TYPE "core"."idp_type_enum" AS ENUM('OIDC', 'SAML');
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE "core"."workspaceSSOIdentityProvider" (
|
||||
"id" uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||
"name" varchar NULL,
|
||||
"workspaceId" uuid NOT NULL,
|
||||
"createdAt" timestamptz DEFAULT now() NOT NULL,
|
||||
"updatedAt" timestamptz DEFAULT now() NOT NULL,
|
||||
"type" "core"."idp_type_enum" DEFAULT 'OIDC' NOT NULL,
|
||||
"issuer" varchar NOT NULL,
|
||||
"ssoURL" varchar NULL,
|
||||
"clientID" varchar NULL,
|
||||
"clientSecret" varchar NULL,
|
||||
"certificate" varchar NULL,
|
||||
"fingerprint" varchar NULL,
|
||||
"status" varchar DEFAULT 'Active' NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "core"."workspaceSSOIdentityProvider"
|
||||
ADD CONSTRAINT "FK_workspaceId"
|
||||
FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id")
|
||||
ON DELETE CASCADE;
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD CONSTRAINT "CHK_OIDC" CHECK (
|
||||
("type" = 'OIDC' AND "clientID" IS NOT NULL AND "clientSecret" IS NOT NULL) OR "type" = 'SAML'
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD CONSTRAINT "CHK_SAML" CHECK (
|
||||
("type" = 'SAML' AND "ssoURL" IS NOT NULL AND "certificate" IS NOT NULL) OR "type" = 'OIDC'
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "core"."workspaceSSOIdentityProvider"
|
||||
DROP CONSTRAINT "FK_workspaceId";
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
DROP TABLE "core"."workspaceSSOIdentityProvider";
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
DROP TYPE "core"."idp_type_enum";
|
||||
`);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddIsPublicInviteLinkEnabledOnWorkspace1728986317196
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddIsPublicInviteLinkEnabledOnWorkspace1728986317196';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "isPublicInviteLinkEnabled" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "isPublicInviteLinkEnabled"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
@ -36,6 +37,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
BillingSubscription,
|
||||
BillingSubscriptionItem,
|
||||
PostgresCredentials,
|
||||
WorkspaceSSOIdentityProvider,
|
||||
],
|
||||
metadataTableName: '_typeorm_generated_columns_and_materialized_views',
|
||||
ssl: environmentService.get('PG_SSL_ALLOW_SELF_SIGNED')
|
||||
|
||||
Reference in New Issue
Block a user