[permissions] Place lab + billing behind settings/workspace permission gates (#10354)
This commit is contained in:
@ -421,7 +421,15 @@ export const SettingsRoutes = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Route path={SettingsPath.Lab} element={<SettingsLab />} />
|
<Route
|
||||||
|
element={
|
||||||
|
<SettingsProtectedRouteWrapper
|
||||||
|
settingsPermission={SettingsFeatures.WORKSPACE}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path={SettingsPath.Lab} element={<SettingsLab />} />
|
||||||
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -117,7 +117,8 @@ export const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
|||||||
label: t`Billing`,
|
label: t`Billing`,
|
||||||
path: SettingsPath.Billing,
|
path: SettingsPath.Billing,
|
||||||
Icon: IconCurrencyDollar,
|
Icon: IconCurrencyDollar,
|
||||||
isHidden: !isBillingEnabled,
|
isHidden:
|
||||||
|
!isBillingEnabled || !permissionMap[SettingsFeatures.WORKSPACE],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Roles`,
|
label: t`Roles`,
|
||||||
@ -181,7 +182,9 @@ export const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
|||||||
label: t`Lab`,
|
label: t`Lab`,
|
||||||
path: SettingsPath.Lab,
|
path: SettingsPath.Lab,
|
||||||
Icon: IconFlask,
|
Icon: IconFlask,
|
||||||
isHidden: !labPublicFeatureFlags.length,
|
isHidden:
|
||||||
|
!labPublicFeatureFlags.length ||
|
||||||
|
!permissionMap[SettingsFeatures.WORKSPACE],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Releases`,
|
label: t`Releases`,
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
|
|||||||
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -41,6 +42,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|||||||
StripeModule,
|
StripeModule,
|
||||||
DomainManagerModule,
|
DomainManagerModule,
|
||||||
MessageQueueModule,
|
MessageQueueModule,
|
||||||
|
PermissionsModule,
|
||||||
TypeOrmModule.forFeature(
|
TypeOrmModule.forFeature(
|
||||||
[
|
[
|
||||||
BillingSubscription,
|
BillingSubscription,
|
||||||
|
|||||||
@ -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 { GraphQLError } from 'graphql';
|
import { GraphQLError } from 'graphql';
|
||||||
|
import { SettingsFeatures } from 'twenty-shared';
|
||||||
|
|
||||||
import { BillingCheckoutSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-checkout-session.input';
|
import { BillingCheckoutSessionInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-checkout-session.input';
|
||||||
import { BillingProductInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-product.input';
|
import { BillingProductInput } from 'src/engine/core-modules/billing/dtos/inputs/billing-product.input';
|
||||||
@ -26,10 +27,13 @@ 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 { 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';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
export class BillingResolver {
|
export class BillingResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly billingSubscriptionService: BillingSubscriptionService,
|
private readonly billingSubscriptionService: BillingSubscriptionService,
|
||||||
@ -55,7 +59,10 @@ export class BillingResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => BillingSessionOutput)
|
@Query(() => BillingSessionOutput)
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(
|
||||||
|
WorkspaceAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingsFeatures.WORKSPACE),
|
||||||
|
)
|
||||||
async billingPortalSession(
|
async billingPortalSession(
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args() { returnUrlPath }: BillingSessionInput,
|
@Args() { returnUrlPath }: BillingSessionInput,
|
||||||
@ -134,7 +141,10 @@ export class BillingResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => BillingUpdateOutput)
|
@Mutation(() => BillingUpdateOutput)
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(
|
||||||
|
WorkspaceAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingsFeatures.WORKSPACE),
|
||||||
|
)
|
||||||
async updateBillingSubscription(@AuthWorkspace() workspace: Workspace) {
|
async updateBillingSubscription(@AuthWorkspace() workspace: Workspace) {
|
||||||
await this.billingSubscriptionService.applyBillingSubscription(workspace);
|
await this.billingSubscriptionService.applyBillingSubscription(workspace);
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,20 @@ import { Module } from '@nestjs/common';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
|
||||||
import { LabResolver } from './lab.resolver';
|
import { LabResolver } from './lab.resolver';
|
||||||
|
|
||||||
import { LabService } from './services/lab.service';
|
import { LabService } from './services/lab.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([FeatureFlag, Workspace], 'core')],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([FeatureFlag, Workspace], 'core'),
|
||||||
|
FeatureFlagModule,
|
||||||
|
PermissionsModule,
|
||||||
|
],
|
||||||
providers: [LabService, LabResolver],
|
providers: [LabService, LabResolver],
|
||||||
exports: [LabService],
|
exports: [LabService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,16 +1,21 @@
|
|||||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { SettingsFeatures } from 'twenty-shared';
|
||||||
|
|
||||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
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 { UpdateLabPublicFeatureFlagInput } from 'src/engine/core-modules/lab/dtos/update-lab-public-feature-flag.input';
|
import { UpdateLabPublicFeatureFlagInput } from 'src/engine/core-modules/lab/dtos/update-lab-public-feature-flag.input';
|
||||||
import { LabService } from 'src/engine/core-modules/lab/services/lab.service';
|
import { LabService } from 'src/engine/core-modules/lab/services/lab.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 { 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(AuthGraphqlApiExceptionFilter)
|
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
||||||
|
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.WORKSPACE))
|
||||||
export class LabResolver {
|
export class LabResolver {
|
||||||
constructor(private labService: LabService) {}
|
constructor(private labService: LabService) {}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,586 @@
|
|||||||
|
import { gql } from 'graphql-tag';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||||
|
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
||||||
|
|
||||||
|
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
|
||||||
|
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
|
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
|
||||||
|
const client = request(`http://localhost:${APP_PORT}`);
|
||||||
|
|
||||||
|
describe('Security permissions', () => {
|
||||||
|
let originalWorkspaceState;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Store original workspace state
|
||||||
|
const query = gql`
|
||||||
|
query getWorkspace {
|
||||||
|
currentWorkspace {
|
||||||
|
displayName
|
||||||
|
isGoogleAuthEnabled
|
||||||
|
isMicrosoftAuthEnabled
|
||||||
|
isPasswordAuthEnabled
|
||||||
|
logo
|
||||||
|
isPublicInviteLinkEnabled
|
||||||
|
subdomain
|
||||||
|
isCustomDomainEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest({ query });
|
||||||
|
|
||||||
|
originalWorkspaceState = response.body.data.currentWorkspace;
|
||||||
|
|
||||||
|
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IsPermissionsEnabled',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const disablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IsPermissionsEnabled',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||||
|
|
||||||
|
// Restore workspace state
|
||||||
|
const restoreQuery = gql`
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: {
|
||||||
|
displayName: "${originalWorkspaceState.displayName}",
|
||||||
|
subdomain: "${originalWorkspaceState.subdomain}",
|
||||||
|
logo: "${originalWorkspaceState.logo}",
|
||||||
|
isGoogleAuthEnabled: ${originalWorkspaceState.isGoogleAuthEnabled},
|
||||||
|
isMicrosoftAuthEnabled: ${originalWorkspaceState.isMicrosoftAuthEnabled},
|
||||||
|
isPasswordAuthEnabled: ${originalWorkspaceState.isPasswordAuthEnabled}
|
||||||
|
isPublicInviteLinkEnabled: ${originalWorkspaceState.isPublicInviteLinkEnabled}
|
||||||
|
}) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest({ query: restoreQuery });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('security permissions', () => {
|
||||||
|
describe('microsoft auth', () => {
|
||||||
|
it('should update workspace when user has permission (admin role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isMicrosoftAuthEnabled: false }) {
|
||||||
|
id
|
||||||
|
isMicrosoftAuthEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.isMicrosoftAuthEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isMicrosoftAuthEnabled: true }) {
|
||||||
|
id
|
||||||
|
isMicrosoftAuthEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('google auth', () => {
|
||||||
|
it('should update workspace when user has permission (admin role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isGoogleAuthEnabled: false }) {
|
||||||
|
id
|
||||||
|
isGoogleAuthEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.isGoogleAuthEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isGoogleAuthEnabled: true }) {
|
||||||
|
id
|
||||||
|
isGoogleAuthEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('password auth', () => {
|
||||||
|
it('should update workspace when user has permission (admin role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isPasswordAuthEnabled: false }) {
|
||||||
|
id
|
||||||
|
isPasswordAuthEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.isPasswordAuthEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isPasswordAuthEnabled: true }) {
|
||||||
|
id
|
||||||
|
isPasswordAuthEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('public invite link', () => {
|
||||||
|
it('should update isPublicInviteLinkEnabled when user has permission (admin role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isPublicInviteLinkEnabled: false }) {
|
||||||
|
id
|
||||||
|
isPublicInviteLinkEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.isPublicInviteLinkEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { isPublicInviteLinkEnabled: true }) {
|
||||||
|
id
|
||||||
|
isPublicInviteLinkEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('workspace permissions', () => {
|
||||||
|
describe('delete workspace', () => {
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation DeleteCurrentWorkspace {
|
||||||
|
deleteCurrentWorkspace {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('display name update', () => {
|
||||||
|
it('should update workspace display name when user has workspace settings permission', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { displayName: "New Workspace Name" }) {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.displayName).toBe('New Workspace Name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { displayName: "Another New Workspace Name" }) {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subdomain update', () => {
|
||||||
|
it('should update workspace subdomain when user has workspace settings permission', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { subdomain: "new-subdomain" }) {
|
||||||
|
id
|
||||||
|
subdomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.subdomain).toBe('new-subdomain');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { subdomain: "another-new-subdomain" }) {
|
||||||
|
id
|
||||||
|
subdomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('custom domain update', () => {
|
||||||
|
it('should update workspace custom domain when user has workspace settings permission', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { customDomain: null }) {
|
||||||
|
id
|
||||||
|
customDomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.customDomain).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { customDomain: "another-new-custom-domain" }) {
|
||||||
|
id
|
||||||
|
customDomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logo update', () => {
|
||||||
|
it('should update workspace logo when user has workspace settings permission', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { logo: "new-logo" }) {
|
||||||
|
id
|
||||||
|
logo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeUndefined();
|
||||||
|
})
|
||||||
|
.expect((res) => {
|
||||||
|
const data = res.body.data.updateWorkspace;
|
||||||
|
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
expect(data.logo).toContain('new-logo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation updateWorkspace {
|
||||||
|
updateWorkspace(data: { logo: "another-new-logo" }) {
|
||||||
|
id
|
||||||
|
logo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -71,247 +71,6 @@ describe('WorkspaceResolver', () => {
|
|||||||
await makeGraphqlAPIRequest({ query: restoreQuery });
|
await makeGraphqlAPIRequest({ query: restoreQuery });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('security permissions', () => {
|
|
||||||
describe('microsoft auth', () => {
|
|
||||||
it('should update workspace when user has permission (admin role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isMicrosoftAuthEnabled: false }) {
|
|
||||||
id
|
|
||||||
isMicrosoftAuthEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeDefined();
|
|
||||||
expect(res.body.errors).toBeUndefined();
|
|
||||||
})
|
|
||||||
.expect((res) => {
|
|
||||||
const data = res.body.data.updateWorkspace;
|
|
||||||
|
|
||||||
expect(data).toBeDefined();
|
|
||||||
expect(data.isMicrosoftAuthEnabled).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isMicrosoftAuthEnabled: true }) {
|
|
||||||
id
|
|
||||||
isMicrosoftAuthEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
await client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeNull();
|
|
||||||
expect(res.body.errors).toBeDefined();
|
|
||||||
expect(res.body.errors[0].message).toBe(
|
|
||||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
|
||||||
);
|
|
||||||
expect(res.body.errors[0].extensions.code).toBe(
|
|
||||||
ErrorCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('google auth', () => {
|
|
||||||
it('should update workspace when user has permission (admin role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isGoogleAuthEnabled: false }) {
|
|
||||||
id
|
|
||||||
isGoogleAuthEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeDefined();
|
|
||||||
expect(res.body.errors).toBeUndefined();
|
|
||||||
})
|
|
||||||
.expect((res) => {
|
|
||||||
const data = res.body.data.updateWorkspace;
|
|
||||||
|
|
||||||
expect(data).toBeDefined();
|
|
||||||
expect(data.isGoogleAuthEnabled).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isGoogleAuthEnabled: true }) {
|
|
||||||
id
|
|
||||||
isGoogleAuthEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
await client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeNull();
|
|
||||||
expect(res.body.errors).toBeDefined();
|
|
||||||
expect(res.body.errors[0].message).toBe(
|
|
||||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
|
||||||
);
|
|
||||||
expect(res.body.errors[0].extensions.code).toBe(
|
|
||||||
ErrorCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('password auth', () => {
|
|
||||||
it('should update workspace when user has permission (admin role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isPasswordAuthEnabled: false }) {
|
|
||||||
id
|
|
||||||
isPasswordAuthEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeDefined();
|
|
||||||
expect(res.body.errors).toBeUndefined();
|
|
||||||
})
|
|
||||||
.expect((res) => {
|
|
||||||
const data = res.body.data.updateWorkspace;
|
|
||||||
|
|
||||||
expect(data).toBeDefined();
|
|
||||||
expect(data.isPasswordAuthEnabled).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isPasswordAuthEnabled: true }) {
|
|
||||||
id
|
|
||||||
isPasswordAuthEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
await client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeNull();
|
|
||||||
expect(res.body.errors).toBeDefined();
|
|
||||||
expect(res.body.errors[0].message).toBe(
|
|
||||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
|
||||||
);
|
|
||||||
expect(res.body.errors[0].extensions.code).toBe(
|
|
||||||
ErrorCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('public invite link', () => {
|
|
||||||
it('should update isPublicInviteLinkEnabled when user has permission (admin role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isPublicInviteLinkEnabled: false }) {
|
|
||||||
id
|
|
||||||
isPublicInviteLinkEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeDefined();
|
|
||||||
expect(res.body.errors).toBeUndefined();
|
|
||||||
})
|
|
||||||
.expect((res) => {
|
|
||||||
const data = res.body.data.updateWorkspace;
|
|
||||||
|
|
||||||
expect(data).toBeDefined();
|
|
||||||
expect(data.isPublicInviteLinkEnabled).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
|
||||||
const queryData = {
|
|
||||||
query: `
|
|
||||||
mutation updateWorkspace {
|
|
||||||
updateWorkspace(data: { isPublicInviteLinkEnabled: true }) {
|
|
||||||
id
|
|
||||||
isPublicInviteLinkEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
await client
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
|
||||||
.send(queryData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.data).toBeNull();
|
|
||||||
expect(res.body.errors).toBeDefined();
|
|
||||||
expect(res.body.errors[0].message).toBe(
|
|
||||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
|
||||||
);
|
|
||||||
expect(res.body.errors[0].extensions.code).toBe(
|
|
||||||
ErrorCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('workspace permissions', () => {
|
describe('workspace permissions', () => {
|
||||||
describe('delete workspace', () => {
|
describe('delete workspace', () => {
|
||||||
it('should throw a permission error when user does not have permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
@ -583,4 +342,143 @@ describe('WorkspaceResolver', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('billing', () => {
|
||||||
|
describe('updateBillingSubscription', () => {
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation UpdateBillingSubscription {
|
||||||
|
updateBillingSubscription {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('billingPortalSession', () => {
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
query BillingPortalSession($returnUrlPath: String!) {
|
||||||
|
billingPortalSession(returnUrlPath: $returnUrlPath) {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
returnUrlPath: '/settings/billing',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('lab', () => {
|
||||||
|
describe('updateLabPublicFeatureFlag', () => {
|
||||||
|
it('should update feature flag when user has workspace settings permission', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation UpdateLabPublicFeatureFlag(
|
||||||
|
$input: UpdateLabPublicFeatureFlagInput!
|
||||||
|
) {
|
||||||
|
updateLabPublicFeatureFlag(input: $input) {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
publicFeatureFlag: 'TestFeature',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeDefined();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe('Invalid feature flag key'); // this error shows that update has been attempted after the permission check
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (member role)', async () => {
|
||||||
|
const queryData = {
|
||||||
|
query: `
|
||||||
|
mutation UpdateLabPublicFeatureFlag(
|
||||||
|
$input: UpdateLabPublicFeatureFlagInput!
|
||||||
|
) {
|
||||||
|
updateLabPublicFeatureFlag(input: $input) {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
publicFeatureFlag: 'TestFeature',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(queryData)
|
||||||
|
.expect(200)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.data).toBeNull();
|
||||||
|
expect(res.body.errors).toBeDefined();
|
||||||
|
expect(res.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(res.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user