fix(auth): Improve error management with sso + fix microsoft saml (#9799)
Fix #9760 #9758
This commit is contained in:
@ -62,12 +62,12 @@ export const SettingsSSOSAMLForm = () => {
|
||||
if (isDefined(e.target.files)) {
|
||||
const text = await e.target.files[0].text();
|
||||
const samlMetadataParsed = parseSAMLMetadataFromXMLFile(text);
|
||||
e.target.value = '';
|
||||
if (!samlMetadataParsed.success) {
|
||||
enqueueSnackBar('Invalid File', {
|
||||
return enqueueSnackBar('Invalid File', {
|
||||
variant: SnackBarVariant.Error,
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
setValue('ssoURL', samlMetadataParsed.data.ssoUrl);
|
||||
setValue('certificate', samlMetadataParsed.data.certificate);
|
||||
|
||||
@ -8,6 +8,29 @@ const validator = z.object({
|
||||
certificate: z.string().min(1),
|
||||
});
|
||||
|
||||
const getByPrefixAndKey = (
|
||||
xmlDoc: Document | Element,
|
||||
key: string,
|
||||
prefix = 'md',
|
||||
): Element | undefined => {
|
||||
return (
|
||||
xmlDoc.getElementsByTagName(`${prefix}:${key}`)?.[0] ??
|
||||
xmlDoc.getElementsByTagName(`${key}`)?.[0]
|
||||
);
|
||||
};
|
||||
|
||||
const getAllByPrefixAndKey = (
|
||||
xmlDoc: Document | Element,
|
||||
key: string,
|
||||
prefix = 'md',
|
||||
) => {
|
||||
const withPrefix = xmlDoc.getElementsByTagName(`${prefix}:${key}`);
|
||||
if (withPrefix.length !== 0) {
|
||||
return Array.from(withPrefix);
|
||||
}
|
||||
return Array.from(xmlDoc.getElementsByTagName(`${key}`));
|
||||
};
|
||||
|
||||
export const parseSAMLMetadataFromXMLFile = (
|
||||
xmlString: string,
|
||||
):
|
||||
@ -20,33 +43,44 @@ export const parseSAMLMetadataFromXMLFile = (
|
||||
throw new Error('Error parsing XML');
|
||||
}
|
||||
|
||||
const entityDescriptor = xmlDoc.getElementsByTagName(
|
||||
'md:EntityDescriptor',
|
||||
)?.[0];
|
||||
const idpSSODescriptor = xmlDoc.getElementsByTagName(
|
||||
'md:IDPSSODescriptor',
|
||||
)?.[0];
|
||||
const keyDescriptor = xmlDoc.getElementsByTagName('md:KeyDescriptor')[0];
|
||||
const keyInfo = keyDescriptor?.getElementsByTagName('ds:KeyInfo')[0];
|
||||
const x509Data = keyInfo?.getElementsByTagName('ds:X509Data')[0];
|
||||
const x509Certificate = x509Data
|
||||
?.getElementsByTagName('ds:X509Certificate')?.[0]
|
||||
.textContent?.trim();
|
||||
const entityDescriptor = getByPrefixAndKey(xmlDoc, 'EntityDescriptor');
|
||||
if (!entityDescriptor) throw new Error('No EntityDescriptor found');
|
||||
|
||||
const singleSignOnServices = Array.from(
|
||||
idpSSODescriptor.getElementsByTagName('md:SingleSignOnService'),
|
||||
).map((service) => ({
|
||||
Binding: service.getAttribute('Binding'),
|
||||
Location: service.getAttribute('Location'),
|
||||
}));
|
||||
const IDPSSODescriptor = getByPrefixAndKey(xmlDoc, 'IDPSSODescriptor');
|
||||
if (!IDPSSODescriptor) throw new Error('No IDPSSODescriptor found');
|
||||
|
||||
const keyDescriptors = getByPrefixAndKey(IDPSSODescriptor, 'KeyDescriptor');
|
||||
if (!keyDescriptors) throw new Error('No KeyDescriptor found');
|
||||
|
||||
const keyInfo = getByPrefixAndKey(keyDescriptors, 'KeyInfo', 'ds');
|
||||
if (!keyInfo) throw new Error('No KeyInfo found');
|
||||
|
||||
const x509Data = getByPrefixAndKey(keyInfo, 'X509Data', 'ds');
|
||||
if (!x509Data) throw new Error('No X509Data found');
|
||||
|
||||
const x509Certificate = getByPrefixAndKey(
|
||||
x509Data,
|
||||
'X509Certificate',
|
||||
'ds',
|
||||
)?.textContent?.trim();
|
||||
if (!x509Certificate) throw new Error('No X509Certificate found');
|
||||
|
||||
const singleSignOnServices = getAllByPrefixAndKey(
|
||||
IDPSSODescriptor,
|
||||
'SingleSignOnService',
|
||||
);
|
||||
|
||||
const result = {
|
||||
ssoUrl: singleSignOnServices.find((singleSignOnService) => {
|
||||
return (
|
||||
singleSignOnService.Binding ===
|
||||
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
);
|
||||
})?.Location,
|
||||
ssoUrl: singleSignOnServices
|
||||
.map((service) => ({
|
||||
Binding: service.getAttribute('Binding'),
|
||||
Location: service.getAttribute('Location'),
|
||||
}))
|
||||
.find(
|
||||
(singleSignOnService) =>
|
||||
singleSignOnService.Binding ===
|
||||
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
|
||||
)?.Location,
|
||||
certificate: x509Certificate,
|
||||
entityID: entityDescriptor?.getAttribute('entityID'),
|
||||
};
|
||||
|
||||
@ -5,17 +5,17 @@ import { z } from 'zod';
|
||||
export const SSOIdentitiesProvidersOIDCParamsSchema = z
|
||||
.object({
|
||||
type: z.literal('OIDC'),
|
||||
clientID: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
clientID: z.string().nonempty(),
|
||||
clientSecret: z.string().nonempty(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export const SSOIdentitiesProvidersSAMLParamsSchema = z
|
||||
.object({
|
||||
type: z.literal('SAML'),
|
||||
id: z.string().optional(),
|
||||
ssoURL: z.string().url().optional(),
|
||||
certificate: z.string().optional(),
|
||||
id: z.string().nonempty(),
|
||||
ssoURL: z.string().url().nonempty(),
|
||||
certificate: z.string().nonempty(),
|
||||
})
|
||||
.required();
|
||||
|
||||
@ -27,8 +27,8 @@ export const SSOIdentitiesProvidersParamsSchema = z
|
||||
.and(
|
||||
z
|
||||
.object({
|
||||
name: z.string().min(1),
|
||||
issuer: z.string().url().optional(),
|
||||
name: z.string().nonempty(),
|
||||
issuer: z.string().url().nonempty(),
|
||||
})
|
||||
.required(),
|
||||
);
|
||||
|
||||
@ -21,7 +21,7 @@ import { Query } from 'src/engine/api/rest/core/types/query.type';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Injectable()
|
||||
export class CoreQueryBuilderFactory {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class AuthException extends CustomException {
|
||||
code: AuthExceptionCode;
|
||||
constructor(message: string, code: AuthExceptionCode) {
|
||||
super(message, code);
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guard-redirect.module';
|
||||
|
||||
import { AuthResolver } from './auth.resolver';
|
||||
|
||||
@ -81,6 +82,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
FeatureFlagModule,
|
||||
WorkspaceInvitationModule,
|
||||
EmailVerificationModule,
|
||||
GuardRedirectModule,
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
|
||||
@ -3,7 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
@ -31,7 +31,7 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
||||
import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/renew-token.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
@ -24,7 +24,7 @@ import { GoogleAPIsRequest } from 'src/engine/core-modules/auth/types/google-api
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Controller('auth/google-apis')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
|
||||
@ -22,8 +22,9 @@ import { GoogleProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
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 { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Controller('auth/google')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@ -32,6 +33,7 @@ export class GoogleAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
@ -120,9 +122,11 @@ export class GoogleAuthController {
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(err.message, {
|
||||
subdomain: currentWorkspace?.subdomain,
|
||||
}),
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
err.message,
|
||||
currentWorkspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
),
|
||||
);
|
||||
}
|
||||
throw new AuthException(err, AuthExceptionCode.INTERNAL_SERVER_ERROR);
|
||||
|
||||
@ -25,7 +25,7 @@ import { EnvironmentService } from 'src/engine/core-modules/environment/environm
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Controller('auth/microsoft-apis')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
|
||||
@ -18,8 +18,9 @@ import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guar
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
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 { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Controller('auth/microsoft')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@ -28,6 +29,7 @@ export class MicrosoftAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
@ -119,9 +121,11 @@ export class MicrosoftAuthController {
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(err.message, {
|
||||
subdomain: currentWorkspace?.subdomain,
|
||||
}),
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
err.message,
|
||||
currentWorkspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
),
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter';
|
||||
import { OIDCAuthGuard } from 'src/engine/core-modules/auth/guards/oidc-auth.guard';
|
||||
import { SAMLAuthGuard } from 'src/engine/core-modules/auth/guards/saml-auth.guard';
|
||||
import { SSOProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/sso-provider-enabled.guard';
|
||||
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
@ -30,17 +30,19 @@ import {
|
||||
IdentityProviderType,
|
||||
WorkspaceSSOIdentityProvider,
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { AuthOAuthExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-oauth-exception.filter';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Controller('auth')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
export class SSOAuthController {
|
||||
constructor(
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly ssoService: SSOService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly sSOService: SSOService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(WorkspaceSSOIdentityProvider, 'core')
|
||||
@ -48,15 +50,16 @@ export class SSOAuthController {
|
||||
) {}
|
||||
|
||||
@Get('saml/metadata/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard)
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
async generateMetadata(@Req() req: any): Promise<string | void> {
|
||||
return generateServiceProviderMetadata({
|
||||
wantAssertionsSigned: false,
|
||||
issuer: this.ssoService.buildIssuerURL({
|
||||
issuer: this.sSOService.buildIssuerURL({
|
||||
id: req.params.identityProviderId,
|
||||
type: IdentityProviderType.SAML,
|
||||
}),
|
||||
callbackUrl: this.ssoService.buildCallbackUrl({
|
||||
callbackUrl: this.sSOService.buildCallbackUrl({
|
||||
id: req.params.identityProviderId,
|
||||
type: IdentityProviderType.SAML,
|
||||
}),
|
||||
@ -64,29 +67,40 @@ export class SSOAuthController {
|
||||
}
|
||||
|
||||
@Get('oidc/login/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard, OIDCAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard)
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
async oidcAuth() {
|
||||
// As this method is protected by OIDC Auth guard, it will trigger OIDC SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('saml/login/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard, SAMLAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard)
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
async samlAuth() {
|
||||
// As this method is protected by SAML Auth guard, it will trigger SAML SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('oidc/callback')
|
||||
@UseGuards(SSOProviderEnabledGuard, OIDCAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard)
|
||||
@UseFilters(AuthOAuthExceptionFilter)
|
||||
async oidcAuthCallback(@Req() req: any, @Res() res: Response) {
|
||||
return this.authCallback(req, res);
|
||||
return await this.authCallback(req, res);
|
||||
}
|
||||
|
||||
@Post('saml/callback/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard, SAMLAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard)
|
||||
@UseFilters(AuthOAuthExceptionFilter)
|
||||
async samlAuthCallback(@Req() req: any, @Res() res: Response) {
|
||||
return this.authCallback(req, res);
|
||||
try {
|
||||
return await this.authCallback(req, res);
|
||||
} catch (err) {
|
||||
return new AuthException(
|
||||
err.message ?? 'Access denied',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async authCallback({ user }: any, res: Response) {
|
||||
@ -95,21 +109,21 @@ export class SSOAuthController {
|
||||
user.identityProviderId,
|
||||
);
|
||||
|
||||
if (!workspaceIdentityProvider) {
|
||||
throw new AuthException(
|
||||
'Identity provider not found',
|
||||
AuthExceptionCode.INVALID_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.user.email) {
|
||||
throw new AuthException(
|
||||
'Email not found',
|
||||
AuthExceptionCode.INVALID_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!workspaceIdentityProvider) {
|
||||
throw new AuthException(
|
||||
'Identity provider not found',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.user.email) {
|
||||
throw new AuthException(
|
||||
'Email not found from identity provider.',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
const { loginToken, identityProvider } = await this.generateLoginToken(
|
||||
user.user,
|
||||
workspaceIdentityProvider,
|
||||
@ -122,14 +136,13 @@ export class SSOAuthController {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(err.message, {
|
||||
subdomain: workspaceIdentityProvider.workspace.subdomain,
|
||||
}),
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
err.message,
|
||||
workspaceIdentityProvider?.workspace.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
|
||||
@Catch(AuthException)
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Injectable()
|
||||
export class EnterpriseFeaturesEnabledGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
try {
|
||||
if (!this.environmentService.get('ENTERPRISE_KEY')) {
|
||||
throw new AuthException(
|
||||
'Enterprise key missing',
|
||||
AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,25 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
constructor() {
|
||||
constructor(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
});
|
||||
@ -16,43 +27,59 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
let workspace: Workspace | null = null;
|
||||
|
||||
if (request.query.error === 'access_denied') {
|
||||
throw new AuthException(
|
||||
'Google OAuth access denied',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
try {
|
||||
if (
|
||||
request.query.workspaceId &&
|
||||
typeof request.query.workspaceId === 'string'
|
||||
) {
|
||||
request.params.workspaceId = request.query.workspaceId;
|
||||
workspace = await this.workspaceRepository.findOneBy({
|
||||
id: request.query.workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
|
||||
if (request.query.error === 'access_denied') {
|
||||
throw new AuthException(
|
||||
'Google OAuth access denied',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
}
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.workspaceId &&
|
||||
typeof request.query.workspaceId === 'string'
|
||||
) {
|
||||
request.params.workspaceId = request.query.workspaceId;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { CanActivate, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@ -8,21 +6,35 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { GoogleStrategy } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
constructor(
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('AUTH_GOOGLE_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Google auth is not enabled',
|
||||
AuthExceptionCode.GOOGLE_API_AUTH_DISABLED,
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
try {
|
||||
if (!this.environmentService.get('AUTH_GOOGLE_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Google auth is not enabled',
|
||||
AuthExceptionCode.GOOGLE_API_AUTH_DISABLED,
|
||||
);
|
||||
}
|
||||
|
||||
new GoogleStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
new GoogleStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,21 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
constructor() {
|
||||
constructor(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
});
|
||||
@ -11,36 +23,52 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
let workspace: Workspace | null = null;
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
try {
|
||||
if (
|
||||
request.query.workspaceId &&
|
||||
typeof request.query.workspaceId === 'string'
|
||||
) {
|
||||
request.params.workspaceId = request.query.workspaceId;
|
||||
workspace = await this.workspaceRepository.findOneBy({
|
||||
id: request.query.workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.workspaceSubdomain &&
|
||||
typeof request.query.workspaceSubdomain === 'string'
|
||||
) {
|
||||
request.params.workspaceSubdomain = request.query.workspaceSubdomain;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { CanActivate, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@ -8,21 +6,35 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { MicrosoftStrategy } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
constructor(
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('AUTH_MICROSOFT_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Microsoft auth is not enabled',
|
||||
AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED,
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
try {
|
||||
if (!this.environmentService.get('AUTH_MICROSOFT_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Microsoft auth is not enabled',
|
||||
AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED,
|
||||
);
|
||||
}
|
||||
|
||||
new MicrosoftStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
new MicrosoftStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,10 +11,18 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { OIDCAuthStrategy } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||
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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
|
||||
@Injectable()
|
||||
export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
constructor(private readonly ssoService: SSOService) {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ -38,13 +46,17 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
let identityProvider:
|
||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||
| null = null;
|
||||
|
||||
try {
|
||||
const identityProviderId = this.getIdentityProviderId(request);
|
||||
|
||||
const identityProvider =
|
||||
await this.ssoService.findSSOIdentityProviderById(identityProviderId);
|
||||
identityProvider =
|
||||
await this.sSOService.findSSOIdentityProviderById(identityProviderId);
|
||||
|
||||
if (!identityProvider) {
|
||||
throw new AuthException(
|
||||
@ -56,17 +68,19 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
const issuer = await Issuer.discover(identityProvider.issuer);
|
||||
|
||||
new OIDCAuthStrategy(
|
||||
this.ssoService.getOIDCClient(identityProvider, issuer),
|
||||
this.sSOService.getOIDCClient(identityProvider, issuer),
|
||||
identityProvider.id,
|
||||
);
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return false;
|
||||
}
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
identityProvider?.workspace.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
|
||||
// TODO AMOREAUX: trigger sentry error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,33 +9,50 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||
constructor(private readonly sSOService: SSOService) {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
try {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
if (!request.params.identityProviderId) {
|
||||
let identityProvider:
|
||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||
| null = null;
|
||||
|
||||
try {
|
||||
identityProvider = await this.sSOService.findSSOIdentityProviderById(
|
||||
request.params.identityProviderId,
|
||||
);
|
||||
|
||||
if (!identityProvider) {
|
||||
throw new AuthException(
|
||||
'Invalid SAML identity provider',
|
||||
'Identity provider not found',
|
||||
AuthExceptionCode.INVALID_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
new SamlAuthStrategy(this.sSOService);
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return false;
|
||||
}
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
identityProvider?.workspace.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
|
||||
// TODO AMOREAUX: trigger sentry error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { CanActivate, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class SSOProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('ENTERPRISE_KEY')) {
|
||||
throw new AuthException(
|
||||
'Enterprise key must be defined to use SSO',
|
||||
AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
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 { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
|
||||
@ -37,7 +37,7 @@ import { WorkspaceInviteHashValid } from 'src/engine/core-modules/auth/dto/works
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
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 { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
@ -460,7 +460,7 @@ export class AuthService {
|
||||
billingCheckoutSessionState,
|
||||
}: {
|
||||
loginToken: string;
|
||||
subdomain?: string;
|
||||
subdomain: string;
|
||||
billingCheckoutSessionState?: string;
|
||||
}) {
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
|
||||
@ -13,7 +13,7 @@ import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
import { ResetPasswordService } from './reset-password.service';
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-p
|
||||
import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
|
||||
import { PasswordResetToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
import { ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
ExistingUserOrPartialUserWithPicture,
|
||||
SignInUpBaseParams,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/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 { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
|
||||
@ -29,7 +29,7 @@ import {
|
||||
SignInUpBaseParams,
|
||||
SignInUpNewUserPayload,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/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 { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
|
||||
@ -9,11 +9,6 @@ import {
|
||||
StrategyVerifyCallbackReq,
|
||||
} from 'openid-client';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
|
||||
@Injectable()
|
||||
export class OIDCAuthStrategy extends PassportStrategy(
|
||||
Strategy,
|
||||
@ -47,7 +42,7 @@ export class OIDCAuthStrategy extends PassportStrategy(
|
||||
validate: StrategyVerifyCallbackReq<{
|
||||
identityProviderId: string;
|
||||
user: {
|
||||
email: string;
|
||||
email?: string;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
};
|
||||
@ -66,12 +61,6 @@ export class OIDCAuthStrategy extends PassportStrategy(
|
||||
|
||||
const userinfo = await this.client.userinfo(tokenset);
|
||||
|
||||
if (!userinfo || !userinfo.email) {
|
||||
return done(
|
||||
new AuthException('Email not found', AuthExceptionCode.INVALID_DATA),
|
||||
);
|
||||
}
|
||||
|
||||
const user = {
|
||||
email: userinfo.email,
|
||||
...(userinfo.given_name ? { firstName: userinfo.given_name } : {}),
|
||||
|
||||
@ -14,11 +14,11 @@ import { StripeBillingPortalService } from 'src/engine/core-modules/billing/stri
|
||||
import { StripeCheckoutService } from 'src/engine/core-modules/billing/stripe/services/stripe-checkout.service';
|
||||
import { BillingGetPricesPerPlanResult } from 'src/engine/core-modules/billing/types/billing-get-prices-per-plan-result.type';
|
||||
import { BillingPortalCheckoutSessionParameters } from 'src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-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';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/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';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/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 { ClientConfigResolver } from './client-config.resolver';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/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 { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
|
||||
@ -125,6 +125,7 @@ describe('DomainManagerService', () => {
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
subdomain: 'subdomain',
|
||||
pathname: '/path/to/resource',
|
||||
});
|
||||
|
||||
@ -144,6 +145,8 @@ describe('DomainManagerService', () => {
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
subdomain: 'subdomain',
|
||||
|
||||
searchParams: {
|
||||
foo: 'bar',
|
||||
baz: 123,
|
||||
@ -62,14 +62,14 @@ export class DomainManagerService {
|
||||
buildEmailVerificationURL({
|
||||
emailVerificationToken,
|
||||
email,
|
||||
workspaceSubdomain,
|
||||
subdomain,
|
||||
}: {
|
||||
emailVerificationToken: string;
|
||||
email: string;
|
||||
workspaceSubdomain?: string;
|
||||
subdomain: string;
|
||||
}) {
|
||||
return this.buildWorkspaceURL({
|
||||
subdomain: workspaceSubdomain,
|
||||
subdomain,
|
||||
pathname: 'verify-email',
|
||||
searchParams: { emailVerificationToken, email },
|
||||
});
|
||||
@ -80,28 +80,14 @@ export class DomainManagerService {
|
||||
pathname,
|
||||
searchParams,
|
||||
}: {
|
||||
subdomain?: string;
|
||||
subdomain: string;
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number>;
|
||||
}) {
|
||||
const url = this.getBaseUrl();
|
||||
const url = this.getFrontUrl();
|
||||
|
||||
if (
|
||||
this.environmentService.get('IS_MULTIWORKSPACE_ENABLED') &&
|
||||
!subdomain
|
||||
) {
|
||||
throw new Error('subdomain is required when multiworkspace is enable');
|
||||
}
|
||||
|
||||
if (
|
||||
subdomain &&
|
||||
subdomain.length > 0 &&
|
||||
this.environmentService.get('IS_MULTIWORKSPACE_ENABLED')
|
||||
) {
|
||||
url.hostname = url.hostname.replace(
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
subdomain,
|
||||
);
|
||||
if (this.environmentService.get('IS_MULTIWORKSPACE_ENABLED')) {
|
||||
url.hostname = `${subdomain}.${url.hostname}`;
|
||||
}
|
||||
|
||||
if (pathname) {
|
||||
@ -119,18 +105,18 @@ export class DomainManagerService {
|
||||
return url;
|
||||
}
|
||||
|
||||
getWorkspaceSubdomainByOrigin = (origin: string) => {
|
||||
const { hostname: originHostname } = new URL(origin);
|
||||
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}`, '');
|
||||
|
||||
if (this.isDefaultSubdomain(subdomain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return subdomain;
|
||||
return this.isDefaultSubdomain(subdomain) ? null : subdomain;
|
||||
};
|
||||
|
||||
async getWorkspaceBySubdomainOrDefaultWorkspace(subdomain?: string) {
|
||||
@ -145,16 +131,9 @@ export class DomainManagerService {
|
||||
return subdomain === this.environmentService.get('DEFAULT_SUBDOMAIN');
|
||||
}
|
||||
|
||||
computeRedirectErrorUrl(
|
||||
errorMessage: string,
|
||||
{
|
||||
subdomain,
|
||||
}: {
|
||||
subdomain?: string;
|
||||
},
|
||||
) {
|
||||
computeRedirectErrorUrl(errorMessage: string, subdomain: string) {
|
||||
const url = this.buildWorkspaceURL({
|
||||
subdomain: subdomain ?? this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
subdomain: subdomain,
|
||||
pathname: '/verify',
|
||||
searchParams: { errorMessage },
|
||||
});
|
||||
@ -206,7 +185,7 @@ export class DomainManagerService {
|
||||
return this.getDefaultWorkspace();
|
||||
}
|
||||
|
||||
const subdomain = this.getWorkspaceSubdomainByOrigin(origin);
|
||||
const subdomain = this.getWorkspaceSubdomainFromUrl(origin);
|
||||
|
||||
if (!isDefined(subdomain)) return;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { ResendEmailVerificationTokenInput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.input';
|
||||
import { ResendEmailVerificationTokenOutput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.output';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
AppTokenType,
|
||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import {
|
||||
EmailVerificationException,
|
||||
EmailVerificationExceptionCode,
|
||||
@ -37,7 +37,7 @@ export class EmailVerificationService {
|
||||
async sendVerificationEmail(
|
||||
userId: string,
|
||||
email: string,
|
||||
workspaceSubdomain?: string,
|
||||
subdomain: string,
|
||||
) {
|
||||
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||
return { success: false };
|
||||
@ -50,7 +50,7 @@ export class EmailVerificationService {
|
||||
this.domainManagerService.buildEmailVerificationURL({
|
||||
emailVerificationToken,
|
||||
email,
|
||||
workspaceSubdomain,
|
||||
subdomain,
|
||||
});
|
||||
|
||||
const emailData = {
|
||||
@ -80,10 +80,7 @@ export class EmailVerificationService {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async resendEmailVerificationToken(
|
||||
email: string,
|
||||
workspaceSubdomain?: string,
|
||||
) {
|
||||
async resendEmailVerificationToken(email: string, subdomain: string) {
|
||||
if (!this.environmentService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||
throw new EmailVerificationException(
|
||||
'Email verification token cannot be sent because email verification is not required',
|
||||
@ -124,7 +121,7 @@ export class EmailVerificationService {
|
||||
await this.appTokenRepository.delete(existingToken.id);
|
||||
}
|
||||
|
||||
await this.sendVerificationEmail(user.id, email, workspaceSubdomain);
|
||||
await this.sendVerificationEmail(user.id, email, subdomain);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Module({
|
||||
imports: [DomainManagerModule],
|
||||
providers: [GuardRedirectService],
|
||||
exports: [GuardRedirectService],
|
||||
})
|
||||
export class GuardRedirectModule {}
|
||||
@ -0,0 +1,38 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class GuardRedirectService {
|
||||
constructor(
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
dispatchErrorFromGuard(context: any, err: any, subdomain: string) {
|
||||
if ('contextType' in context && context.contextType === 'graphql') {
|
||||
throw err;
|
||||
}
|
||||
|
||||
context
|
||||
.switchToHttp()
|
||||
.getResponse()
|
||||
.redirect(
|
||||
this.domainManagerService
|
||||
.computeRedirectErrorUrl(err.message ?? 'Unknown error', subdomain)
|
||||
.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
getSubdomainFromContext(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
const subdomainFromUrl =
|
||||
this.domainManagerService.getWorkspaceSubdomainFromUrl(
|
||||
request.headers.referer,
|
||||
);
|
||||
|
||||
return subdomainFromUrl ?? this.environmentService.get('DEFAULT_SUBDOMAIN');
|
||||
}
|
||||
}
|
||||
@ -129,13 +129,10 @@ export class SSOService {
|
||||
};
|
||||
}
|
||||
|
||||
async findSSOIdentityProviderById(identityProviderId?: string) {
|
||||
// if identityProviderId is not provide, typeorm return a random idp instead of undefined
|
||||
if (!identityProviderId) return undefined;
|
||||
|
||||
async findSSOIdentityProviderById(identityProviderId: string) {
|
||||
return (await this.workspaceSSOIdentityProviderRepository.findOne({
|
||||
where: { id: identityProviderId },
|
||||
})) as (SSOConfiguration & WorkspaceSSOIdentityProvider) | undefined;
|
||||
})) as (SSOConfiguration & WorkspaceSSOIdentityProvider) | null;
|
||||
}
|
||||
|
||||
buildCallbackUrl(
|
||||
|
||||
@ -11,6 +11,8 @@ import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { SSOResolver } from 'src/engine/core-modules/sso/sso.resolver';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guard-redirect.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -19,6 +21,8 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
'core',
|
||||
),
|
||||
BillingModule,
|
||||
DomainManagerModule,
|
||||
GuardRedirectModule,
|
||||
],
|
||||
exports: [SSOService],
|
||||
providers: [SSOService, SSOResolver],
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { SSOProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/sso-provider-enabled.guard';
|
||||
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
|
||||
import { DeleteSsoInput } from 'src/engine/core-modules/sso/dtos/delete-sso.input';
|
||||
import { DeleteSsoOutput } from 'src/engine/core-modules/sso/dtos/delete-sso.output';
|
||||
import { EditSsoInput } from 'src/engine/core-modules/sso/dtos/edit-sso.input';
|
||||
@ -26,7 +26,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
export class SSOResolver {
|
||||
constructor(private readonly sSOService: SSOService) {}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, SSOProviderEnabledGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, EnterpriseFeaturesEnabledGuard)
|
||||
@Mutation(() => SetupSsoOutput)
|
||||
async createOIDCIdentityProvider(
|
||||
@Args('input') setupSsoInput: SetupOIDCSsoInput,
|
||||
@ -38,7 +38,7 @@ export class SSOResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(SSOProviderEnabledGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard)
|
||||
@Query(() => [FindAvailableSSOIDPOutput])
|
||||
async listSSOIdentityProvidersByWorkspaceId(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@ -53,7 +53,7 @@ export class SSOResolver {
|
||||
return this.sSOService.getAuthorizationUrl(identityProviderId);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, SSOProviderEnabledGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, EnterpriseFeaturesEnabledGuard)
|
||||
@Mutation(() => SetupSsoOutput)
|
||||
async createSAMLIdentityProvider(
|
||||
@Args('input') setupSsoInput: SetupSAMLSsoInput,
|
||||
@ -65,7 +65,7 @@ export class SSOResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, SSOProviderEnabledGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, EnterpriseFeaturesEnabledGuard)
|
||||
@Mutation(() => DeleteSsoOutput)
|
||||
async deleteSSOIdentityProvider(
|
||||
@Args('input') { identityProviderId }: DeleteSsoInput,
|
||||
@ -77,7 +77,7 @@ export class SSOResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, SSOProviderEnabledGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, EnterpriseFeaturesEnabledGuard)
|
||||
@Mutation(() => EditSsoOutput)
|
||||
async editSSOIdentityProvider(
|
||||
@Args('input') input: EditSsoInput,
|
||||
|
||||
@ -45,7 +45,7 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
|
||||
const getHMACKey = (email?: string, key?: string | null) => {
|
||||
|
||||
@ -14,7 +14,7 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceInvitationException } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.exception';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
|
||||
import { WorkspaceInvitationService } from './workspace-invitation.service';
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
|
||||
@ -3,7 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
|
||||
@ -16,7 +16,7 @@ import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.
|
||||
|
||||
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 { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/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 { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
|
||||
@ -16,7 +16,7 @@ export class UnhandledExceptionFilter implements ExceptionFilter {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
if (!response.header) {
|
||||
if (!response.header || response.headersSent) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user