fix(auth): Improve error management with sso + fix microsoft saml (#9799)
Fix #9760 #9758
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class AuthException extends CustomException {
|
||||
code: AuthExceptionCode;
|
||||
constructor(message: string, code: AuthExceptionCode) {
|
||||
super(message, code);
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guard-redirect.module';
|
||||
|
||||
import { AuthResolver } from './auth.resolver';
|
||||
|
||||
@ -81,6 +82,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
FeatureFlagModule,
|
||||
WorkspaceInvitationModule,
|
||||
EmailVerificationModule,
|
||||
GuardRedirectModule,
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
|
||||
@ -3,7 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
@ -31,7 +31,7 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
||||
import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/renew-token.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
@ -24,7 +24,7 @@ import { GoogleAPIsRequest } from 'src/engine/core-modules/auth/types/google-api
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Controller('auth/google-apis')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
|
||||
@ -22,8 +22,9 @@ import { GoogleProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Controller('auth/google')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@ -32,6 +33,7 @@ export class GoogleAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
@ -120,9 +122,11 @@ export class GoogleAuthController {
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(err.message, {
|
||||
subdomain: currentWorkspace?.subdomain,
|
||||
}),
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
err.message,
|
||||
currentWorkspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
),
|
||||
);
|
||||
}
|
||||
throw new AuthException(err, AuthExceptionCode.INTERNAL_SERVER_ERROR);
|
||||
|
||||
@ -25,7 +25,7 @@ import { EnvironmentService } from 'src/engine/core-modules/environment/environm
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Controller('auth/microsoft-apis')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
|
||||
@ -18,8 +18,9 @@ import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guar
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Controller('auth/microsoft')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@ -28,6 +29,7 @@ export class MicrosoftAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
@ -119,9 +121,11 @@ export class MicrosoftAuthController {
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(err.message, {
|
||||
subdomain: currentWorkspace?.subdomain,
|
||||
}),
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
err.message,
|
||||
currentWorkspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
),
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter';
|
||||
import { OIDCAuthGuard } from 'src/engine/core-modules/auth/guards/oidc-auth.guard';
|
||||
import { SAMLAuthGuard } from 'src/engine/core-modules/auth/guards/saml-auth.guard';
|
||||
import { SSOProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/sso-provider-enabled.guard';
|
||||
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
@ -30,17 +30,19 @@ import {
|
||||
IdentityProviderType,
|
||||
WorkspaceSSOIdentityProvider,
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { AuthOAuthExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-oauth-exception.filter';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Controller('auth')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
export class SSOAuthController {
|
||||
constructor(
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly ssoService: SSOService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly sSOService: SSOService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(WorkspaceSSOIdentityProvider, 'core')
|
||||
@ -48,15 +50,16 @@ export class SSOAuthController {
|
||||
) {}
|
||||
|
||||
@Get('saml/metadata/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard)
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
async generateMetadata(@Req() req: any): Promise<string | void> {
|
||||
return generateServiceProviderMetadata({
|
||||
wantAssertionsSigned: false,
|
||||
issuer: this.ssoService.buildIssuerURL({
|
||||
issuer: this.sSOService.buildIssuerURL({
|
||||
id: req.params.identityProviderId,
|
||||
type: IdentityProviderType.SAML,
|
||||
}),
|
||||
callbackUrl: this.ssoService.buildCallbackUrl({
|
||||
callbackUrl: this.sSOService.buildCallbackUrl({
|
||||
id: req.params.identityProviderId,
|
||||
type: IdentityProviderType.SAML,
|
||||
}),
|
||||
@ -64,29 +67,40 @@ export class SSOAuthController {
|
||||
}
|
||||
|
||||
@Get('oidc/login/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard, OIDCAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard)
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
async oidcAuth() {
|
||||
// As this method is protected by OIDC Auth guard, it will trigger OIDC SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('saml/login/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard, SAMLAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard)
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
async samlAuth() {
|
||||
// As this method is protected by SAML Auth guard, it will trigger SAML SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('oidc/callback')
|
||||
@UseGuards(SSOProviderEnabledGuard, OIDCAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard)
|
||||
@UseFilters(AuthOAuthExceptionFilter)
|
||||
async oidcAuthCallback(@Req() req: any, @Res() res: Response) {
|
||||
return this.authCallback(req, res);
|
||||
return await this.authCallback(req, res);
|
||||
}
|
||||
|
||||
@Post('saml/callback/:identityProviderId')
|
||||
@UseGuards(SSOProviderEnabledGuard, SAMLAuthGuard)
|
||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard)
|
||||
@UseFilters(AuthOAuthExceptionFilter)
|
||||
async samlAuthCallback(@Req() req: any, @Res() res: Response) {
|
||||
return this.authCallback(req, res);
|
||||
try {
|
||||
return await this.authCallback(req, res);
|
||||
} catch (err) {
|
||||
return new AuthException(
|
||||
err.message ?? 'Access denied',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async authCallback({ user }: any, res: Response) {
|
||||
@ -95,21 +109,21 @@ export class SSOAuthController {
|
||||
user.identityProviderId,
|
||||
);
|
||||
|
||||
if (!workspaceIdentityProvider) {
|
||||
throw new AuthException(
|
||||
'Identity provider not found',
|
||||
AuthExceptionCode.INVALID_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.user.email) {
|
||||
throw new AuthException(
|
||||
'Email not found',
|
||||
AuthExceptionCode.INVALID_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!workspaceIdentityProvider) {
|
||||
throw new AuthException(
|
||||
'Identity provider not found',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.user.email) {
|
||||
throw new AuthException(
|
||||
'Email not found from identity provider.',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
const { loginToken, identityProvider } = await this.generateLoginToken(
|
||||
user.user,
|
||||
workspaceIdentityProvider,
|
||||
@ -122,14 +136,13 @@ export class SSOAuthController {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(err.message, {
|
||||
subdomain: workspaceIdentityProvider.workspace.subdomain,
|
||||
}),
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
return res.redirect(
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
err.message,
|
||||
workspaceIdentityProvider?.workspace.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
|
||||
@Catch(AuthException)
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Injectable()
|
||||
export class EnterpriseFeaturesEnabledGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
try {
|
||||
if (!this.environmentService.get('ENTERPRISE_KEY')) {
|
||||
throw new AuthException(
|
||||
'Enterprise key missing',
|
||||
AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,25 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
constructor() {
|
||||
constructor(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
});
|
||||
@ -16,43 +27,59 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
let workspace: Workspace | null = null;
|
||||
|
||||
if (request.query.error === 'access_denied') {
|
||||
throw new AuthException(
|
||||
'Google OAuth access denied',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
try {
|
||||
if (
|
||||
request.query.workspaceId &&
|
||||
typeof request.query.workspaceId === 'string'
|
||||
) {
|
||||
request.params.workspaceId = request.query.workspaceId;
|
||||
workspace = await this.workspaceRepository.findOneBy({
|
||||
id: request.query.workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
|
||||
if (request.query.error === 'access_denied') {
|
||||
throw new AuthException(
|
||||
'Google OAuth access denied',
|
||||
AuthExceptionCode.OAUTH_ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
}
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.workspaceId &&
|
||||
typeof request.query.workspaceId === 'string'
|
||||
) {
|
||||
request.params.workspaceId = request.query.workspaceId;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { CanActivate, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@ -8,21 +6,35 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { GoogleStrategy } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
constructor(
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('AUTH_GOOGLE_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Google auth is not enabled',
|
||||
AuthExceptionCode.GOOGLE_API_AUTH_DISABLED,
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
try {
|
||||
if (!this.environmentService.get('AUTH_GOOGLE_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Google auth is not enabled',
|
||||
AuthExceptionCode.GOOGLE_API_AUTH_DISABLED,
|
||||
);
|
||||
}
|
||||
|
||||
new GoogleStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
new GoogleStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,21 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
constructor() {
|
||||
constructor(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
});
|
||||
@ -11,36 +23,52 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
let workspace: Workspace | null = null;
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
try {
|
||||
if (
|
||||
request.query.workspaceId &&
|
||||
typeof request.query.workspaceId === 'string'
|
||||
) {
|
||||
request.params.workspaceId = request.query.workspaceId;
|
||||
workspace = await this.workspaceRepository.findOneBy({
|
||||
id: request.query.workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceInviteHash = request.query.inviteHash;
|
||||
const workspacePersonalInviteToken = request.query.inviteToken;
|
||||
|
||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace?.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
workspacePersonalInviteToken &&
|
||||
typeof workspacePersonalInviteToken === 'string'
|
||||
) {
|
||||
request.params.workspacePersonalInviteToken =
|
||||
workspacePersonalInviteToken;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.workspaceSubdomain &&
|
||||
typeof request.query.workspaceSubdomain === 'string'
|
||||
) {
|
||||
request.params.workspaceSubdomain = request.query.workspaceSubdomain;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { CanActivate, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@ -8,21 +6,35 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { MicrosoftStrategy } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
constructor(
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('AUTH_MICROSOFT_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Microsoft auth is not enabled',
|
||||
AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED,
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
try {
|
||||
if (!this.environmentService.get('AUTH_MICROSOFT_ENABLED')) {
|
||||
throw new AuthException(
|
||||
'Microsoft auth is not enabled',
|
||||
AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED,
|
||||
);
|
||||
}
|
||||
|
||||
new MicrosoftStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
new MicrosoftStrategy(this.environmentService);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,10 +11,18 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { OIDCAuthStrategy } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
|
||||
@Injectable()
|
||||
export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
constructor(private readonly ssoService: SSOService) {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ -38,13 +46,17 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
try {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
let identityProvider:
|
||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||
| null = null;
|
||||
|
||||
try {
|
||||
const identityProviderId = this.getIdentityProviderId(request);
|
||||
|
||||
const identityProvider =
|
||||
await this.ssoService.findSSOIdentityProviderById(identityProviderId);
|
||||
identityProvider =
|
||||
await this.sSOService.findSSOIdentityProviderById(identityProviderId);
|
||||
|
||||
if (!identityProvider) {
|
||||
throw new AuthException(
|
||||
@ -56,17 +68,19 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
const issuer = await Issuer.discover(identityProvider.issuer);
|
||||
|
||||
new OIDCAuthStrategy(
|
||||
this.ssoService.getOIDCClient(identityProvider, issuer),
|
||||
this.sSOService.getOIDCClient(identityProvider, issuer),
|
||||
identityProvider.id,
|
||||
);
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return false;
|
||||
}
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
identityProvider?.workspace.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
|
||||
// TODO AMOREAUX: trigger sentry error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,33 +9,50 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||
constructor(private readonly sSOService: SSOService) {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
try {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
if (!request.params.identityProviderId) {
|
||||
let identityProvider:
|
||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||
| null = null;
|
||||
|
||||
try {
|
||||
identityProvider = await this.sSOService.findSSOIdentityProviderById(
|
||||
request.params.identityProviderId,
|
||||
);
|
||||
|
||||
if (!identityProvider) {
|
||||
throw new AuthException(
|
||||
'Invalid SAML identity provider',
|
||||
'Identity provider not found',
|
||||
AuthExceptionCode.INVALID_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
new SamlAuthStrategy(this.sSOService);
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
if (err instanceof AuthException) {
|
||||
return false;
|
||||
}
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
identityProvider?.workspace.subdomain ??
|
||||
this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
);
|
||||
|
||||
// TODO AMOREAUX: trigger sentry error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { CanActivate, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class SSOProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.get('ENTERPRISE_KEY')) {
|
||||
throw new AuthException(
|
||||
'Enterprise key must be defined to use SSO',
|
||||
AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
|
||||
@ -37,7 +37,7 @@ import { WorkspaceInviteHashValid } from 'src/engine/core-modules/auth/dto/works
|
||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
@ -460,7 +460,7 @@ export class AuthService {
|
||||
billingCheckoutSessionState,
|
||||
}: {
|
||||
loginToken: string;
|
||||
subdomain?: string;
|
||||
subdomain: string;
|
||||
billingCheckoutSessionState?: string;
|
||||
}) {
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
|
||||
@ -13,7 +13,7 @@ import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
import { ResetPasswordService } from './reset-password.service';
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-p
|
||||
import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
|
||||
import { PasswordResetToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
import { ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
ExistingUserOrPartialUserWithPicture,
|
||||
SignInUpBaseParams,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
|
||||
@ -29,7 +29,7 @@ import {
|
||||
SignInUpBaseParams,
|
||||
SignInUpNewUserPayload,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
|
||||
@ -9,11 +9,6 @@ import {
|
||||
StrategyVerifyCallbackReq,
|
||||
} from 'openid-client';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
|
||||
@Injectable()
|
||||
export class OIDCAuthStrategy extends PassportStrategy(
|
||||
Strategy,
|
||||
@ -47,7 +42,7 @@ export class OIDCAuthStrategy extends PassportStrategy(
|
||||
validate: StrategyVerifyCallbackReq<{
|
||||
identityProviderId: string;
|
||||
user: {
|
||||
email: string;
|
||||
email?: string;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
};
|
||||
@ -66,12 +61,6 @@ export class OIDCAuthStrategy extends PassportStrategy(
|
||||
|
||||
const userinfo = await this.client.userinfo(tokenset);
|
||||
|
||||
if (!userinfo || !userinfo.email) {
|
||||
return done(
|
||||
new AuthException('Email not found', AuthExceptionCode.INVALID_DATA),
|
||||
);
|
||||
}
|
||||
|
||||
const user = {
|
||||
email: userinfo.email,
|
||||
...(userinfo.given_name ? { firstName: userinfo.given_name } : {}),
|
||||
|
||||
Reference in New Issue
Block a user