feat(): enable custom domain usage (#9911)
# Content - Introduce the `workspaceUrls` property. It contains two sub-properties: `customUrl, subdomainUrl`. These endpoints are used to access the workspace. Even if the `workspaceUrls` is invalid for multiple reasons, the `subdomainUrl` remains valid. - Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL computation on the frontend part. - Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
This commit is contained in:
@ -221,7 +221,7 @@ export class AuthResolver {
|
||||
await this.emailVerificationService.sendVerificationEmail(
|
||||
user.id,
|
||||
user.email,
|
||||
workspace.subdomain,
|
||||
workspace,
|
||||
);
|
||||
|
||||
const loginToken = await this.loginTokenService.generateLoginToken(
|
||||
@ -233,7 +233,7 @@ export class AuthResolver {
|
||||
loginToken,
|
||||
workspace: {
|
||||
id: workspace.id,
|
||||
subdomain: workspace.subdomain,
|
||||
workspaceUrls: this.domainManagerService.getworkspaceUrls(workspace),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ export class GoogleAPIsAuthController {
|
||||
return res.redirect(
|
||||
this.domainManagerService
|
||||
.buildWorkspaceURL({
|
||||
subdomain: workspace.subdomain,
|
||||
workspace,
|
||||
pathname: redirectLocation || '/settings/accounts',
|
||||
})
|
||||
.toString(),
|
||||
|
||||
@ -19,7 +19,6 @@ 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 { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Controller('auth/google')
|
||||
@ -28,7 +27,6 @@ export class GoogleAuthController {
|
||||
constructor(
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@ -110,7 +108,7 @@ export class GoogleAuthController {
|
||||
return res.redirect(
|
||||
this.authService.computeRedirectURI({
|
||||
loginToken: loginToken.token,
|
||||
subdomain: workspace.subdomain,
|
||||
workspace,
|
||||
billingCheckoutSessionState,
|
||||
}),
|
||||
);
|
||||
@ -118,9 +116,9 @@ export class GoogleAuthController {
|
||||
return res.redirect(
|
||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
||||
err,
|
||||
currentWorkspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
currentWorkspace,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ export class MicrosoftAPIsAuthController {
|
||||
return res.redirect(
|
||||
this.domainManagerService
|
||||
.buildWorkspaceURL({
|
||||
subdomain: workspace.subdomain,
|
||||
workspace,
|
||||
pathname: redirectLocation || '/settings/accounts',
|
||||
})
|
||||
.toString(),
|
||||
|
||||
@ -18,7 +18,6 @@ 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 { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Controller('auth/microsoft')
|
||||
@ -28,7 +27,6 @@ export class MicrosoftAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
@ -111,8 +109,7 @@ export class MicrosoftAuthController {
|
||||
return res.redirect(
|
||||
this.authService.computeRedirectURI({
|
||||
loginToken: loginToken.token,
|
||||
subdomain: workspace.subdomain,
|
||||
|
||||
workspace,
|
||||
billingCheckoutSessionState,
|
||||
}),
|
||||
);
|
||||
@ -120,9 +117,9 @@ export class MicrosoftAuthController {
|
||||
return res.redirect(
|
||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
||||
err,
|
||||
currentWorkspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
currentWorkspace,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -32,7 +32,6 @@ import {
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.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';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { SAMLRequest } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
import { OIDCRequest } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||
@ -45,7 +44,6 @@ export class SSOAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly sSOService: SSOService,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@ -152,16 +150,16 @@ export class SSOAuthController {
|
||||
return res.redirect(
|
||||
this.authService.computeRedirectURI({
|
||||
loginToken: loginToken.token,
|
||||
subdomain: currentWorkspace.subdomain,
|
||||
workspace: currentWorkspace,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
return res.redirect(
|
||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions(
|
||||
err,
|
||||
workspaceIdentityProvider?.workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
workspaceIdentityProvider?.workspace,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
IdentityProviderType,
|
||||
SSOIdentityProviderStatus,
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-endpoints.dto';
|
||||
|
||||
@ObjectType()
|
||||
class SSOConnection {
|
||||
@ -34,8 +35,8 @@ export class AvailableWorkspaceOutput {
|
||||
@Field(() => String, { nullable: true })
|
||||
displayName?: string;
|
||||
|
||||
@Field(() => String)
|
||||
subdomain: string;
|
||||
@Field(() => workspaceUrls)
|
||||
workspaceUrls: workspaceUrls;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
hostname?: string;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { WorkspaceSubdomainAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
||||
import { workspaceUrlsAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
||||
|
||||
import { AuthToken } from './token.entity';
|
||||
|
||||
@ -9,6 +9,6 @@ export class SignUpOutput {
|
||||
@Field(() => AuthToken)
|
||||
loginToken: AuthToken;
|
||||
|
||||
@Field(() => WorkspaceSubdomainAndId)
|
||||
workspace: WorkspaceSubdomainAndId;
|
||||
@Field(() => workspaceUrlsAndId)
|
||||
workspace: workspaceUrlsAndId;
|
||||
}
|
||||
|
||||
@ -27,9 +27,11 @@ export class EnterpriseFeaturesEnabledGuard implements CanActivate {
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
||||
});
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -50,9 +50,11 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
||||
});
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -71,9 +71,9 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -11,13 +11,11 @@ import {
|
||||
} 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(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
@ -53,9 +51,9 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -28,9 +28,11 @@ export class GoogleProviderEnabledGuard implements CanActivate {
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
||||
});
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -57,9 +57,7 @@ export class MicrosoftAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
||||
AuthExceptionCode.INSUFFICIENT_SCOPES,
|
||||
)
|
||||
: error,
|
||||
{
|
||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -72,9 +72,9 @@ export class MicrosoftAPIsOauthRequestCodeGuard extends AuthGuard(
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -5,14 +5,12 @@ 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(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
@ -41,9 +39,9 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -28,9 +28,11 @@ export class MicrosoftProviderEnabledGuard implements CanActivate {
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(context, err, {
|
||||
subdomain: this.guardRedirectService.getSubdomainFromContext(context),
|
||||
});
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromContext(context),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
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';
|
||||
|
||||
@ -21,12 +20,13 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private getIdentityProviderId(request: any): string {
|
||||
private getStateByRequest(request: any): {
|
||||
identityProviderId: string;
|
||||
} {
|
||||
if (request.params.identityProviderId) {
|
||||
return request.params.identityProviderId;
|
||||
}
|
||||
@ -39,24 +39,27 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
) {
|
||||
const state = JSON.parse(request.query.state);
|
||||
|
||||
return state.identityProviderId;
|
||||
return {
|
||||
identityProviderId: state.identityProviderId,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Invalid OIDC identity provider params');
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
let identityProvider:
|
||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||
| null = null;
|
||||
|
||||
try {
|
||||
const identityProviderId = this.getIdentityProviderId(request);
|
||||
const state = this.getStateByRequest(request);
|
||||
|
||||
identityProvider =
|
||||
await this.sSOService.findSSOIdentityProviderById(identityProviderId);
|
||||
identityProvider = await this.sSOService.findSSOIdentityProviderById(
|
||||
state.identityProviderId,
|
||||
);
|
||||
|
||||
if (!identityProvider) {
|
||||
throw new AuthException(
|
||||
@ -77,9 +80,9 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
identityProvider?.workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
identityProvider?.workspace,
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@ -10,22 +12,34 @@ import {
|
||||
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';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
|
||||
@Injectable()
|
||||
export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private getRelayStateByRequest(request: Request) {
|
||||
try {
|
||||
const relayStateRaw = request.body.RelayState || request.query.RelayState;
|
||||
|
||||
if (relayStateRaw) {
|
||||
return JSON.parse(relayStateRaw);
|
||||
}
|
||||
} catch (error) {
|
||||
this.exceptionHandlerService.captureExceptions(error);
|
||||
}
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
let identityProvider:
|
||||
| (SSOConfiguration & WorkspaceSSOIdentityProvider)
|
||||
@ -49,9 +63,9 @@ export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
identityProvider?.workspace ?? {
|
||||
subdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
|
||||
},
|
||||
this.guardRedirectService.getSubdomainAndHostnameFromWorkspace(
|
||||
identityProvider?.workspace,
|
||||
),
|
||||
);
|
||||
|
||||
return false;
|
||||
|
||||
@ -455,15 +455,15 @@ export class AuthService {
|
||||
|
||||
computeRedirectURI({
|
||||
loginToken,
|
||||
subdomain,
|
||||
workspace,
|
||||
billingCheckoutSessionState,
|
||||
}: {
|
||||
loginToken: string;
|
||||
subdomain: string;
|
||||
workspace: Pick<Workspace, 'subdomain' | 'hostname'>;
|
||||
billingCheckoutSessionState?: string;
|
||||
}) {
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
subdomain,
|
||||
workspace,
|
||||
pathname: '/verify',
|
||||
searchParams: {
|
||||
loginToken,
|
||||
|
||||
@ -50,7 +50,6 @@ export class OIDCAuthStrategy extends PassportStrategy(
|
||||
...options,
|
||||
state: JSON.stringify({
|
||||
identityProviderId: req.params.identityProviderId,
|
||||
...(req.query.forceSubdomainUrl ? { forceSubdomainUrl: true } : {}),
|
||||
...(req.query.workspaceInviteHash
|
||||
? { workspaceInviteHash: req.query.workspaceInviteHash }
|
||||
: {}),
|
||||
|
||||
Reference in New Issue
Block a user