Enforce system wide sso providers (#9058)

We have recently introduced the possibility to specify workspace
specific auth providers.
I'm:
- introducing system wide auth providers (provided by clientConfig)
- making sure workspace specific auth providers belong to system wide
auth providers set
This commit is contained in:
Charles Bochet
2024-12-13 16:38:04 +01:00
committed by GitHub
parent 57869d3c8c
commit 7e67b1c5a6
26 changed files with 382 additions and 236 deletions

View File

@ -6,9 +6,10 @@ import { Repository } from 'typeorm';
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { SwitchWorkspaceService } from './switch-workspace.service';
@ -50,6 +51,12 @@ describe('SwitchWorkspaceService', () => {
saveDefaultWorkspaceIfUserHasAccessOrThrow: jest.fn(),
},
},
{
provide: EnvironmentService,
useValue: {
get: jest.fn(),
},
},
],
}).compile();

View File

@ -7,13 +7,15 @@ import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { AuthTokens } from 'src/engine/core-modules/auth/dto/token.entity';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthTokens } from 'src/engine/core-modules/auth/dto/token.entity';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/getAuthProvidersByWorkspace';
import { User } from 'src/engine/core-modules/user/user.entity';
import { AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data.output';
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Injectable()
export class SwitchWorkspaceService {
@ -25,6 +27,7 @@ export class SwitchWorkspaceService {
private readonly userService: UserService,
private readonly accessTokenService: AccessTokenService,
private readonly refreshTokenService: RefreshTokenService,
private readonly environmentService: EnvironmentService,
) {}
async switchWorkspace(user: User, workspaceId: string) {
@ -65,12 +68,23 @@ export class SwitchWorkspaceService {
defaultWorkspace: workspace,
});
const systemEnabledProviders: AuthProviders = {
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
magicLink: false,
password: this.environmentService.get('AUTH_PASSWORD_ENABLED'),
microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'),
sso: [],
};
return {
id: workspace.id,
subdomain: workspace.subdomain,
logo: workspace.logo,
displayName: workspace.displayName,
authProviders: getAuthProvidersByWorkspace(workspace),
authProviders: getAuthProvidersByWorkspace({
workspace,
systemEnabledProviders,
}),
};
}

View File

@ -1,6 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
import { AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data.output';
@ObjectType()
class Billing {
@ -52,6 +53,9 @@ class ApiConfig {
@ObjectType()
export class ClientConfig {
@Field(() => AuthProviders, { nullable: false })
authProviders: AuthProviders;
@Field(() => Billing, { nullable: false })
billing: Billing;

View File

@ -22,6 +22,13 @@ export class ClientConfigResolver {
'BILLING_FREE_TRIAL_DURATION_IN_DAYS',
),
},
authProviders: {
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
magicLink: false,
password: this.environmentService.get('AUTH_PASSWORD_ENABLED'),
microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'),
sso: [],
},
isSSOEnabled: this.environmentService.get('AUTH_SSO_ENABLED'),
signInPrefilled: this.environmentService.get('SIGN_IN_PREFILLED'),
isMultiWorkspaceEnabled: this.environmentService.get(

View File

@ -23,19 +23,24 @@ export class DomainManagerService {
getFrontUrl() {
let baseUrl: URL;
const frontPort = this.environmentService.get('FRONT_PORT');
const frontDomain = this.environmentService.get('FRONT_DOMAIN');
const frontProtocol = this.environmentService.get('FRONT_PROTOCOL');
if (!this.environmentService.get('FRONT_DOMAIN')) {
baseUrl = new URL(this.environmentService.get('SERVER_URL'));
const serverUrl = this.environmentService.get('SERVER_URL');
if (!frontDomain) {
baseUrl = new URL(serverUrl);
} else {
baseUrl = new URL(
`${this.environmentService.get('FRONT_PROTOCOL')}://${this.environmentService.get('FRONT_DOMAIN')}`,
);
baseUrl = new URL(`${frontProtocol}://${frontDomain}`);
}
const port = this.environmentService.get('FRONT_PORT');
if (frontPort) {
baseUrl.port = frontPort.toString();
}
if (port) {
baseUrl.port = port.toString();
}
if (frontProtocol) {
baseUrl.protocol = frontProtocol;
}
return baseUrl;

View File

@ -1,7 +1,6 @@
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { getAuthProvidersByWorkspace } from './getAuthProvidersByWorkspace';
describe('getAuthProvidersByWorkspace', () => {
const mockWorkspace = {
isGoogleAuthEnabled: true,
@ -20,7 +19,14 @@ describe('getAuthProvidersByWorkspace', () => {
it('should return correct auth providers for given workspace', () => {
const result = getAuthProvidersByWorkspace({
...mockWorkspace,
workspace: mockWorkspace,
systemEnabledProviders: {
google: true,
magicLink: false,
password: true,
microsoft: true,
sso: [],
},
});
expect(result).toEqual({
@ -42,8 +48,14 @@ describe('getAuthProvidersByWorkspace', () => {
it('should handle workspace with no SSO providers', () => {
const result = getAuthProvidersByWorkspace({
...mockWorkspace,
workspaceSSOIdentityProviders: [],
workspace: { ...mockWorkspace, workspaceSSOIdentityProviders: [] },
systemEnabledProviders: {
google: true,
magicLink: false,
password: true,
microsoft: true,
sso: [],
},
});
expect(result).toEqual({
@ -57,8 +69,14 @@ describe('getAuthProvidersByWorkspace', () => {
it('should disable Microsoft auth if isMicrosoftAuthEnabled is false', () => {
const result = getAuthProvidersByWorkspace({
...mockWorkspace,
isMicrosoftAuthEnabled: false,
workspace: { ...mockWorkspace, isMicrosoftAuthEnabled: false },
systemEnabledProviders: {
google: true,
magicLink: false,
password: true,
microsoft: true,
sso: [],
},
});
expect(result).toEqual({

View File

@ -3,13 +3,12 @@ import {
InternalServerError,
NotFoundError,
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util';
import {
WorkspaceException,
WorkspaceExceptionCode,
} from 'src/engine/core-modules/workspace/workspace.exception';
import { workspaceGraphqlApiExceptionHandler } from './workspaceGraphqlApiExceptionHandler';
describe('workspaceGraphqlApiExceptionHandler', () => {
it('should throw NotFoundError when WorkspaceExceptionCode is SUBDOMAIN_NOT_FOUND', () => {
const error = new WorkspaceException(

View File

@ -0,0 +1,32 @@
import { AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data.output';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
export const getAuthProvidersByWorkspace = ({
workspace,
systemEnabledProviders,
}: {
workspace: Pick<
Workspace,
| 'isGoogleAuthEnabled'
| 'isPasswordAuthEnabled'
| 'isMicrosoftAuthEnabled'
| 'workspaceSSOIdentityProviders'
>;
systemEnabledProviders: AuthProviders;
}) => {
return {
google: workspace.isGoogleAuthEnabled && systemEnabledProviders.google,
magicLink: false,
password:
workspace.isPasswordAuthEnabled && systemEnabledProviders.password,
microsoft:
workspace.isMicrosoftAuthEnabled && systemEnabledProviders.microsoft,
sso: workspace.workspaceSSOIdentityProviders.map((identityProvider) => ({
id: identityProvider.id,
name: identityProvider.name,
type: identityProvider.type,
status: identityProvider.status,
issuer: identityProvider.issuer,
})),
};
};

View File

@ -1,17 +0,0 @@
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
export const getAuthProvidersByWorkspace = (workspace: Workspace) => {
return {
google: workspace.isGoogleAuthEnabled,
magicLink: false,
password: workspace.isPasswordAuthEnabled,
microsoft: workspace.isMicrosoftAuthEnabled,
sso: workspace.workspaceSSOIdentityProviders.map((identityProvider) => ({
id: identityProvider.id,
name: identityProvider.name,
type: identityProvider.type,
status: identityProvider.status,
issuer: identityProvider.issuer,
})),
};
};

View File

@ -23,10 +23,13 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { User } from 'src/engine/core-modules/user/user.entity';
import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-input';
import { ActivateWorkspaceOutput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-output';
import { PublicWorkspaceDataOutput } from 'src/engine/core-modules/workspace/dtos/public-workspace-data.output';
import {
AuthProviders,
PublicWorkspaceDataOutput,
} from 'src/engine/core-modules/workspace/dtos/public-workspace-data.output';
import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/update-workspace-input';
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/getAuthProvidersByWorkspace';
import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspaceGraphqlApiExceptionHandler';
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util';
import {
WorkspaceException,
WorkspaceExceptionCode,
@ -207,12 +210,23 @@ export class WorkspaceResolver {
}
}
const systemEnabledProviders: AuthProviders = {
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
magicLink: false,
password: this.environmentService.get('AUTH_PASSWORD_ENABLED'),
microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'),
sso: [],
};
return {
id: workspace.id,
logo: workspaceLogoWithToken,
displayName: workspace.displayName,
subdomain: workspace.subdomain,
authProviders: getAuthProvidersByWorkspace(workspace),
authProviders: getAuthProvidersByWorkspace({
workspace,
systemEnabledProviders,
}),
};
}
}