[permissions] Add permission gates on API & Webhooks + Security settings (#10133)
Closes https://github.com/twentyhq/core-team-issues/issues/312 Closes https://github.com/twentyhq/core-team-issues/issues/315
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
import * as Apollo from '@apollo/client';
|
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
export type Maybe<T> = T | null;
|
export type Maybe<T> = T | null;
|
||||||
export type InputMaybe<T> = Maybe<T>;
|
export type InputMaybe<T> = Maybe<T>;
|
||||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||||
@ -488,7 +488,6 @@ export type Field = {
|
|||||||
label: Scalars['String'];
|
label: Scalars['String'];
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
object?: Maybe<Object>;
|
object?: Maybe<Object>;
|
||||||
objectMetadataId: Scalars['UUID'];
|
|
||||||
options?: Maybe<Scalars['JSON']>;
|
options?: Maybe<Scalars['JSON']>;
|
||||||
relation?: Maybe<Relation>;
|
relation?: Maybe<Relation>;
|
||||||
relationDefinition?: Maybe<RelationDefinition>;
|
relationDefinition?: Maybe<RelationDefinition>;
|
||||||
@ -520,7 +519,6 @@ export type FieldFilter = {
|
|||||||
isActive?: InputMaybe<BooleanFieldComparison>;
|
isActive?: InputMaybe<BooleanFieldComparison>;
|
||||||
isCustom?: InputMaybe<BooleanFieldComparison>;
|
isCustom?: InputMaybe<BooleanFieldComparison>;
|
||||||
isSystem?: InputMaybe<BooleanFieldComparison>;
|
isSystem?: InputMaybe<BooleanFieldComparison>;
|
||||||
objectMetadataId?: InputMaybe<StringFieldComparison>;
|
|
||||||
or?: InputMaybe<Array<FieldFilter>>;
|
or?: InputMaybe<Array<FieldFilter>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1073,6 +1071,7 @@ export type Object = {
|
|||||||
dataSourceId: Scalars['String'];
|
dataSourceId: Scalars['String'];
|
||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
fields: ObjectFieldsConnection;
|
fields: ObjectFieldsConnection;
|
||||||
|
fieldsList: Array<Field>;
|
||||||
icon?: Maybe<Scalars['String']>;
|
icon?: Maybe<Scalars['String']>;
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
imageIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
imageIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
||||||
@ -1535,7 +1534,7 @@ export enum SettingsFeatures {
|
|||||||
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||||
DATA_MODEL = 'DATA_MODEL',
|
DATA_MODEL = 'DATA_MODEL',
|
||||||
ROLES = 'ROLES',
|
ROLES = 'ROLES',
|
||||||
SECURITY_SETTINGS = 'SECURITY_SETTINGS',
|
SECURITY = 'SECURITY',
|
||||||
WORKSPACE_SETTINGS = 'WORKSPACE_SETTINGS',
|
WORKSPACE_SETTINGS = 'WORKSPACE_SETTINGS',
|
||||||
WORKSPACE_USERS = 'WORKSPACE_USERS'
|
WORKSPACE_USERS = 'WORKSPACE_USERS'
|
||||||
}
|
}
|
||||||
@ -1571,23 +1570,6 @@ export type SignUpOutput = {
|
|||||||
workspace: WorkspaceUrlsAndId;
|
workspace: WorkspaceUrlsAndId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StringFieldComparison = {
|
|
||||||
eq?: InputMaybe<Scalars['String']>;
|
|
||||||
gt?: InputMaybe<Scalars['String']>;
|
|
||||||
gte?: InputMaybe<Scalars['String']>;
|
|
||||||
iLike?: InputMaybe<Scalars['String']>;
|
|
||||||
in?: InputMaybe<Array<Scalars['String']>>;
|
|
||||||
is?: InputMaybe<Scalars['Boolean']>;
|
|
||||||
isNot?: InputMaybe<Scalars['Boolean']>;
|
|
||||||
like?: InputMaybe<Scalars['String']>;
|
|
||||||
lt?: InputMaybe<Scalars['String']>;
|
|
||||||
lte?: InputMaybe<Scalars['String']>;
|
|
||||||
neq?: InputMaybe<Scalars['String']>;
|
|
||||||
notILike?: InputMaybe<Scalars['String']>;
|
|
||||||
notIn?: InputMaybe<Array<Scalars['String']>>;
|
|
||||||
notLike?: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum SubscriptionInterval {
|
export enum SubscriptionInterval {
|
||||||
Day = 'Day',
|
Day = 'Day',
|
||||||
Month = 'Month',
|
Month = 'Month',
|
||||||
@ -1709,7 +1691,6 @@ export type UpdateFieldInput = {
|
|||||||
isUnique?: InputMaybe<Scalars['Boolean']>;
|
isUnique?: InputMaybe<Scalars['Boolean']>;
|
||||||
label?: InputMaybe<Scalars['String']>;
|
label?: InputMaybe<Scalars['String']>;
|
||||||
name?: InputMaybe<Scalars['String']>;
|
name?: InputMaybe<Scalars['String']>;
|
||||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
|
||||||
options?: InputMaybe<Scalars['JSON']>;
|
options?: InputMaybe<Scalars['JSON']>;
|
||||||
settings?: InputMaybe<Scalars['JSON']>;
|
settings?: InputMaybe<Scalars['JSON']>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -70,7 +70,13 @@ export class GraphQLConfigService
|
|||||||
let workspace: Workspace | undefined;
|
let workspace: Workspace | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { user, workspace, apiKey, workspaceMemberId } = context.req;
|
const {
|
||||||
|
user,
|
||||||
|
workspace,
|
||||||
|
apiKey,
|
||||||
|
workspaceMemberId,
|
||||||
|
userWorkspaceId,
|
||||||
|
} = context.req;
|
||||||
|
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
return new GraphQLSchema({});
|
return new GraphQLSchema({});
|
||||||
@ -81,6 +87,7 @@ export class GraphQLConfigService
|
|||||||
workspace,
|
workspace,
|
||||||
apiKey,
|
apiKey,
|
||||||
workspaceMemberId,
|
workspaceMemberId,
|
||||||
|
userWorkspaceId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof UnauthorizedException) {
|
if (error instanceof UnauthorizedException) {
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { SettingsFeatures } from 'twenty-shared';
|
||||||
|
|
||||||
|
export const SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS = {
|
||||||
|
apiKey: SettingsFeatures.API_KEYS_AND_WEBHOOKS,
|
||||||
|
webhook: SettingsFeatures.API_KEYS_AND_WEBHOOKS,
|
||||||
|
} as const;
|
||||||
@ -21,6 +21,7 @@ import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-run
|
|||||||
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
||||||
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
|
||||||
const graphqlQueryResolvers = [
|
const graphqlQueryResolvers = [
|
||||||
GraphqlQueryCreateManyResolverService,
|
GraphqlQueryCreateManyResolverService,
|
||||||
@ -44,6 +45,7 @@ const graphqlQueryResolvers = [
|
|||||||
WorkspaceQueryHookModule,
|
WorkspaceQueryHookModule,
|
||||||
WorkspaceQueryRunnerModule,
|
WorkspaceQueryRunnerModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
|
PermissionsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ApiEventEmitterService,
|
ApiEventEmitterService,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import graphqlFields from 'graphql-fields';
|
import graphqlFields from 'graphql-fields';
|
||||||
import { capitalize } from 'twenty-shared';
|
import { capitalize, SettingsFeatures } from 'twenty-shared';
|
||||||
import { DataSource, ObjectLiteral } from 'typeorm';
|
import { DataSource, ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
WorkspaceResolverBuilderMethodNames,
|
WorkspaceResolverBuilderMethodNames,
|
||||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/system-objects-permissions-requirements.constant';
|
||||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
@ -22,7 +23,18 @@ import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-quer
|
|||||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||||
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
||||||
|
import {
|
||||||
|
AuthException,
|
||||||
|
AuthExceptionCode,
|
||||||
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
|
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 {
|
||||||
|
PermissionsException,
|
||||||
|
PermissionsExceptionCode,
|
||||||
|
PermissionsExceptionMessage,
|
||||||
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
@ -58,6 +70,8 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly featureFlagService: FeatureFlagService;
|
protected readonly featureFlagService: FeatureFlagService;
|
||||||
|
@Inject()
|
||||||
|
protected readonly permissionsService: PermissionsService;
|
||||||
|
|
||||||
public async execute(
|
public async execute(
|
||||||
args: Input,
|
args: Input,
|
||||||
@ -69,6 +83,18 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
|
|
||||||
await this.validate(args, options);
|
await this.validate(args, options);
|
||||||
|
|
||||||
|
const permissionsEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsEnabled,
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
permissionsEnabled === true &&
|
||||||
|
objectMetadataItemWithFieldMaps.isSystem === true
|
||||||
|
) {
|
||||||
|
await this.validateSystemObjectPermissions(options);
|
||||||
|
}
|
||||||
|
|
||||||
const hookedArgs =
|
const hookedArgs =
|
||||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
await this.workspaceQueryHookService.executePreQueryHooks(
|
||||||
authContext,
|
authContext,
|
||||||
@ -146,6 +172,45 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateSystemObjectPermissions(
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
) {
|
||||||
|
const { authContext, objectMetadataItemWithFieldMaps } = options;
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.keys(SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS).includes(
|
||||||
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (!authContext.apiKey) {
|
||||||
|
if (!authContext.userWorkspaceId) {
|
||||||
|
throw new AuthException(
|
||||||
|
'Missing userWorkspaceId in authContext',
|
||||||
|
AuthExceptionCode.USER_WORKSPACE_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionRequired: SettingsFeatures =
|
||||||
|
SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS[
|
||||||
|
objectMetadataItemWithFieldMaps.nameSingular
|
||||||
|
];
|
||||||
|
|
||||||
|
const userHasPermission =
|
||||||
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
_setting: permissionRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userHasPermission) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract resolve(
|
protected abstract resolve(
|
||||||
executionArgs: GraphqlQueryResolverExecutionArgs<Input>,
|
executionArgs: GraphqlQueryResolverExecutionArgs<Input>,
|
||||||
): Promise<Response>;
|
): Promise<Response>;
|
||||||
|
|||||||
@ -45,6 +45,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|||||||
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||||
@ -89,6 +90,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
|||||||
EmailVerificationModule,
|
EmailVerificationModule,
|
||||||
GuardRedirectModule,
|
GuardRedirectModule,
|
||||||
HealthModule,
|
HealthModule,
|
||||||
|
PermissionsModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
GoogleAuthController,
|
GoogleAuthController,
|
||||||
|
|||||||
@ -5,10 +5,12 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
|||||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||||
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 { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
|
||||||
import { AuthResolver } from './auth.resolver';
|
import { AuthResolver } from './auth.resolver';
|
||||||
|
|
||||||
@ -85,6 +87,14 @@ describe('AuthResolver', () => {
|
|||||||
provide: EmailVerificationTokenService,
|
provide: EmailVerificationTokenService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: PermissionsService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: FeatureFlagService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// provide: OAuthService,
|
// provide: OAuthService,
|
||||||
// useValue: {},
|
// useValue: {},
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { UseFilters, UseGuards } from '@nestjs/common';
|
|||||||
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { SOURCE_LOCALE } from 'twenty-shared';
|
import { SettingsFeatures, SOURCE_LOCALE } from 'twenty-shared';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ApiKeyTokenInput } from 'src/engine/core-modules/auth/dto/api-key-token.input';
|
import { ApiKeyTokenInput } from 'src/engine/core-modules/auth/dto/api-key-token.input';
|
||||||
@ -43,8 +43,10 @@ import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.
|
|||||||
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 { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
||||||
|
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';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
|
||||||
import { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
import { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
||||||
import { GetLoginTokenFromCredentialsInput } from './dto/get-login-token-from-credentials.input';
|
import { GetLoginTokenFromCredentialsInput } from './dto/get-login-token-from-credentials.input';
|
||||||
@ -58,7 +60,7 @@ import { WorkspaceInviteHashValidInput } from './dto/workspace-invite-hash.input
|
|||||||
import { AuthService } from './services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(AuthGraphqlApiExceptionFilter)
|
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
||||||
export class AuthResolver {
|
export class AuthResolver {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
@ -323,7 +325,10 @@ export class AuthResolver {
|
|||||||
return { tokens: tokens };
|
return { tokens: tokens };
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(
|
||||||
|
WorkspaceAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingsFeatures.API_KEYS_AND_WEBHOOKS),
|
||||||
|
)
|
||||||
@Mutation(() => ApiKeyToken)
|
@Mutation(() => ApiKeyToken)
|
||||||
async generateApiKeyToken(
|
async generateApiKeyToken(
|
||||||
@Args() args: ApiKeyTokenInput,
|
@Args() args: ApiKeyTokenInput,
|
||||||
|
|||||||
@ -6,14 +6,15 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
|||||||
|
|
||||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||||
|
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guard-redirect.module';
|
||||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||||
import { SSOResolver } from 'src/engine/core-modules/sso/sso.resolver';
|
import { SSOResolver } from 'src/engine/core-modules/sso/sso.resolver';
|
||||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guard-redirect.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
NestjsQueryTypeOrmModule.forFeature(
|
NestjsQueryTypeOrmModule.forFeature(
|
||||||
@ -23,6 +24,8 @@ import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guar
|
|||||||
BillingModule,
|
BillingModule,
|
||||||
DomainManagerModule,
|
DomainManagerModule,
|
||||||
GuardRedirectModule,
|
GuardRedirectModule,
|
||||||
|
PermissionsModule,
|
||||||
|
FeatureFlagModule,
|
||||||
],
|
],
|
||||||
exports: [SSOService],
|
exports: [SSOService],
|
||||||
providers: [SSOService, SSOResolver],
|
providers: [SSOService, SSOResolver],
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
/* @license Enterprise */
|
/* @license Enterprise */
|
||||||
|
|
||||||
import { UseGuards } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
|
import { SettingsFeatures } from 'twenty-shared';
|
||||||
|
|
||||||
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
|
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
|
||||||
import { DeleteSsoInput } from 'src/engine/core-modules/sso/dtos/delete-sso.input';
|
import { DeleteSsoInput } from 'src/engine/core-modules/sso/dtos/delete-sso.input';
|
||||||
@ -22,9 +23,13 @@ import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
|||||||
import { SSOException } from 'src/engine/core-modules/sso/sso.exception';
|
import { SSOException } from 'src/engine/core-modules/sso/sso.exception';
|
||||||
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 { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
|
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.SECURITY))
|
||||||
export class SSOResolver {
|
export class SSOResolver {
|
||||||
constructor(private readonly sSOService: SSOService) {}
|
constructor(private readonly sSOService: SSOService) {}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { BillingService } from 'src/engine/core-modules/billing/services/billing
|
|||||||
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 { EmailService } from 'src/engine/core-modules/email/email.service';
|
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
@ -14,8 +15,8 @@ import { UserService } from 'src/engine/core-modules/user/services/user.service'
|
|||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
|
||||||
|
|
||||||
import { WorkspaceService } from './workspace.service';
|
import { WorkspaceService } from './workspace.service';
|
||||||
|
|
||||||
@ -86,6 +87,10 @@ describe('WorkspaceService', () => {
|
|||||||
provide: ExceptionHandlerService,
|
provide: ExceptionHandlerService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: PermissionsService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -4,13 +4,20 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { isDefined, WorkspaceActivationStatus } from 'twenty-shared';
|
import {
|
||||||
|
isDefined,
|
||||||
|
SettingsFeatures,
|
||||||
|
WorkspaceActivationStatus,
|
||||||
|
} from 'twenty-shared';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||||
@ -22,10 +29,14 @@ import {
|
|||||||
WorkspaceExceptionCode,
|
WorkspaceExceptionCode,
|
||||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
|
import {
|
||||||
|
PermissionsException,
|
||||||
|
PermissionsExceptionCode,
|
||||||
|
PermissionsExceptionMessage,
|
||||||
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
||||||
import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags';
|
import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags';
|
||||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
|
||||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||||
@ -47,6 +58,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
private readonly domainManagerService: DomainManagerService,
|
private readonly domainManagerService: DomainManagerService,
|
||||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||||
|
private readonly permissionsService: PermissionsService,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository);
|
||||||
}
|
}
|
||||||
@ -114,7 +126,13 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWorkspaceById(payload: Partial<Workspace> & { id: string }) {
|
async updateWorkspaceById({
|
||||||
|
payload,
|
||||||
|
userWorkspaceId,
|
||||||
|
}: {
|
||||||
|
payload: Partial<Workspace> & { id: string };
|
||||||
|
userWorkspaceId?: string;
|
||||||
|
}) {
|
||||||
const workspace = await this.workspaceRepository.findOneBy({
|
const workspace = await this.workspaceRepository.findOneBy({
|
||||||
id: payload.id,
|
id: payload.id,
|
||||||
});
|
});
|
||||||
@ -141,6 +159,18 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
customDomainRegistered = true;
|
customDomainRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const permissionsEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsEnabled,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (permissionsEnabled) {
|
||||||
|
await this.validateSecurityPermissions({
|
||||||
|
payload,
|
||||||
|
userWorkspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.workspaceRepository.save({
|
return await this.workspaceRepository.save({
|
||||||
...workspace,
|
...workspace,
|
||||||
@ -258,4 +288,36 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
|
|
||||||
return !existingWorkspace;
|
return !existingWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateSecurityPermissions({
|
||||||
|
payload,
|
||||||
|
userWorkspaceId,
|
||||||
|
}: {
|
||||||
|
payload: Partial<Workspace>;
|
||||||
|
userWorkspaceId?: string;
|
||||||
|
}) {
|
||||||
|
if (
|
||||||
|
isDefined(payload.isGoogleAuthEnabled) ||
|
||||||
|
isDefined(payload.isMicrosoftAuthEnabled) ||
|
||||||
|
isDefined(payload.isPasswordAuthEnabled) ||
|
||||||
|
isDefined(payload.isPublicInviteLinkEnabled)
|
||||||
|
) {
|
||||||
|
if (!userWorkspaceId) {
|
||||||
|
throw new Error('Missing userWorkspaceId in authContext');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userHasPermission =
|
||||||
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
|
userWorkspaceId,
|
||||||
|
_setting: SettingsFeatures.SECURITY,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userHasPermission) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
|||||||
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
|
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
|
||||||
import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.resolver';
|
import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.resolver';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ import { WorkspaceService } from './services/workspace.service';
|
|||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
OnboardingModule,
|
OnboardingModule,
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
|
PermissionsModule,
|
||||||
],
|
],
|
||||||
services: [WorkspaceService],
|
services: [WorkspaceService],
|
||||||
resolvers: workspaceAutoResolverOpts,
|
resolvers: workspaceAutoResolverOpts,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.
|
|||||||
|
|
||||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||||
|
import { CustomDomainDetails } from 'src/engine/core-modules/domain-manager/dtos/custom-domain-details';
|
||||||
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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
@ -32,26 +33,30 @@ import {
|
|||||||
PublicWorkspaceDataOutput,
|
PublicWorkspaceDataOutput,
|
||||||
} from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
|
} from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
|
||||||
import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/update-workspace-input';
|
import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/update-workspace-input';
|
||||||
|
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
|
||||||
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
|
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
|
||||||
import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util';
|
import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
|
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 { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
|
||||||
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';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter';
|
import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||||
import { CustomDomainDetails } from 'src/engine/core-modules/domain-manager/dtos/custom-domain-details';
|
|
||||||
import { workspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
|
|
||||||
|
|
||||||
import { Workspace } from './workspace.entity';
|
import { Workspace } from './workspace.entity';
|
||||||
|
|
||||||
import { WorkspaceService } from './services/workspace.service';
|
import { WorkspaceService } from './services/workspace.service';
|
||||||
|
|
||||||
@Resolver(() => Workspace)
|
@Resolver(() => Workspace)
|
||||||
@UseFilters(GraphqlValidationExceptionFilter)
|
@UseFilters(
|
||||||
|
GraphqlValidationExceptionFilter,
|
||||||
|
PermissionsGraphqlApiExceptionFilter,
|
||||||
|
)
|
||||||
export class WorkspaceResolver {
|
export class WorkspaceResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceService: WorkspaceService,
|
private readonly workspaceService: WorkspaceService,
|
||||||
@ -98,11 +103,15 @@ export class WorkspaceResolver {
|
|||||||
async updateWorkspace(
|
async updateWorkspace(
|
||||||
@Args('data') data: UpdateWorkspaceInput,
|
@Args('data') data: UpdateWorkspaceInput,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceService.updateWorkspaceById({
|
return await this.workspaceService.updateWorkspaceById({
|
||||||
...data,
|
payload: {
|
||||||
id: workspace.id,
|
...data,
|
||||||
|
id: workspace.id,
|
||||||
|
},
|
||||||
|
userWorkspaceId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceGraphqlApiExceptionHandler(error);
|
workspaceGraphqlApiExceptionHandler(error);
|
||||||
|
|||||||
@ -8,13 +8,25 @@ export class PermissionsException extends CustomException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum PermissionsExceptionCode {
|
export enum PermissionsExceptionCode {
|
||||||
|
PERMISSION_DENIED = 'PERMISSION_DENIED',
|
||||||
ADMIN_ROLE_NOT_FOUND = 'ADMIN_ROLE_NOT_FOUND',
|
ADMIN_ROLE_NOT_FOUND = 'ADMIN_ROLE_NOT_FOUND',
|
||||||
USER_WORKSPACE_NOT_FOUND = 'USER_WORKSPACE_NOT_FOUND',
|
USER_WORKSPACE_NOT_FOUND = 'USER_WORKSPACE_NOT_FOUND',
|
||||||
WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH = 'WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH',
|
WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH = 'WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH',
|
||||||
TOO_MANY_ADMIN_CANDIDATES = 'TOO_MANY_ADMIN_CANDIDATES',
|
TOO_MANY_ADMIN_CANDIDATES = 'TOO_MANY_ADMIN_CANDIDATES',
|
||||||
USER_WORKSPACE_ALREADY_HAS_ROLE = 'USER_WORKSPACE_ALREADY_HAS_ROLE',
|
USER_WORKSPACE_ALREADY_HAS_ROLE = 'USER_WORKSPACE_ALREADY_HAS_ROLE',
|
||||||
PERMISSION_DENIED = 'PERMISSION_DENIED',
|
|
||||||
WORKSPACE_MEMBER_NOT_FOUND = 'WORKSPACE_MEMBER_NOT_FOUND',
|
WORKSPACE_MEMBER_NOT_FOUND = 'WORKSPACE_MEMBER_NOT_FOUND',
|
||||||
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
|
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
|
||||||
CANNOT_UNASSIGN_LAST_ADMIN = 'CANNOT_UNASSIGN_LAST_ADMIN',
|
CANNOT_UNASSIGN_LAST_ADMIN = 'CANNOT_UNASSIGN_LAST_ADMIN',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PermissionsExceptionMessage {
|
||||||
|
PERMISSION_DENIED = 'User does not have permission',
|
||||||
|
ADMIN_ROLE_NOT_FOUND = 'Admin role not found',
|
||||||
|
USER_WORKSPACE_NOT_FOUND = 'User workspace not found',
|
||||||
|
WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH = 'Workspace id role user workspace mismatch',
|
||||||
|
TOO_MANY_ADMIN_CANDIDATES = 'Too many admin candidates',
|
||||||
|
USER_WORKSPACE_ALREADY_HAS_ROLE = 'User workspace already has role',
|
||||||
|
WORKSPACE_MEMBER_NOT_FOUND = 'Workspace member not found',
|
||||||
|
ROLE_NOT_FOUND = 'Role not found',
|
||||||
|
CANNOT_UNASSIGN_LAST_ADMIN = 'Cannot unassign last admin',
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
export enum SettingsFeatures {
|
export enum SettingsFeatures {
|
||||||
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
|
||||||
WORKSPACE_SETTINGS = 'WORKSPACE_SETTINGS',
|
WORKSPACE = 'WORKSPACE',
|
||||||
WORKSPACE_USERS = 'WORKSPACE_USERS',
|
WORKSPACE_USERS = 'WORKSPACE_USERS',
|
||||||
ROLES = 'ROLES',
|
ROLES = 'ROLES',
|
||||||
DATA_MODEL = 'DATA_MODEL',
|
DATA_MODEL = 'DATA_MODEL',
|
||||||
ADMIN_PANEL = 'ADMIN_PANEL',
|
ADMIN_PANEL = 'ADMIN_PANEL',
|
||||||
SECURITY_SETTINGS = 'SECURITY_SETTINGS',
|
SECURITY = 'SECURITY',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user