feat(): enable custom domain usage (#9911)
# Content - Introduce the `workspaceUrls` property. It contains two sub-properties: `customUrl, subdomainUrl`. These endpoints are used to access the workspace. Even if the `workspaceUrls` is invalid for multiple reasons, the `subdomainUrl` remains valid. - Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL computation on the frontend part. - Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
This commit is contained in:
@ -81,7 +81,6 @@ module.exports = {
|
|||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
'@typescript-eslint/no-empty-function': 'off',
|
|
||||||
'@typescript-eslint/no-empty-interface': [
|
'@typescript-eslint/no-empty-interface': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|||||||
@ -18,7 +18,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
namingConvention: { enumValues: 'keep' },
|
namingConvention: { enumValues: 'keep' },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -113,7 +113,7 @@ export type AvailableWorkspaceOutput = {
|
|||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
logo?: Maybe<Scalars['String']['output']>;
|
logo?: Maybe<Scalars['String']['output']>;
|
||||||
sso: Array<SsoConnection>;
|
sso: Array<SsoConnection>;
|
||||||
subdomain: Scalars['String']['output'];
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Billing = {
|
export type Billing = {
|
||||||
@ -475,19 +475,11 @@ export type EnvironmentVariable = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum EnvironmentVariablesGroup {
|
export enum EnvironmentVariablesGroup {
|
||||||
Analytics = 'Analytics',
|
|
||||||
Authentication = 'Authentication',
|
Authentication = 'Authentication',
|
||||||
Billing = 'Billing',
|
|
||||||
Cache = 'Cache',
|
|
||||||
Database = 'Database',
|
|
||||||
Email = 'Email',
|
Email = 'Email',
|
||||||
Frontend = 'Frontend',
|
|
||||||
Logging = 'Logging',
|
Logging = 'Logging',
|
||||||
Other = 'Other',
|
Other = 'Other',
|
||||||
QueueConfig = 'QueueConfig',
|
|
||||||
ServerConfig = 'ServerConfig',
|
ServerConfig = 'ServerConfig',
|
||||||
Storage = 'Storage',
|
|
||||||
Support = 'Support',
|
|
||||||
Workspace = 'Workspace'
|
Workspace = 'Workspace'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,7 +698,7 @@ export enum IdentityProviderType {
|
|||||||
export type ImpersonateOutput = {
|
export type ImpersonateOutput = {
|
||||||
__typename?: 'ImpersonateOutput';
|
__typename?: 'ImpersonateOutput';
|
||||||
loginToken: AuthToken;
|
loginToken: AuthToken;
|
||||||
workspace: WorkspaceSubdomainAndId;
|
workspace: WorkspaceUrlsAndId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Index = {
|
export type Index = {
|
||||||
@ -1360,10 +1352,9 @@ export type PublicWorkspaceDataOutput = {
|
|||||||
__typename?: 'PublicWorkspaceDataOutput';
|
__typename?: 'PublicWorkspaceDataOutput';
|
||||||
authProviders: AuthProviders;
|
authProviders: AuthProviders;
|
||||||
displayName?: Maybe<Scalars['String']['output']>;
|
displayName?: Maybe<Scalars['String']['output']>;
|
||||||
hostname?: Maybe<Scalars['String']['output']>;
|
|
||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
logo?: Maybe<Scalars['String']['output']>;
|
logo?: Maybe<Scalars['String']['output']>;
|
||||||
subdomain: Scalars['String']['output'];
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PublishServerlessFunctionInput = {
|
export type PublishServerlessFunctionInput = {
|
||||||
@ -1394,7 +1385,7 @@ export type Query = {
|
|||||||
getHostnameDetails?: Maybe<CustomHostnameDetails>;
|
getHostnameDetails?: Maybe<CustomHostnameDetails>;
|
||||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||||
getProductPrices: BillingProductPricesOutput;
|
getProductPrices: BillingProductPricesOutput;
|
||||||
getPublicWorkspaceDataBySubdomain: PublicWorkspaceDataOutput;
|
getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput;
|
||||||
getRoles: Array<Role>;
|
getRoles: Array<Role>;
|
||||||
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']['output']>;
|
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']['output']>;
|
||||||
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
|
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
|
||||||
@ -1792,7 +1783,7 @@ export type SetupSsoOutput = {
|
|||||||
export type SignUpOutput = {
|
export type SignUpOutput = {
|
||||||
__typename?: 'SignUpOutput';
|
__typename?: 'SignUpOutput';
|
||||||
loginToken: AuthToken;
|
loginToken: AuthToken;
|
||||||
workspace: WorkspaceSubdomainAndId;
|
workspace: WorkspaceUrlsAndId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SubscriptionInterval {
|
export enum SubscriptionInterval {
|
||||||
@ -1994,7 +1985,7 @@ export type User = {
|
|||||||
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
||||||
canImpersonate: Scalars['Boolean']['output'];
|
canImpersonate: Scalars['Boolean']['output'];
|
||||||
createdAt: Scalars['DateTime']['output'];
|
createdAt: Scalars['DateTime']['output'];
|
||||||
currentWorkspace?: Maybe<Workspace>;
|
currentWorkspace: Workspace;
|
||||||
defaultAvatarUrl?: Maybe<Scalars['String']['output']>;
|
defaultAvatarUrl?: Maybe<Scalars['String']['output']>;
|
||||||
deletedAt?: Maybe<Scalars['DateTime']['output']>;
|
deletedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||||
disabled?: Maybe<Scalars['Boolean']['output']>;
|
disabled?: Maybe<Scalars['Boolean']['output']>;
|
||||||
@ -2126,6 +2117,7 @@ export type Workspace = {
|
|||||||
subdomain: Scalars['String']['output'];
|
subdomain: Scalars['String']['output'];
|
||||||
updatedAt: Scalars['DateTime']['output'];
|
updatedAt: Scalars['DateTime']['output'];
|
||||||
workspaceMembersCount?: Maybe<Scalars['Float']['output']>;
|
workspaceMembersCount?: Maybe<Scalars['Float']['output']>;
|
||||||
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum WorkspaceActivationStatus {
|
export enum WorkspaceActivationStatus {
|
||||||
@ -2202,10 +2194,16 @@ export type WorkspaceNameAndId = {
|
|||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceSubdomainAndId = {
|
export type WorkspaceUrls = {
|
||||||
__typename?: 'WorkspaceSubdomainAndId';
|
__typename?: 'workspaceUrls';
|
||||||
|
customUrl?: Maybe<Scalars['String']['output']>;
|
||||||
|
subdomainUrl: Scalars['String']['output'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceUrlsAndId = {
|
||||||
|
__typename?: 'workspaceUrlsAndId';
|
||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
subdomain: Scalars['String']['output'];
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RemoteServerFieldsFragment = { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, label: string, userMappingOptions?: { __typename?: 'UserMappingOptionsUser', user?: string | null } | null };
|
export type RemoteServerFieldsFragment = { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, label: string, userMappingOptions?: { __typename?: 'UserMappingOptionsUser', user?: string | null } | null };
|
||||||
|
|||||||
@ -106,7 +106,7 @@ export type AvailableWorkspaceOutput = {
|
|||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
logo?: Maybe<Scalars['String']>;
|
logo?: Maybe<Scalars['String']>;
|
||||||
sso: Array<SsoConnection>;
|
sso: Array<SsoConnection>;
|
||||||
subdomain: Scalars['String'];
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Billing = {
|
export type Billing = {
|
||||||
@ -407,19 +407,11 @@ export type EnvironmentVariable = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum EnvironmentVariablesGroup {
|
export enum EnvironmentVariablesGroup {
|
||||||
Analytics = 'Analytics',
|
|
||||||
Authentication = 'Authentication',
|
Authentication = 'Authentication',
|
||||||
Billing = 'Billing',
|
|
||||||
Cache = 'Cache',
|
|
||||||
Database = 'Database',
|
|
||||||
Email = 'Email',
|
Email = 'Email',
|
||||||
Frontend = 'Frontend',
|
|
||||||
Logging = 'Logging',
|
Logging = 'Logging',
|
||||||
Other = 'Other',
|
Other = 'Other',
|
||||||
QueueConfig = 'QueueConfig',
|
|
||||||
ServerConfig = 'ServerConfig',
|
ServerConfig = 'ServerConfig',
|
||||||
Storage = 'Storage',
|
|
||||||
Support = 'Support',
|
|
||||||
Workspace = 'Workspace'
|
Workspace = 'Workspace'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,7 +623,7 @@ export enum IdentityProviderType {
|
|||||||
export type ImpersonateOutput = {
|
export type ImpersonateOutput = {
|
||||||
__typename?: 'ImpersonateOutput';
|
__typename?: 'ImpersonateOutput';
|
||||||
loginToken: AuthToken;
|
loginToken: AuthToken;
|
||||||
workspace: WorkspaceSubdomainAndId;
|
workspace: WorkspaceUrlsAndId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Index = {
|
export type Index = {
|
||||||
@ -1227,10 +1219,9 @@ export type PublicWorkspaceDataOutput = {
|
|||||||
__typename?: 'PublicWorkspaceDataOutput';
|
__typename?: 'PublicWorkspaceDataOutput';
|
||||||
authProviders: AuthProviders;
|
authProviders: AuthProviders;
|
||||||
displayName?: Maybe<Scalars['String']>;
|
displayName?: Maybe<Scalars['String']>;
|
||||||
hostname?: Maybe<Scalars['String']>;
|
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
logo?: Maybe<Scalars['String']>;
|
logo?: Maybe<Scalars['String']>;
|
||||||
subdomain: Scalars['String'];
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PublishServerlessFunctionInput = {
|
export type PublishServerlessFunctionInput = {
|
||||||
@ -1258,7 +1249,7 @@ export type Query = {
|
|||||||
getHostnameDetails?: Maybe<CustomHostnameDetails>;
|
getHostnameDetails?: Maybe<CustomHostnameDetails>;
|
||||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||||
getProductPrices: BillingProductPricesOutput;
|
getProductPrices: BillingProductPricesOutput;
|
||||||
getPublicWorkspaceDataBySubdomain: PublicWorkspaceDataOutput;
|
getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput;
|
||||||
getRoles: Array<Role>;
|
getRoles: Array<Role>;
|
||||||
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']>;
|
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']>;
|
||||||
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
|
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
|
||||||
@ -1588,7 +1579,7 @@ export type SetupSsoOutput = {
|
|||||||
export type SignUpOutput = {
|
export type SignUpOutput = {
|
||||||
__typename?: 'SignUpOutput';
|
__typename?: 'SignUpOutput';
|
||||||
loginToken: AuthToken;
|
loginToken: AuthToken;
|
||||||
workspace: WorkspaceSubdomainAndId;
|
workspace: WorkspaceUrlsAndId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SubscriptionInterval {
|
export enum SubscriptionInterval {
|
||||||
@ -1782,7 +1773,7 @@ export type User = {
|
|||||||
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
analyticsTinybirdJwts?: Maybe<AnalyticsTinybirdJwtMap>;
|
||||||
canImpersonate: Scalars['Boolean'];
|
canImpersonate: Scalars['Boolean'];
|
||||||
createdAt: Scalars['DateTime'];
|
createdAt: Scalars['DateTime'];
|
||||||
currentWorkspace?: Maybe<Workspace>;
|
currentWorkspace: Workspace;
|
||||||
defaultAvatarUrl?: Maybe<Scalars['String']>;
|
defaultAvatarUrl?: Maybe<Scalars['String']>;
|
||||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||||
disabled?: Maybe<Scalars['Boolean']>;
|
disabled?: Maybe<Scalars['Boolean']>;
|
||||||
@ -1904,6 +1895,7 @@ export type Workspace = {
|
|||||||
subdomain: Scalars['String'];
|
subdomain: Scalars['String'];
|
||||||
updatedAt: Scalars['DateTime'];
|
updatedAt: Scalars['DateTime'];
|
||||||
workspaceMembersCount?: Maybe<Scalars['Float']>;
|
workspaceMembersCount?: Maybe<Scalars['Float']>;
|
||||||
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum WorkspaceActivationStatus {
|
export enum WorkspaceActivationStatus {
|
||||||
@ -1980,10 +1972,16 @@ export type WorkspaceNameAndId = {
|
|||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceSubdomainAndId = {
|
export type WorkspaceUrls = {
|
||||||
__typename?: 'WorkspaceSubdomainAndId';
|
__typename?: 'workspaceUrls';
|
||||||
|
customUrl?: Maybe<Scalars['String']>;
|
||||||
|
subdomainUrl: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceUrlsAndId = {
|
||||||
|
__typename?: 'workspaceUrlsAndId';
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
subdomain: Scalars['String'];
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TimelineCalendarEventFragmentFragment = { __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
|
export type TimelineCalendarEventFragmentFragment = { __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
|
||||||
@ -2130,7 +2128,7 @@ export type ImpersonateMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'ImpersonateOutput', workspace: { __typename?: 'WorkspaceSubdomainAndId', subdomain: string, id: string }, loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } };
|
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'ImpersonateOutput', workspace: { __typename?: 'workspaceUrlsAndId', id: string, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } }, loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } };
|
||||||
|
|
||||||
export type RenewTokenMutationVariables = Exact<{
|
export type RenewTokenMutationVariables = Exact<{
|
||||||
appToken: Scalars['String'];
|
appToken: Scalars['String'];
|
||||||
@ -2156,7 +2154,7 @@ export type SignUpMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'SignUpOutput', loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, workspace: { __typename?: 'WorkspaceSubdomainAndId', id: string, subdomain: string } } };
|
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'SignUpOutput', loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, workspace: { __typename?: 'workspaceUrlsAndId', id: string, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } } };
|
||||||
|
|
||||||
export type UpdatePasswordViaResetTokenMutationVariables = Exact<{
|
export type UpdatePasswordViaResetTokenMutationVariables = Exact<{
|
||||||
token: Scalars['String'];
|
token: Scalars['String'];
|
||||||
@ -2172,12 +2170,12 @@ export type CheckUserExistsQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __typename: 'UserExists', exists: boolean, isEmailVerified: boolean, availableWorkspaces: Array<{ __typename?: 'AvailableWorkspaceOutput', id: string, displayName?: string | null, subdomain: string, hostname?: string | null, logo?: string | null, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } | { __typename: 'UserNotExists', exists: boolean } };
|
export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __typename: 'UserExists', exists: boolean, isEmailVerified: boolean, availableWorkspaces: Array<{ __typename?: 'AvailableWorkspaceOutput', id: string, displayName?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } | { __typename: 'UserNotExists', exists: boolean } };
|
||||||
|
|
||||||
export type GetPublicWorkspaceDataBySubdomainQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetPublicWorkspaceDataByDomainQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetPublicWorkspaceDataBySubdomainQuery = { __typename?: 'Query', getPublicWorkspaceDataBySubdomain: { __typename?: 'PublicWorkspaceDataOutput', id: string, logo?: string | null, displayName?: string | null, subdomain: string, hostname?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, magicLink: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> } } };
|
export type GetPublicWorkspaceDataByDomainQuery = { __typename?: 'Query', getPublicWorkspaceDataByDomain: { __typename?: 'PublicWorkspaceDataOutput', id: string, logo?: string | null, displayName?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, authProviders: { __typename?: 'AuthProviders', google: boolean, magicLink: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> } } };
|
||||||
|
|
||||||
export type ValidatePasswordResetTokenQueryVariables = Exact<{
|
export type ValidatePasswordResetTokenQueryVariables = Exact<{
|
||||||
token: Scalars['String'];
|
token: Scalars['String'];
|
||||||
@ -2291,7 +2289,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key:
|
|||||||
|
|
||||||
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
||||||
|
|
||||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, hostname?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string } | null }> };
|
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, hostname?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, hostname?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||||
|
|
||||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@ -2308,7 +2306,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
|||||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, hostname?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string } | null }> } };
|
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, hostname?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, hostname?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||||
|
|
||||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
@ -2399,7 +2397,7 @@ export type ActivateWorkspaceMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ActivateWorkspaceMutation = { __typename?: 'Mutation', activateWorkspace: { __typename?: 'Workspace', id: any, subdomain: string } };
|
export type ActivateWorkspaceMutation = { __typename?: 'Mutation', activateWorkspace: { __typename?: 'Workspace', id: any } };
|
||||||
|
|
||||||
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
|
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@ -2430,7 +2428,7 @@ export type GetWorkspaceFromInviteHashQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, subdomain: string } };
|
export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } };
|
||||||
|
|
||||||
export const TimelineCalendarEventParticipantFragmentFragmentDoc = gql`
|
export const TimelineCalendarEventParticipantFragmentFragmentDoc = gql`
|
||||||
fragment TimelineCalendarEventParticipantFragment on TimelineCalendarEventParticipant {
|
fragment TimelineCalendarEventParticipantFragment on TimelineCalendarEventParticipant {
|
||||||
@ -2583,6 +2581,10 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
subdomain
|
subdomain
|
||||||
hasValidEnterpriseKey
|
hasValidEnterpriseKey
|
||||||
hostname
|
hostname
|
||||||
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
featureFlags {
|
featureFlags {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
@ -2607,6 +2609,11 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
logo
|
logo
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
subdomain
|
||||||
|
hostname
|
||||||
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userVars
|
userVars
|
||||||
@ -3168,7 +3175,10 @@ export const ImpersonateDocument = gql`
|
|||||||
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
||||||
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
||||||
workspace {
|
workspace {
|
||||||
subdomain
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
loginToken {
|
loginToken {
|
||||||
@ -3287,7 +3297,10 @@ export const SignUpDocument = gql`
|
|||||||
}
|
}
|
||||||
workspace {
|
workspace {
|
||||||
id
|
id
|
||||||
subdomain
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3369,8 +3382,10 @@ export const CheckUserExistsDocument = gql`
|
|||||||
availableWorkspaces {
|
availableWorkspaces {
|
||||||
id
|
id
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
workspaceUrls {
|
||||||
hostname
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
logo
|
logo
|
||||||
sso {
|
sso {
|
||||||
type
|
type
|
||||||
@ -3417,14 +3432,16 @@ export function useCheckUserExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
|
|||||||
export type CheckUserExistsQueryHookResult = ReturnType<typeof useCheckUserExistsQuery>;
|
export type CheckUserExistsQueryHookResult = ReturnType<typeof useCheckUserExistsQuery>;
|
||||||
export type CheckUserExistsLazyQueryHookResult = ReturnType<typeof useCheckUserExistsLazyQuery>;
|
export type CheckUserExistsLazyQueryHookResult = ReturnType<typeof useCheckUserExistsLazyQuery>;
|
||||||
export type CheckUserExistsQueryResult = Apollo.QueryResult<CheckUserExistsQuery, CheckUserExistsQueryVariables>;
|
export type CheckUserExistsQueryResult = Apollo.QueryResult<CheckUserExistsQuery, CheckUserExistsQueryVariables>;
|
||||||
export const GetPublicWorkspaceDataBySubdomainDocument = gql`
|
export const GetPublicWorkspaceDataByDomainDocument = gql`
|
||||||
query GetPublicWorkspaceDataBySubdomain {
|
query GetPublicWorkspaceDataByDomain {
|
||||||
getPublicWorkspaceDataBySubdomain {
|
getPublicWorkspaceDataByDomain {
|
||||||
id
|
id
|
||||||
logo
|
logo
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
workspaceUrls {
|
||||||
hostname
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
authProviders {
|
authProviders {
|
||||||
sso {
|
sso {
|
||||||
id
|
id
|
||||||
@ -3443,31 +3460,31 @@ export const GetPublicWorkspaceDataBySubdomainDocument = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useGetPublicWorkspaceDataBySubdomainQuery__
|
* __useGetPublicWorkspaceDataByDomainQuery__
|
||||||
*
|
*
|
||||||
* To run a query within a React component, call `useGetPublicWorkspaceDataBySubdomainQuery` and pass it any options that fit your needs.
|
* To run a query within a React component, call `useGetPublicWorkspaceDataByDomainQuery` and pass it any options that fit your needs.
|
||||||
* When your component renders, `useGetPublicWorkspaceDataBySubdomainQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
* When your component renders, `useGetPublicWorkspaceDataByDomainQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
* you can use to render your UI.
|
* you can use to render your UI.
|
||||||
*
|
*
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const { data, loading, error } = useGetPublicWorkspaceDataBySubdomainQuery({
|
* const { data, loading, error } = useGetPublicWorkspaceDataByDomainQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetPublicWorkspaceDataBySubdomainQuery(baseOptions?: Apollo.QueryHookOptions<GetPublicWorkspaceDataBySubdomainQuery, GetPublicWorkspaceDataBySubdomainQueryVariables>) {
|
export function useGetPublicWorkspaceDataByDomainQuery(baseOptions?: Apollo.QueryHookOptions<GetPublicWorkspaceDataByDomainQuery, GetPublicWorkspaceDataByDomainQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
return Apollo.useQuery<GetPublicWorkspaceDataBySubdomainQuery, GetPublicWorkspaceDataBySubdomainQueryVariables>(GetPublicWorkspaceDataBySubdomainDocument, options);
|
return Apollo.useQuery<GetPublicWorkspaceDataByDomainQuery, GetPublicWorkspaceDataByDomainQueryVariables>(GetPublicWorkspaceDataByDomainDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetPublicWorkspaceDataBySubdomainLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetPublicWorkspaceDataBySubdomainQuery, GetPublicWorkspaceDataBySubdomainQueryVariables>) {
|
export function useGetPublicWorkspaceDataByDomainLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetPublicWorkspaceDataByDomainQuery, GetPublicWorkspaceDataByDomainQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
return Apollo.useLazyQuery<GetPublicWorkspaceDataBySubdomainQuery, GetPublicWorkspaceDataBySubdomainQueryVariables>(GetPublicWorkspaceDataBySubdomainDocument, options);
|
return Apollo.useLazyQuery<GetPublicWorkspaceDataByDomainQuery, GetPublicWorkspaceDataByDomainQueryVariables>(GetPublicWorkspaceDataByDomainDocument, options);
|
||||||
}
|
}
|
||||||
export type GetPublicWorkspaceDataBySubdomainQueryHookResult = ReturnType<typeof useGetPublicWorkspaceDataBySubdomainQuery>;
|
export type GetPublicWorkspaceDataByDomainQueryHookResult = ReturnType<typeof useGetPublicWorkspaceDataByDomainQuery>;
|
||||||
export type GetPublicWorkspaceDataBySubdomainLazyQueryHookResult = ReturnType<typeof useGetPublicWorkspaceDataBySubdomainLazyQuery>;
|
export type GetPublicWorkspaceDataByDomainLazyQueryHookResult = ReturnType<typeof useGetPublicWorkspaceDataByDomainLazyQuery>;
|
||||||
export type GetPublicWorkspaceDataBySubdomainQueryResult = Apollo.QueryResult<GetPublicWorkspaceDataBySubdomainQuery, GetPublicWorkspaceDataBySubdomainQueryVariables>;
|
export type GetPublicWorkspaceDataByDomainQueryResult = Apollo.QueryResult<GetPublicWorkspaceDataByDomainQuery, GetPublicWorkspaceDataByDomainQueryVariables>;
|
||||||
export const ValidatePasswordResetTokenDocument = gql`
|
export const ValidatePasswordResetTokenDocument = gql`
|
||||||
query ValidatePasswordResetToken($token: String!) {
|
query ValidatePasswordResetToken($token: String!) {
|
||||||
validatePasswordResetToken(passwordResetToken: $token) {
|
validatePasswordResetToken(passwordResetToken: $token) {
|
||||||
@ -4698,7 +4715,6 @@ export const ActivateWorkspaceDocument = gql`
|
|||||||
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
||||||
activateWorkspace(data: $input) {
|
activateWorkspace(data: $input) {
|
||||||
id
|
id
|
||||||
subdomain
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -4887,7 +4903,6 @@ export const GetWorkspaceFromInviteHashDocument = gql`
|
|||||||
displayName
|
displayName
|
||||||
logo
|
logo
|
||||||
allowImpersonation
|
allowImpersonation
|
||||||
subdomain
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { within } from '@storybook/test';
|
import { within } from '@storybook/test';
|
||||||
import { HttpResponse, graphql } from 'msw';
|
import { HttpResponse, graphql } from 'msw';
|
||||||
|
|
||||||
import { GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataBySubdomain';
|
import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain';
|
||||||
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
||||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||||
@ -36,7 +36,7 @@ const userMetadataLoaderMocks = {
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
graphql.query(
|
graphql.query(
|
||||||
getOperationName(GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN) ?? '',
|
getOperationName(GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN) ?? '',
|
||||||
() => {
|
() => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@ -5,7 +5,10 @@ export const IMPERSONATE = gql`
|
|||||||
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
||||||
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
||||||
workspace {
|
workspace {
|
||||||
subdomain
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
loginToken {
|
loginToken {
|
||||||
|
|||||||
@ -22,7 +22,10 @@ export const SIGN_UP = gql`
|
|||||||
}
|
}
|
||||||
workspace {
|
workspace {
|
||||||
id
|
id
|
||||||
subdomain
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,10 @@ export const CHECK_USER_EXISTS = gql`
|
|||||||
availableWorkspaces {
|
availableWorkspaces {
|
||||||
id
|
id
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
workspaceUrls {
|
||||||
hostname
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
logo
|
logo
|
||||||
sso {
|
sso {
|
||||||
type
|
type
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN = gql`
|
export const GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN = gql`
|
||||||
query GetPublicWorkspaceDataBySubdomain {
|
query GetPublicWorkspaceDataByDomain {
|
||||||
getPublicWorkspaceDataBySubdomain {
|
getPublicWorkspaceDataByDomain {
|
||||||
id
|
id
|
||||||
logo
|
logo
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
workspaceUrls {
|
||||||
hostname
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
authProviders {
|
authProviders {
|
||||||
sso {
|
sso {
|
||||||
id
|
id
|
||||||
@ -53,7 +53,7 @@ import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type
|
|||||||
import { captchaState } from '@/client-config/states/captchaState';
|
import { captchaState } from '@/client-config/states/captchaState';
|
||||||
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
|
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
@ -62,6 +62,7 @@ import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/state
|
|||||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||||
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||||
@ -96,8 +97,7 @@ export const useAuth = () => {
|
|||||||
useGetLoginTokenFromEmailVerificationTokenMutation();
|
useGetLoginTokenFromEmailVerificationTokenMutation();
|
||||||
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
||||||
|
|
||||||
const { isOnAWorkspaceSubdomain } =
|
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||||
useIsCurrentLocationOnAWorkspaceSubdomain();
|
|
||||||
|
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
|
||||||
@ -289,10 +289,10 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
setCurrentWorkspace(workspace);
|
setCurrentWorkspace(workspace);
|
||||||
|
|
||||||
if (isDefined(workspace) && isOnAWorkspaceSubdomain) {
|
if (isDefined(workspace) && isOnAWorkspace) {
|
||||||
setLastAuthenticateWorkspaceDomain({
|
setLastAuthenticateWorkspaceDomain({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
subdomain: workspace.subdomain,
|
workspaceUrl: getWorkspaceUrl(workspace.workspaceUrls),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ export const useAuth = () => {
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
getCurrentUser,
|
getCurrentUser,
|
||||||
isOnAWorkspaceSubdomain,
|
isOnAWorkspace,
|
||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
setCurrentWorkspace,
|
setCurrentWorkspace,
|
||||||
setCurrentWorkspaceMember,
|
setCurrentWorkspaceMember,
|
||||||
@ -413,7 +413,8 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
if (isMultiWorkspaceEnabled) {
|
if (isMultiWorkspaceEnabled) {
|
||||||
return redirectToWorkspaceDomain(
|
return redirectToWorkspaceDomain(
|
||||||
signUpResult.data.signUp.workspace.subdomain,
|
getWorkspaceUrl(signUpResult.data.signUp.workspace.workspaceUrls),
|
||||||
|
|
||||||
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
||||||
{
|
{
|
||||||
...(!isEmailVerificationRequired && {
|
...(!isEmailVerificationRequired && {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirect
|
|||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
|
|
||||||
const StyledContentContainer = styled(motion.div)`
|
const StyledContentContainer = styled(motion.div)`
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||||
@ -92,9 +93,13 @@ export const SignInUpGlobalScopeForm = () => {
|
|||||||
if (response.__typename === 'UserExists') {
|
if (response.__typename === 'UserExists') {
|
||||||
if (response.availableWorkspaces.length >= 1) {
|
if (response.availableWorkspaces.length >= 1) {
|
||||||
const workspace = response.availableWorkspaces[0];
|
const workspace = response.availableWorkspaces[0];
|
||||||
return redirectToWorkspaceDomain(workspace.subdomain, pathname, {
|
return redirectToWorkspaceDomain(
|
||||||
email: form.getValues('email'),
|
getWorkspaceUrl(workspace.workspaceUrls),
|
||||||
});
|
pathname,
|
||||||
|
{
|
||||||
|
email: form.getValues('email'),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response.__typename === 'UserNotExists') {
|
if (response.__typename === 'UserNotExists') {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
|||||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
|
|
||||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||||
@ -52,9 +53,11 @@ const apolloMocks = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
<MemoryRouter>
|
||||||
{children}
|
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||||
</MockedProvider>
|
{children}
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('useSSO', () => {
|
describe('useSSO', () => {
|
||||||
|
|||||||
@ -13,14 +13,16 @@ export const useSSO = () => {
|
|||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const { redirect } = useRedirect();
|
const { redirect } = useRedirect();
|
||||||
|
|
||||||
const redirectToSSOLoginPage = async (identityProviderId: string) => {
|
const redirectToSSOLoginPage = async (identityProviderId: string) => {
|
||||||
let authorizationUrlForSSOResult;
|
let authorizationUrlForSSOResult;
|
||||||
try {
|
try {
|
||||||
authorizationUrlForSSOResult = await apolloClient.mutate({
|
authorizationUrlForSSOResult = await apolloClient.mutate({
|
||||||
mutation: GET_AUTHORIZATION_URL,
|
mutation: GET_AUTHORIZATION_URL,
|
||||||
variables: {
|
variables: {
|
||||||
input: { identityProviderId, workspaceInviteHash },
|
input: {
|
||||||
|
identityProviderId,
|
||||||
|
workspaceInviteHash,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export type CurrentWorkspace = Pick<
|
|||||||
| 'hasValidEnterpriseKey'
|
| 'hasValidEnterpriseKey'
|
||||||
| 'subdomain'
|
| 'subdomain'
|
||||||
| 'hostname'
|
| 'hostname'
|
||||||
|
| 'workspaceUrls'
|
||||||
| 'metadataVersion'
|
| 'metadataVersion'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { createState } from '@ui/utilities/state/utils/createState';
|
import { createState } from '@ui/utilities/state/utils/createState';
|
||||||
|
|
||||||
import { Workspace } from '~/generated/graphql';
|
import { Workspace } from '~/generated/graphql';
|
||||||
|
|
||||||
export type Workspaces = Pick<
|
export type Workspaces = Pick<
|
||||||
Workspace,
|
Workspace,
|
||||||
'id' | 'logo' | 'displayName' | 'subdomain'
|
'id' | 'logo' | 'displayName' | 'workspaceUrls'
|
||||||
>[];
|
>[];
|
||||||
|
|
||||||
export const workspacesState = createState<Workspaces>({
|
export const workspacesState = createState<Workspaces>({
|
||||||
|
|||||||
@ -1,20 +1,12 @@
|
|||||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
export const useBuildWorkspaceUrl = () => {
|
export const useBuildWorkspaceUrl = () => {
|
||||||
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
|
||||||
|
|
||||||
const buildWorkspaceUrl = (
|
const buildWorkspaceUrl = (
|
||||||
subdomain: string,
|
endpoint: string,
|
||||||
pathname?: string,
|
pathname?: string,
|
||||||
searchParams?: Record<string, string>,
|
searchParams?: Record<string, string | boolean>,
|
||||||
) => {
|
) => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(endpoint);
|
||||||
|
|
||||||
if (subdomain.length !== 0) {
|
|
||||||
url.hostname = `${subdomain}.${domainConfiguration.frontDomain}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefined(pathname)) {
|
if (isDefined(pathname)) {
|
||||||
url.pathname = pathname;
|
url.pathname = pathname;
|
||||||
@ -22,7 +14,7 @@ export const useBuildWorkspaceUrl = () => {
|
|||||||
|
|
||||||
if (isDefined(searchParams)) {
|
if (isDefined(searchParams)) {
|
||||||
Object.entries(searchParams).forEach(([key, value]) =>
|
Object.entries(searchParams).forEach(([key, value]) =>
|
||||||
url.searchParams.set(key, value),
|
url.searchParams.set(key, value.toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return url.toString();
|
return url.toString();
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectTo
|
|||||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { useGetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
import { useGetPublicWorkspaceDataByDomainQuery } from '~/generated/graphql';
|
||||||
|
|
||||||
export const useGetPublicWorkspaceDataBySubdomain = () => {
|
export const useGetPublicWorkspaceDataByDomain = () => {
|
||||||
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
const setWorkspaceAuthProviders = useSetRecoilState(
|
const setWorkspaceAuthProviders = useSetRecoilState(
|
||||||
@ -19,15 +19,15 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
|||||||
workspacePublicDataState,
|
workspacePublicDataState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { loading, data, error } = useGetPublicWorkspaceDataBySubdomainQuery({
|
const { loading, data, error } = useGetPublicWorkspaceDataByDomainQuery({
|
||||||
skip:
|
skip:
|
||||||
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
||||||
isDefined(workspacePublicData),
|
isDefined(workspacePublicData),
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
setWorkspaceAuthProviders(
|
setWorkspaceAuthProviders(
|
||||||
data.getPublicWorkspaceDataBySubdomain.authProviders,
|
data.getPublicWorkspaceDataByDomain.authProviders,
|
||||||
);
|
);
|
||||||
setWorkspacePublicDataState(data.getPublicWorkspaceDataBySubdomain);
|
setWorkspacePublicDataState(data.getPublicWorkspaceDataByDomain);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -38,7 +38,7 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
data: data?.getPublicWorkspaceDataBySubdomain,
|
data: data?.getPublicWorkspaceDataByDomain,
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -4,7 +4,7 @@ import { domainConfigurationState } from '@/domain-manager/states/domainConfigur
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
export const useIsCurrentLocationOnAWorkspaceSubdomain = () => {
|
export const useIsCurrentLocationOnAWorkspace = () => {
|
||||||
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
||||||
|
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
@ -18,10 +18,10 @@ export const useIsCurrentLocationOnAWorkspaceSubdomain = () => {
|
|||||||
throw new Error('frontDomain and defaultSubdomain are required');
|
throw new Error('frontDomain and defaultSubdomain are required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOnAWorkspaceSubdomain =
|
const isOnAWorkspace =
|
||||||
isMultiWorkspaceEnabled && window.location.hostname !== defaultDomain;
|
isMultiWorkspaceEnabled && window.location.hostname !== defaultDomain;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOnAWorkspaceSubdomain,
|
isOnAWorkspace,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -8,7 +8,7 @@ export const useLastAuthenticatedWorkspaceDomain = () => {
|
|||||||
lastAuthenticatedWorkspaceDomainState,
|
lastAuthenticatedWorkspaceDomainState,
|
||||||
);
|
);
|
||||||
const setLastAuthenticateWorkspaceDomainWithCookieAttributes = (
|
const setLastAuthenticateWorkspaceDomainWithCookieAttributes = (
|
||||||
params: { workspaceId: string; subdomain: string } | null,
|
params: { workspaceId: string; workspaceUrl: string } | null,
|
||||||
) => {
|
) => {
|
||||||
setLastAuthenticatedWorkspaceDomain({
|
setLastAuthenticatedWorkspaceDomain({
|
||||||
...(params ? params : {}),
|
...(params ? params : {}),
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
|
||||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const useReadWorkspaceSubdomainFromCurrentLocation = () => {
|
|
||||||
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
|
||||||
const { isOnAWorkspaceSubdomain } =
|
|
||||||
useIsCurrentLocationOnAWorkspaceSubdomain();
|
|
||||||
|
|
||||||
if (!isDefined(domainConfiguration.frontDomain)) {
|
|
||||||
throw new Error('frontDomain is not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceSubdomain = isOnAWorkspaceSubdomain
|
|
||||||
? window.location.hostname.replace(
|
|
||||||
`.${domainConfiguration.frontDomain}`,
|
|
||||||
'',
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
workspaceSubdomain,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||||
|
|
||||||
|
export const useReadWorkspaceUrlFromCurrentLocation = () => {
|
||||||
|
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentLocationHostname: isOnAWorkspace
|
||||||
|
? window.location.hostname
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -9,12 +9,12 @@ export const useRedirectToWorkspaceDomain = () => {
|
|||||||
const { redirect } = useRedirect();
|
const { redirect } = useRedirect();
|
||||||
|
|
||||||
const redirectToWorkspaceDomain = (
|
const redirectToWorkspaceDomain = (
|
||||||
subdomain: string,
|
baseUrl: string,
|
||||||
pathname?: string,
|
pathname?: string,
|
||||||
searchParams?: Record<string, string>,
|
searchParams?: Record<string, string | boolean>,
|
||||||
) => {
|
) => {
|
||||||
if (!isMultiWorkspaceEnabled) return;
|
if (!isMultiWorkspaceEnabled) return;
|
||||||
redirect(buildWorkspaceUrl(subdomain, pathname, searchParams));
|
redirect(buildWorkspaceUrl(baseUrl, pathname, searchParams));
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { cookieStorageEffect } from '~/utils/recoil-effects';
|
|||||||
|
|
||||||
export const lastAuthenticatedWorkspaceDomainState = createState<
|
export const lastAuthenticatedWorkspaceDomainState = createState<
|
||||||
| {
|
| {
|
||||||
subdomain: string;
|
workspaceUrl: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
cookieAttributes?: Cookies.CookieAttributes;
|
cookieAttributes?: Cookies.CookieAttributes;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,6 +159,10 @@ export const queries = {
|
|||||||
subdomain
|
subdomain
|
||||||
hasValidEnterpriseKey
|
hasValidEnterpriseKey
|
||||||
hostname
|
hostname
|
||||||
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
featureFlags {
|
featureFlags {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
@ -183,6 +187,11 @@ export const queries = {
|
|||||||
logo
|
logo
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
subdomain
|
||||||
|
hostname
|
||||||
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userVars
|
userVars
|
||||||
@ -309,6 +318,10 @@ export const responseData = {
|
|||||||
isPasswordAuthEnabled: true,
|
isPasswordAuthEnabled: true,
|
||||||
subdomain: 'test',
|
subdomain: 'test',
|
||||||
hostname: null,
|
hostname: null,
|
||||||
|
workspaceUrls: {
|
||||||
|
customUrl: undefined,
|
||||||
|
subdomainUrl: 'https://test.twenty.com/',
|
||||||
|
},
|
||||||
featureFlags: [],
|
featureFlags: [],
|
||||||
metadataVersion: 1,
|
metadataVersion: 1,
|
||||||
currentBillingSubscription: null,
|
currentBillingSubscription: null,
|
||||||
|
|||||||
@ -27,6 +27,10 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
|||||||
isGoogleAuthEnabled: true,
|
isGoogleAuthEnabled: true,
|
||||||
isMicrosoftAuthEnabled: false,
|
isMicrosoftAuthEnabled: false,
|
||||||
isPasswordAuthEnabled: true,
|
isPasswordAuthEnabled: true,
|
||||||
|
workspaceUrls: {
|
||||||
|
subdomainUrl: 'https://twenty.twenty.com',
|
||||||
|
customUrl: 'https://my-custom-domain.com',
|
||||||
|
},
|
||||||
currentBillingSubscription: {
|
currentBillingSubscription: {
|
||||||
id: '1',
|
id: '1',
|
||||||
interval: SubscriptionInterval.Month,
|
interval: SubscriptionInterval.Month,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { useState } from 'react';
|
|||||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { useImpersonateMutation } from '~/generated/graphql';
|
import { useImpersonateMutation } from '~/generated/graphql';
|
||||||
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
|
|
||||||
export const useImpersonate = () => {
|
export const useImpersonate = () => {
|
||||||
const [currentUser] = useRecoilState(currentUserState);
|
const [currentUser] = useRecoilState(currentUserState);
|
||||||
@ -55,9 +56,13 @@ export const useImpersonate = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirectToWorkspaceDomain(workspace.subdomain, AppPath.Verify, {
|
return redirectToWorkspaceDomain(
|
||||||
loginToken: loginToken.token,
|
getWorkspaceUrl(workspace.workspaceUrls),
|
||||||
});
|
AppPath.Verify,
|
||||||
|
{
|
||||||
|
loginToken: loginToken.token,
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError('Failed to impersonate user. Please try again.');
|
setError('Failed to impersonate user. Please try again.');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
UndecoratedLink,
|
UndecoratedLink,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ isNavigationDrawerExpanded: boolean }>`
|
const StyledContainer = styled.div<{ isNavigationDrawerExpanded: boolean }>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -67,7 +68,7 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||||
|
|
||||||
const handleChange = async (workspace: Workspaces[0]) => {
|
const handleChange = async (workspace: Workspaces[0]) => {
|
||||||
redirectToWorkspaceDomain(workspace.subdomain);
|
redirectToWorkspaceDomain(getWorkspaceUrl(workspace.workspaceUrls));
|
||||||
};
|
};
|
||||||
const [isNavigationDrawerExpanded] = useRecoilState(
|
const [isNavigationDrawerExpanded] = useRecoilState(
|
||||||
isNavigationDrawerExpandedState,
|
isNavigationDrawerExpandedState,
|
||||||
@ -104,7 +105,7 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
{workspaces.map((workspace) => (
|
{workspaces.map((workspace) => (
|
||||||
<UndecoratedLink
|
<UndecoratedLink
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
to={buildWorkspaceUrl(workspace.subdomain)}
|
to={buildWorkspaceUrl(getWorkspaceUrl(workspace.workspaceUrls))}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
handleChange(workspace);
|
handleChange(workspace);
|
||||||
|
|||||||
@ -38,6 +38,10 @@ export const USER_QUERY_FRAGMENT = gql`
|
|||||||
subdomain
|
subdomain
|
||||||
hasValidEnterpriseKey
|
hasValidEnterpriseKey
|
||||||
hostname
|
hostname
|
||||||
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
featureFlags {
|
featureFlags {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
@ -62,6 +66,11 @@ export const USER_QUERY_FRAGMENT = gql`
|
|||||||
logo
|
logo
|
||||||
displayName
|
displayName
|
||||||
subdomain
|
subdomain
|
||||||
|
hostname
|
||||||
|
workspaceUrls {
|
||||||
|
subdomainUrl
|
||||||
|
customUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userVars
|
userVars
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
|
||||||
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
|
||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
|
||||||
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
|
||||||
|
import { useReadWorkspaceUrlFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceUrlFromCurrentLocation';
|
||||||
|
|
||||||
import { useGetPublicWorkspaceDataBySubdomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataBySubdomain';
|
|
||||||
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||||
|
import { useGetPublicWorkspaceDataByDomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataByDomain';
|
||||||
|
import { WorkspaceUrls } from '~/generated/graphql';
|
||||||
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
export const WorkspaceProviderEffect = () => {
|
export const WorkspaceProviderEffect = () => {
|
||||||
const { data: getPublicWorkspaceData } =
|
const { data: getPublicWorkspaceData } = useGetPublicWorkspaceDataByDomain();
|
||||||
useGetPublicWorkspaceDataBySubdomain();
|
|
||||||
|
|
||||||
const lastAuthenticatedWorkspaceDomain = useRecoilValue(
|
const lastAuthenticatedWorkspaceDomain = useRecoilValue(
|
||||||
lastAuthenticatedWorkspaceDomainState,
|
lastAuthenticatedWorkspaceDomainState,
|
||||||
@ -20,23 +21,38 @@ export const WorkspaceProviderEffect = () => {
|
|||||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||||
|
|
||||||
const { workspaceSubdomain } = useReadWorkspaceSubdomainFromCurrentLocation();
|
const { currentLocationHostname } = useReadWorkspaceUrlFromCurrentLocation();
|
||||||
|
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
|
||||||
|
const getHostnamesFromWorkspaceUrls = (workspaceUrls: WorkspaceUrls) => {
|
||||||
|
return {
|
||||||
|
customUrlHostname: workspaceUrls.customUrl
|
||||||
|
? new URL(workspaceUrls.customUrl).hostname
|
||||||
|
: undefined,
|
||||||
|
subdomainUrlHostname: new URL(workspaceUrls.subdomainUrl).hostname,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const hostnames = getPublicWorkspaceData
|
||||||
|
? getHostnamesFromWorkspaceUrls(getPublicWorkspaceData?.workspaceUrls)
|
||||||
|
: null;
|
||||||
if (
|
if (
|
||||||
isMultiWorkspaceEnabled &&
|
isMultiWorkspaceEnabled &&
|
||||||
isDefined(getPublicWorkspaceData?.subdomain) &&
|
isDefined(getPublicWorkspaceData) &&
|
||||||
getPublicWorkspaceData.subdomain !== workspaceSubdomain
|
currentLocationHostname !== hostnames?.customUrlHostname &&
|
||||||
|
currentLocationHostname !== hostnames?.subdomainUrlHostname
|
||||||
) {
|
) {
|
||||||
redirectToWorkspaceDomain(getPublicWorkspaceData.subdomain);
|
redirectToWorkspaceDomain(
|
||||||
|
getWorkspaceUrl(getPublicWorkspaceData.workspaceUrls),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
workspaceSubdomain,
|
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
redirectToWorkspaceDomain,
|
redirectToWorkspaceDomain,
|
||||||
getPublicWorkspaceData,
|
getPublicWorkspaceData,
|
||||||
|
currentLocationHostname,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -44,10 +60,10 @@ export const WorkspaceProviderEffect = () => {
|
|||||||
isMultiWorkspaceEnabled &&
|
isMultiWorkspaceEnabled &&
|
||||||
isDefaultDomain &&
|
isDefaultDomain &&
|
||||||
isDefined(lastAuthenticatedWorkspaceDomain) &&
|
isDefined(lastAuthenticatedWorkspaceDomain) &&
|
||||||
'subdomain' in lastAuthenticatedWorkspaceDomain &&
|
'workspaceUrl' in lastAuthenticatedWorkspaceDomain &&
|
||||||
isDefined(lastAuthenticatedWorkspaceDomain?.subdomain)
|
isDefined(lastAuthenticatedWorkspaceDomain?.workspaceUrl)
|
||||||
) {
|
) {
|
||||||
redirectToWorkspaceDomain(lastAuthenticatedWorkspaceDomain.subdomain);
|
redirectToWorkspaceDomain(lastAuthenticatedWorkspaceDomain.workspaceUrl);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ export const ACTIVATE_WORKSPACE = gql`
|
|||||||
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
||||||
activateWorkspace(data: $input) {
|
activateWorkspace(data: $input) {
|
||||||
id
|
id
|
||||||
subdomain
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ export const GET_WORKSPACE_FROM_INVITE_HASH = gql`
|
|||||||
displayName
|
displayName
|
||||||
logo
|
logo
|
||||||
allowImpersonation
|
allowImpersonation
|
||||||
subdomain
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import { SignInUpSSOIdentityProviderSelection } from '@/auth/sign-in-up/componen
|
|||||||
import { SignInUpWorkspaceScopeForm } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeForm';
|
import { SignInUpWorkspaceScopeForm } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeForm';
|
||||||
import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect';
|
import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect';
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
import { useGetPublicWorkspaceDataBySubdomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataBySubdomain';
|
import { useGetPublicWorkspaceDataByDomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataByDomain';
|
||||||
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||||
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||||
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -24,7 +24,7 @@ import { AnimatedEaseIn } from 'twenty-ui';
|
|||||||
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
|
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { PublicWorkspaceDataOutput } from '~/generated-metadata/graphql';
|
import { PublicWorkspaceDataOutput } from '~/generated/graphql';
|
||||||
|
|
||||||
const StandardContent = ({
|
const StandardContent = ({
|
||||||
workspacePublicData,
|
workspacePublicData,
|
||||||
@ -55,10 +55,9 @@ export const SignInUp = () => {
|
|||||||
const { form } = useSignInUpForm();
|
const { form } = useSignInUpForm();
|
||||||
const { signInUpStep } = useSignInUp(form);
|
const { signInUpStep } = useSignInUp(form);
|
||||||
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||||
const { isOnAWorkspaceSubdomain } =
|
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||||
useIsCurrentLocationOnAWorkspaceSubdomain();
|
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
const { loading } = useGetPublicWorkspaceDataBySubdomain();
|
const { loading } = useGetPublicWorkspaceDataByDomain();
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
const { workspaceInviteHash, workspace: workspaceFromInviteHash } =
|
const { workspaceInviteHash, workspace: workspaceFromInviteHash } =
|
||||||
useWorkspaceFromInviteHash();
|
useWorkspaceFromInviteHash();
|
||||||
@ -91,7 +90,7 @@ export const SignInUp = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(!isMultiWorkspaceEnabled ||
|
(!isMultiWorkspaceEnabled ||
|
||||||
(isMultiWorkspaceEnabled && isOnAWorkspaceSubdomain)) &&
|
(isMultiWorkspaceEnabled && isOnAWorkspace)) &&
|
||||||
signInUpStep === SignInUpStep.SSOIdentityProviderSelection
|
signInUpStep === SignInUpStep.SSOIdentityProviderSelection
|
||||||
) {
|
) {
|
||||||
return <SignInUpSSOIdentityProviderSelection />;
|
return <SignInUpSSOIdentityProviderSelection />;
|
||||||
@ -99,7 +98,7 @@ export const SignInUp = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(workspacePublicData) &&
|
isDefined(workspacePublicData) &&
|
||||||
(!isMultiWorkspaceEnabled || isOnAWorkspaceSubdomain)
|
(!isMultiWorkspaceEnabled || isOnAWorkspace)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -113,7 +112,7 @@ export const SignInUp = () => {
|
|||||||
}, [
|
}, [
|
||||||
isDefaultDomain,
|
isDefaultDomain,
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
isOnAWorkspaceSubdomain,
|
isOnAWorkspace,
|
||||||
loading,
|
loading,
|
||||||
signInUpStep,
|
signInUpStep,
|
||||||
workspacePublicData,
|
workspacePublicData,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||||
@ -13,7 +14,6 @@ import { SettingsSubdomain } from '~/pages/settings/workspace/SettingsSubdomain'
|
|||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
import { ApolloError } from '@apollo/client';
|
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
@ -94,12 +94,18 @@ export const SettingsDomain = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
|
||||||
|
currentUrl.hostname = new URL(
|
||||||
|
currentWorkspace.workspaceUrls.subdomainUrl,
|
||||||
|
).hostname.replace(currentWorkspace.subdomain, values.subdomain);
|
||||||
|
|
||||||
setCurrentWorkspace({
|
setCurrentWorkspace({
|
||||||
...currentWorkspace,
|
...currentWorkspace,
|
||||||
subdomain: values.subdomain,
|
subdomain: values.subdomain,
|
||||||
});
|
});
|
||||||
|
|
||||||
redirectToWorkspaceDomain(values.subdomain);
|
redirectToWorkspaceDomain(currentUrl.toString());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
@ -41,7 +40,6 @@ export const SettingsHostname = () => {
|
|||||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||||
const { data: getHostnameDetailsData } = useGetHostnameDetailsQuery();
|
const { data: getHostnameDetailsData } = useGetHostnameDetailsQuery();
|
||||||
|
|
||||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
@ -75,8 +73,6 @@ export const SettingsHostname = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
redirectToWorkspaceDomain(currentWorkspace.subdomain);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
control.setError('hostname', {
|
control.setError('hostname', {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
@ -106,8 +102,6 @@ export const SettingsHostname = () => {
|
|||||||
...currentWorkspace,
|
...currentWorkspace,
|
||||||
hostname: values.hostname,
|
hostname: values.hostname,
|
||||||
});
|
});
|
||||||
|
|
||||||
// redirectToWorkspaceDomain(values.subdomain);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
control.setError('hostname', {
|
control.setError('hostname', {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
@ -139,12 +133,36 @@ export const SettingsHostname = () => {
|
|||||||
{isDefined(getHostnameDetailsData?.getHostnameDetails?.hostname) && (
|
{isDefined(getHostnameDetailsData?.getHostnameDetails?.hostname) && (
|
||||||
<pre>
|
<pre>
|
||||||
{getHostnameDetailsData.getHostnameDetails.hostname} CNAME
|
{getHostnameDetailsData.getHostnameDetails.hostname} CNAME
|
||||||
app.twenty-main.com
|
twenty-main.com
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
{getHostnameDetailsData && (
|
{getHostnameDetailsData?.getHostnameDetails &&
|
||||||
<pre>{JSON.stringify(getHostnameDetailsData, null, 4)}</pre>
|
getHostnameDetailsData.getHostnameDetails.ownershipVerifications.map(
|
||||||
)}
|
(ownershipVerification) => {
|
||||||
|
if (
|
||||||
|
ownershipVerification.__typename ===
|
||||||
|
'CustomHostnameOwnershipVerificationTxt'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<pre>
|
||||||
|
{ownershipVerification.name} TXT {ownershipVerification.value}
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ownershipVerification.__typename ===
|
||||||
|
'CustomHostnameOwnershipVerificationHttp'
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<pre>
|
||||||
|
{ownershipVerification.url} HTTP {ownershipVerification.body}
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
},
|
||||||
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { mockedUserData } from '~/testing/mock-data/users';
|
|||||||
import { mockedViewsData } from '~/testing/mock-data/views';
|
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||||
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
|
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
|
||||||
|
|
||||||
import { GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataBySubdomain';
|
import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain';
|
||||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
|
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
|
||||||
import { mockedTasks } from '~/testing/mock-data/tasks';
|
import { mockedTasks } from '~/testing/mock-data/tasks';
|
||||||
import {
|
import {
|
||||||
@ -49,15 +49,18 @@ export const graphqlMocks = {
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
graphql.query(
|
graphql.query(
|
||||||
getOperationName(GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN) ?? '',
|
getOperationName(GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN) ?? '',
|
||||||
() => {
|
() => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
getPublicWorkspaceDataBySubdomain: {
|
getPublicWorkspaceDataByDomain: {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
logo: 'logo',
|
logo: 'logo',
|
||||||
displayName: 'displayName',
|
displayName: 'displayName',
|
||||||
subdomain: 'subdomain',
|
workspaceUrls: {
|
||||||
|
customUrl: undefined,
|
||||||
|
subdomainUrl: 'https://twenty.com',
|
||||||
|
},
|
||||||
authProviders: {
|
authProviders: {
|
||||||
google: true,
|
google: true,
|
||||||
microsoft: false,
|
microsoft: false,
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { GetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
import { GetPublicWorkspaceDataByDomainQuery } from '~/generated/graphql';
|
||||||
|
|
||||||
export const mockedPublicWorkspaceDataBySubdomain: GetPublicWorkspaceDataBySubdomainQuery['getPublicWorkspaceDataBySubdomain'] =
|
export const mockedPublicWorkspaceDataBySubdomain: GetPublicWorkspaceDataByDomainQuery['getPublicWorkspaceDataByDomain'] =
|
||||||
{
|
{
|
||||||
__typename: 'PublicWorkspaceDataOutput',
|
__typename: 'PublicWorkspaceDataOutput',
|
||||||
id: '9870323e-22c3-4d14-9b7f-5bdc84f7d6ee',
|
id: '9870323e-22c3-4d14-9b7f-5bdc84f7d6ee',
|
||||||
logo: 'workspace-logo/original/c88deb49-7636-4560-918d-08c3265ffb20.49?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3Jrc3BhY2VJZCI6Ijk4NzAzMjNlLTIyYzMtNGQxNC05YjdmLTViZGM4NGY3ZDZlZSIsImlhdCI6MTczNjU0MDU0MywiZXhwIjoxNzM2NjI2OTQzfQ.C8cnHu09VGseRbQAMM4nhiO6z4TLG03ntFTvxm53-xg',
|
logo: 'workspace-logo/original/c88deb49-7636-4560-918d-08c3265ffb20.49?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3Jrc3BhY2VJZCI6Ijk4NzAzMjNlLTIyYzMtNGQxNC05YjdmLTViZGM4NGY3ZDZlZSIsImlhdCI6MTczNjU0MDU0MywiZXhwIjoxNzM2NjI2OTQzfQ.C8cnHu09VGseRbQAMM4nhiO6z4TLG03ntFTvxm53-xg',
|
||||||
displayName: 'Twenty Eng',
|
displayName: 'Twenty Eng',
|
||||||
subdomain: 'twenty-eng',
|
workspaceUrls: {
|
||||||
|
customUrl: 'https://twenty-eng.com',
|
||||||
|
subdomainUrl: 'https://custom.twenty.com',
|
||||||
|
},
|
||||||
authProviders: {
|
authProviders: {
|
||||||
__typename: 'AuthProviders',
|
__typename: 'AuthProviders',
|
||||||
sso: [],
|
sso: [],
|
||||||
|
|||||||
@ -48,6 +48,10 @@ export const mockCurrentWorkspace: Workspace = {
|
|||||||
hasValidEnterpriseKey: false,
|
hasValidEnterpriseKey: false,
|
||||||
isGoogleAuthEnabled: true,
|
isGoogleAuthEnabled: true,
|
||||||
isPasswordAuthEnabled: true,
|
isPasswordAuthEnabled: true,
|
||||||
|
workspaceUrls: {
|
||||||
|
customUrl: undefined,
|
||||||
|
subdomainUrl: 'twenty.twenty.com',
|
||||||
|
},
|
||||||
isMicrosoftAuthEnabled: false,
|
isMicrosoftAuthEnabled: false,
|
||||||
featureFlags: [
|
featureFlags: [
|
||||||
{
|
{
|
||||||
|
|||||||
5
packages/twenty-front/src/utils/getWorkspaceUrl.ts
Normal file
5
packages/twenty-front/src/utils/getWorkspaceUrl.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { WorkspaceUrls } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const getWorkspaceUrl = (workspaceUrls: WorkspaceUrls) => {
|
||||||
|
return workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl;
|
||||||
|
};
|
||||||
@ -48,7 +48,7 @@ export const seedFeatureFlags = async (
|
|||||||
{
|
{
|
||||||
key: FeatureFlagKey.IsCustomDomainEnabled,
|
key: FeatureFlagKey.IsCustomDomainEnabled,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
value: true,
|
value: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: FeatureFlagKey.IsBillingPlansEnabled,
|
key: FeatureFlagKey.IsBillingPlansEnabled,
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export class CoreQueryBuilderFactory {
|
|||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`No object was found for the workspace associated with this API key. You may generate a new one here ${this.domainManagerService
|
`No object was found for the workspace associated with this API key. You may generate a new one here ${this.domainManagerService
|
||||||
.buildWorkspaceURL({
|
.buildWorkspaceURL({
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
pathname: '/settings/developers',
|
pathname: '/settings/developers',
|
||||||
})
|
})
|
||||||
.toString()}`,
|
.toString()}`,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
|
|||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
|
|
||||||
const UserFindOneMock = jest.fn();
|
const UserFindOneMock = jest.fn();
|
||||||
const WorkspaceFindOneMock = jest.fn();
|
const WorkspaceFindOneMock = jest.fn();
|
||||||
@ -95,6 +96,15 @@ describe('AdminPanelService', () => {
|
|||||||
generateLoginToken: LoginTokenServiceGenerateLoginTokenMock,
|
generateLoginToken: LoginTokenServiceGenerateLoginTokenMock,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: DomainManagerService,
|
||||||
|
useValue: {
|
||||||
|
getworkspaceUrls: jest.fn().mockReturnValue({
|
||||||
|
customUrl: undefined,
|
||||||
|
subdomainUrl: 'https://twenty.twenty.com',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: EnvironmentService,
|
provide: EnvironmentService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -230,7 +240,10 @@ describe('AdminPanelService', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
workspace: {
|
workspace: {
|
||||||
id: 'workspace-id',
|
id: 'workspace-id',
|
||||||
subdomain: 'example-subdomain',
|
workspaceUrls: {
|
||||||
|
customUrl: undefined,
|
||||||
|
subdomainUrl: 'https://twenty.twenty.com',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
loginToken: expect.objectContaining({
|
loginToken: expect.objectContaining({
|
||||||
token: 'mock-login-token',
|
token: 'mock-login-token',
|
||||||
|
|||||||
@ -7,11 +7,13 @@ import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
|||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([User, Workspace, FeatureFlag], 'core'),
|
TypeOrmModule.forFeature([User, Workspace, FeatureFlag], 'core'),
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
DomainManagerModule,
|
||||||
],
|
],
|
||||||
providers: [AdminPanelResolver, AdminPanelService],
|
providers: [AdminPanelResolver, AdminPanelService],
|
||||||
exports: [AdminPanelService],
|
exports: [AdminPanelService],
|
||||||
|
|||||||
@ -28,12 +28,14 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
|||||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminPanelService {
|
export class AdminPanelService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly loginTokenService: LoginTokenService,
|
private readonly loginTokenService: LoginTokenService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
|
private readonly domainManagerService: DomainManagerService,
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
@ -72,7 +74,9 @@ export class AdminPanelService {
|
|||||||
return {
|
return {
|
||||||
workspace: {
|
workspace: {
|
||||||
id: user.workspaces[0].workspace.id,
|
id: user.workspaces[0].workspace.id,
|
||||||
subdomain: user.workspaces[0].workspace.subdomain,
|
workspaceUrls: this.domainManagerService.getworkspaceUrls(
|
||||||
|
user.workspaces[0].workspace,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
loginToken,
|
loginToken,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { Field, ObjectType } from '@nestjs/graphql';
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { AuthToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
import { AuthToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||||
import { WorkspaceSubdomainAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
import { workspaceUrlsAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class ImpersonateOutput {
|
export class ImpersonateOutput {
|
||||||
@Field(() => AuthToken)
|
@Field(() => AuthToken)
|
||||||
loginToken: AuthToken;
|
loginToken: AuthToken;
|
||||||
|
|
||||||
@Field(() => WorkspaceSubdomainAndId)
|
@Field(() => workspaceUrlsAndId)
|
||||||
workspace: WorkspaceSubdomainAndId;
|
workspace: workspaceUrlsAndId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -221,7 +221,7 @@ export class AuthResolver {
|
|||||||
await this.emailVerificationService.sendVerificationEmail(
|
await this.emailVerificationService.sendVerificationEmail(
|
||||||
user.id,
|
user.id,
|
||||||
user.email,
|
user.email,
|
||||||
workspace.subdomain,
|
workspace,
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginToken = await this.loginTokenService.generateLoginToken(
|
const loginToken = await this.loginTokenService.generateLoginToken(
|
||||||
@ -233,7 +233,7 @@ export class AuthResolver {
|
|||||||
loginToken,
|
loginToken,
|
||||||
workspace: {
|
workspace: {
|
||||||
id: workspace.id,
|
id: workspace.id,
|
||||||
subdomain: workspace.subdomain,
|
workspaceUrls: this.domainManagerService.getworkspaceUrls(workspace),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,7 +113,7 @@ export class GoogleAPIsAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.domainManagerService
|
this.domainManagerService
|
||||||
.buildWorkspaceURL({
|
.buildWorkspaceURL({
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
pathname: redirectLocation || '/settings/accounts',
|
pathname: redirectLocation || '/settings/accounts',
|
||||||
})
|
})
|
||||||
.toString(),
|
.toString(),
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'
|
|||||||
import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
||||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
|
|
||||||
@Controller('auth/google')
|
@Controller('auth/google')
|
||||||
@ -28,7 +27,6 @@ export class GoogleAuthController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly loginTokenService: LoginTokenService,
|
private readonly loginTokenService: LoginTokenService,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly environmentService: EnvironmentService,
|
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@ -110,7 +108,7 @@ export class GoogleAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.authService.computeRedirectURI({
|
this.authService.computeRedirectURI({
|
||||||
loginToken: loginToken.token,
|
loginToken: loginToken.token,
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
billingCheckoutSessionState,
|
billingCheckoutSessionState,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -118,9 +116,9 @@ export class GoogleAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
||||||
err,
|
err,
|
||||||
currentWorkspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
currentWorkspace,
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export class MicrosoftAPIsAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.domainManagerService
|
this.domainManagerService
|
||||||
.buildWorkspaceURL({
|
.buildWorkspaceURL({
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
pathname: redirectLocation || '/settings/accounts',
|
pathname: redirectLocation || '/settings/accounts',
|
||||||
})
|
})
|
||||||
.toString(),
|
.toString(),
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'
|
|||||||
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
|
|
||||||
@Controller('auth/microsoft')
|
@Controller('auth/microsoft')
|
||||||
@ -28,7 +27,6 @@ export class MicrosoftAuthController {
|
|||||||
private readonly loginTokenService: LoginTokenService,
|
private readonly loginTokenService: LoginTokenService,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
private readonly environmentService: EnvironmentService,
|
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
) {}
|
) {}
|
||||||
@ -111,8 +109,7 @@ export class MicrosoftAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.authService.computeRedirectURI({
|
this.authService.computeRedirectURI({
|
||||||
loginToken: loginToken.token,
|
loginToken: loginToken.token,
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
|
|
||||||
billingCheckoutSessionState,
|
billingCheckoutSessionState,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -120,9 +117,9 @@ export class MicrosoftAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
||||||
err,
|
err,
|
||||||
currentWorkspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
currentWorkspace,
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import {
|
|||||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { AuthOAuthExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-oauth-exception.filter';
|
import { AuthOAuthExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-oauth-exception.filter';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { SAMLRequest } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
import { SAMLRequest } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||||
import { OIDCRequest } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
import { OIDCRequest } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||||
@ -45,7 +44,6 @@ export class SSOAuthController {
|
|||||||
private readonly loginTokenService: LoginTokenService,
|
private readonly loginTokenService: LoginTokenService,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
private readonly environmentService: EnvironmentService,
|
|
||||||
private readonly sSOService: SSOService,
|
private readonly sSOService: SSOService,
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@ -152,16 +150,16 @@ export class SSOAuthController {
|
|||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.authService.computeRedirectURI({
|
this.authService.computeRedirectURI({
|
||||||
loginToken: loginToken.token,
|
loginToken: loginToken.token,
|
||||||
subdomain: currentWorkspace.subdomain,
|
workspace: currentWorkspace,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
||||||
err,
|
err,
|
||||||
workspaceIdentityProvider?.workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
workspaceIdentityProvider?.workspace,
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
IdentityProviderType,
|
IdentityProviderType,
|
||||||
SSOIdentityProviderStatus,
|
SSOIdentityProviderStatus,
|
||||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||||
|
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-endpoints.dto';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
class SSOConnection {
|
class SSOConnection {
|
||||||
@ -34,8 +35,8 @@ export class AvailableWorkspaceOutput {
|
|||||||
@Field(() => String, { nullable: true })
|
@Field(() => String, { nullable: true })
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => workspaceUrls)
|
||||||
subdomain: string;
|
workspaceUrls: workspaceUrls;
|
||||||
|
|
||||||
@Field(() => String, { nullable: true })
|
@Field(() => String, { nullable: true })
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Field, ObjectType } from '@nestjs/graphql';
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { WorkspaceSubdomainAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
import { workspaceUrlsAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
||||||
|
|
||||||
import { AuthToken } from './token.entity';
|
import { AuthToken } from './token.entity';
|
||||||
|
|
||||||
@ -9,6 +9,6 @@ export class SignUpOutput {
|
|||||||
@Field(() => AuthToken)
|
@Field(() => AuthToken)
|
||||||
loginToken: AuthToken;
|
loginToken: AuthToken;
|
||||||
|
|
||||||
@Field(() => WorkspaceSubdomainAndId)
|
@Field(() => workspaceUrlsAndId)
|
||||||
workspace: WorkspaceSubdomainAndId;
|
workspace: workspaceUrlsAndId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,9 +27,11 @@ export class EnterpriseFeaturesEnabledGuard implements CanActivate {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
context,
|
||||||
});
|
err,
|
||||||
|
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,9 +50,11 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
|||||||
|
|
||||||
return (await super.canActivate(context)) as boolean;
|
return (await super.canActivate(context)) as boolean;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
context,
|
||||||
});
|
err,
|
||||||
|
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,9 +71,9 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
|
|||||||
this.guardRedirectService.dispatchErrorFromGuard(
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
context,
|
context,
|
||||||
err,
|
err,
|
||||||
workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
workspace,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -11,13 +11,11 @@ import {
|
|||||||
} from 'src/engine/core-modules/auth/auth.exception';
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GoogleOauthGuard extends AuthGuard('google') {
|
export class GoogleOauthGuard extends AuthGuard('google') {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
private readonly environmentService: EnvironmentService,
|
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
private readonly workspaceRepository: Repository<Workspace>,
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
) {
|
) {
|
||||||
@ -53,9 +51,9 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
|||||||
this.guardRedirectService.dispatchErrorFromGuard(
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
context,
|
context,
|
||||||
err,
|
err,
|
||||||
workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
workspace,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -28,9 +28,11 @@ export class GoogleProviderEnabledGuard implements CanActivate {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
context,
|
||||||
});
|
err,
|
||||||
|
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,9 +57,7 @@ export class MicrosoftAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
|||||||
AuthExceptionCode.INSUFFICIENT_SCOPES,
|
AuthExceptionCode.INSUFFICIENT_SCOPES,
|
||||||
)
|
)
|
||||||
: error,
|
: error,
|
||||||
{
|
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -72,9 +72,9 @@ export class MicrosoftAPIsOauthRequestCodeGuard extends AuthGuard(
|
|||||||
this.guardRedirectService.dispatchErrorFromGuard(
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
context,
|
context,
|
||||||
err,
|
err,
|
||||||
workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
workspace,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -5,14 +5,12 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
private readonly environmentService: EnvironmentService,
|
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
private readonly workspaceRepository: Repository<Workspace>,
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
) {
|
) {
|
||||||
@ -41,9 +39,9 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
|||||||
this.guardRedirectService.dispatchErrorFromGuard(
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
context,
|
context,
|
||||||
err,
|
err,
|
||||||
workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
workspace,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -28,9 +28,11 @@ export class MicrosoftProviderEnabledGuard implements CanActivate {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
context,
|
||||||
});
|
err,
|
||||||
|
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
import { OIDCAuthStrategy } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
import { OIDCAuthStrategy } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||||
|
|
||||||
@ -21,12 +20,13 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly sSOService: SSOService,
|
private readonly sSOService: SSOService,
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
private readonly environmentService: EnvironmentService,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getIdentityProviderId(request: any): string {
|
private getStateByRequest(request: any): {
|
||||||
|
identityProviderId: string;
|
||||||
|
} {
|
||||||
if (request.params.identityProviderId) {
|
if (request.params.identityProviderId) {
|
||||||
return request.params.identityProviderId;
|
return request.params.identityProviderId;
|
||||||
}
|
}
|
||||||
@ -39,24 +39,27 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
|||||||
) {
|
) {
|
||||||
const state = JSON.parse(request.query.state);
|
const state = JSON.parse(request.query.state);
|
||||||
|
|
||||||
return state.identityProviderId;
|
return {
|
||||||
|
identityProviderId: state.identityProviderId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Invalid OIDC identity provider params');
|
throw new Error('Invalid OIDC identity provider params');
|
||||||
}
|
}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
|
||||||
let identityProvider:
|
let identityProvider:
|
||||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||||
| null = null;
|
| null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const identityProviderId = this.getIdentityProviderId(request);
|
const state = this.getStateByRequest(request);
|
||||||
|
|
||||||
identityProvider =
|
identityProvider = await this.sSOService.findSSOIdentityProviderById(
|
||||||
await this.sSOService.findSSOIdentityProviderById(identityProviderId);
|
state.identityProviderId,
|
||||||
|
);
|
||||||
|
|
||||||
if (!identityProvider) {
|
if (!identityProvider) {
|
||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
@ -77,9 +80,9 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
|||||||
this.guardRedirectService.dispatchErrorFromGuard(
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
context,
|
context,
|
||||||
err,
|
err,
|
||||||
identityProvider?.workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
identityProvider?.workspace,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
import { Request } from 'express';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
@ -10,22 +12,34 @@ import {
|
|||||||
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
|
||||||
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||||
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SAMLAuthGuard extends AuthGuard('saml') {
|
export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly sSOService: SSOService,
|
private readonly sSOService: SSOService,
|
||||||
private readonly guardRedirectService: GuardRedirectService,
|
private readonly guardRedirectService: GuardRedirectService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getRelayStateByRequest(request: Request) {
|
||||||
|
try {
|
||||||
|
const relayStateRaw = request.body.RelayState || request.query.RelayState;
|
||||||
|
|
||||||
|
if (relayStateRaw) {
|
||||||
|
return JSON.parse(relayStateRaw);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.exceptionHandlerService.captureExceptions(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext) {
|
async canActivate(context: ExecutionContext) {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
|
||||||
let identityProvider:
|
let identityProvider:
|
||||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||||
@ -49,9 +63,9 @@ export class SAMLAuthGuard extends AuthGuard('saml') {
|
|||||||
this.guardRedirectService.dispatchErrorFromGuard(
|
this.guardRedirectService.dispatchErrorFromGuard(
|
||||||
context,
|
context,
|
||||||
err,
|
err,
|
||||||
identityProvider?.workspace ?? {
|
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
identityProvider?.workspace,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -455,15 +455,15 @@ export class AuthService {
|
|||||||
|
|
||||||
computeRedirectURI({
|
computeRedirectURI({
|
||||||
loginToken,
|
loginToken,
|
||||||
subdomain,
|
workspace,
|
||||||
billingCheckoutSessionState,
|
billingCheckoutSessionState,
|
||||||
}: {
|
}: {
|
||||||
loginToken: string;
|
loginToken: string;
|
||||||
subdomain: string;
|
workspace: Pick<Workspace, 'subdomain' | 'hostname'>;
|
||||||
billingCheckoutSessionState?: string;
|
billingCheckoutSessionState?: string;
|
||||||
}) {
|
}) {
|
||||||
const url = this.domainManagerService.buildWorkspaceURL({
|
const url = this.domainManagerService.buildWorkspaceURL({
|
||||||
subdomain,
|
workspace,
|
||||||
pathname: '/verify',
|
pathname: '/verify',
|
||||||
searchParams: {
|
searchParams: {
|
||||||
loginToken,
|
loginToken,
|
||||||
|
|||||||
@ -50,7 +50,6 @@ export class OIDCAuthStrategy extends PassportStrategy(
|
|||||||
...options,
|
...options,
|
||||||
state: JSON.stringify({
|
state: JSON.stringify({
|
||||||
identityProviderId: req.params.identityProviderId,
|
identityProviderId: req.params.identityProviderId,
|
||||||
...(req.query.forceSubdomainUrl ? { forceSubdomainUrl: true } : {}),
|
|
||||||
...(req.query.workspaceInviteHash
|
...(req.query.workspaceInviteHash
|
||||||
? { workspaceInviteHash: req.query.workspaceInviteHash }
|
? { workspaceInviteHash: req.query.workspaceInviteHash }
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
export enum BillingEntitlementKey {
|
export enum BillingEntitlementKey {
|
||||||
SSO = 'SSO',
|
SSO = 'SSO',
|
||||||
|
CUSTOM_DOMAIN = 'CUSTOM_DOMAIN',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export class BillingPortalWorkspaceService {
|
|||||||
requirePaymentMethod,
|
requirePaymentMethod,
|
||||||
}: BillingPortalCheckoutSessionParameters): Promise<string> {
|
}: BillingPortalCheckoutSessionParameters): Promise<string> {
|
||||||
const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({
|
const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
});
|
});
|
||||||
const cancelUrl = frontBaseUrl.toString();
|
const cancelUrl = frontBaseUrl.toString();
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ export class BillingPortalWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({
|
const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (returnUrlPath) {
|
if (returnUrlPath) {
|
||||||
|
|||||||
@ -41,6 +41,12 @@ describe('transformStripeEntitlementUpdatedEventToDatabaseEntitlement', () => {
|
|||||||
value: true,
|
value: true,
|
||||||
stripeCustomerId: 'cus_123',
|
stripeCustomerId: 'cus_123',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: BillingEntitlementKey.CUSTOM_DOMAIN,
|
||||||
|
stripeCustomerId: 'cus_123',
|
||||||
|
value: false,
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,6 +85,12 @@ describe('transformStripeEntitlementUpdatedEventToDatabaseEntitlement', () => {
|
|||||||
value: false,
|
value: false,
|
||||||
stripeCustomerId: 'cus_123',
|
stripeCustomerId: 'cus_123',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'CUSTOM_DOMAIN',
|
||||||
|
stripeCustomerId: 'cus_123',
|
||||||
|
value: false,
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,6 +9,53 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|||||||
import { DomainManagerService } from './domain-manager.service';
|
import { DomainManagerService } from './domain-manager.service';
|
||||||
|
|
||||||
describe('DomainManagerService', () => {
|
describe('DomainManagerService', () => {
|
||||||
|
describe('getworkspaceUrls', () => {
|
||||||
|
it('should return a URL containing the correct hostname if hostname is provided', () => {
|
||||||
|
jest
|
||||||
|
.spyOn(environmentService, 'get')
|
||||||
|
.mockImplementation((key: string) => {
|
||||||
|
const env = {
|
||||||
|
FRONT_PROTOCOL: 'https',
|
||||||
|
FRONT_DOMAIN: 'example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
return env[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = domainManagerService.getworkspaceUrls({
|
||||||
|
subdomain: 'subdomain',
|
||||||
|
hostname: 'custom-host.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
customUrl: 'https://custom-host.com/',
|
||||||
|
subdomainUrl: 'https://subdomain.example.com/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a URL containing the correct subdomain if hostname is not provided but subdomain is', () => {
|
||||||
|
jest
|
||||||
|
.spyOn(environmentService, 'get')
|
||||||
|
.mockImplementation((key: string) => {
|
||||||
|
const env = {
|
||||||
|
FRONT_PROTOCOL: 'https',
|
||||||
|
FRONT_DOMAIN: 'example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
return env[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = domainManagerService.getworkspaceUrls({
|
||||||
|
subdomain: 'subdomain',
|
||||||
|
hostname: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
customUrl: undefined,
|
||||||
|
subdomainUrl: 'https://subdomain.example.com/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
let domainManagerService: DomainManagerService;
|
let domainManagerService: DomainManagerService;
|
||||||
let environmentService: EnvironmentService;
|
let environmentService: EnvironmentService;
|
||||||
|
|
||||||
@ -106,7 +153,10 @@ describe('DomainManagerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = domainManagerService.buildWorkspaceURL({
|
const result = domainManagerService.buildWorkspaceURL({
|
||||||
subdomain: 'test',
|
workspace: {
|
||||||
|
subdomain: 'test',
|
||||||
|
hostname: undefined,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.toString()).toBe('https://test.example.com/');
|
expect(result.toString()).toBe('https://test.example.com/');
|
||||||
@ -125,7 +175,10 @@ describe('DomainManagerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = domainManagerService.buildWorkspaceURL({
|
const result = domainManagerService.buildWorkspaceURL({
|
||||||
subdomain: 'subdomain',
|
workspace: {
|
||||||
|
subdomain: 'test',
|
||||||
|
hostname: undefined,
|
||||||
|
},
|
||||||
pathname: '/path/to/resource',
|
pathname: '/path/to/resource',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -145,8 +198,10 @@ describe('DomainManagerService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = domainManagerService.buildWorkspaceURL({
|
const result = domainManagerService.buildWorkspaceURL({
|
||||||
subdomain: 'subdomain',
|
workspace: {
|
||||||
|
subdomain: 'test',
|
||||||
|
hostname: undefined,
|
||||||
|
},
|
||||||
searchParams: {
|
searchParams: {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
baz: 123,
|
baz: 123,
|
||||||
|
|||||||
@ -74,33 +74,31 @@ export class DomainManagerService {
|
|||||||
buildEmailVerificationURL({
|
buildEmailVerificationURL({
|
||||||
emailVerificationToken,
|
emailVerificationToken,
|
||||||
email,
|
email,
|
||||||
subdomain,
|
workspace,
|
||||||
}: {
|
}: {
|
||||||
emailVerificationToken: string;
|
emailVerificationToken: string;
|
||||||
email: string;
|
email: string;
|
||||||
subdomain: string;
|
workspace: Pick<Workspace, 'subdomain' | 'hostname'>;
|
||||||
}) {
|
}) {
|
||||||
return this.buildWorkspaceURL({
|
return this.buildWorkspaceURL({
|
||||||
subdomain,
|
workspace,
|
||||||
pathname: 'verify-email',
|
pathname: 'verify-email',
|
||||||
searchParams: { emailVerificationToken, email },
|
searchParams: { emailVerificationToken, email },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
buildWorkspaceURL({
|
buildWorkspaceURL({
|
||||||
subdomain,
|
workspace,
|
||||||
pathname,
|
pathname,
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
subdomain: string;
|
workspace: Pick<Workspace, 'subdomain' | 'hostname'>;
|
||||||
pathname?: string;
|
pathname?: string;
|
||||||
searchParams?: Record<string, string | number>;
|
searchParams?: Record<string, string | number>;
|
||||||
}) {
|
}) {
|
||||||
const url = this.getFrontUrl();
|
const workspaceUrls = this.getworkspaceUrls(workspace);
|
||||||
|
|
||||||
if (this.environmentService.get('IS_MULTIWORKSPACE_ENABLED')) {
|
const url = new URL(workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl);
|
||||||
url.hostname = `${subdomain}.${url.hostname}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathname) {
|
if (pathname) {
|
||||||
url.pathname = pathname;
|
url.pathname = pathname;
|
||||||
@ -117,21 +115,6 @@ export class DomainManagerService {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Deprecated
|
|
||||||
getWorkspaceSubdomainFromUrl = (url: string) => {
|
|
||||||
const { hostname: originHostname } = new URL(url);
|
|
||||||
|
|
||||||
if (!originHostname.endsWith(this.getFrontUrl().hostname)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const frontDomain = this.getFrontUrl().hostname;
|
|
||||||
|
|
||||||
const subdomain = originHostname.replace(`.${frontDomain}`, '');
|
|
||||||
|
|
||||||
return this.isDefaultSubdomain(subdomain) ? null : subdomain;
|
|
||||||
};
|
|
||||||
|
|
||||||
getSubdomainAndHostnameFromUrl = (url: string) => {
|
getSubdomainAndHostnameFromUrl = (url: string) => {
|
||||||
const { hostname: originHostname } = new URL(url);
|
const { hostname: originHostname } = new URL(url);
|
||||||
|
|
||||||
@ -162,9 +145,12 @@ export class DomainManagerService {
|
|||||||
return subdomain === this.environmentService.get('DEFAULT_SUBDOMAIN');
|
return subdomain === this.environmentService.get('DEFAULT_SUBDOMAIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
computeRedirectErrorUrl(errorMessage: string, subdomain: string) {
|
computeRedirectErrorUrl(
|
||||||
|
errorMessage: string,
|
||||||
|
workspace: Pick<Workspace, 'subdomain' | 'hostname'>,
|
||||||
|
) {
|
||||||
const url = this.buildWorkspaceURL({
|
const url = this.buildWorkspaceURL({
|
||||||
subdomain: subdomain,
|
workspace,
|
||||||
pathname: '/verify',
|
pathname: '/verify',
|
||||||
searchParams: { errorMessage },
|
searchParams: { errorMessage },
|
||||||
});
|
});
|
||||||
@ -352,7 +338,7 @@ export class DomainManagerService {
|
|||||||
await this.deleteCustomHostname(fromCustomHostname.id);
|
await this.deleteCustomHostname(fromCustomHostname.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.registerCustomHostname(toHostname);
|
return this.registerCustomHostname(toHostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteCustomHostnameByHostnameSilently(hostname: string) {
|
async deleteCustomHostnameByHostnameSilently(hostname: string) {
|
||||||
@ -378,4 +364,32 @@ export class DomainManagerService {
|
|||||||
zone_id: this.environmentService.get('CLOUDFLARE_ZONE_ID'),
|
zone_id: this.environmentService.get('CLOUDFLARE_ZONE_ID'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCustomWorkspaceEndpoint(hostname: string) {
|
||||||
|
const url = this.getFrontUrl();
|
||||||
|
|
||||||
|
url.hostname = hostname;
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTwentyWorkspaceEndpoint(subdomain: string) {
|
||||||
|
const url = this.getFrontUrl();
|
||||||
|
|
||||||
|
url.hostname = `${subdomain}.${url.hostname}`;
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
getworkspaceUrls({
|
||||||
|
subdomain,
|
||||||
|
hostname,
|
||||||
|
}: Pick<Workspace, 'subdomain' | 'hostname'>) {
|
||||||
|
return {
|
||||||
|
customUrl: hostname
|
||||||
|
? this.getCustomWorkspaceEndpoint(hostname)
|
||||||
|
: undefined,
|
||||||
|
subdomainUrl: this.getTwentyWorkspaceEndpoint(subdomain),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export class EmailVerificationResolver {
|
|||||||
|
|
||||||
return await this.emailVerificationService.resendEmailVerificationToken(
|
return await this.emailVerificationService.resendEmailVerificationToken(
|
||||||
resendEmailVerificationTokenInput.email,
|
resendEmailVerificationTokenInput.email,
|
||||||
workspace.subdomain,
|
workspace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import {
|
|||||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||||
@ -38,7 +39,7 @@ export class EmailVerificationService {
|
|||||||
async sendVerificationEmail(
|
async sendVerificationEmail(
|
||||||
userId: string,
|
userId: string,
|
||||||
email: string,
|
email: string,
|
||||||
subdomain: string,
|
workspace: Pick<Workspace, 'subdomain' | 'hostname'>,
|
||||||
) {
|
) {
|
||||||
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||||
return { success: false };
|
return { success: false };
|
||||||
@ -51,7 +52,7 @@ export class EmailVerificationService {
|
|||||||
this.domainManagerService.buildEmailVerificationURL({
|
this.domainManagerService.buildEmailVerificationURL({
|
||||||
emailVerificationToken,
|
emailVerificationToken,
|
||||||
email,
|
email,
|
||||||
subdomain,
|
workspace,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emailData = {
|
const emailData = {
|
||||||
@ -80,7 +81,10 @@ export class EmailVerificationService {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async resendEmailVerificationToken(email: string, subdomain: string) {
|
async resendEmailVerificationToken(
|
||||||
|
email: string,
|
||||||
|
workspace: Pick<Workspace, 'subdomain' | 'hostname'>,
|
||||||
|
) {
|
||||||
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||||
throw new EmailVerificationException(
|
throw new EmailVerificationException(
|
||||||
'Email verification token cannot be sent because email verification is not required',
|
'Email verification token cannot be sent because email verification is not required',
|
||||||
@ -121,7 +125,7 @@ export class EmailVerificationService {
|
|||||||
await this.appTokenRepository.delete(existingToken.id);
|
await this.appTokenRepository.delete(existingToken.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sendVerificationEmail(user.id, email, subdomain);
|
await this.sendVerificationEmail(user.id, email, workspace);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Request } from 'express';
|
||||||
|
|
||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
import { CustomException } from 'src/utils/custom-exception';
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GuardRedirectService {
|
export class GuardRedirectService {
|
||||||
@ -17,7 +20,7 @@ export class GuardRedirectService {
|
|||||||
dispatchErrorFromGuard(
|
dispatchErrorFromGuard(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
error: Error | CustomException,
|
error: Error | CustomException,
|
||||||
workspace: { id?: string; subdomain: string },
|
workspace: { id?: string; subdomain: string; hostname?: string },
|
||||||
) {
|
) {
|
||||||
if ('contextType' in context && context.contextType === 'graphql') {
|
if ('contextType' in context && context.contextType === 'graphql') {
|
||||||
throw error;
|
throw error;
|
||||||
@ -29,15 +32,36 @@ export class GuardRedirectService {
|
|||||||
.redirect(this.getRedirectErrorUrlAndCaptureExceptions(error, workspace));
|
.redirect(this.getRedirectErrorUrlAndCaptureExceptions(error, workspace));
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubdomainFromContext(context: ExecutionContext) {
|
getSubdomainAndHostnameFromWorkspace(
|
||||||
const request = context.switchToHttp().getRequest();
|
workspace?: Pick<Workspace, 'subdomain' | 'hostname'> | null,
|
||||||
|
) {
|
||||||
|
if (!workspace) {
|
||||||
|
return {
|
||||||
|
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const subdomainFromUrl =
|
return workspace;
|
||||||
this.domainManagerService.getWorkspaceSubdomainFromUrl(
|
}
|
||||||
request.headers.referer,
|
|
||||||
);
|
|
||||||
|
|
||||||
return subdomainFromUrl ?? this.environmentService.get('DEFAULT_SUBDOMAIN');
|
getSubdomainAndHostnameFromContext(context: ExecutionContext): {
|
||||||
|
subdomain: string;
|
||||||
|
hostname?: string;
|
||||||
|
} {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
|
||||||
|
const subdomainAndHostnameFromReferer = request.headers.referer
|
||||||
|
? this.domainManagerService.getSubdomainAndHostnameFromUrl(
|
||||||
|
request.headers.referer,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
subdomain:
|
||||||
|
subdomainAndHostnameFromReferer?.subdomain ??
|
||||||
|
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||||
|
hostname: subdomainAndHostnameFromReferer?.hostname,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private captureException(err: Error | CustomException, workspaceId?: string) {
|
private captureException(err: Error | CustomException, workspaceId?: string) {
|
||||||
@ -52,13 +76,13 @@ export class GuardRedirectService {
|
|||||||
|
|
||||||
getRedirectErrorUrlAndCaptureExceptions(
|
getRedirectErrorUrlAndCaptureExceptions(
|
||||||
err: Error | CustomException,
|
err: Error | CustomException,
|
||||||
workspace: { id?: string; subdomain: string },
|
workspace: { id?: string; subdomain: string; hostname?: string },
|
||||||
) {
|
) {
|
||||||
this.captureException(err, workspace.id);
|
this.captureException(err, workspace.id);
|
||||||
|
|
||||||
return this.domainManagerService.computeRedirectErrorUrl(
|
return this.domainManagerService.computeRedirectErrorUrl(
|
||||||
err instanceof AuthException ? err.message : 'Unknown error',
|
err instanceof AuthException ? err.message : 'Unknown error',
|
||||||
workspace.subdomain,
|
workspace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { IsOptional, IsBoolean, IsString } from 'class-validator';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
export class GetAuthorizationUrlInput {
|
export class GetAuthorizationUrlInput {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
|
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -29,6 +30,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
|||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
WorkspaceInvitationModule,
|
WorkspaceInvitationModule,
|
||||||
|
DomainManagerModule,
|
||||||
TwentyORMModule,
|
TwentyORMModule,
|
||||||
],
|
],
|
||||||
services: [UserWorkspaceService],
|
services: [UserWorkspaceService],
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.
|
|||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
|
|
||||||
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||||
constructor(
|
constructor(
|
||||||
@ -37,6 +38,7 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
private readonly typeORMService: TypeORMService,
|
private readonly typeORMService: TypeORMService,
|
||||||
private readonly workspaceInvitationService: WorkspaceInvitationService,
|
private readonly workspaceInvitationService: WorkspaceInvitationService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
|
private readonly domainManagerService: DomainManagerService,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {
|
) {
|
||||||
super(userWorkspaceRepository);
|
super(userWorkspaceRepository);
|
||||||
@ -179,7 +181,9 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
return user.workspaces.map<AvailableWorkspaceOutput>((userWorkspace) => ({
|
return user.workspaces.map<AvailableWorkspaceOutput>((userWorkspace) => ({
|
||||||
id: userWorkspace.workspaceId,
|
id: userWorkspace.workspaceId,
|
||||||
displayName: userWorkspace.workspace.displayName,
|
displayName: userWorkspace.workspace.displayName,
|
||||||
subdomain: userWorkspace.workspace.subdomain,
|
workspaceUrls: this.domainManagerService.getworkspaceUrls(
|
||||||
|
userWorkspace.workspace,
|
||||||
|
),
|
||||||
logo: userWorkspace.workspace.logo,
|
logo: userWorkspace.workspace.logo,
|
||||||
sso: userWorkspace.workspace.workspaceSSOIdentityProviders.reduce(
|
sso: userWorkspace.workspace.workspaceSSOIdentityProviders.reduce(
|
||||||
(acc, identityProvider) =>
|
(acc, identityProvider) =>
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-p
|
|||||||
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
|
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
|
|
||||||
registerEnumType(OnboardingStatus, {
|
registerEnumType(OnboardingStatus, {
|
||||||
name: 'OnboardingStatus',
|
name: 'OnboardingStatus',
|
||||||
@ -100,7 +99,4 @@ export class User {
|
|||||||
|
|
||||||
@Field(() => OnboardingStatus, { nullable: true })
|
@Field(() => OnboardingStatus, { nullable: true })
|
||||||
onboardingStatus: OnboardingStatus;
|
onboardingStatus: OnboardingStatus;
|
||||||
|
|
||||||
@Field(() => Workspace, { nullable: true })
|
|
||||||
currentWorkspace: Relation<Workspace>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,22 +81,7 @@ export class UserResolver {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => User)
|
@Query(() => User)
|
||||||
async currentUser(
|
async currentUser(@AuthUser() { id: userId }: User): Promise<User> {
|
||||||
@AuthUser() { id: userId }: User,
|
|
||||||
@OriginHeader() origin: string,
|
|
||||||
): Promise<User> {
|
|
||||||
const workspace =
|
|
||||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
|
||||||
origin,
|
|
||||||
);
|
|
||||||
|
|
||||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
|
||||||
|
|
||||||
await this.userService.hasUserAccessToWorkspaceOrThrow(
|
|
||||||
userId,
|
|
||||||
workspace.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
@ -109,7 +94,7 @@ export class UserResolver {
|
|||||||
new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND),
|
new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ...user, currentWorkspace: workspace };
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => GraphQLJSONObject)
|
@ResolveField(() => GraphQLJSONObject)
|
||||||
@ -314,4 +299,9 @@ export class UserResolver {
|
|||||||
|
|
||||||
return this.onboardingService.getOnboardingStatus(user, workspace);
|
return this.onboardingService.getOnboardingStatus(user, workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => Workspace)
|
||||||
|
async currentWorkspace(@AuthWorkspace() workspace: Workspace) {
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -279,7 +279,7 @@ export class WorkspaceInvitationService {
|
|||||||
for (const invitation of invitationsPr) {
|
for (const invitation of invitationsPr) {
|
||||||
if (invitation.status === 'fulfilled') {
|
if (invitation.status === 'fulfilled') {
|
||||||
const link = this.domainManagerService.buildWorkspaceURL({
|
const link = this.domainManagerService.buildWorkspaceURL({
|
||||||
subdomain: workspace.subdomain,
|
workspace,
|
||||||
pathname: `invite/${workspace?.inviteHash}`,
|
pathname: `invite/${workspace?.inviteHash}`,
|
||||||
searchParams: invitation.value.isPersonalInvitation
|
searchParams: invitation.value.isPersonalInvitation
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
IdentityProviderType,
|
IdentityProviderType,
|
||||||
SSOIdentityProviderStatus,
|
SSOIdentityProviderStatus,
|
||||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||||
|
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-endpoints.dto';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class SSOIdentityProvider {
|
export class SSOIdentityProvider {
|
||||||
@ -56,9 +57,6 @@ export class PublicWorkspaceDataOutput {
|
|||||||
@Field(() => String, { nullable: true })
|
@Field(() => String, { nullable: true })
|
||||||
displayName: Workspace['displayName'];
|
displayName: Workspace['displayName'];
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => workspaceUrls)
|
||||||
subdomain: Workspace['subdomain'];
|
workspaceUrls: workspaceUrls;
|
||||||
|
|
||||||
@Field(() => String, { nullable: true })
|
|
||||||
hostname: Workspace['hostname'];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { ObjectType, Field } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class workspaceUrls {
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
customUrl?: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
subdomainUrl: string;
|
||||||
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import { Field, ObjectType } from '@nestjs/graphql';
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-endpoints.dto';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class WorkspaceSubdomainAndId {
|
export class workspaceUrlsAndId {
|
||||||
@Field()
|
@Field(() => workspaceUrls)
|
||||||
subdomain: string;
|
workspaceUrls: workspaceUrls;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
|||||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
||||||
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
|
||||||
import { WorkspaceService } from './workspace.service';
|
import { WorkspaceService } from './workspace.service';
|
||||||
|
|
||||||
@ -81,6 +82,10 @@ describe('WorkspaceService', () => {
|
|||||||
provide: FeatureFlagService,
|
provide: FeatureFlagService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: ExceptionHandlerService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { WorkspaceActivationStatus, isDefined } from 'twenty-shared';
|
import { isDefined, WorkspaceActivationStatus } from 'twenty-shared';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||||
@ -24,10 +24,14 @@ import {
|
|||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
||||||
import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags';
|
import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags';
|
||||||
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||||
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||||
|
private readonly featureLookUpKey = BillingEntitlementKey.CUSTOM_DOMAIN;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
private readonly workspaceRepository: Repository<Workspace>,
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
@ -42,10 +46,26 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
private readonly userWorkspaceService: UserWorkspaceService,
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
private readonly domainManagerService: DomainManagerService,
|
private readonly domainManagerService: DomainManagerService,
|
||||||
|
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async isCustomDomainEnabled(workspaceId: string) {
|
||||||
|
const isCustomDomainBillingEnabled =
|
||||||
|
await this.billingService.hasEntitlement(
|
||||||
|
workspaceId,
|
||||||
|
this.featureLookUpKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isCustomDomainBillingEnabled) {
|
||||||
|
throw new WorkspaceException(
|
||||||
|
`No entitlement found for this workspace`,
|
||||||
|
WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async validateSubdomainUpdate(newSubdomain: string) {
|
private async validateSubdomainUpdate(newSubdomain: string) {
|
||||||
const subdomainAvailable = await this.isSubdomainAvailable(newSubdomain);
|
const subdomainAvailable = await this.isSubdomainAvailable(newSubdomain);
|
||||||
|
|
||||||
@ -61,6 +81,8 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async setCustomDomain(workspace: Workspace, hostname: string) {
|
private async setCustomDomain(workspace: Workspace, hostname: string) {
|
||||||
|
await this.isCustomDomainEnabled(workspace.id);
|
||||||
|
|
||||||
const existingWorkspace = await this.workspaceRepository.findOne({
|
const existingWorkspace = await this.workspaceRepository.findOne({
|
||||||
where: { hostname },
|
where: { hostname },
|
||||||
});
|
});
|
||||||
@ -126,10 +148,11 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
if (payload.hostname && customDomainRegistered) {
|
if (payload.hostname && customDomainRegistered) {
|
||||||
this.domainManagerService
|
this.domainManagerService
|
||||||
.deleteCustomHostnameByHostnameSilently(payload.hostname)
|
.deleteCustomHostnameByHostnameSilently(payload.hostname)
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
// send to sentry
|
this.exceptionHandlerService.captureExceptions([err]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,4 +11,5 @@ export enum WorkspaceExceptionCode {
|
|||||||
SUBDOMAIN_ALREADY_TAKEN = 'SUBDOMAIN_ALREADY_TAKEN',
|
SUBDOMAIN_ALREADY_TAKEN = 'SUBDOMAIN_ALREADY_TAKEN',
|
||||||
DOMAIN_ALREADY_TAKEN = 'DOMAIN_ALREADY_TAKEN',
|
DOMAIN_ALREADY_TAKEN = 'DOMAIN_ALREADY_TAKEN',
|
||||||
WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND',
|
WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND',
|
||||||
|
WORKSPACE_CUSTOM_DOMAIN_DISABLED = 'WORKSPACE_CUSTOM_DOMAIN_DISABLED',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.
|
|||||||
|
|
||||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||||
import { CustomHostnameDetails } from 'src/engine/core-modules/domain-manager/dtos/custom-hostname-details';
|
|
||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
@ -44,6 +43,8 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
|||||||
import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter';
|
import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||||
|
import { CustomHostnameDetails } from 'src/engine/core-modules/domain-manager/dtos/custom-hostname-details';
|
||||||
|
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-endpoints.dto';
|
||||||
|
|
||||||
import { Workspace } from './workspace.entity';
|
import { Workspace } from './workspace.entity';
|
||||||
|
|
||||||
@ -214,6 +215,11 @@ export class WorkspaceResolver {
|
|||||||
return isDefined(this.environmentService.get('ENTERPRISE_KEY'));
|
return isDefined(this.environmentService.get('ENTERPRISE_KEY'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => workspaceUrls)
|
||||||
|
workspaceUrls(@Parent() workspace: Workspace) {
|
||||||
|
return this.domainManagerService.getworkspaceUrls(workspace);
|
||||||
|
}
|
||||||
|
|
||||||
@Query(() => CustomHostnameDetails, { nullable: true })
|
@Query(() => CustomHostnameDetails, { nullable: true })
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
async getHostnameDetails(
|
async getHostnameDetails(
|
||||||
@ -225,7 +231,7 @@ export class WorkspaceResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => PublicWorkspaceDataOutput)
|
@Query(() => PublicWorkspaceDataOutput)
|
||||||
async getPublicWorkspaceDataBySubdomain(
|
async getPublicWorkspaceDataByDomain(
|
||||||
@OriginHeader() origin: string,
|
@OriginHeader() origin: string,
|
||||||
): Promise<PublicWorkspaceDataOutput | undefined> {
|
): Promise<PublicWorkspaceDataOutput | undefined> {
|
||||||
try {
|
try {
|
||||||
@ -262,8 +268,7 @@ export class WorkspaceResolver {
|
|||||||
id: workspace.id,
|
id: workspace.id,
|
||||||
logo: workspaceLogoWithToken,
|
logo: workspaceLogoWithToken,
|
||||||
displayName: workspace.displayName,
|
displayName: workspace.displayName,
|
||||||
subdomain: workspace.subdomain,
|
workspaceUrls: this.domainManagerService.getworkspaceUrls(workspace),
|
||||||
hostname: workspace.hostname,
|
|
||||||
authProviders: getAuthProvidersByWorkspace({
|
authProviders: getAuthProvidersByWorkspace({
|
||||||
workspace,
|
workspace,
|
||||||
systemEnabledProviders,
|
systemEnabledProviders,
|
||||||
|
|||||||
@ -15,5 +15,5 @@ export const EXCLUDED_MIDDLEWARE_OPERATIONS = [
|
|||||||
'IntrospectionQuery',
|
'IntrospectionQuery',
|
||||||
'ExchangeAuthorizationCode',
|
'ExchangeAuthorizationCode',
|
||||||
'GetAuthorizationUrl',
|
'GetAuthorizationUrl',
|
||||||
'GetPublicWorkspaceDataBySubdomain',
|
'GetPublicWorkspaceDataByDomain',
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
Reference in New Issue
Block a user