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:
Antoine Moreaux
2024-10-21 20:07:08 +02:00
committed by GitHub
parent 11c3f1c399
commit 0f0a7966b1
132 changed files with 5245 additions and 306 deletions

View File

@ -71,6 +71,7 @@ export type AuthProviders = {
magicLink: Scalars['Boolean']['output'];
microsoft: Scalars['Boolean']['output'];
password: Scalars['Boolean']['output'];
sso: Scalars['Boolean']['output'];
};
export type AuthToken = {
@ -148,6 +149,7 @@ export enum CaptchaDriverType {
export type ClientConfig = {
__typename?: 'ClientConfig';
analyticsEnabled: Scalars['Boolean']['output'];
api: ApiConfig;
authProviders: AuthProviders;
billing: Billing;
@ -275,6 +277,15 @@ export type DeleteServerlessFunctionInput = {
id: Scalars['ID']['input'];
};
export type DeleteSsoInput = {
identityProviderId: Scalars['String']['input'];
};
export type DeleteSsoOutput = {
__typename?: 'DeleteSsoOutput';
identityProviderId: Scalars['String']['output'];
};
/** Schema update on a table */
export enum DistantTableUpdate {
ColumnsAdded = 'COLUMNS_ADDED',
@ -283,6 +294,20 @@ export enum DistantTableUpdate {
TableDeleted = 'TABLE_DELETED'
}
export type EditSsoInput = {
id: Scalars['String']['input'];
status: SsoIdentityProviderStatus;
};
export type EditSsoOutput = {
__typename?: 'EditSsoOutput';
id: Scalars['String']['output'];
issuer: Scalars['String']['output'];
name: Scalars['String']['output'];
status: SsoIdentityProviderStatus;
type: IdpType;
};
export type EmailPasswordResetLink = {
__typename?: 'EmailPasswordResetLink';
/** Boolean that confirms query was dispatched */
@ -372,6 +397,20 @@ export enum FileFolder {
WorkspaceLogo = 'WorkspaceLogo'
}
export type FindAvailableSsoidpInput = {
email: Scalars['String']['input'];
};
export type FindAvailableSsoidpOutput = {
__typename?: 'FindAvailableSSOIDPOutput';
id: Scalars['String']['output'];
issuer: Scalars['String']['output'];
name: Scalars['String']['output'];
status: SsoIdentityProviderStatus;
type: IdpType;
workspace: WorkspaceNameAndId;
};
export type FindManyRemoteTablesInput = {
/** The id of the remote server. */
id: Scalars['ID']['input'];
@ -385,6 +424,33 @@ export type FullName = {
lastName: Scalars['String']['output'];
};
export type GenerateJwt = GenerateJwtOutputWithAuthTokens | GenerateJwtOutputWithSsoauth;
export type GenerateJwtOutputWithAuthTokens = {
__typename?: 'GenerateJWTOutputWithAuthTokens';
authTokens: AuthTokens;
reason: Scalars['String']['output'];
success: Scalars['Boolean']['output'];
};
export type GenerateJwtOutputWithSsoauth = {
__typename?: 'GenerateJWTOutputWithSSOAUTH';
availableSSOIDPs: Array<FindAvailableSsoidpOutput>;
reason: Scalars['String']['output'];
success: Scalars['Boolean']['output'];
};
export type GetAuthorizationUrlInput = {
identityProviderId: Scalars['String']['input'];
};
export type GetAuthorizationUrlOutput = {
__typename?: 'GetAuthorizationUrlOutput';
authorizationURL: Scalars['String']['output'];
id: Scalars['String']['output'];
type: Scalars['String']['output'];
};
export type GetServerlessFunctionSourceCodeInput = {
/** The id of the function. */
id: Scalars['ID']['input'];
@ -392,6 +458,11 @@ export type GetServerlessFunctionSourceCodeInput = {
version?: Scalars['String']['input'];
};
export enum IdpType {
Oidc = 'OIDC',
Saml = 'SAML'
}
export type IndexConnection = {
__typename?: 'IndexConnection';
/** Array of edges. */
@ -461,12 +532,14 @@ export type Mutation = {
authorizeApp: AuthorizeApp;
challenge: LoginToken;
checkoutSession: SessionEntity;
createOIDCIdentityProvider: SetupSsoOutput;
createOneAppToken: AppToken;
createOneField: Field;
createOneObject: Object;
createOneRelation: Relation;
createOneRemoteServer: RemoteServer;
createOneServerlessFunction: ServerlessFunction;
createSAMLIdentityProvider: SetupSsoOutput;
deactivateWorkflowVersion: Scalars['Boolean']['output'];
deleteCurrentWorkspace: Workspace;
deleteOneField: Field;
@ -474,16 +547,20 @@ export type Mutation = {
deleteOneRelation: Relation;
deleteOneRemoteServer: RemoteServer;
deleteOneServerlessFunction: ServerlessFunction;
deleteSSOIdentityProvider: DeleteSsoOutput;
deleteUser: User;
deleteWorkspaceInvitation: Scalars['String']['output'];
disablePostgresProxy: PostgresCredentials;
editSSOIdentityProvider: EditSsoOutput;
emailPasswordResetLink: EmailPasswordResetLink;
enablePostgresProxy: PostgresCredentials;
exchangeAuthorizationCode: ExchangeAuthCode;
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
findAvailableSSOIdentityProviders: Array<FindAvailableSsoidpOutput>;
generateApiKeyToken: ApiKeyToken;
generateJWT: AuthTokens;
generateJWT: GenerateJwt;
generateTransientToken: TransientToken;
getAuthorizationUrl: GetAuthorizationUrlOutput;
impersonate: Verify;
publishServerlessFunction: ServerlessFunction;
renewToken: AuthTokens;
@ -551,6 +628,11 @@ export type MutationCheckoutSessionArgs = {
};
export type MutationCreateOidcIdentityProviderArgs = {
input: SetupOidcSsoInput;
};
export type MutationCreateOneAppTokenArgs = {
input: CreateOneAppTokenInput;
};
@ -581,6 +663,11 @@ export type MutationCreateOneServerlessFunctionArgs = {
};
export type MutationCreateSamlIdentityProviderArgs = {
input: SetupSamlSsoInput;
};
export type MutationDeactivateWorkflowVersionArgs = {
workflowVersionId: Scalars['String']['input'];
};
@ -611,11 +698,21 @@ export type MutationDeleteOneServerlessFunctionArgs = {
};
export type MutationDeleteSsoIdentityProviderArgs = {
input: DeleteSsoInput;
};
export type MutationDeleteWorkspaceInvitationArgs = {
appTokenId: Scalars['String']['input'];
};
export type MutationEditSsoIdentityProviderArgs = {
input: EditSsoInput;
};
export type MutationEmailPasswordResetLinkArgs = {
email: Scalars['String']['input'];
};
@ -633,6 +730,11 @@ export type MutationExecuteOneServerlessFunctionArgs = {
};
export type MutationFindAvailableSsoIdentityProvidersArgs = {
input: FindAvailableSsoidpInput;
};
export type MutationGenerateApiKeyTokenArgs = {
apiKeyId: Scalars['String']['input'];
expiresAt: Scalars['String']['input'];
@ -644,6 +746,11 @@ export type MutationGenerateJwtArgs = {
};
export type MutationGetAuthorizationUrlArgs = {
input: GetAuthorizationUrlInput;
};
export type MutationImpersonateArgs = {
userId: Scalars['String']['input'];
};
@ -865,6 +972,7 @@ export type Query = {
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
index: Index;
indexMetadatas: IndexConnection;
listSSOIdentityProvidersByWorkspaceId: Array<FindAvailableSsoidpOutput>;
object: Object;
objects: ObjectConnection;
relation: Relation;
@ -1091,6 +1199,12 @@ export type RunWorkflowVersionInput = {
workflowVersionId: Scalars['String']['input'];
};
export enum SsoIdentityProviderStatus {
Active = 'Active',
Error = 'Error',
Inactive = 'Inactive'
}
export type SendInvitationsOutput = {
__typename?: 'SendInvitationsOutput';
errors: Array<Scalars['String']['output']>;
@ -1179,6 +1293,31 @@ export type SessionEntity = {
url?: Maybe<Scalars['String']['output']>;
};
export type SetupOidcSsoInput = {
clientID: Scalars['String']['input'];
clientSecret: Scalars['String']['input'];
issuer: Scalars['String']['input'];
name: Scalars['String']['input'];
};
export type SetupSamlSsoInput = {
certificate: Scalars['String']['input'];
fingerprint?: InputMaybe<Scalars['String']['input']>;
id: Scalars['String']['input'];
issuer: Scalars['String']['input'];
name: Scalars['String']['input'];
ssoURL: Scalars['String']['input'];
};
export type SetupSsoOutput = {
__typename?: 'SetupSsoOutput';
id: Scalars['String']['output'];
issuer: Scalars['String']['output'];
name: Scalars['String']['output'];
status: SsoIdentityProviderStatus;
type: IdpType;
};
/** Sort Directions */
export enum SortDirection {
Asc = 'ASC',
@ -1368,11 +1507,13 @@ export type UpdateWorkspaceInput = {
displayName?: InputMaybe<Scalars['String']['input']>;
domainName?: InputMaybe<Scalars['String']['input']>;
inviteHash?: InputMaybe<Scalars['String']['input']>;
isPublicInviteLinkEnabled?: InputMaybe<Scalars['Boolean']['input']>;
logo?: InputMaybe<Scalars['String']['input']>;
};
export type User = {
__typename?: 'User';
analyticsTinybirdJwt?: Maybe<Scalars['String']['output']>;
canImpersonate: Scalars['Boolean']['output'];
createdAt: Scalars['DateTime']['output'];
defaultAvatarUrl?: Maybe<Scalars['String']['output']>;
@ -1467,6 +1608,7 @@ export type Workspace = {
featureFlags?: Maybe<Array<FeatureFlag>>;
id: Scalars['UUID']['output'];
inviteHash?: Maybe<Scalars['String']['output']>;
isPublicInviteLinkEnabled: Scalars['Boolean']['output'];
logo?: Maybe<Scalars['String']['output']>;
metadataVersion: Scalars['Float']['output'];
updatedAt: Scalars['DateTime']['output'];
@ -1539,6 +1681,12 @@ export enum WorkspaceMemberTimeFormatEnum {
System = 'SYSTEM'
}
export type WorkspaceNameAndId = {
__typename?: 'WorkspaceNameAndId';
displayName?: Maybe<Scalars['String']['output']>;
id: Scalars['String']['output'];
};
export type Field = {
__typename?: 'field';
createdAt: Scalars['DateTime']['output'];