Skip to content

Commit

Permalink
feat(settings-security): add feature flag for trusted domains (#10338)
Browse files Browse the repository at this point in the history
Introduce a new feature flag, IsTrustedDomainsEnabled, to control the
visibility of the trusted domains section in security settings. Updated
back-end seeds, enums, and front-end logic to support this change.
  • Loading branch information
AMoreaux authored Feb 21, 2025
1 parent e86de64 commit 6fbb8b6
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 115 deletions.
1 change: 1 addition & 0 deletions packages/twenty-front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ export enum FeatureFlagKey {
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
IsBillingPlansEnabled = 'IsBillingPlansEnabled',
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
IsCopilotEnabled = 'IsCopilotEnabled',
Expand Down
1 change: 1 addition & 0 deletions packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ export enum FeatureFlagKey {
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
IsBillingPlansEnabled = 'IsBillingPlansEnabled',
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
IsCopilotEnabled = 'IsCopilotEnabled',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ export const SettingsSecurityApprovedAccessDomainValidationEffect = () => {
},
onCompleted: () => {
enqueueSnackBar('Approved access domain validated', {
dedupeKey: 'approved-access-domain-validation-dedupe-key',
variant: SnackBarVariant.Success,
});
},
onError: () => {
enqueueSnackBar('Error validating approved access domain', {
dedupeKey: 'approved-access-domain-validation-error-dedupe-key',
variant: SnackBarVariant.Error,
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';

const StyledContainer = styled.div`
width: 100%;
Expand All @@ -29,6 +31,10 @@ const StyledSection = styled(Section)`
export const SettingsSecurity = () => {
const { t } = useLingui();

const IsApprovedAccessDomainsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsApprovedAccessDomainsEnabled,
);

return (
<SubMenuTopBarContainer
title={t`Security`}
Expand Down Expand Up @@ -58,13 +64,15 @@ export const SettingsSecurity = () => {
/>
<SettingsSSOIdentitiesProvidersListCard />
</StyledSection>
<StyledSection>
<H2Title
title={t`Approved Email Domain`}
description={t`Anyone with an email address at these domains is allowed to sign up for this workspace.`}
/>
<SettingsApprovedAccessDomainsListCard />
</StyledSection>
{IsApprovedAccessDomainsEnabled && (
<StyledSection>
<H2Title
title={t`Approved Email Domain`}
description={t`Anyone with an email address at these domains is allowed to sign up for this workspace.`}
/>
<SettingsApprovedAccessDomainsListCard />
</StyledSection>
)}
<Section>
<StyledContainer>
<H2Title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: false,
},
{
key: FeatureFlagKey.IsApprovedAccessDomainsEnabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IsBillingPlansEnabled,
workspaceId: workspaceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/micr
// import { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service';
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
import { SocialSsoService } from 'src/engine/core-modules/auth/services/social-sso.service';
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.service';
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
Expand Down Expand Up @@ -114,7 +114,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
ResetPasswordService,
TransientTokenService,
ApiKeyService,
SocialSsoService,
AuthSsoService,
// reenable when working on: https://github.com/twentyhq/twenty/issues/9143
// OAuthService,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type';

@Injectable()
export class SocialSsoService {
export class AuthSsoService {
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
Expand Down Expand Up @@ -55,14 +55,21 @@ export class SocialSsoService {
},
},
},
relations: ['workspaceUsers', 'workspaceUsers.user'],
relations: [
'workspaceUsers',
'workspaceUsers.user',
'approvedAccessDomains',
],
});

return workspace ?? undefined;
}

return await this.workspaceRepository.findOneBy({
id: workspaceId,
return await this.workspaceRepository.findOne({
where: {
id: workspaceId,
},
relations: ['approvedAccessDomains'],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ import { getRepositoryToken } from '@nestjs/typeorm';

import { Repository } from 'typeorm';

import { SocialSsoService } from 'src/engine/core-modules/auth/services/social-sso.service';
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';

describe('SocialSsoService', () => {
let socialSsoService: SocialSsoService;
describe('AuthSsoService', () => {
let authSsoService: AuthSsoService;
let workspaceRepository: Repository<Workspace>;
let environmentService: EnvironmentService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SocialSsoService,
AuthSsoService,
{
provide: getRepositoryToken(Workspace, 'core'),
useClass: Repository,
useValue: {
findOne: jest.fn(),
},
},
{
provide: EnvironmentService,
Expand All @@ -29,7 +31,7 @@ describe('SocialSsoService', () => {
],
}).compile();

socialSsoService = module.get<SocialSsoService>(SocialSsoService);
authSsoService = module.get<AuthSsoService>(AuthSsoService);
workspaceRepository = module.get<Repository<Workspace>>(
getRepositoryToken(Workspace, 'core'),
);
Expand All @@ -42,18 +44,21 @@ describe('SocialSsoService', () => {
const mockWorkspace = { id: workspaceId } as Workspace;

jest
.spyOn(workspaceRepository, 'findOneBy')
.spyOn(workspaceRepository, 'findOne')
.mockResolvedValue(mockWorkspace);

const result =
await socialSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider(
await authSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider(
{ authProvider: 'google', email: '[email protected]' },
workspaceId,
);

expect(result).toEqual(mockWorkspace);
expect(workspaceRepository.findOneBy).toHaveBeenCalledWith({
id: workspaceId,
expect(workspaceRepository.findOne).toHaveBeenCalledWith({
where: {
id: workspaceId,
},
relations: ['approvedAccessDomains'],
});
});

Expand All @@ -68,7 +73,7 @@ describe('SocialSsoService', () => {
.mockResolvedValue(mockWorkspace);

const result =
await socialSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider({
await authSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider({
authProvider,
email,
});
Expand All @@ -83,7 +88,11 @@ describe('SocialSsoService', () => {
},
},
},
relations: ['workspaceUsers', 'workspaceUsers.user'],
relations: [
'workspaceUsers',
'workspaceUsers.user',
'approvedAccessDomains',
],
});
});

Expand All @@ -92,7 +101,7 @@ describe('SocialSsoService', () => {
jest.spyOn(workspaceRepository, 'findOne').mockResolvedValue(null);

const result =
await socialSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider({
await authSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider({
authProvider: 'google',
email: '[email protected]',
});
Expand All @@ -104,7 +113,7 @@ describe('SocialSsoService', () => {
jest.spyOn(environmentService, 'get').mockReturnValue(true);

await expect(
socialSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider({
authSsoService.findWorkspaceFromWorkspaceIdOrAuthProvider({
authProvider: 'invalid-provider' as any,
email: '[email protected]',
}),
Expand Down
Loading

0 comments on commit 6fbb8b6

Please sign in to comment.