feat(server): make permission a standalone module (#7880)

This commit is contained in:
forehalo
2024-08-15 10:01:52 +00:00
parent ba8958f39b
commit 624f3514fc
30 changed files with 123 additions and 116 deletions

View File

@@ -12,6 +12,7 @@ import { AuthModule } from './core/auth';
import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config'; import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config';
import { DocModule } from './core/doc'; import { DocModule } from './core/doc';
import { FeatureModule } from './core/features'; import { FeatureModule } from './core/features';
import { PermissionModule } from './core/permission';
import { QuotaModule } from './core/quota'; import { QuotaModule } from './core/quota';
import { SelfhostModule } from './core/selfhost'; import { SelfhostModule } from './core/selfhost';
import { StorageModule } from './core/storage'; import { StorageModule } from './core/storage';
@@ -41,7 +42,6 @@ import { ENABLED_PLUGINS } from './plugins/registry';
export const FunctionalityModules = [ export const FunctionalityModules = [
ConfigModule.forRoot(), ConfigModule.forRoot(),
ScheduleModule.forRoot(),
EventModule, EventModule,
CacheModule, CacheModule,
MutexModule, MutexModule,
@@ -147,12 +147,12 @@ export function buildAppModule() {
const factor = new AppModuleBuilder(AFFiNE); const factor = new AppModuleBuilder(AFFiNE);
factor factor
// common fundamental modules // basic
.use(...FunctionalityModules) .use(...FunctionalityModules)
.useIf(config => config.flavor.sync, WebSocketModule) .useIf(config => config.flavor.sync, WebSocketModule)
// auth // auth
.use(UserModule, AuthModule) .use(UserModule, AuthModule, PermissionModule)
// business modules // business modules
.use(DocModule) .use(DocModule)
@@ -163,9 +163,10 @@ export function buildAppModule() {
// graphql server only // graphql server only
.useIf( .useIf(
config => config.flavor.graphql, config => config.flavor.graphql,
ServerConfigModule, ScheduleModule.forRoot(),
GqlModule, GqlModule,
StorageModule, StorageModule,
ServerConfigModule,
WorkspaceModule, WorkspaceModule,
FeatureModule, FeatureModule,
QuotaModule QuotaModule

View File

@@ -11,10 +11,9 @@ import {
DocNotFound, DocNotFound,
metrics, metrics,
OnEvent, OnEvent,
WorkspaceNotFound,
} from '../../fundamentals'; } from '../../fundamentals';
import { PermissionService } from '../permission';
import { QuotaService } from '../quota'; import { QuotaService } from '../quota';
import { Permission } from '../workspaces/types';
import { isEmptyBuffer } from './manager'; import { isEmptyBuffer } from './manager';
@Injectable() @Injectable()
@@ -23,7 +22,8 @@ export class DocHistoryManager {
constructor( constructor(
private readonly config: Config, private readonly config: Config,
private readonly db: PrismaClient, private readonly db: PrismaClient,
private readonly quota: QuotaService private readonly quota: QuotaService,
private readonly permission: PermissionService
) {} ) {}
@OnEvent('workspace.deleted') @OnEvent('workspace.deleted')
@@ -235,21 +235,8 @@ export class DocHistoryManager {
} }
async getExpiredDateFromNow(workspaceId: string) { async getExpiredDateFromNow(workspaceId: string) {
const permission = await this.db.workspaceUserPermission.findFirst({ const owner = await this.permission.getWorkspaceOwner(workspaceId);
select: { const quota = await this.quota.getUserQuota(owner.id);
userId: true,
},
where: {
workspaceId,
type: Permission.Owner,
},
});
if (!permission) {
throw new WorkspaceNotFound({ workspaceId });
}
const quota = await this.quota.getUserQuota(permission.userId);
return quota.feature.historyPeriodFromNow; return quota.feature.historyPeriodFromNow;
} }

View File

@@ -2,12 +2,13 @@ import './config';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PermissionModule } from '../permission';
import { QuotaModule } from '../quota'; import { QuotaModule } from '../quota';
import { DocHistoryManager } from './history'; import { DocHistoryManager } from './history';
import { DocManager } from './manager'; import { DocManager } from './manager';
@Module({ @Module({
imports: [QuotaModule], imports: [QuotaModule, PermissionModule],
providers: [DocManager, DocHistoryManager], providers: [DocManager, DocHistoryManager],
exports: [DocManager, DocHistoryManager], exports: [DocManager, DocHistoryManager],
}) })

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { PermissionService } from './service';
@Module({
providers: [PermissionService],
exports: [PermissionService],
})
export class PermissionModule {}
export { PermissionService } from './service';
export { Permission, PublicPageMode } from './types';

View File

@@ -2,13 +2,12 @@ import { Injectable } from '@nestjs/common';
import type { Prisma } from '@prisma/client'; import type { Prisma } from '@prisma/client';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { DocAccessDenied, WorkspaceAccessDenied } from '../../fundamentals'; import {
import { Permission } from './types'; DocAccessDenied,
WorkspaceAccessDenied,
export enum PublicPageMode { WorkspaceOwnerNotFound,
Page, } from '../../fundamentals';
Edgeless, import { Permission, PublicPageMode } from './types';
}
@Injectable() @Injectable()
export class PermissionService { export class PermissionService {
@@ -59,7 +58,7 @@ export class PermissionService {
} }
async getWorkspaceOwner(workspaceId: string) { async getWorkspaceOwner(workspaceId: string) {
return this.prisma.workspaceUserPermission.findFirstOrThrow({ const owner = await this.prisma.workspaceUserPermission.findFirst({
where: { where: {
workspaceId, workspaceId,
type: Permission.Owner, type: Permission.Owner,
@@ -68,6 +67,12 @@ export class PermissionService {
user: true, user: true,
}, },
}); });
if (!owner) {
throw new WorkspaceOwnerNotFound({ workspaceId });
}
return owner.user;
} }
async tryGetWorkspaceOwner(workspaceId: string) { async tryGetWorkspaceOwner(workspaceId: string) {
@@ -195,6 +200,17 @@ export class PermissionService {
return false; return false;
} }
async allowUrlPreview(ws: string) {
const count = await this.prisma.workspace.count({
where: {
id: ws,
public: true,
},
});
return count > 0;
}
async grant( async grant(
ws: string, ws: string,
user: string, user: string,

View File

@@ -0,0 +1,11 @@
export enum Permission {
Read = 0,
Write = 1,
Admin = 10,
Owner = 99,
}
export enum PublicPageMode {
Page,
Edgeless,
}

View File

@@ -1,8 +1,8 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { FeatureModule } from '../features'; import { FeatureModule } from '../features';
import { PermissionModule } from '../permission';
import { StorageModule } from '../storage'; import { StorageModule } from '../storage';
import { PermissionService } from '../workspaces/permission';
import { QuotaManagementResolver } from './resolver'; import { QuotaManagementResolver } from './resolver';
import { QuotaService } from './service'; import { QuotaService } from './service';
import { QuotaManagementService } from './storage'; import { QuotaManagementService } from './storage';
@@ -14,13 +14,8 @@ import { QuotaManagementService } from './storage';
* - quota statistics * - quota statistics
*/ */
@Module({ @Module({
imports: [FeatureModule, StorageModule], imports: [FeatureModule, StorageModule, PermissionModule],
providers: [ providers: [QuotaService, QuotaManagementResolver, QuotaManagementService],
PermissionService,
QuotaService,
QuotaManagementResolver,
QuotaManagementService,
],
exports: [QuotaService, QuotaManagementService], exports: [QuotaService, QuotaManagementService],
}) })
export class QuotaModule {} export class QuotaModule {}

View File

@@ -1,9 +1,8 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { WorkspaceOwnerNotFound } from '../../fundamentals';
import { FeatureService, FeatureType } from '../features'; import { FeatureService, FeatureType } from '../features';
import { PermissionService } from '../permission';
import { WorkspaceBlobStorage } from '../storage'; import { WorkspaceBlobStorage } from '../storage';
import { PermissionService } from '../workspaces/permission';
import { OneGB } from './constant'; import { OneGB } from './constant';
import { QuotaService } from './service'; import { QuotaService } from './service';
import { formatSize, QuotaQueryType } from './types'; import { formatSize, QuotaQueryType } from './types';
@@ -113,9 +112,7 @@ export class QuotaManagementService {
// get workspace's owner quota and total size of used // get workspace's owner quota and total size of used
// quota was apply to owner's account // quota was apply to owner's account
async getWorkspaceUsage(workspaceId: string): Promise<QuotaBusinessType> { async getWorkspaceUsage(workspaceId: string): Promise<QuotaBusinessType> {
const { user: owner } = const owner = await this.permissions.getWorkspaceOwner(workspaceId);
await this.permissions.getWorkspaceOwner(workspaceId);
if (!owner) throw new WorkspaceOwnerNotFound({ workspaceId });
const { const {
feature: { feature: {
name, name,

View File

@@ -23,9 +23,8 @@ import {
} from '../../../fundamentals'; } from '../../../fundamentals';
import { Auth, CurrentUser } from '../../auth'; import { Auth, CurrentUser } from '../../auth';
import { DocManager } from '../../doc'; import { DocManager } from '../../doc';
import { Permission, PermissionService } from '../../permission';
import { DocID } from '../../utils/doc'; import { DocID } from '../../utils/doc';
import { PermissionService } from '../../workspaces/permission';
import { Permission } from '../../workspaces/types';
const SubscribeMessage = (event: string) => const SubscribeMessage = (event: string) =>
applyDecorators( applyDecorators(

View File

@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { DocModule } from '../../doc'; import { DocModule } from '../../doc';
import { PermissionService } from '../../workspaces/permission'; import { PermissionModule } from '../../permission';
import { EventsGateway } from './events.gateway'; import { EventsGateway } from './events.gateway';
@Module({ @Module({
imports: [DocModule], imports: [DocModule, PermissionModule],
providers: [EventsGateway, PermissionService], providers: [EventsGateway],
}) })
export class EventsModule {} export class EventsModule {}

View File

@@ -13,10 +13,9 @@ import {
} from '../../fundamentals'; } from '../../fundamentals';
import { CurrentUser, Public } from '../auth'; import { CurrentUser, Public } from '../auth';
import { DocHistoryManager, DocManager } from '../doc'; import { DocHistoryManager, DocManager } from '../doc';
import { Permission, PermissionService, PublicPageMode } from '../permission';
import { WorkspaceBlobStorage } from '../storage'; import { WorkspaceBlobStorage } from '../storage';
import { DocID } from '../utils/doc'; import { DocID } from '../utils/doc';
import { PermissionService, PublicPageMode } from './permission';
import { Permission } from './types';
@Controller('/api/workspaces') @Controller('/api/workspaces')
export class WorkspacesController { export class WorkspacesController {

View File

@@ -2,12 +2,12 @@ import { Module } from '@nestjs/common';
import { DocModule } from '../doc'; import { DocModule } from '../doc';
import { FeatureModule } from '../features'; import { FeatureModule } from '../features';
import { PermissionModule } from '../permission';
import { QuotaModule } from '../quota'; import { QuotaModule } from '../quota';
import { StorageModule } from '../storage'; import { StorageModule } from '../storage';
import { UserModule } from '../user'; import { UserModule } from '../user';
import { WorkspacesController } from './controller'; import { WorkspacesController } from './controller';
import { WorkspaceManagementResolver } from './management'; import { WorkspaceManagementResolver } from './management';
import { PermissionService } from './permission';
import { import {
DocHistoryResolver, DocHistoryResolver,
PagePermissionResolver, PagePermissionResolver,
@@ -16,17 +16,22 @@ import {
} from './resolvers'; } from './resolvers';
@Module({ @Module({
imports: [DocModule, FeatureModule, QuotaModule, StorageModule, UserModule], imports: [
DocModule,
FeatureModule,
QuotaModule,
StorageModule,
UserModule,
PermissionModule,
],
controllers: [WorkspacesController], controllers: [WorkspacesController],
providers: [ providers: [
WorkspaceResolver, WorkspaceResolver,
WorkspaceManagementResolver, WorkspaceManagementResolver,
PermissionService,
PagePermissionResolver, PagePermissionResolver,
DocHistoryResolver, DocHistoryResolver,
WorkspaceBlobResolver, WorkspaceBlobResolver,
], ],
exports: [PermissionService],
}) })
export class WorkspaceModule {} export class WorkspaceModule {}

View File

@@ -12,7 +12,7 @@ import { ActionForbidden } from '../../fundamentals';
import { CurrentUser } from '../auth'; import { CurrentUser } from '../auth';
import { Admin } from '../common'; import { Admin } from '../common';
import { FeatureManagementService, FeatureType } from '../features'; import { FeatureManagementService, FeatureType } from '../features';
import { PermissionService } from './permission'; import { PermissionService } from '../permission';
import { WorkspaceType } from './types'; import { WorkspaceType } from './types';
@Resolver(() => WorkspaceType) @Resolver(() => WorkspaceType)
@@ -61,7 +61,7 @@ export class WorkspaceManagementResolver {
const owner = await this.permission.getWorkspaceOwner(workspaceId); const owner = await this.permission.getWorkspaceOwner(workspaceId);
const availableFeatures = await this.availableFeatures(user); const availableFeatures = await this.availableFeatures(user);
if (owner.user.id !== user.id || !availableFeatures.includes(feature)) { if (owner.id !== user.id || !availableFeatures.includes(feature)) {
throw new ActionForbidden(); throw new ActionForbidden();
} }

View File

@@ -19,10 +19,10 @@ import {
PreventCache, PreventCache,
} from '../../../fundamentals'; } from '../../../fundamentals';
import { CurrentUser } from '../../auth'; import { CurrentUser } from '../../auth';
import { Permission, PermissionService } from '../../permission';
import { QuotaManagementService } from '../../quota'; import { QuotaManagementService } from '../../quota';
import { WorkspaceBlobStorage } from '../../storage'; import { WorkspaceBlobStorage } from '../../storage';
import { PermissionService } from '../permission'; import { WorkspaceBlobSizes, WorkspaceType } from '../types';
import { Permission, WorkspaceBlobSizes, WorkspaceType } from '../types';
@UseGuards(CloudThrottlerGuard) @UseGuards(CloudThrottlerGuard)
@Resolver(() => WorkspaceType) @Resolver(() => WorkspaceType)

View File

@@ -13,9 +13,9 @@ import type { SnapshotHistory } from '@prisma/client';
import { CurrentUser } from '../../auth'; import { CurrentUser } from '../../auth';
import { DocHistoryManager } from '../../doc'; import { DocHistoryManager } from '../../doc';
import { Permission, PermissionService } from '../../permission';
import { DocID } from '../../utils/doc'; import { DocID } from '../../utils/doc';
import { PermissionService } from '../permission'; import { WorkspaceType } from '../types';
import { Permission, WorkspaceType } from '../types';
@ObjectType() @ObjectType()
class DocHistoryType implements Partial<SnapshotHistory> { class DocHistoryType implements Partial<SnapshotHistory> {

View File

@@ -17,9 +17,13 @@ import {
PageIsNotPublic, PageIsNotPublic,
} from '../../../fundamentals'; } from '../../../fundamentals';
import { CurrentUser } from '../../auth'; import { CurrentUser } from '../../auth';
import {
Permission,
PermissionService,
PublicPageMode,
} from '../../permission';
import { DocID } from '../../utils/doc'; import { DocID } from '../../utils/doc';
import { PermissionService, PublicPageMode } from '../permission'; import { WorkspaceType } from '../types';
import { Permission, WorkspaceType } from '../types';
registerEnumType(PublicPageMode, { registerEnumType(PublicPageMode, {
name: 'PublicPageMode', name: 'PublicPageMode',

View File

@@ -26,17 +26,15 @@ import {
UserNotFound, UserNotFound,
WorkspaceAccessDenied, WorkspaceAccessDenied,
WorkspaceNotFound, WorkspaceNotFound,
WorkspaceOwnerNotFound,
} from '../../../fundamentals'; } from '../../../fundamentals';
import { CurrentUser, Public } from '../../auth'; import { CurrentUser, Public } from '../../auth';
import { Permission, PermissionService } from '../../permission';
import { QuotaManagementService, QuotaQueryType } from '../../quota'; import { QuotaManagementService, QuotaQueryType } from '../../quota';
import { WorkspaceBlobStorage } from '../../storage'; import { WorkspaceBlobStorage } from '../../storage';
import { UserService, UserType } from '../../user'; import { UserService, UserType } from '../../user';
import { PermissionService } from '../permission';
import { import {
InvitationType, InvitationType,
InviteUserType, InviteUserType,
Permission,
UpdateWorkspaceInput, UpdateWorkspaceInput,
WorkspaceType, WorkspaceType,
} from '../types'; } from '../types';
@@ -101,9 +99,7 @@ export class WorkspaceResolver {
complexity: 2, complexity: 2,
}) })
async owner(@Parent() workspace: WorkspaceType) { async owner(@Parent() workspace: WorkspaceType) {
const data = await this.permissions.getWorkspaceOwner(workspace.id); return this.permissions.getWorkspaceOwner(workspace.id);
return data.user;
} }
@ResolveField(() => [InviteUserType], { @ResolveField(() => [InviteUserType], {
@@ -449,7 +445,7 @@ export class WorkspaceResolver {
avatar: avatar || defaultWorkspaceAvatar, avatar: avatar || defaultWorkspaceAvatar,
id: workspaceId, id: workspaceId,
}, },
user: owner.user, user: owner,
invitee: invitee.user, invitee: invitee.user,
}; };
} }
@@ -507,12 +503,8 @@ export class WorkspaceResolver {
const owner = await this.permissions.getWorkspaceOwner(workspaceId); const owner = await this.permissions.getWorkspaceOwner(workspaceId);
if (!owner.user) {
throw new WorkspaceOwnerNotFound({ workspaceId: workspaceId });
}
if (sendLeaveMail) { if (sendLeaveMail) {
await this.mailer.sendLeaveWorkspaceEmail(owner.user.email, { await this.mailer.sendLeaveWorkspaceEmail(owner.email, {
workspaceName, workspaceName,
inviteeName: user.name, inviteeName: user.name,
}); });

View File

@@ -11,15 +11,9 @@ import {
import type { Workspace } from '@prisma/client'; import type { Workspace } from '@prisma/client';
import { SafeIntResolver } from 'graphql-scalars'; import { SafeIntResolver } from 'graphql-scalars';
import { Permission } from '../permission';
import { UserType } from '../user/types'; import { UserType } from '../user/types';
export enum Permission {
Read = 0,
Write = 1,
Admin = 10,
Owner = 99,
}
registerEnumType(Permission, { registerEnumType(Permission, {
name: 'Permission', name: 'Permission',
description: 'User permission in workspace', description: 'User permission in workspace',

View File

@@ -198,6 +198,10 @@ export const USER_FRIENDLY_ERRORS = {
type: 'too_many_requests', type: 'too_many_requests',
message: 'Too many requests.', message: 'Too many requests.',
}, },
not_found: {
type: 'resource_not_found',
message: 'Resource not found.',
},
// User Errors // User Errors
user_not_found: { user_not_found: {

View File

@@ -16,6 +16,12 @@ export class TooManyRequest extends UserFriendlyError {
} }
} }
export class NotFound extends UserFriendlyError {
constructor(message?: string) {
super('resource_not_found', 'not_found', message);
}
}
export class UserNotFound extends UserFriendlyError { export class UserNotFound extends UserFriendlyError {
constructor(message?: string) { constructor(message?: string) {
super('resource_not_found', 'user_not_found', message); super('resource_not_found', 'user_not_found', message);
@@ -508,6 +514,7 @@ export class CannotDeleteOwnAccount extends UserFriendlyError {
export enum ErrorNames { export enum ErrorNames {
INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR,
TOO_MANY_REQUEST, TOO_MANY_REQUEST,
NOT_FOUND,
USER_NOT_FOUND, USER_NOT_FOUND,
USER_AVATAR_NOT_FOUND, USER_AVATAR_NOT_FOUND,
EMAIL_ALREADY_USED, EMAIL_ALREADY_USED,

View File

@@ -40,5 +40,3 @@ export class ErrorModule implements OnModuleInit {
export { UserFriendlyError } from './def'; export { UserFriendlyError } from './def';
export * from './errors.gen'; export * from './errors.gen';
export * from './payment-required';
export * from './too-many-requests';

View File

@@ -1,10 +0,0 @@
import { HttpException, HttpStatus } from '@nestjs/common';
export class PaymentRequiredException extends HttpException {
constructor(desc?: string, code: string = 'Payment Required') {
super(
HttpException.createBody(desc ?? code, code, HttpStatus.PAYMENT_REQUIRED),
HttpStatus.PAYMENT_REQUIRED
);
}
}

View File

@@ -1,14 +0,0 @@
import { HttpException, HttpStatus } from '@nestjs/common';
export class TooManyRequestsException extends HttpException {
constructor(desc?: string, code: string = 'Too Many Requests') {
super(
HttpException.createBody(
desc ?? code,
code,
HttpStatus.TOO_MANY_REQUESTS
),
HttpStatus.TOO_MANY_REQUESTS
);
}
}

View File

@@ -1,4 +1,9 @@
import { ArgumentsHost, Catch, Logger } from '@nestjs/common'; import {
ArgumentsHost,
Catch,
Logger,
NotFoundException,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core'; import { BaseExceptionFilter } from '@nestjs/core';
import { GqlContextType } from '@nestjs/graphql'; import { GqlContextType } from '@nestjs/graphql';
import { ThrottlerException } from '@nestjs/throttler'; import { ThrottlerException } from '@nestjs/throttler';
@@ -9,6 +14,7 @@ import { Socket } from 'socket.io';
import { import {
InternalServerError, InternalServerError,
NotFound,
TooManyRequest, TooManyRequest,
UserFriendlyError, UserFriendlyError,
} from '../error'; } from '../error';
@@ -19,6 +25,8 @@ export function mapAnyError(error: any): UserFriendlyError {
return error; return error;
} else if (error instanceof ThrottlerException) { } else if (error instanceof ThrottlerException) {
return new TooManyRequest(); return new TooManyRequest();
} else if (error instanceof NotFoundException) {
return new NotFound();
} else { } else {
const e = new InternalServerError(); const e = new InternalServerError();
e.cause = error; e.cause = error;

View File

@@ -2,8 +2,8 @@ import './config';
import { ServerFeature } from '../../core/config'; import { ServerFeature } from '../../core/config';
import { FeatureModule } from '../../core/features'; import { FeatureModule } from '../../core/features';
import { PermissionModule } from '../../core/permission';
import { QuotaModule } from '../../core/quota'; import { QuotaModule } from '../../core/quota';
import { PermissionService } from '../../core/workspaces/permission';
import { Plugin } from '../registry'; import { Plugin } from '../registry';
import { CopilotController } from './controller'; import { CopilotController } from './controller';
import { ChatMessageCache } from './message'; import { ChatMessageCache } from './message';
@@ -29,9 +29,8 @@ registerCopilotProvider(OpenAIProvider);
@Plugin({ @Plugin({
name: 'copilot', name: 'copilot',
imports: [FeatureModule, QuotaModule], imports: [FeatureModule, QuotaModule, PermissionModule],
providers: [ providers: [
PermissionService,
ChatSessionService, ChatSessionService,
CopilotResolver, CopilotResolver,
ChatMessageCache, ChatMessageCache,

View File

@@ -21,8 +21,8 @@ import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
import { CurrentUser } from '../../core/auth'; import { CurrentUser } from '../../core/auth';
import { Admin } from '../../core/common'; import { Admin } from '../../core/common';
import { PermissionService } from '../../core/permission';
import { UserType } from '../../core/user'; import { UserType } from '../../core/user';
import { PermissionService } from '../../core/workspaces/permission';
import { import {
CopilotFailedToCreateMessage, CopilotFailedToCreateMessage,
FileUpload, FileUpload,

View File

@@ -21,6 +21,6 @@ import { OAuthService } from './service';
], ],
controllers: [OAuthController], controllers: [OAuthController],
contributesTo: ServerFeature.OAuth, contributesTo: ServerFeature.OAuth,
if: config => !!config.plugins.oauth, if: config => config.flavor.graphql && !!config.plugins.oauth,
}) })
export class OAuthModule {} export class OAuthModule {}

View File

@@ -239,6 +239,7 @@ enum ErrorNames {
MAILER_SERVICE_IS_NOT_CONFIGURED MAILER_SERVICE_IS_NOT_CONFIGURED
MEMBER_QUOTA_EXCEEDED MEMBER_QUOTA_EXCEEDED
MISSING_OAUTH_QUERY_PARAMETER MISSING_OAUTH_QUERY_PARAMETER
NOT_FOUND
NOT_IN_WORKSPACE NOT_IN_WORKSPACE
NO_COPILOT_PROVIDER_AVAILABLE NO_COPILOT_PROVIDER_AVAILABLE
OAUTH_ACCOUNT_ALREADY_CONNECTED OAUTH_ACCOUNT_ALREADY_CONNECTED

View File

@@ -12,9 +12,9 @@ import {
FeatureService, FeatureService,
FeatureType, FeatureType,
} from '../src/core/features'; } from '../src/core/features';
import { Permission } from '../src/core/permission';
import { UserType } from '../src/core/user/types'; import { UserType } from '../src/core/user/types';
import { WorkspaceResolver } from '../src/core/workspaces/resolvers'; import { WorkspaceResolver } from '../src/core/workspaces/resolvers';
import { Permission } from '../src/core/workspaces/types';
import { Config, ConfigModule } from '../src/fundamentals/config'; import { Config, ConfigModule } from '../src/fundamentals/config';
import { createTestingApp } from './utils'; import { createTestingApp } from './utils';

View File

@@ -5,6 +5,7 @@ import test from 'ava';
import * as Sinon from 'sinon'; import * as Sinon from 'sinon';
import { DocHistoryManager } from '../src/core/doc'; import { DocHistoryManager } from '../src/core/doc';
import { PermissionModule } from '../src/core/permission';
import { QuotaModule } from '../src/core/quota'; import { QuotaModule } from '../src/core/quota';
import { StorageModule } from '../src/core/storage'; import { StorageModule } from '../src/core/storage';
import type { EventPayload } from '../src/fundamentals/event'; import type { EventPayload } from '../src/fundamentals/event';
@@ -17,7 +18,7 @@ let db: PrismaClient;
// cleanup database before each test // cleanup database before each test
test.beforeEach(async () => { test.beforeEach(async () => {
m = await createTestingModule({ m = await createTestingModule({
imports: [StorageModule, QuotaModule], imports: [StorageModule, QuotaModule, PermissionModule],
providers: [DocHistoryManager], providers: [DocHistoryManager],
}); });