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