Fix workspace hydratation (#12452)
We must separate the concept of hydratation which happens at the request level (take the token and pass auth/user context), from the concept of authorization which happens at the query/endpoint/mutation level. Previously, hydratation exemption happened at the operation name level which is not correct because the operation name is meaningless and optional. Still this gave an impression of security by enforcing a blacklist. So in this PR we introduce linting rule that aim to achieve a similar behavior, now every api method has to have a guard. That way if and endpoint is not protected by AuthUserGuard or AuthWorspaceGuard, then it has to be stated explicitly next to its code. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -97,6 +97,8 @@ module.exports = {
|
|||||||
'prefer-arrow/prefer-arrow-functions': 'off',
|
'prefer-arrow/prefer-arrow-functions': 'off',
|
||||||
'@nx/workspace-max-consts-per-file': 'off',
|
'@nx/workspace-max-consts-per-file': 'off',
|
||||||
'@nx/workspace-inject-workspace-repository': 'warn',
|
'@nx/workspace-inject-workspace-repository': 'warn',
|
||||||
|
'@nx/workspace-rest-api-methods-should-be-guarded': 'error',
|
||||||
|
'@nx/workspace-graphql-resolvers-should-be-guarded': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { UseFilters } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { AuditExceptionFilter } from 'src/engine/core-modules/audit/audit-exception-filter';
|
import { AuditExceptionFilter } from 'src/engine/core-modules/audit/audit-exception-filter';
|
||||||
@ -11,6 +11,8 @@ 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 { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateAnalyticsInputV2,
|
CreateAnalyticsInputV2,
|
||||||
@ -29,13 +31,14 @@ export class AuditResolver {
|
|||||||
async createPageview(
|
async createPageview(
|
||||||
@Args()
|
@Args()
|
||||||
createAnalyticsInput: CreateAnalyticsInputV2,
|
createAnalyticsInput: CreateAnalyticsInputV2,
|
||||||
@AuthWorkspace() workspace: Workspace | undefined,
|
@AuthWorkspace({ allowUndefined: true }) workspace: Workspace | undefined,
|
||||||
@AuthUser({ allowUndefined: true }) user: User | undefined,
|
@AuthUser({ allowUndefined: true }) user: User | undefined,
|
||||||
) {
|
) {
|
||||||
return this.trackAnalytics(createAnalyticsInput, workspace, user);
|
return this.trackAnalytics(createAnalyticsInput, workspace, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Analytics)
|
@Mutation(() => Analytics)
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
async createObjectEvent(
|
async createObjectEvent(
|
||||||
@Args()
|
@Args()
|
||||||
createObjectEventInput: CreateObjectEventInput,
|
createObjectEventInput: CreateObjectEventInput,
|
||||||
@ -63,10 +66,11 @@ export class AuditResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Analytics)
|
@Mutation(() => Analytics)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async trackAnalytics(
|
async trackAnalytics(
|
||||||
@Args()
|
@Args()
|
||||||
createAnalyticsInput: CreateAnalyticsInputV2,
|
createAnalyticsInput: CreateAnalyticsInputV2,
|
||||||
@AuthWorkspace() workspace: Workspace | undefined,
|
@AuthWorkspace({ allowUndefined: true }) workspace: Workspace | undefined,
|
||||||
@AuthUser({ allowUndefined: true }) user: User | undefined,
|
@AuthUser({ allowUndefined: true }) user: User | undefined,
|
||||||
) {
|
) {
|
||||||
const analyticsContext = this.auditService.createContext({
|
const analyticsContext = this.auditService.createContext({
|
||||||
|
|||||||
@ -49,6 +49,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
@ -94,7 +95,7 @@ export class AuthResolver {
|
|||||||
private sSOService: SSOService,
|
private sSOService: SSOService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@UseGuards(CaptchaGuard)
|
@UseGuards(CaptchaGuard, PublicEndpointGuard)
|
||||||
@Query(() => UserExistsOutput)
|
@Query(() => UserExistsOutput)
|
||||||
async checkUserExists(
|
async checkUserExists(
|
||||||
@Args() checkUserExistsInput: CheckUserExistsInput,
|
@Args() checkUserExistsInput: CheckUserExistsInput,
|
||||||
@ -105,6 +106,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => GetAuthorizationUrlForSSOOutput)
|
@Mutation(() => GetAuthorizationUrlForSSOOutput)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async getAuthorizationUrlForSSO(
|
async getAuthorizationUrlForSSO(
|
||||||
@Args('input') params: GetAuthorizationUrlForSSOInput,
|
@Args('input') params: GetAuthorizationUrlForSSOInput,
|
||||||
) {
|
) {
|
||||||
@ -115,6 +117,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => WorkspaceInviteHashValid)
|
@Query(() => WorkspaceInviteHashValid)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async checkWorkspaceInviteHashIsValid(
|
async checkWorkspaceInviteHashIsValid(
|
||||||
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
||||||
): Promise<WorkspaceInviteHashValid> {
|
): Promise<WorkspaceInviteHashValid> {
|
||||||
@ -124,6 +127,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => Workspace)
|
@Query(() => Workspace)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async findWorkspaceFromInviteHash(
|
async findWorkspaceFromInviteHash(
|
||||||
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
|
||||||
): Promise<Workspace> {
|
): Promise<Workspace> {
|
||||||
@ -132,7 +136,7 @@ export class AuthResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(CaptchaGuard)
|
@UseGuards(CaptchaGuard, PublicEndpointGuard)
|
||||||
@Mutation(() => LoginToken)
|
@Mutation(() => LoginToken)
|
||||||
async getLoginTokenFromCredentials(
|
async getLoginTokenFromCredentials(
|
||||||
@Args()
|
@Args()
|
||||||
@ -166,6 +170,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => GetLoginTokenFromEmailVerificationTokenOutput)
|
@Mutation(() => GetLoginTokenFromEmailVerificationTokenOutput)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async getLoginTokenFromEmailVerificationToken(
|
async getLoginTokenFromEmailVerificationToken(
|
||||||
@Args()
|
@Args()
|
||||||
getLoginTokenFromEmailVerificationTokenInput: GetLoginTokenFromEmailVerificationTokenInput,
|
getLoginTokenFromEmailVerificationTokenInput: GetLoginTokenFromEmailVerificationTokenInput,
|
||||||
@ -197,7 +202,7 @@ export class AuthResolver {
|
|||||||
return { loginToken, workspaceUrls };
|
return { loginToken, workspaceUrls };
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(CaptchaGuard)
|
@UseGuards(CaptchaGuard, PublicEndpointGuard)
|
||||||
@Mutation(() => SignUpOutput)
|
@Mutation(() => SignUpOutput)
|
||||||
async signUp(@Args() signUpInput: SignUpInput): Promise<SignUpOutput> {
|
async signUp(@Args() signUpInput: SignUpInput): Promise<SignUpOutput> {
|
||||||
const currentWorkspace = await this.authService.findWorkspaceForSignInUp({
|
const currentWorkspace = await this.authService.findWorkspaceForSignInUp({
|
||||||
@ -269,6 +274,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => SignUpOutput)
|
@Mutation(() => SignUpOutput)
|
||||||
|
@UseGuards(UserAuthGuard)
|
||||||
async signUpInNewWorkspace(
|
async signUpInNewWorkspace(
|
||||||
@AuthUser() currentUser: User,
|
@AuthUser() currentUser: User,
|
||||||
): Promise<SignUpOutput> {
|
): Promise<SignUpOutput> {
|
||||||
@ -300,7 +306,7 @@ export class AuthResolver {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
@Mutation(() => TransientToken)
|
@Mutation(() => TransientToken)
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(UserAuthGuard)
|
||||||
async generateTransientToken(
|
async generateTransientToken(
|
||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@ -324,6 +330,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => AuthTokens)
|
@Mutation(() => AuthTokens)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async getAuthTokensFromLoginToken(
|
async getAuthTokensFromLoginToken(
|
||||||
@Args() getAuthTokensFromLoginTokenInput: GetAuthTokensFromLoginTokenInput,
|
@Args() getAuthTokensFromLoginTokenInput: GetAuthTokensFromLoginTokenInput,
|
||||||
@Args('origin') origin: string,
|
@Args('origin') origin: string,
|
||||||
@ -351,7 +358,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => AuthorizeApp)
|
@Mutation(() => AuthorizeApp)
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(UserAuthGuard)
|
||||||
async authorizeApp(
|
async authorizeApp(
|
||||||
@Args() authorizeAppInput: AuthorizeAppInput,
|
@Args() authorizeAppInput: AuthorizeAppInput,
|
||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@ -365,6 +372,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => AuthTokens)
|
@Mutation(() => AuthTokens)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async renewToken(@Args() args: AppTokenInput): Promise<AuthTokens> {
|
async renewToken(@Args() args: AppTokenInput): Promise<AuthTokens> {
|
||||||
const tokens = await this.renewTokenService.generateTokensFromRefreshToken(
|
const tokens = await this.renewTokenService.generateTokensFromRefreshToken(
|
||||||
args.appToken,
|
args.appToken,
|
||||||
@ -390,6 +398,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => EmailPasswordResetLink)
|
@Mutation(() => EmailPasswordResetLink)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async emailPasswordResetLink(
|
async emailPasswordResetLink(
|
||||||
@Args() emailPasswordResetInput: EmailPasswordResetLinkInput,
|
@Args() emailPasswordResetInput: EmailPasswordResetLinkInput,
|
||||||
@Context() context: I18nContext,
|
@Context() context: I18nContext,
|
||||||
@ -408,6 +417,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => InvalidatePassword)
|
@Mutation(() => InvalidatePassword)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async updatePasswordViaResetToken(
|
async updatePasswordViaResetToken(
|
||||||
@Args()
|
@Args()
|
||||||
{ passwordResetToken, newPassword }: UpdatePasswordViaResetTokenInput,
|
{ passwordResetToken, newPassword }: UpdatePasswordViaResetTokenInput,
|
||||||
@ -428,6 +438,7 @@ export class AuthResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => ValidatePasswordResetToken)
|
@Query(() => ValidatePasswordResetToken)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async validatePasswordResetToken(
|
async validatePasswordResetToken(
|
||||||
@Args() args: ValidatePasswordResetTokenInput,
|
@Args() args: ValidatePasswordResetTokenInput,
|
||||||
): Promise<ValidatePasswordResetToken> {
|
): Promise<ValidatePasswordResetToken> {
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/ser
|
|||||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('auth/google-apis')
|
@Controller('auth/google-apis')
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@UseFilters(AuthRestApiExceptionFilter)
|
||||||
@ -42,14 +43,14 @@ export class GoogleAPIsAuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(GoogleAPIsOauthRequestCodeGuard)
|
@UseGuards(GoogleAPIsOauthRequestCodeGuard, PublicEndpointGuard)
|
||||||
async googleAuth() {
|
async googleAuth() {
|
||||||
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('get-access-token')
|
@Get('get-access-token')
|
||||||
@UseGuards(GoogleAPIsOauthExchangeCodeForTokenGuard)
|
@UseGuards(GoogleAPIsOauthExchangeCodeForTokenGuard, PublicEndpointGuard)
|
||||||
async googleAuthGetAccessToken(
|
async googleAuthGetAccessToken(
|
||||||
@Req() req: GoogleAPIsRequest,
|
@Req() req: GoogleAPIsRequest,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
|||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('auth/google')
|
@Controller('auth/google')
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@UseFilters(AuthRestApiExceptionFilter)
|
||||||
@ -35,14 +36,14 @@ export class GoogleAuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard)
|
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard, PublicEndpointGuard)
|
||||||
async googleAuth() {
|
async googleAuth() {
|
||||||
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('redirect')
|
@Get('redirect')
|
||||||
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard)
|
@UseGuards(GoogleProviderEnabledGuard, GoogleOauthGuard, PublicEndpointGuard)
|
||||||
@UseFilters(AuthOAuthExceptionFilter)
|
@UseFilters(AuthOAuthExceptionFilter)
|
||||||
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
|
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/ser
|
|||||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('auth/microsoft-apis')
|
@Controller('auth/microsoft-apis')
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@UseFilters(AuthRestApiExceptionFilter)
|
||||||
@ -42,14 +43,14 @@ export class MicrosoftAPIsAuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(MicrosoftAPIsOauthRequestCodeGuard)
|
@UseGuards(MicrosoftAPIsOauthRequestCodeGuard, PublicEndpointGuard)
|
||||||
async MicrosoftAuth() {
|
async MicrosoftAuth() {
|
||||||
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('get-access-token')
|
@Get('get-access-token')
|
||||||
@UseGuards(MicrosoftAPIsOauthExchangeCodeForTokenGuard)
|
@UseGuards(MicrosoftAPIsOauthExchangeCodeForTokenGuard, PublicEndpointGuard)
|
||||||
async MicrosoftAuthGetAccessToken(
|
async MicrosoftAuthGetAccessToken(
|
||||||
@Req() req: MicrosoftAPIsRequest,
|
@Req() req: MicrosoftAPIsRequest,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
|||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('auth/microsoft')
|
@Controller('auth/microsoft')
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@UseFilters(AuthRestApiExceptionFilter)
|
||||||
@ -34,14 +35,22 @@ export class MicrosoftAuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(MicrosoftProviderEnabledGuard, MicrosoftOAuthGuard)
|
@UseGuards(
|
||||||
|
MicrosoftProviderEnabledGuard,
|
||||||
|
MicrosoftOAuthGuard,
|
||||||
|
PublicEndpointGuard,
|
||||||
|
)
|
||||||
async microsoftAuth() {
|
async microsoftAuth() {
|
||||||
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('redirect')
|
@Get('redirect')
|
||||||
@UseGuards(MicrosoftProviderEnabledGuard, MicrosoftOAuthGuard)
|
@UseGuards(
|
||||||
|
MicrosoftProviderEnabledGuard,
|
||||||
|
MicrosoftOAuthGuard,
|
||||||
|
PublicEndpointGuard,
|
||||||
|
)
|
||||||
async microsoftAuthRedirect(
|
async microsoftAuthRedirect(
|
||||||
@Req() req: MicrosoftRequest,
|
@Req() req: MicrosoftRequest,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import {
|
|||||||
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 { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class SSOAuthController {
|
export class SSOAuthController {
|
||||||
@ -55,7 +56,7 @@ export class SSOAuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('saml/metadata/:identityProviderId')
|
@Get('saml/metadata/:identityProviderId')
|
||||||
@UseGuards(EnterpriseFeaturesEnabledGuard)
|
@UseGuards(EnterpriseFeaturesEnabledGuard, PublicEndpointGuard)
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@UseFilters(AuthRestApiExceptionFilter)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async generateMetadata(@Req() req: any): Promise<string | void> {
|
async generateMetadata(@Req() req: any): Promise<string | void> {
|
||||||
@ -73,7 +74,7 @@ export class SSOAuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('oidc/login/:identityProviderId')
|
@Get('oidc/login/:identityProviderId')
|
||||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard)
|
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard, PublicEndpointGuard)
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@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
|
||||||
@ -81,7 +82,7 @@ export class SSOAuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('saml/login/:identityProviderId')
|
@Get('saml/login/:identityProviderId')
|
||||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard)
|
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard, PublicEndpointGuard)
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@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
|
||||||
@ -89,14 +90,14 @@ export class SSOAuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('oidc/callback')
|
@Get('oidc/callback')
|
||||||
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard)
|
@UseGuards(EnterpriseFeaturesEnabledGuard, OIDCAuthGuard, PublicEndpointGuard)
|
||||||
@UseFilters(AuthOAuthExceptionFilter)
|
@UseFilters(AuthOAuthExceptionFilter)
|
||||||
async oidcAuthCallback(@Req() req: OIDCRequest, @Res() res: Response) {
|
async oidcAuthCallback(@Req() req: OIDCRequest, @Res() res: Response) {
|
||||||
return await this.authCallback(req, res);
|
return await this.authCallback(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('saml/callback/:identityProviderId')
|
@Post('saml/callback/:identityProviderId')
|
||||||
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard)
|
@UseGuards(EnterpriseFeaturesEnabledGuard, SAMLAuthGuard, PublicEndpointGuard)
|
||||||
@UseFilters(AuthOAuthExceptionFilter)
|
@UseFilters(AuthOAuthExceptionFilter)
|
||||||
async samlAuthCallback(@Req() req: SAMLRequest, @Res() res: Response) {
|
async samlAuthCallback(@Req() req: SAMLRequest, @Res() res: Response) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
Req,
|
Req,
|
||||||
Res,
|
Res,
|
||||||
UseFilters,
|
UseFilters,
|
||||||
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
@ -29,6 +30,8 @@ import { BillingWebhookInvoiceService } from 'src/engine/core-modules/billing/we
|
|||||||
import { BillingWebhookPriceService } from 'src/engine/core-modules/billing/webhooks/services/billing-webhook-price.service';
|
import { BillingWebhookPriceService } from 'src/engine/core-modules/billing/webhooks/services/billing-webhook-price.service';
|
||||||
import { BillingWebhookProductService } from 'src/engine/core-modules/billing/webhooks/services/billing-webhook-product.service';
|
import { BillingWebhookProductService } from 'src/engine/core-modules/billing/webhooks/services/billing-webhook-product.service';
|
||||||
import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing/webhooks/services/billing-webhook-subscription.service';
|
import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing/webhooks/services/billing-webhook-subscription.service';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@UseFilters(BillingRestApiExceptionFilter)
|
@UseFilters(BillingRestApiExceptionFilter)
|
||||||
export class BillingController {
|
export class BillingController {
|
||||||
@ -47,6 +50,7 @@ export class BillingController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post(['webhooks/stripe'])
|
@Post(['webhooks/stripe'])
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async handleWebhooks(
|
async handleWebhooks(
|
||||||
@Headers('stripe-signature') signature: string,
|
@Headers('stripe-signature') signature: string,
|
||||||
@Req() req: RawBodyRequest<Request>,
|
@Req() req: RawBodyRequest<Request>,
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { ClientConfig } from 'src/engine/core-modules/client-config/client-config.entity';
|
import { ClientConfig } from 'src/engine/core-modules/client-config/client-config.entity';
|
||||||
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
|
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('/client-config')
|
@Controller('/client-config')
|
||||||
export class ClientConfigController {
|
export class ClientConfigController {
|
||||||
constructor(private readonly clientConfigService: ClientConfigService) {}
|
constructor(private readonly clientConfigService: ClientConfigService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async getClientConfig(): Promise<ClientConfig> {
|
async getClientConfig(): Promise<ClientConfig> {
|
||||||
return this.clientConfigService.getClientConfig();
|
return this.clientConfigService.getClientConfig();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
import { UseGuards } from '@nestjs/common';
|
||||||
import { Query, Resolver } from '@nestjs/graphql';
|
import { Query, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
|
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
import { ClientConfig } from './client-config.entity';
|
import { ClientConfig } from './client-config.entity';
|
||||||
|
|
||||||
@ -9,6 +11,7 @@ export class ClientConfigResolver {
|
|||||||
constructor(private clientConfigService: ClientConfigService) {}
|
constructor(private clientConfigService: ClientConfigService) {}
|
||||||
|
|
||||||
@Query(() => ClientConfig)
|
@Query(() => ClientConfig)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async clientConfig(): Promise<ClientConfig> {
|
async clientConfig(): Promise<ClientConfig> {
|
||||||
return this.clientConfigService.getClientConfig();
|
return this.clientConfigService.getClientConfig();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,8 +25,8 @@ import { CustomDomainService } from 'src/engine/core-modules/domain-manager/serv
|
|||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
import { handleException } from 'src/engine/utils/global-exception-handler.util';
|
import { handleException } from 'src/engine/utils/global-exception-handler.util';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@UseFilters(AuthRestApiExceptionFilter)
|
@UseFilters(AuthRestApiExceptionFilter)
|
||||||
export class CloudflareController {
|
export class CloudflareController {
|
||||||
@ -40,7 +40,7 @@ export class CloudflareController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post(['cloudflare/custom-hostname-webhooks', 'webhooks/cloudflare'])
|
@Post(['cloudflare/custom-hostname-webhooks', 'webhooks/cloudflare'])
|
||||||
@UseGuards(CloudflareSecretMatchGuard)
|
@UseGuards(CloudflareSecretMatchGuard, PublicEndpointGuard)
|
||||||
async customHostnameWebhooks(@Req() req: Request, @Res() res: Response) {
|
async customHostnameWebhooks(@Req() req: Request, @Res() res: Response) {
|
||||||
if (!req.body?.data?.data?.hostname) {
|
if (!req.body?.data?.data?.hostname) {
|
||||||
handleException({
|
handleException({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { UseFilters } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
|
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||||
@ -9,6 +9,7 @@ import { ResendEmailVerificationTokenOutput } from 'src/engine/core-modules/emai
|
|||||||
import { EmailVerificationExceptionFilter } from 'src/engine/core-modules/email-verification/email-verification-exception-filter.util';
|
import { EmailVerificationExceptionFilter } from 'src/engine/core-modules/email-verification/email-verification-exception-filter.util';
|
||||||
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 { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(EmailVerificationExceptionFilter)
|
@UseFilters(EmailVerificationExceptionFilter)
|
||||||
@ -19,6 +20,7 @@ export class EmailVerificationResolver {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Mutation(() => ResendEmailVerificationTokenOutput)
|
@Mutation(() => ResendEmailVerificationTokenOutput)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async resendEmailVerificationToken(
|
async resendEmailVerificationToken(
|
||||||
@Args()
|
@Args()
|
||||||
resendEmailVerificationTokenInput: ResendEmailVerificationTokenInput,
|
resendEmailVerificationTokenInput: ResendEmailVerificationTokenInput,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { FileApiExceptionFilter } from 'src/engine/core-modules/file/filters/fil
|
|||||||
import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard';
|
import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard';
|
||||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||||
import { extractFileInfoFromRequest } from 'src/engine/core-modules/file/utils/extract-file-info-from-request.utils';
|
import { extractFileInfoFromRequest } from 'src/engine/core-modules/file/utils/extract-file-info-from-request.utils';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('files')
|
@Controller('files')
|
||||||
@UseFilters(FileApiExceptionFilter)
|
@UseFilters(FileApiExceptionFilter)
|
||||||
@ -31,6 +32,7 @@ export class FileController {
|
|||||||
constructor(private readonly fileService: FileService) {}
|
constructor(private readonly fileService: FileService) {}
|
||||||
|
|
||||||
@Get('*/:filename')
|
@Get('*/:filename')
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async getFile(
|
async getFile(
|
||||||
@Param() params: string[],
|
@Param() params: string[],
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { BadRequestException, Controller, Get, Param } from '@nestjs/common';
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
|
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
|
||||||
|
|
||||||
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
|
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
|
||||||
@ -7,6 +13,8 @@ import { ConnectedAccountHealth } from 'src/engine/core-modules/health/indicator
|
|||||||
import { DatabaseHealthIndicator } from 'src/engine/core-modules/health/indicators/database.health';
|
import { DatabaseHealthIndicator } from 'src/engine/core-modules/health/indicators/database.health';
|
||||||
import { RedisHealthIndicator } from 'src/engine/core-modules/health/indicators/redis.health';
|
import { RedisHealthIndicator } from 'src/engine/core-modules/health/indicators/redis.health';
|
||||||
import { WorkerHealthIndicator } from 'src/engine/core-modules/health/indicators/worker.health';
|
import { WorkerHealthIndicator } from 'src/engine/core-modules/health/indicators/worker.health';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller('healthz')
|
@Controller('healthz')
|
||||||
export class HealthController {
|
export class HealthController {
|
||||||
constructor(
|
constructor(
|
||||||
@ -19,12 +27,14 @@ export class HealthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
@HealthCheck()
|
@HealthCheck()
|
||||||
check() {
|
check() {
|
||||||
return this.health.check([]);
|
return this.health.check([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':indicatorId')
|
@Get(':indicatorId')
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
@HealthCheck()
|
@HealthCheck()
|
||||||
checkService(@Param('indicatorId') indicatorId: HealthIndicatorId) {
|
checkService(@Param('indicatorId') indicatorId: HealthIndicatorId) {
|
||||||
const checks = {
|
const checks = {
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import { Controller, Get, Req, Res } from '@nestjs/common';
|
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class OpenApiController {
|
export class OpenApiController {
|
||||||
constructor(private readonly openApiService: OpenApiService) {}
|
constructor(private readonly openApiService: OpenApiService) {}
|
||||||
|
|
||||||
@Get(['open-api/core', 'rest/open-api/core'])
|
@Get(['open-api/core', 'rest/open-api/core'])
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async generateOpenApiSchemaCore(
|
async generateOpenApiSchemaCore(
|
||||||
@Req() request: Request,
|
@Req() request: Request,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@ -19,6 +21,7 @@ export class OpenApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(['open-api/metadata', 'rest/open-api/metadata'])
|
@Get(['open-api/metadata', 'rest/open-api/metadata'])
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async generateOpenApiSchemaMetaData(
|
async generateOpenApiSchemaMetaData(
|
||||||
@Req() request: Request,
|
@Req() request: Request,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { UseFilters } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Query, Resolver } from '@nestjs/graphql';
|
import { Args, Query, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
|
import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
|
||||||
|
import { SearchResultConnectionDTO } from 'src/engine/core-modules/search/dtos/search-result-connection.dto';
|
||||||
import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters/search-api-exception.filter';
|
import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters/search-api-exception.filter';
|
||||||
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
|
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { SearchResultConnectionDTO } from 'src/engine/core-modules/search/dtos/search-result-connection.dto';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(SearchApiExceptionFilter)
|
@UseFilters(SearchApiExceptionFilter)
|
||||||
@ -14,6 +15,7 @@ export class SearchResolver {
|
|||||||
constructor(private readonly searchService: SearchService) {}
|
constructor(private readonly searchService: SearchService) {}
|
||||||
|
|
||||||
@Query(() => SearchResultConnectionDTO)
|
@Query(() => SearchResultConnectionDTO)
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
async search(
|
async search(
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args()
|
@Args()
|
||||||
|
|||||||
@ -1,9 +1,18 @@
|
|||||||
import { Controller, Get, Param, Post, Req, UseFilters } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Req,
|
||||||
|
UseFilters,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { WorkflowTriggerRestApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-rest-api-exception.filter';
|
import { WorkflowTriggerRestApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-rest-api-exception.filter';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import {
|
import {
|
||||||
@ -27,6 +36,7 @@ export class WorkflowTriggerController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('workflows/:workspaceId/:workflowId')
|
@Post('workflows/:workspaceId/:workflowId')
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async runWorkflowByPostRequest(
|
async runWorkflowByPostRequest(
|
||||||
@Param('workspaceId') workspaceId: string,
|
@Param('workspaceId') workspaceId: string,
|
||||||
@Param('workflowId') workflowId: string,
|
@Param('workflowId') workflowId: string,
|
||||||
@ -40,6 +50,7 @@ export class WorkflowTriggerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('workflows/:workspaceId/:workflowId')
|
@Get('workflows/:workspaceId/:workflowId')
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async runWorkflowByGetRequest(
|
async runWorkflowByGetRequest(
|
||||||
@Param('workspaceId') workspaceId: string,
|
@Param('workspaceId') workspaceId: string,
|
||||||
@Param('workflowId') workflowId: string,
|
@Param('workflowId') workflowId: string,
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { DomainManagerService } from 'src/engine/core-modules/domain-manager/ser
|
|||||||
import { FeatureFlagDTO } from 'src/engine/core-modules/feature-flag/dtos/feature-flag-dto';
|
import { FeatureFlagDTO } from 'src/engine/core-modules/feature-flag/dtos/feature-flag-dto';
|
||||||
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 { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto';
|
||||||
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 { FileService } from 'src/engine/core-modules/file/services/file.service';
|
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
@ -43,6 +44,7 @@ import { AuthApiKey } from 'src/engine/decorators/auth/auth-api-key.decorator';
|
|||||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
@ -52,7 +54,6 @@ import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
|||||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter';
|
import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter';
|
||||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||||
import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto';
|
|
||||||
|
|
||||||
import { Workspace } from './workspace.entity';
|
import { Workspace } from './workspace.entity';
|
||||||
|
|
||||||
@ -283,6 +284,7 @@ export class WorkspaceResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => PublicWorkspaceDataOutput)
|
@Query(() => PublicWorkspaceDataOutput)
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
async getPublicWorkspaceDataByDomain(
|
async getPublicWorkspaceDataByDomain(
|
||||||
@Args('origin') origin: string,
|
@Args('origin') origin: string,
|
||||||
): Promise<PublicWorkspaceDataOutput | undefined> {
|
): Promise<PublicWorkspaceDataOutput | undefined> {
|
||||||
|
|||||||
@ -16,7 +16,8 @@ export const AuthUser = createParamDecorator(
|
|||||||
|
|
||||||
if (!options?.allowUndefined && !request.user) {
|
if (!options?.allowUndefined && !request.user) {
|
||||||
throw new ForbiddenException(
|
throw new ForbiddenException(
|
||||||
"You're not authorized to do this. Note: This endpoint requires a user and won't work with just an API key.",
|
"You're not authorized to do this. " +
|
||||||
|
"Note: This endpoint requires a user and won't work with just an API key.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,28 @@
|
|||||||
import { ExecutionContext, createParamDecorator } from '@nestjs/common';
|
import {
|
||||||
|
ExecutionContext,
|
||||||
|
InternalServerErrorException,
|
||||||
|
createParamDecorator,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { getRequest } from 'src/utils/extract-request';
|
import { getRequest } from 'src/utils/extract-request';
|
||||||
|
|
||||||
|
interface DecoratorOptions {
|
||||||
|
allowUndefined?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const AuthWorkspace = createParamDecorator(
|
export const AuthWorkspace = createParamDecorator(
|
||||||
(data: unknown, ctx: ExecutionContext) => {
|
(options: DecoratorOptions | undefined, ctx: ExecutionContext) => {
|
||||||
const request = getRequest(ctx);
|
const request = getRequest(ctx);
|
||||||
|
|
||||||
|
if (!options?.allowUndefined && !request.workspace) {
|
||||||
|
// We're throwing an internal error and not a ForbiddenException
|
||||||
|
// because this should never happen, this is an extra security measure
|
||||||
|
// but Auth should be handled through the guards not the decorator
|
||||||
|
throw new InternalServerErrorException(
|
||||||
|
"You're not authorized to do this. This should not ever happen.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return request.workspace;
|
return request.workspace;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
|
|
||||||
|
describe('PublicEndpointGuard', () => {
|
||||||
|
let guard: PublicEndpointGuard;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [PublicEndpointGuard],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
guard = module.get<PublicEndpointGuard>(PublicEndpointGuard);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(guard).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always return true for any execution context', () => {
|
||||||
|
const mockContext = {} as ExecutionContext;
|
||||||
|
const result = guard.canActivate(mockContext);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true even with null context', () => {
|
||||||
|
const result = guard.canActivate(null as any);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be injectable', () => {
|
||||||
|
expect(guard).toBeInstanceOf(PublicEndpointGuard);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guard that explicitly marks an endpoint as public/unprotected.
|
||||||
|
* This guard always returns true and serves as documentation
|
||||||
|
* that the endpoint is intentionally accessible without authentication.
|
||||||
|
*
|
||||||
|
* Usage: @UseGuards(PublicEndpointGuard)
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class PublicEndpointGuard implements CanActivate {
|
||||||
|
canActivate(_context: ExecutionContext): boolean {
|
||||||
|
// Always allow access - this is an explicit marker for public endpoints
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|||||||
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||||
import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input';
|
import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input';
|
||||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||||
@ -38,6 +40,7 @@ import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role
|
|||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
@Resolver(() => RoleDTO)
|
@Resolver(() => RoleDTO)
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.ROLES))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.ROLES))
|
||||||
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
export class RoleResolver {
|
export class RoleResolver {
|
||||||
@ -57,6 +60,7 @@ export class RoleResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => WorkspaceMember)
|
@Mutation(() => WorkspaceMember)
|
||||||
|
@UseGuards(UserAuthGuard)
|
||||||
async updateWorkspaceMemberRole(
|
async updateWorkspaceMemberRole(
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args('workspaceMemberId') workspaceMemberId: string,
|
@Args('workspaceMemberId') workspaceMemberId: string,
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
export const EXCLUDED_MIDDLEWARE_OPERATIONS = [
|
|
||||||
'GetClientConfig',
|
|
||||||
'GetWorkspaceFromInviteHash',
|
|
||||||
'Track',
|
|
||||||
'TrackAnalytics',
|
|
||||||
'AuditTrack',
|
|
||||||
'CheckUserExists',
|
|
||||||
'GetLoginTokenFromCredentials',
|
|
||||||
'GetAuthTokensFromLoginToken',
|
|
||||||
'GetLoginTokenFromEmailVerificationToken',
|
|
||||||
'ResendEmailVerificationToken',
|
|
||||||
'SignUp',
|
|
||||||
'RenewToken',
|
|
||||||
'EmailPasswordResetLink',
|
|
||||||
'ValidatePasswordResetToken',
|
|
||||||
'UpdatePasswordViaResetToken',
|
|
||||||
'IntrospectionQuery',
|
|
||||||
'ExchangeAuthorizationCode',
|
|
||||||
'GetAuthorizationUrlForSSO',
|
|
||||||
'GetPublicWorkspaceDataByDomain',
|
|
||||||
] as const;
|
|
||||||
@ -11,12 +11,8 @@ export class GraphQLHydrateRequestFromTokenMiddleware
|
|||||||
constructor(private readonly middlewareService: MiddlewareService) {}
|
constructor(private readonly middlewareService: MiddlewareService) {}
|
||||||
|
|
||||||
async use(req: Request, res: Response, next: NextFunction) {
|
async use(req: Request, res: Response, next: NextFunction) {
|
||||||
if (this.middlewareService.checkUnauthenticatedAccess(req)) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.middlewareService.authenticateGraphqlRequest(req);
|
await this.middlewareService.hydrateGraphqlRequest(req);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.middlewareService.writeGraphqlResponseOnExceptionCaught(res, error);
|
this.middlewareService.writeGraphqlResponseOnExceptionCaught(res, error);
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Request, Response } from 'express';
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||||
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
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 { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { getAuthExceptionRestStatus } from 'src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util';
|
import { getAuthExceptionRestStatus } from 'src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util';
|
||||||
@ -13,8 +14,6 @@ import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrap
|
|||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
import { INTERNAL_SERVER_ERROR } from 'src/engine/middlewares/constants/default-error-message.constant';
|
import { INTERNAL_SERVER_ERROR } from 'src/engine/middlewares/constants/default-error-message.constant';
|
||||||
import { EXCLUDED_MIDDLEWARE_OPERATIONS } from 'src/engine/middlewares/constants/excluded-middleware-operations.constant';
|
|
||||||
import { GraphqlTokenValidationProxy } from 'src/engine/middlewares/utils/graphql-token-validation-utils';
|
|
||||||
import {
|
import {
|
||||||
handleException,
|
handleException,
|
||||||
handleExceptionAndConvertToGraphQLError,
|
handleExceptionAndConvertToGraphQLError,
|
||||||
@ -33,25 +32,12 @@ export class MiddlewareService {
|
|||||||
private readonly jwtWrapperService: JwtWrapperService,
|
private readonly jwtWrapperService: JwtWrapperService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private excludedOperations = EXCLUDED_MIDDLEWARE_OPERATIONS;
|
|
||||||
|
|
||||||
public isTokenPresent(request: Request): boolean {
|
public isTokenPresent(request: Request): boolean {
|
||||||
const token = this.jwtWrapperService.extractJwtFromRequest()(request);
|
const token = this.jwtWrapperService.extractJwtFromRequest()(request);
|
||||||
|
|
||||||
return !!token;
|
return !!token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkUnauthenticatedAccess(request: Request): boolean {
|
|
||||||
const { body } = request;
|
|
||||||
|
|
||||||
const isUserUnauthenticated = !this.isTokenPresent(request);
|
|
||||||
const isExcludedOperation =
|
|
||||||
!body?.operationName ||
|
|
||||||
this.excludedOperations.includes(body.operationName);
|
|
||||||
|
|
||||||
return isUserUnauthenticated && isExcludedOperation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
public writeRestResponseOnExceptionCaught(res: Response, error: any) {
|
public writeRestResponseOnExceptionCaught(res: Response, error: any) {
|
||||||
const statusCode = this.getStatus(error);
|
const statusCode = this.getStatus(error);
|
||||||
@ -77,12 +63,24 @@ export class MiddlewareService {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
public writeGraphqlResponseOnExceptionCaught(res: Response, error: any) {
|
public writeGraphqlResponseOnExceptionCaught(res: Response, error: any) {
|
||||||
const errors = [
|
let errors;
|
||||||
|
|
||||||
|
if (error instanceof AuthException) {
|
||||||
|
try {
|
||||||
|
const authFilter = new AuthGraphqlApiExceptionFilter();
|
||||||
|
|
||||||
|
authFilter.catch(error);
|
||||||
|
} catch (transformedError) {
|
||||||
|
errors = [transformedError];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors = [
|
||||||
handleExceptionAndConvertToGraphQLError(
|
handleExceptionAndConvertToGraphQLError(
|
||||||
error as Error,
|
error as Error,
|
||||||
this.exceptionHandlerService,
|
this.exceptionHandlerService,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const statusCode = 200;
|
const statusCode = 200;
|
||||||
|
|
||||||
@ -99,7 +97,7 @@ export class MiddlewareService {
|
|||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async authenticateRestRequest(request: Request) {
|
public async hydrateRestRequest(request: Request) {
|
||||||
const data = await this.accessTokenService.validateTokenByRequest(request);
|
const data = await this.accessTokenService.validateTokenByRequest(request);
|
||||||
const metadataVersion =
|
const metadataVersion =
|
||||||
await this.workspaceStorageCacheService.getMetadataVersion(
|
await this.workspaceStorageCacheService.getMetadataVersion(
|
||||||
@ -125,12 +123,12 @@ export class MiddlewareService {
|
|||||||
this.bindDataToRequestObject(data, request, metadataVersion);
|
this.bindDataToRequestObject(data, request, metadataVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async authenticateGraphqlRequest(request: Request) {
|
public async hydrateGraphqlRequest(request: Request) {
|
||||||
const graphqlTokenValidationProxy = new GraphqlTokenValidationProxy(
|
if (!this.isTokenPresent(request)) {
|
||||||
this.accessTokenService,
|
return;
|
||||||
);
|
}
|
||||||
|
|
||||||
const data = await graphqlTokenValidationProxy.validateToken(request);
|
const data = await this.accessTokenService.validateTokenByRequest(request);
|
||||||
const metadataVersion =
|
const metadataVersion =
|
||||||
await this.workspaceStorageCacheService.getMetadataVersion(
|
await this.workspaceStorageCacheService.getMetadataVersion(
|
||||||
data.workspace.id,
|
data.workspace.id,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export class RestCoreMiddleware implements NestMiddleware {
|
|||||||
|
|
||||||
async use(req: Request, res: Response, next: NextFunction) {
|
async use(req: Request, res: Response, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
await this.middlewareService.authenticateRestRequest(req);
|
await this.middlewareService.hydrateRestRequest(req);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.middlewareService.writeRestResponseOnExceptionCaught(res, error);
|
this.middlewareService.writeRestResponseOnExceptionCaught(res, error);
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
import { Request } from 'express';
|
|
||||||
|
|
||||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
|
||||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
|
||||||
|
|
||||||
export class GraphqlTokenValidationProxy {
|
|
||||||
private accessTokenService: AccessTokenService;
|
|
||||||
|
|
||||||
constructor(accessTokenService: AccessTokenService) {
|
|
||||||
this.accessTokenService = accessTokenService;
|
|
||||||
}
|
|
||||||
|
|
||||||
async validateToken(req: Request) {
|
|
||||||
try {
|
|
||||||
return await this.accessTokenService.validateTokenByRequest(req);
|
|
||||||
} catch (error) {
|
|
||||||
const authGraphqlApiExceptionFilter = new AuthGraphqlApiExceptionFilter();
|
|
||||||
|
|
||||||
throw authGraphqlApiExceptionFilter.catch(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,3 @@
|
|||||||
import {
|
|
||||||
RULE_NAME as injectWorkspaceRepositoryName,
|
|
||||||
rule as injectWorkspaceRepository,
|
|
||||||
} from './rules/inject-workspace-repository';
|
|
||||||
import {
|
import {
|
||||||
rule as componentPropsNaming,
|
rule as componentPropsNaming,
|
||||||
RULE_NAME as componentPropsNamingName,
|
RULE_NAME as componentPropsNamingName,
|
||||||
@ -14,6 +10,14 @@ import {
|
|||||||
rule as explicitBooleanPredicatesInIf,
|
rule as explicitBooleanPredicatesInIf,
|
||||||
RULE_NAME as explicitBooleanPredicatesInIfName,
|
RULE_NAME as explicitBooleanPredicatesInIfName,
|
||||||
} from './rules/explicit-boolean-predicates-in-if';
|
} from './rules/explicit-boolean-predicates-in-if';
|
||||||
|
import {
|
||||||
|
rule as graphqlResolversShouldBeGuarded,
|
||||||
|
RULE_NAME as graphqlResolversShouldBeGuardedName,
|
||||||
|
} from './rules/graphql-resolvers-should-be-guarded';
|
||||||
|
import {
|
||||||
|
rule as injectWorkspaceRepository,
|
||||||
|
RULE_NAME as injectWorkspaceRepositoryName,
|
||||||
|
} from './rules/inject-workspace-repository';
|
||||||
import {
|
import {
|
||||||
rule as matchingStateVariable,
|
rule as matchingStateVariable,
|
||||||
RULE_NAME as matchingStateVariableName,
|
RULE_NAME as matchingStateVariableName,
|
||||||
@ -34,6 +38,10 @@ import {
|
|||||||
rule as noStateUseref,
|
rule as noStateUseref,
|
||||||
RULE_NAME as noStateUserefName,
|
RULE_NAME as noStateUserefName,
|
||||||
} from './rules/no-state-useref';
|
} from './rules/no-state-useref';
|
||||||
|
import {
|
||||||
|
rule as restApiMethodsShouldBeGuarded,
|
||||||
|
RULE_NAME as restApiMethodsShouldBeGuardedName,
|
||||||
|
} from './rules/rest-api-methods-should-be-guarded';
|
||||||
import {
|
import {
|
||||||
rule as sortCssPropertiesAlphabetically,
|
rule as sortCssPropertiesAlphabetically,
|
||||||
RULE_NAME as sortCssPropertiesAlphabeticallyName,
|
RULE_NAME as sortCssPropertiesAlphabeticallyName,
|
||||||
@ -93,5 +101,7 @@ module.exports = {
|
|||||||
useRecoilCallbackHasDependencyArray,
|
useRecoilCallbackHasDependencyArray,
|
||||||
[noNavigatePreferLinkName]: noNavigatePreferLink,
|
[noNavigatePreferLinkName]: noNavigatePreferLink,
|
||||||
[injectWorkspaceRepositoryName]: injectWorkspaceRepository,
|
[injectWorkspaceRepositoryName]: injectWorkspaceRepository,
|
||||||
|
[restApiMethodsShouldBeGuardedName]: restApiMethodsShouldBeGuarded,
|
||||||
|
[graphqlResolversShouldBeGuardedName]: graphqlResolversShouldBeGuarded,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,159 @@
|
|||||||
|
import { TSESLint } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import { rule, RULE_NAME } from './graphql-resolvers-should-be-guarded';
|
||||||
|
|
||||||
|
const ruleTester = new TSESLint.RuleTester({
|
||||||
|
parser: require.resolve('@typescript-eslint/parser'),
|
||||||
|
});
|
||||||
|
|
||||||
|
ruleTester.run(RULE_NAME, rule, {
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
@UseGuards(UserAuthGuard)
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
@UseGuards(CaptchaGuard, PublicEndpointGuard)
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(UserAuthGuard)
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(PublicEndpointGuard)
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
regularMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@ResolveField()
|
||||||
|
testField() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'graphqlResolversShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Mutation()
|
||||||
|
testMutation() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'graphqlResolversShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Subscription()
|
||||||
|
testSubscription() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'graphqlResolversShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
@UseGuards(CaptchaGuard)
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'graphqlResolversShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(CaptchaGuard)
|
||||||
|
class TestResolver {
|
||||||
|
@Query()
|
||||||
|
testQuery() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'graphqlResolversShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import { TSESTree } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import { createRule } from '../utils/createRule';
|
||||||
|
import { typedTokenHelpers } from '../utils/typedTokenHelpers';
|
||||||
|
|
||||||
|
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-graphql-resolvers-should-be-guarded"
|
||||||
|
export const RULE_NAME = 'graphql-resolvers-should-be-guarded';
|
||||||
|
|
||||||
|
export const graphqlResolversShouldBeGuarded = (node: TSESTree.MethodDefinition) => {
|
||||||
|
const hasGraphQLResolverDecorator = typedTokenHelpers.nodeHasDecoratorsNamed(
|
||||||
|
node,
|
||||||
|
['Query', 'Mutation', 'Subscription']
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAuthGuards = typedTokenHelpers.nodeHasAuthGuards(node);
|
||||||
|
|
||||||
|
function findClassDeclaration(
|
||||||
|
node: TSESTree.Node
|
||||||
|
): TSESTree.ClassDeclaration | null {
|
||||||
|
if (node.type === TSESTree.AST_NODE_TYPES.ClassDeclaration) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (node.parent) {
|
||||||
|
return findClassDeclaration(node.parent);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classNode = findClassDeclaration(node);
|
||||||
|
|
||||||
|
const hasAuthGuardsOnResolver = classNode
|
||||||
|
? typedTokenHelpers.nodeHasAuthGuards(classNode)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
hasGraphQLResolverDecorator &&
|
||||||
|
!hasAuthGuards &&
|
||||||
|
!hasAuthGuardsOnResolver
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = createRule<[], 'graphqlResolversShouldBeGuarded'>({
|
||||||
|
name: RULE_NAME,
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
'GraphQL root resolvers (Query, Mutation, Subscription) should have authentication guards (UserAuthGuard or WorkspaceAuthGuard) or be explicitly marked as public (PublicEndpointGuard) to maintain our security model.',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
graphqlResolversShouldBeGuarded:
|
||||||
|
'All GraphQL root resolver methods (@Query, @Mutation, @Subscription) should have @UseGuards(UserAuthGuard), @UseGuards(WorkspaceAuthGuard), or @UseGuards(PublicEndpointGuard) decorators, or one decorating the root of the Resolver class.',
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
hasSuggestions: false,
|
||||||
|
type: 'suggestion',
|
||||||
|
},
|
||||||
|
defaultOptions: [],
|
||||||
|
create(context) {
|
||||||
|
return {
|
||||||
|
MethodDefinition(node: TSESTree.MethodDefinition): void {
|
||||||
|
if (graphqlResolversShouldBeGuarded(node)) {
|
||||||
|
context.report({
|
||||||
|
node: node,
|
||||||
|
messageId: 'graphqlResolversShouldBeGuarded',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
import { TSESLint } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import { rule, RULE_NAME } from './rest-api-methods-should-be-guarded';
|
||||||
|
|
||||||
|
const ruleTester = new TSESLint.RuleTester({
|
||||||
|
parser: require.resolve('@typescript-eslint/parser'),
|
||||||
|
});
|
||||||
|
|
||||||
|
ruleTester.run(RULE_NAME, rule, {
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
@UseGuards(UserAuthGuard)
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
@UseGuards(PublicEndpoint)
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
@UseGuards(CaptchaGuard, PublicEndpoint)
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(UserAuthGuard)
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(PublicEndpoint)
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
regularMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'restApiMethodsShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Post()
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'restApiMethodsShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
@UseGuards(CaptchaGuard)
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'restApiMethodsShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
@UseGuards(CaptchaGuard)
|
||||||
|
class TestController {
|
||||||
|
@Get()
|
||||||
|
testMethod() {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: 'restApiMethodsShouldBeGuarded',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
import { TSESTree } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
import { createRule } from '../utils/createRule';
|
||||||
|
import { typedTokenHelpers } from '../utils/typedTokenHelpers';
|
||||||
|
|
||||||
|
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-rest-api-methods-should-be-guarded"
|
||||||
|
export const RULE_NAME = 'rest-api-methods-should-be-guarded';
|
||||||
|
|
||||||
|
export const restApiMethodsShouldBeGuarded = (node: TSESTree.MethodDefinition) => {
|
||||||
|
const hasRestApiMethodDecorator = typedTokenHelpers.nodeHasDecoratorsNamed(
|
||||||
|
node,
|
||||||
|
['Get', 'Post', 'Put', 'Delete', 'Patch', 'Options', 'Head', 'All']
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAuthGuards = typedTokenHelpers.nodeHasAuthGuards(node);
|
||||||
|
|
||||||
|
function findClassDeclaration(
|
||||||
|
node: TSESTree.Node
|
||||||
|
): TSESTree.ClassDeclaration | null {
|
||||||
|
if (node.type === TSESTree.AST_NODE_TYPES.ClassDeclaration) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (node.parent) {
|
||||||
|
return findClassDeclaration(node.parent);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classNode = findClassDeclaration(node);
|
||||||
|
|
||||||
|
const hasAuthGuardsOnController = classNode
|
||||||
|
? typedTokenHelpers.nodeHasAuthGuards(classNode)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
hasRestApiMethodDecorator &&
|
||||||
|
!hasAuthGuards &&
|
||||||
|
!hasAuthGuardsOnController
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rule = createRule<[], 'restApiMethodsShouldBeGuarded'>({
|
||||||
|
name: RULE_NAME,
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
'REST API endpoints should have authentication guards (UserAuthGuard or WorkspaceAuthGuard) or be explicitly marked as public (PublicEndpointGuard) to maintain our security model.',
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
restApiMethodsShouldBeGuarded:
|
||||||
|
'All REST API controller endpoints should have @UseGuards(UserAuthGuard), @UseGuards(WorkspaceAuthGuard), or @UseGuards(PublicEndpointGuard) decorators, or one decorating the root of the Controller.',
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
hasSuggestions: false,
|
||||||
|
type: 'suggestion',
|
||||||
|
},
|
||||||
|
defaultOptions: [],
|
||||||
|
create(context) {
|
||||||
|
return {
|
||||||
|
MethodDefinition(node: TSESTree.MethodDefinition): void {
|
||||||
|
if (restApiMethodsShouldBeGuarded(node)) {
|
||||||
|
context.report({
|
||||||
|
node: node,
|
||||||
|
messageId: 'restApiMethodsShouldBeGuarded',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
3
tools/eslint-rules/utils/createRule.ts
Normal file
3
tools/eslint-rules/utils/createRule.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
export const createRule = ESLintUtils.RuleCreator(() => __filename);
|
||||||
56
tools/eslint-rules/utils/typedTokenHelpers.ts
Normal file
56
tools/eslint-rules/utils/typedTokenHelpers.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { TSESTree } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
export const typedTokenHelpers = {
|
||||||
|
nodeHasDecoratorsNamed(
|
||||||
|
node: TSESTree.MethodDefinition | TSESTree.ClassDeclaration,
|
||||||
|
decoratorNames: string[]
|
||||||
|
): boolean {
|
||||||
|
if (!node.decorators) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.decorators.some((decorator) => {
|
||||||
|
if (decorator.expression.type === TSESTree.AST_NODE_TYPES.Identifier) {
|
||||||
|
return decoratorNames.includes(decorator.expression.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decorator.expression.type === TSESTree.AST_NODE_TYPES.CallExpression) {
|
||||||
|
const callee = decorator.expression.callee;
|
||||||
|
if (callee.type === TSESTree.AST_NODE_TYPES.Identifier) {
|
||||||
|
return decoratorNames.includes(callee.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
nodeHasAuthGuards(
|
||||||
|
node: TSESTree.MethodDefinition | TSESTree.ClassDeclaration
|
||||||
|
): boolean {
|
||||||
|
if (!node.decorators) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.decorators.some((decorator) => {
|
||||||
|
// Check for @UseGuards() call expression
|
||||||
|
if (
|
||||||
|
decorator.expression.type === TSESTree.AST_NODE_TYPES.CallExpression &&
|
||||||
|
decorator.expression.callee.type === TSESTree.AST_NODE_TYPES.Identifier &&
|
||||||
|
decorator.expression.callee.name === 'UseGuards'
|
||||||
|
) {
|
||||||
|
// Check the arguments for UserAuthGuard, WorkspaceAuthGuard, or PublicEndpoint
|
||||||
|
return decorator.expression.arguments.some((arg) => {
|
||||||
|
if (arg.type === TSESTree.AST_NODE_TYPES.Identifier) {
|
||||||
|
return arg.name === 'UserAuthGuard' ||
|
||||||
|
arg.name === 'WorkspaceAuthGuard' ||
|
||||||
|
arg.name === 'PublicEndpointGuard';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user