mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
feat(server): doc level permission (#9760)
close CLOUD-89 CLOUD-90 CLOUD-91 CLOUD-92
This commit is contained in:
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import { CannotDeleteAllAdminAccount } from '../../base';
|
||||
import { WorkspaceType } from '../workspaces/types';
|
||||
import { WorkspaceFeatureType } from '../workspaces/types';
|
||||
import { FeatureConfigType, getFeature } from './feature';
|
||||
import { FeatureKind, FeatureType } from './types';
|
||||
|
||||
@@ -20,7 +20,7 @@ export class FeatureService {
|
||||
if (data) {
|
||||
return getFeature(this.prisma, data.id) as Promise<FeatureConfigType<F>>;
|
||||
}
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// ======== User Features ========
|
||||
@@ -315,7 +315,7 @@ export class FeatureService {
|
||||
|
||||
async listWorkspacesByFeature(
|
||||
feature: FeatureType
|
||||
): Promise<WorkspaceType[]> {
|
||||
): Promise<WorkspaceFeatureType[]> {
|
||||
return this.prisma.workspaceFeature
|
||||
.findMany({
|
||||
where: {
|
||||
@@ -335,7 +335,7 @@ export class FeatureService {
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(wss => wss.map(ws => ws.workspace as WorkspaceType));
|
||||
.then(wss => wss.map(ws => ws.workspace));
|
||||
}
|
||||
|
||||
async hasWorkspaceFeature(workspaceId: string, feature: FeatureType) {
|
||||
|
||||
@@ -0,0 +1,665 @@
|
||||
# Snapshot report for `src/core/permission/__tests__/role.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `role.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: External and DocRole: External
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: false,
|
||||
Doc_Duplicate: false,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: false,
|
||||
Doc_Publish: false,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: false,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: false,
|
||||
Doc_Update: false,
|
||||
Doc_Users_Manage: false,
|
||||
Doc_Users_Read: false,
|
||||
Workspace_CreateDoc: false,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: false,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: false,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: false,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: false,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Reader
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: false,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: false,
|
||||
Doc_Publish: false,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: false,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: false,
|
||||
Doc_Update: false,
|
||||
Doc_Users_Manage: false,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: false,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: false,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: false,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: false,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: false,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Editor
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: false,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: false,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: false,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: false,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: false,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: false,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: false,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Manager
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: false,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: false,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: false,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: false,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: false,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: External and DocRole: Owner
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: true,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: false,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: false,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: false,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: false,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: false,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: External
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: false,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: false,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Reader
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: false,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: false,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Editor
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: false,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: false,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Manager
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Collaborator and DocRole: Owner
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: true,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: false,
|
||||
Workspace_Properties_Delete: false,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: false,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: false,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: false,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: External
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Reader
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Editor
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Manager
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Admin and DocRole: Owner
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: true,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: false,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: false,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: External
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: true,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: true,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Reader
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: true,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: true,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Editor
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: true,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: true,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Manager
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: false,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: true,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: true,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
|
||||
## should be able to get correct permissions from WorkspaceRole: Owner and DocRole: Owner
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
Doc_Copy: true,
|
||||
Doc_Delete: true,
|
||||
Doc_Duplicate: true,
|
||||
Doc_Properties_Read: true,
|
||||
Doc_Properties_Update: true,
|
||||
Doc_Publish: true,
|
||||
Doc_Read: true,
|
||||
Doc_Restore: true,
|
||||
Doc_TransferOwner: true,
|
||||
Doc_Trash: true,
|
||||
Doc_Update: true,
|
||||
Doc_Users_Manage: true,
|
||||
Doc_Users_Read: true,
|
||||
Workspace_CreateDoc: true,
|
||||
Workspace_Delete: true,
|
||||
Workspace_Organize_Read: true,
|
||||
Workspace_Properties_Create: true,
|
||||
Workspace_Properties_Delete: true,
|
||||
Workspace_Properties_Read: true,
|
||||
Workspace_Properties_Update: true,
|
||||
Workspace_Settings_Read: true,
|
||||
Workspace_Settings_Update: true,
|
||||
Workspace_Sync: true,
|
||||
Workspace_TransferOwner: true,
|
||||
Workspace_Users_Manage: true,
|
||||
Workspace_Users_Read: true,
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
import test from 'ava';
|
||||
|
||||
import { DocRole, WorkspaceRole } from '../index';
|
||||
import { Actions, ActionsKeys, mapRoleToActions } from '../types';
|
||||
|
||||
// create a matrix representing the all possible permission of WorkspaceRole and DocRole
|
||||
const matrix = Object.values(WorkspaceRole)
|
||||
.filter(r => typeof r !== 'string')
|
||||
.flatMap(workspaceRole =>
|
||||
Object.values(DocRole)
|
||||
.filter(r => typeof r !== 'string')
|
||||
.map(docRole => ({
|
||||
workspaceRole,
|
||||
docRole,
|
||||
}))
|
||||
);
|
||||
|
||||
for (const { workspaceRole, docRole } of matrix) {
|
||||
const permission = mapRoleToActions(workspaceRole, docRole);
|
||||
test(`should be able to get correct permissions from WorkspaceRole: ${WorkspaceRole[workspaceRole]} and DocRole: ${DocRole[docRole]}`, t => {
|
||||
t.snapshot(permission);
|
||||
});
|
||||
}
|
||||
|
||||
test('ActionsKeys value should be the same order of the Actions objects', t => {
|
||||
for (const [index, value] of ActionsKeys.entries()) {
|
||||
const [k, k1, k2] = value.split('.');
|
||||
if (k2) {
|
||||
// @ts-expect-error
|
||||
t.is(Actions[k][k1][k2], index);
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
t.is(Actions[k][k1], index);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -9,4 +9,4 @@ import { PermissionService } from './service';
|
||||
export class PermissionModule {}
|
||||
|
||||
export { PermissionService } from './service';
|
||||
export { Permission, PublicPageMode } from './types';
|
||||
export { DocRole, PublicPageMode, WorkspaceRole } from './types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
|
||||
import { groupBy } from 'lodash-es';
|
||||
@@ -8,11 +8,22 @@ import {
|
||||
EventBus,
|
||||
SpaceAccessDenied,
|
||||
SpaceOwnerNotFound,
|
||||
SpaceShouldHaveOnlyOneOwner,
|
||||
WorkspacePermissionNotFound,
|
||||
} from '../../base';
|
||||
import { Permission, PublicPageMode } from './types';
|
||||
import {
|
||||
AllPossibleGraphQLDocActionsKeys,
|
||||
DocRole,
|
||||
findMinimalDocRole,
|
||||
PublicPageMode,
|
||||
requiredWorkspaceRoleByDocRole,
|
||||
WorkspaceRole,
|
||||
} from './types';
|
||||
|
||||
@Injectable()
|
||||
export class PermissionService {
|
||||
private readonly logger = new Logger(PermissionService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaClient,
|
||||
private readonly event: EventBus
|
||||
@@ -30,7 +41,7 @@ export class PermissionService {
|
||||
}
|
||||
|
||||
/// Start regin: workspace permission
|
||||
async get(ws: string, user: string) {
|
||||
async get(ws: string, user: string): Promise<WorkspaceRole> {
|
||||
const data = await this.prisma.workspaceUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
@@ -39,7 +50,11 @@ export class PermissionService {
|
||||
},
|
||||
});
|
||||
|
||||
return data?.type as Permission;
|
||||
if (!data) {
|
||||
throw new WorkspacePermissionNotFound({ spaceId: ws });
|
||||
}
|
||||
|
||||
return data.type;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +78,7 @@ export class PermissionService {
|
||||
.findMany({
|
||||
where: {
|
||||
userId,
|
||||
type: Permission.Owner,
|
||||
type: WorkspaceRole.Owner,
|
||||
OR: this.acceptedCondition,
|
||||
},
|
||||
select: {
|
||||
@@ -77,7 +92,7 @@ export class PermissionService {
|
||||
const owner = await this.prisma.workspaceUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId,
|
||||
type: Permission.Owner,
|
||||
type: WorkspaceRole.Owner,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
@@ -95,7 +110,7 @@ export class PermissionService {
|
||||
const admin = await this.prisma.workspaceUserPermission.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
type: Permission.Admin,
|
||||
type: WorkspaceRole.Admin,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
@@ -117,7 +132,7 @@ export class PermissionService {
|
||||
return this.prisma.workspaceUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId,
|
||||
type: Permission.Owner,
|
||||
type: WorkspaceRole.Owner,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
@@ -136,7 +151,7 @@ export class PermissionService {
|
||||
if (ws === id) {
|
||||
// if workspace is public or have any public page, then allow to access
|
||||
const [isPublicWorkspace, publicPages] = await Promise.all([
|
||||
this.tryCheckWorkspace(ws, user, Permission.Read),
|
||||
this.tryCheckWorkspace(ws, user, WorkspaceRole.Collaborator),
|
||||
this.prisma.workspacePage.count({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
@@ -147,7 +162,7 @@ export class PermissionService {
|
||||
return isPublicWorkspace || publicPages > 0;
|
||||
}
|
||||
|
||||
return this.tryCheckPage(ws, id, user);
|
||||
return this.tryCheckPage(ws, id, 'Doc_Read', user);
|
||||
}
|
||||
|
||||
async getWorkspaceMemberStatus(ws: string, user: string) {
|
||||
@@ -168,7 +183,7 @@ export class PermissionService {
|
||||
async isWorkspaceMember(
|
||||
ws: string,
|
||||
user: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
): Promise<boolean> {
|
||||
const count = await this.prisma.workspaceUserPermission.count({
|
||||
where: {
|
||||
@@ -193,7 +208,7 @@ export class PermissionService {
|
||||
async checkCloudWorkspace(
|
||||
workspaceId: string,
|
||||
userId?: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
const hasWorkspace = await this.hasWorkspace(workspaceId);
|
||||
if (hasWorkspace) {
|
||||
@@ -204,7 +219,7 @@ export class PermissionService {
|
||||
async checkWorkspace(
|
||||
ws: string,
|
||||
user?: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
if (!(await this.tryCheckWorkspace(ws, user, permission))) {
|
||||
throw new SpaceAccessDenied({ spaceId: ws });
|
||||
@@ -214,10 +229,10 @@ export class PermissionService {
|
||||
async tryCheckWorkspace(
|
||||
ws: string,
|
||||
user?: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
// If the permission is read, we should check if the workspace is public
|
||||
if (permission === Permission.Read) {
|
||||
if (permission === WorkspaceRole.Collaborator) {
|
||||
const count = await this.prisma.workspace.count({
|
||||
where: { id: ws, public: true },
|
||||
});
|
||||
@@ -242,7 +257,15 @@ export class PermissionService {
|
||||
},
|
||||
});
|
||||
|
||||
return count > 0;
|
||||
if (count > 0) {
|
||||
return true;
|
||||
} else {
|
||||
this.logger.log("User's WorkspaceRole is lower than required", {
|
||||
workspaceId: ws,
|
||||
userId: user,
|
||||
requiredRole: WorkspaceRole[permission],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// unsigned in, workspace is not public
|
||||
@@ -253,7 +276,7 @@ export class PermissionService {
|
||||
async checkWorkspaceIs(
|
||||
ws: string,
|
||||
user: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
if (!(await this.tryCheckWorkspaceIs(ws, user, permission))) {
|
||||
throw new SpaceAccessDenied({ spaceId: ws });
|
||||
@@ -263,7 +286,7 @@ export class PermissionService {
|
||||
async tryCheckWorkspaceIs(
|
||||
ws: string,
|
||||
user: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
const count = await this.prisma.workspaceUserPermission.count({
|
||||
where: {
|
||||
@@ -307,7 +330,7 @@ export class PermissionService {
|
||||
async grant(
|
||||
ws: string,
|
||||
user: string,
|
||||
permission: Permission = Permission.Read,
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator,
|
||||
status: WorkspaceMemberStatus = WorkspaceMemberStatus.Pending
|
||||
): Promise<string> {
|
||||
const data = await this.prisma.workspaceUserPermission.findFirst({
|
||||
@@ -315,7 +338,7 @@ export class PermissionService {
|
||||
});
|
||||
|
||||
if (data) {
|
||||
const toBeOwner = permission === Permission.Owner;
|
||||
const toBeOwner = permission === WorkspaceRole.Owner;
|
||||
if (data.accepted && data.status === WorkspaceMemberStatus.Accepted) {
|
||||
const [p] = await this.prisma.$transaction(
|
||||
[
|
||||
@@ -331,10 +354,10 @@ export class PermissionService {
|
||||
? this.prisma.workspaceUserPermission.updateMany({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
type: Permission.Owner,
|
||||
type: WorkspaceRole.Owner,
|
||||
userId: { not: user },
|
||||
},
|
||||
data: { type: Permission.Admin },
|
||||
data: { type: WorkspaceRole.Admin },
|
||||
})
|
||||
: null,
|
||||
].filter(Boolean) as Prisma.PrismaPromise<any>[]
|
||||
@@ -441,7 +464,7 @@ export class PermissionService {
|
||||
|
||||
// We shouldn't revoke owner permission
|
||||
// should auto deleted by workspace/user delete cascading
|
||||
if (!permission || permission.type === Permission.Owner) {
|
||||
if (!permission || permission.type === WorkspaceRole.Owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -490,22 +513,22 @@ export class PermissionService {
|
||||
async checkCloudPagePermission(
|
||||
workspaceId: string,
|
||||
pageId: string,
|
||||
userId?: string,
|
||||
permission = Permission.Read
|
||||
action: AllPossibleGraphQLDocActionsKeys,
|
||||
userId?: string
|
||||
) {
|
||||
const hasWorkspace = await this.hasWorkspace(workspaceId);
|
||||
if (hasWorkspace) {
|
||||
await this.checkPagePermission(workspaceId, pageId, userId, permission);
|
||||
await this.checkPagePermission(workspaceId, pageId, action, userId);
|
||||
}
|
||||
}
|
||||
|
||||
async checkPagePermission(
|
||||
ws: string,
|
||||
page: string,
|
||||
user?: string,
|
||||
permission = Permission.Read
|
||||
action: AllPossibleGraphQLDocActionsKeys,
|
||||
user?: string
|
||||
) {
|
||||
if (!(await this.tryCheckPage(ws, page, user, permission))) {
|
||||
if (!(await this.tryCheckPage(ws, page, action, user))) {
|
||||
throw new DocAccessDenied({ spaceId: ws, docId: page });
|
||||
}
|
||||
}
|
||||
@@ -513,11 +536,12 @@ export class PermissionService {
|
||||
async tryCheckPage(
|
||||
ws: string,
|
||||
page: string,
|
||||
user?: string,
|
||||
permission = Permission.Read
|
||||
action: AllPossibleGraphQLDocActionsKeys,
|
||||
user?: string
|
||||
) {
|
||||
const role = findMinimalDocRole(action);
|
||||
// check whether page is public
|
||||
if (permission === Permission.Read) {
|
||||
if (action === 'Doc_Read') {
|
||||
const count = await this.prisma.workspacePage.count({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
@@ -541,7 +565,7 @@ export class PermissionService {
|
||||
userId: user,
|
||||
accepted: true,
|
||||
type: {
|
||||
gte: permission,
|
||||
gte: role,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -550,11 +574,23 @@ export class PermissionService {
|
||||
// accessible
|
||||
if (count > 0) {
|
||||
return true;
|
||||
} else {
|
||||
this.logger.log("User's PageRole is lower than required", {
|
||||
workspaceId: ws,
|
||||
pageId: page,
|
||||
userId: user,
|
||||
requiredRole: DocRole[role],
|
||||
action,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// check whether user has workspace related permission
|
||||
return this.tryCheckWorkspace(ws, user, permission);
|
||||
return this.tryCheckWorkspace(
|
||||
ws,
|
||||
user,
|
||||
requiredWorkspaceRoleByDocRole(role)
|
||||
);
|
||||
}
|
||||
|
||||
async isPublicPage(ws: string, page: string) {
|
||||
@@ -613,8 +649,8 @@ export class PermissionService {
|
||||
ws: string,
|
||||
page: string,
|
||||
user: string,
|
||||
permission: Permission = Permission.Read
|
||||
) {
|
||||
permission: DocRole
|
||||
): Promise<string> {
|
||||
const data = await this.prisma.workspacePageUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
@@ -637,18 +673,18 @@ export class PermissionService {
|
||||
}),
|
||||
|
||||
// If the new permission is owner, we need to revoke old owner
|
||||
permission === Permission.Owner
|
||||
permission === DocRole.Owner
|
||||
? this.prisma.workspacePageUserPermission.updateMany({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
pageId: page,
|
||||
type: Permission.Owner,
|
||||
type: DocRole.Owner,
|
||||
userId: {
|
||||
not: user,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: Permission.Admin,
|
||||
type: DocRole.Manager,
|
||||
},
|
||||
})
|
||||
: null,
|
||||
@@ -670,20 +706,93 @@ export class PermissionService {
|
||||
.then(p => p.id);
|
||||
}
|
||||
|
||||
async revokePage(ws: string, page: string, user: string) {
|
||||
async revokePage(ws: string, page: string, users: string[]) {
|
||||
const result = await this.prisma.workspacePageUserPermission.deleteMany({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
pageId: page,
|
||||
userId: user,
|
||||
userId: {
|
||||
in: users,
|
||||
},
|
||||
type: {
|
||||
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
|
||||
not: Permission.Owner,
|
||||
not: DocRole.Owner,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result.count > 0;
|
||||
}
|
||||
/// End regin: page permission
|
||||
|
||||
async grantPagePermission(
|
||||
workspaceId: string,
|
||||
pageId: string,
|
||||
userIds: string[],
|
||||
role: DocRole
|
||||
) {
|
||||
if (userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (role === DocRole.Owner) {
|
||||
if (userIds.length > 1) {
|
||||
throw new SpaceShouldHaveOnlyOneOwner({ spaceId: workspaceId });
|
||||
}
|
||||
return [await this.grantPage(workspaceId, pageId, userIds[0], role)];
|
||||
}
|
||||
|
||||
const ret = await this.prisma.$transaction(async tx =>
|
||||
Promise.all(
|
||||
userIds.map(id =>
|
||||
tx.workspacePageUserPermission.upsert({
|
||||
where: {
|
||||
workspaceId_pageId_userId: {
|
||||
workspaceId,
|
||||
pageId,
|
||||
userId: id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
workspaceId,
|
||||
pageId,
|
||||
userId: id,
|
||||
type: role,
|
||||
},
|
||||
update: {
|
||||
type: role,
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
return ret.map(p => p.id);
|
||||
}
|
||||
|
||||
async updatePagePermission(
|
||||
workspaceId: string,
|
||||
pageId: string,
|
||||
userId: string,
|
||||
role: DocRole
|
||||
) {
|
||||
const permission = await this.prisma.workspacePageUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId,
|
||||
pageId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!permission) {
|
||||
return this.grantPage(workspaceId, pageId, userId, role);
|
||||
}
|
||||
|
||||
const { id } = await this.prisma.workspacePageUserPermission.update({
|
||||
where: {
|
||||
id: permission.id,
|
||||
},
|
||||
data: {
|
||||
type: role,
|
||||
},
|
||||
});
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,328 @@
|
||||
export enum Permission {
|
||||
Read = 0,
|
||||
Write = 1,
|
||||
Admin = 10,
|
||||
Owner = 99,
|
||||
}
|
||||
import assert from 'node:assert';
|
||||
|
||||
export enum PublicPageMode {
|
||||
Page,
|
||||
Edgeless,
|
||||
}
|
||||
|
||||
export enum DocRole {
|
||||
External = 0,
|
||||
Reader = 10,
|
||||
Editor = 20,
|
||||
Manager = 30,
|
||||
Owner = 99,
|
||||
}
|
||||
|
||||
export enum WorkspaceRole {
|
||||
External = -99,
|
||||
Collaborator = 1,
|
||||
Admin = 10,
|
||||
Owner = 99,
|
||||
}
|
||||
|
||||
export const Actions = {
|
||||
Workspace: {
|
||||
Sync: 1,
|
||||
CreateDoc: 2,
|
||||
Delete: 11,
|
||||
TransferOwner: 12,
|
||||
Organize: {
|
||||
Read: 0,
|
||||
},
|
||||
Users: {
|
||||
Read: 3,
|
||||
Manage: 6,
|
||||
},
|
||||
Properties: {
|
||||
Read: 4,
|
||||
Create: 8,
|
||||
Update: 9,
|
||||
Delete: 10,
|
||||
},
|
||||
Settings: {
|
||||
Read: 5,
|
||||
Update: 7,
|
||||
},
|
||||
},
|
||||
Doc: {
|
||||
Read: 13,
|
||||
Copy: 14,
|
||||
Duplicate: 17,
|
||||
Trash: 18,
|
||||
Restore: 19,
|
||||
Delete: 20,
|
||||
Update: 22,
|
||||
Publish: 23,
|
||||
TransferOwner: 25,
|
||||
Properties: {
|
||||
Read: 15,
|
||||
Update: 21,
|
||||
},
|
||||
Users: {
|
||||
Read: 16,
|
||||
Manage: 24,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
type ActionsKeysUnion = typeof Actions extends {
|
||||
[k in infer _K extends string]: infer _V;
|
||||
}
|
||||
? _V extends {
|
||||
[k1 in infer _K1 extends string]: infer _V1;
|
||||
}
|
||||
? _V1 extends {
|
||||
[k2 in infer _K2 extends string]: number;
|
||||
}
|
||||
? _K1 extends keyof (typeof Actions)[_K]
|
||||
? _K2 extends keyof (typeof Actions)[_K][_K1]
|
||||
? `${_K}.${_K1}.${_K2}`
|
||||
: never
|
||||
: never
|
||||
: _V1 extends number
|
||||
? `${_K}.${_K1}`
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
type ExcludeObjectKeys<
|
||||
T,
|
||||
Key extends keyof typeof Actions,
|
||||
Split extends string,
|
||||
> = T extends `${infer _K extends Key}.${infer _K1}.${infer _K2}`
|
||||
? _K1 extends keyof (typeof Actions)[_K]
|
||||
? _K2 extends keyof (typeof Actions)[_K][_K1]
|
||||
? `${_K}${Split}${_K1}${Split}${_K2}`
|
||||
: never
|
||||
: never
|
||||
: T extends `${infer _K extends Key}.${infer _K1}`
|
||||
? _K1 extends keyof (typeof Actions)[_K]
|
||||
? (typeof Actions)[_K][_K1] extends number
|
||||
? `${_K}${Split}${_K1}`
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
export type AllPossibleActionsKeys = ExcludeObjectKeys<
|
||||
ActionsKeysUnion,
|
||||
keyof typeof Actions,
|
||||
'.'
|
||||
>;
|
||||
|
||||
export type AllPossibleGraphQLWorkspaceActionsKeys = ExcludeObjectKeys<
|
||||
ActionsKeysUnion,
|
||||
'Workspace',
|
||||
'_'
|
||||
>;
|
||||
export type AllPossibleGraphQLDocActionsKeys = ExcludeObjectKeys<
|
||||
ActionsKeysUnion,
|
||||
'Doc',
|
||||
'_'
|
||||
>;
|
||||
|
||||
type AllPossibleGraphQLActionsKeys =
|
||||
| AllPossibleGraphQLWorkspaceActionsKeys
|
||||
| AllPossibleGraphQLDocActionsKeys;
|
||||
|
||||
export const ActionsKeys: AllPossibleActionsKeys[] = [
|
||||
'Workspace.Organize.Read',
|
||||
'Workspace.Sync',
|
||||
'Workspace.CreateDoc',
|
||||
'Workspace.Users.Read',
|
||||
'Workspace.Properties.Read',
|
||||
'Workspace.Settings.Read',
|
||||
'Workspace.Users.Manage',
|
||||
'Workspace.Settings.Update',
|
||||
'Workspace.Properties.Create',
|
||||
'Workspace.Properties.Update',
|
||||
'Workspace.Properties.Delete',
|
||||
'Workspace.Delete',
|
||||
'Workspace.TransferOwner',
|
||||
'Doc.Read',
|
||||
'Doc.Copy',
|
||||
'Doc.Properties.Read',
|
||||
'Doc.Users.Read',
|
||||
'Doc.Duplicate',
|
||||
'Doc.Trash',
|
||||
'Doc.Restore',
|
||||
'Doc.Delete',
|
||||
'Doc.Properties.Update',
|
||||
'Doc.Update',
|
||||
'Doc.Publish',
|
||||
'Doc.Users.Manage',
|
||||
'Doc.TransferOwner',
|
||||
] as const;
|
||||
|
||||
assert(
|
||||
ActionsKeys.length === Actions.Doc.TransferOwner + 1,
|
||||
'ActionsKeys length is not correct'
|
||||
);
|
||||
|
||||
function permissionKeyToGraphQLKey(key: string) {
|
||||
const k = key.split('.');
|
||||
return k.join('_') as keyof PermissionsList;
|
||||
}
|
||||
|
||||
const DefaultActionsMap = Object.fromEntries(
|
||||
ActionsKeys.map(key => [permissionKeyToGraphQLKey(key), false])
|
||||
) as PermissionsList;
|
||||
|
||||
export type WorkspacePermissionsList = {
|
||||
[k in AllPossibleGraphQLWorkspaceActionsKeys]: boolean;
|
||||
};
|
||||
|
||||
export type PermissionsList = {
|
||||
[key in AllPossibleGraphQLActionsKeys]: boolean;
|
||||
};
|
||||
|
||||
export function mapWorkspaceRoleToWorkspaceActions(
|
||||
workspaceRole: WorkspaceRole
|
||||
) {
|
||||
const permissionList = { ...DefaultActionsMap };
|
||||
(RoleActionsMap.WorkspaceRole[workspaceRole] ?? []).forEach(action => {
|
||||
permissionList[permissionKeyToGraphQLKey(ActionsKeys[action])] = true;
|
||||
});
|
||||
return Object.fromEntries(
|
||||
Object.entries(permissionList).filter(([k, _]) =>
|
||||
k.startsWith('Workspace_')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function mapRoleToActions(
|
||||
workspaceRole?: WorkspaceRole,
|
||||
docRole?: DocRole
|
||||
) {
|
||||
const workspaceActions = workspaceRole
|
||||
? (RoleActionsMap.WorkspaceRole[workspaceRole] ?? [])
|
||||
: [];
|
||||
const docActions = (function () {
|
||||
// Doc owner/manager permission can not be overridden by workspace role
|
||||
if (docRole !== undefined && docRole >= DocRole.Manager) {
|
||||
return RoleActionsMap.DocRole[docRole];
|
||||
}
|
||||
switch (workspaceRole) {
|
||||
case WorkspaceRole.Admin:
|
||||
case WorkspaceRole.Owner:
|
||||
return RoleActionsMap.DocRole[DocRole.Manager];
|
||||
case WorkspaceRole.Collaborator:
|
||||
return RoleActionsMap.DocRole[DocRole.Editor];
|
||||
default:
|
||||
return docRole !== undefined
|
||||
? (RoleActionsMap.DocRole[docRole] ?? [])
|
||||
: [];
|
||||
}
|
||||
})();
|
||||
const permissionList = { ...DefaultActionsMap };
|
||||
[...workspaceActions, ...docActions].forEach(action => {
|
||||
permissionList[permissionKeyToGraphQLKey(ActionsKeys[action])] = true;
|
||||
});
|
||||
return permissionList;
|
||||
}
|
||||
|
||||
export function findMinimalDocRole(
|
||||
action: AllPossibleGraphQLDocActionsKeys
|
||||
): DocRole {
|
||||
const [_, actionKey, actionKey2] = action.split('_');
|
||||
|
||||
const actionValue: number = actionKey2
|
||||
? // @ts-expect-error Actions[actionKey] exists
|
||||
Actions.Doc[actionKey][actionKey2]
|
||||
: // @ts-expect-error Actions[actionKey] exists
|
||||
Actions.Doc[actionKey];
|
||||
if (actionValue <= Actions.Doc.Properties.Read) {
|
||||
return DocRole.External;
|
||||
}
|
||||
if (actionValue <= Actions.Doc.Duplicate) {
|
||||
return DocRole.Reader;
|
||||
}
|
||||
if (actionValue <= Actions.Doc.Update) {
|
||||
return DocRole.Editor;
|
||||
}
|
||||
if (actionValue <= Actions.Doc.Users.Manage) {
|
||||
return DocRole.Manager;
|
||||
}
|
||||
return DocRole.Owner;
|
||||
}
|
||||
|
||||
export function requiredWorkspaceRoleByDocRole(
|
||||
docRole: DocRole
|
||||
): WorkspaceRole {
|
||||
switch (docRole) {
|
||||
case DocRole.Owner:
|
||||
return WorkspaceRole.Owner;
|
||||
case DocRole.Manager:
|
||||
return WorkspaceRole.Admin;
|
||||
case DocRole.Editor:
|
||||
case DocRole.Reader:
|
||||
case DocRole.External:
|
||||
return WorkspaceRole.Collaborator;
|
||||
}
|
||||
}
|
||||
|
||||
export const RoleActionsMap = {
|
||||
WorkspaceRole: {
|
||||
get [WorkspaceRole.External]() {
|
||||
return [Actions.Workspace.Organize.Read];
|
||||
},
|
||||
get [WorkspaceRole.Collaborator]() {
|
||||
return [
|
||||
...this[WorkspaceRole.External],
|
||||
Actions.Workspace.Sync,
|
||||
Actions.Workspace.CreateDoc,
|
||||
Actions.Workspace.Users.Read,
|
||||
Actions.Workspace.Properties.Read,
|
||||
Actions.Workspace.Settings.Read,
|
||||
];
|
||||
},
|
||||
get [WorkspaceRole.Admin]() {
|
||||
return [
|
||||
...this[WorkspaceRole.Collaborator],
|
||||
Actions.Workspace.Users.Manage,
|
||||
Actions.Workspace.Settings.Update,
|
||||
Actions.Workspace.Properties.Create,
|
||||
Actions.Workspace.Properties.Update,
|
||||
Actions.Workspace.Properties.Delete,
|
||||
];
|
||||
},
|
||||
get [WorkspaceRole.Owner]() {
|
||||
return [
|
||||
...this[WorkspaceRole.Admin],
|
||||
Actions.Workspace.Delete,
|
||||
Actions.Workspace.TransferOwner,
|
||||
];
|
||||
},
|
||||
},
|
||||
DocRole: {
|
||||
get [DocRole.External]() {
|
||||
return [Actions.Doc.Read, Actions.Doc.Copy, Actions.Doc.Properties.Read];
|
||||
},
|
||||
get [DocRole.Reader]() {
|
||||
return [
|
||||
...this[DocRole.External],
|
||||
Actions.Doc.Users.Read,
|
||||
Actions.Doc.Duplicate,
|
||||
];
|
||||
},
|
||||
get [DocRole.Editor]() {
|
||||
return [
|
||||
...this[DocRole.Reader],
|
||||
Actions.Doc.Trash,
|
||||
Actions.Doc.Restore,
|
||||
Actions.Doc.Delete,
|
||||
Actions.Doc.Properties.Update,
|
||||
Actions.Doc.Update,
|
||||
];
|
||||
},
|
||||
get [DocRole.Manager]() {
|
||||
return [
|
||||
...this[DocRole.Editor],
|
||||
Actions.Doc.Publish,
|
||||
Actions.Doc.Users.Manage,
|
||||
];
|
||||
},
|
||||
get [DocRole.Owner]() {
|
||||
return [...this[DocRole.Manager], Actions.Doc.TransferOwner];
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
PgUserspaceDocStorageAdapter,
|
||||
PgWorkspaceDocStorageAdapter,
|
||||
} from '../doc';
|
||||
import { Permission, PermissionService } from '../permission';
|
||||
import { PermissionService, WorkspaceRole } from '../permission';
|
||||
import { DocID } from '../utils/doc';
|
||||
|
||||
const SubscribeMessage = (event: string) =>
|
||||
@@ -615,7 +615,7 @@ abstract class SyncSocketAdapter {
|
||||
|
||||
async join(userId: string, spaceId: string, roomType: RoomType = 'sync') {
|
||||
this.assertNotIn(spaceId, roomType);
|
||||
await this.assertAccessible(spaceId, userId, Permission.Read);
|
||||
await this.assertAccessible(spaceId, userId, WorkspaceRole.Collaborator);
|
||||
return this.client.join(this.room(spaceId, roomType));
|
||||
}
|
||||
|
||||
@@ -643,7 +643,7 @@ abstract class SyncSocketAdapter {
|
||||
abstract assertAccessible(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
permission?: Permission
|
||||
permission?: WorkspaceRole
|
||||
): Promise<void>;
|
||||
|
||||
push(spaceId: string, docId: string, updates: Buffer[], editorId: string) {
|
||||
@@ -694,7 +694,7 @@ class WorkspaceSyncAdapter extends SyncSocketAdapter {
|
||||
async assertAccessible(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
permission: Permission = Permission.Read
|
||||
permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
if (
|
||||
!(await this.permission.isWorkspaceMember(spaceId, userId, permission))
|
||||
@@ -712,7 +712,7 @@ class UserspaceSyncAdapter extends SyncSocketAdapter {
|
||||
async assertAccessible(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
_permission: Permission = Permission.Read
|
||||
_permission: WorkspaceRole = WorkspaceRole.Collaborator
|
||||
) {
|
||||
if (spaceId !== userId) {
|
||||
throw new SpaceAccessDenied({ spaceId });
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '../../base';
|
||||
import { CurrentUser, Public } from '../auth';
|
||||
import { PgWorkspaceDocStorageAdapter } from '../doc';
|
||||
import { Permission, PermissionService, PublicPageMode } from '../permission';
|
||||
import { PermissionService, PublicPageMode } from '../permission';
|
||||
import { WorkspaceBlobStorage } from '../storage';
|
||||
import { DocID } from '../utils/doc';
|
||||
|
||||
@@ -147,8 +147,8 @@ export class WorkspacesController {
|
||||
await this.permission.checkPagePermission(
|
||||
docId.workspace,
|
||||
docId.guid,
|
||||
user.id,
|
||||
Permission.Write
|
||||
'Doc_Read',
|
||||
user.id
|
||||
);
|
||||
|
||||
const history = await this.workspace.getDocHistory(
|
||||
|
||||
@@ -13,7 +13,7 @@ import { CurrentUser } from '../auth';
|
||||
import { Admin } from '../common';
|
||||
import { FeatureManagementService, FeatureType } from '../features';
|
||||
import { PermissionService } from '../permission';
|
||||
import { WorkspaceType } from './types';
|
||||
import { WorkspaceFeatureType, WorkspaceType } from './types';
|
||||
|
||||
@Resolver(() => WorkspaceType)
|
||||
export class WorkspaceManagementResolver {
|
||||
@@ -41,10 +41,10 @@ export class WorkspaceManagementResolver {
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Query(() => [WorkspaceType])
|
||||
@Query(() => [WorkspaceFeatureType])
|
||||
async listWorkspaceFeatures(
|
||||
@Args('feature', { type: () => FeatureType }) feature: FeatureType
|
||||
): Promise<WorkspaceType[]> {
|
||||
): Promise<WorkspaceFeatureType[]> {
|
||||
return this.feature.listFeatureWorkspaces(feature);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
import type { FileUpload } from '../../../base';
|
||||
import { BlobQuotaExceeded, CloudThrottlerGuard } from '../../../base';
|
||||
import { CurrentUser } from '../../auth';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||
import { QuotaManagementService } from '../../quota';
|
||||
import { WorkspaceBlobStorage } from '../../storage';
|
||||
import { WorkspaceBlobSizes, WorkspaceType } from '../types';
|
||||
@@ -102,7 +102,7 @@ export class WorkspaceBlobResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Write
|
||||
WorkspaceRole.Collaborator
|
||||
);
|
||||
|
||||
const checkExceeded =
|
||||
@@ -174,7 +174,7 @@ export class WorkspaceBlobResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Write
|
||||
WorkspaceRole.Collaborator
|
||||
);
|
||||
|
||||
await this.storage.release(workspaceId);
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { SnapshotHistory } from '@prisma/client';
|
||||
|
||||
import { CurrentUser } from '../../auth';
|
||||
import { PgWorkspaceDocStorageAdapter } from '../../doc';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { PermissionService } from '../../permission';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { WorkspaceType } from '../types';
|
||||
import { EditorType } from './workspace';
|
||||
@@ -79,8 +79,8 @@ export class DocHistoryResolver {
|
||||
await this.permission.checkPagePermission(
|
||||
docId.workspace,
|
||||
docId.guid,
|
||||
user.id,
|
||||
Permission.Write
|
||||
'Doc_Restore',
|
||||
user.id
|
||||
);
|
||||
|
||||
await this.workspace.rollbackDoc(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import {
|
||||
Args,
|
||||
Field,
|
||||
InputType,
|
||||
Int,
|
||||
Mutation,
|
||||
ObjectType,
|
||||
Parent,
|
||||
@@ -12,18 +15,25 @@ import type { WorkspacePage as PrismaWorkspacePage } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import {
|
||||
ExpectToGrantDocUserRoles,
|
||||
ExpectToPublishPage,
|
||||
ExpectToRevokeDocUserRoles,
|
||||
ExpectToRevokePublicPage,
|
||||
ExpectToUpdateDocUserRole,
|
||||
PageIsNotPublic,
|
||||
} from '../../../base';
|
||||
import { CurrentUser } from '../../auth';
|
||||
import {
|
||||
Permission,
|
||||
DocRole,
|
||||
PermissionService,
|
||||
PublicPageMode,
|
||||
WorkspaceRole,
|
||||
} from '../../permission';
|
||||
import { mapRoleToActions, PermissionsList } from '../../permission/types';
|
||||
import { UserType } from '../../user';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { WorkspaceType } from '../types';
|
||||
import { WorkspacePermissions } from './workspace';
|
||||
|
||||
registerEnumType(PublicPageMode, {
|
||||
name: 'PublicPageMode',
|
||||
@@ -45,8 +55,133 @@ class WorkspacePage implements Partial<PrismaWorkspacePage> {
|
||||
public!: boolean;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
class GrantDocUserRolesInput {
|
||||
@Field(() => String)
|
||||
docId!: string;
|
||||
|
||||
@Field(() => String)
|
||||
workspaceId!: string;
|
||||
|
||||
@Field(() => DocRole)
|
||||
role!: DocRole;
|
||||
|
||||
@Field(() => [String])
|
||||
userIds!: string[];
|
||||
}
|
||||
|
||||
@InputType()
|
||||
class PageGrantedUsersInput {
|
||||
@Field(() => Int)
|
||||
first!: number;
|
||||
|
||||
@Field(() => Int)
|
||||
offset?: number;
|
||||
|
||||
@Field(() => String, { description: 'Cursor', nullable: true })
|
||||
after?: string;
|
||||
|
||||
@Field(() => String, { description: 'Cursor', nullable: true })
|
||||
before?: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class GrantedDocUserType {
|
||||
@Field(() => UserType)
|
||||
user!: UserType;
|
||||
|
||||
@Field(() => DocRole)
|
||||
role!: DocRole;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class PageInfo {
|
||||
@Field(() => String, { nullable: true })
|
||||
startCursor?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
endCursor?: string;
|
||||
|
||||
@Field(() => Boolean)
|
||||
hasNextPage!: boolean;
|
||||
|
||||
@Field(() => Boolean)
|
||||
hasPreviousPage!: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class GrantedDocUserEdge {
|
||||
@Field(() => GrantedDocUserType)
|
||||
user!: GrantedDocUserType;
|
||||
|
||||
@Field(() => String)
|
||||
cursor!: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class GrantedDocUsersConnection {
|
||||
@Field(() => Int)
|
||||
totalCount!: number;
|
||||
|
||||
@Field(() => [GrantedDocUserEdge])
|
||||
edges!: GrantedDocUserEdge[];
|
||||
|
||||
@Field(() => PageInfo)
|
||||
pageInfo!: PageInfo;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class RolePermissions
|
||||
extends WorkspacePermissions
|
||||
implements PermissionsList
|
||||
{
|
||||
@Field()
|
||||
Doc_Read!: boolean;
|
||||
@Field()
|
||||
Doc_Copy!: boolean;
|
||||
@Field()
|
||||
Doc_Properties_Read!: boolean;
|
||||
@Field()
|
||||
Doc_Users_Read!: boolean;
|
||||
@Field()
|
||||
Doc_Duplicate!: boolean;
|
||||
@Field()
|
||||
Doc_Trash!: boolean;
|
||||
@Field()
|
||||
Doc_Restore!: boolean;
|
||||
@Field()
|
||||
Doc_Delete!: boolean;
|
||||
@Field()
|
||||
Doc_Properties_Update!: boolean;
|
||||
@Field()
|
||||
Doc_Update!: boolean;
|
||||
@Field()
|
||||
Doc_Publish!: boolean;
|
||||
@Field()
|
||||
Doc_Users_Manage!: boolean;
|
||||
@Field()
|
||||
Doc_TransferOwner!: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class DocType {
|
||||
@Field(() => String)
|
||||
id!: string;
|
||||
|
||||
@Field(() => Boolean)
|
||||
public!: boolean;
|
||||
|
||||
@Field(() => DocRole)
|
||||
role!: DocRole;
|
||||
|
||||
@Field(() => RolePermissions)
|
||||
permissions!: RolePermissions;
|
||||
}
|
||||
|
||||
@Resolver(() => WorkspaceType)
|
||||
export class PagePermissionResolver {
|
||||
private readonly logger = new Logger(PagePermissionResolver.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaClient,
|
||||
private readonly permission: PermissionService
|
||||
@@ -102,6 +237,122 @@ export class PagePermissionResolver {
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => DocType, {
|
||||
description: 'Check if current user has permission to access the page',
|
||||
complexity: 2,
|
||||
})
|
||||
async pagePermission(
|
||||
@Parent() workspace: WorkspaceType,
|
||||
@Args('pageId') pageId: string,
|
||||
@CurrentUser() user: CurrentUser
|
||||
): Promise<DocType> {
|
||||
const page = await this.prisma.workspacePage.findFirst({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
pageId,
|
||||
},
|
||||
select: {
|
||||
public: true,
|
||||
},
|
||||
});
|
||||
|
||||
const [permission, workspacePermission] = await this.prisma.$transaction(
|
||||
tx =>
|
||||
Promise.all([
|
||||
tx.workspacePageUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
pageId,
|
||||
userId: user.id,
|
||||
},
|
||||
}),
|
||||
tx.workspaceUserPermission.findFirst({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
userId: user.id,
|
||||
},
|
||||
}),
|
||||
])
|
||||
);
|
||||
return {
|
||||
id: pageId,
|
||||
public: page?.public ?? false,
|
||||
role: permission?.type ?? DocRole.External,
|
||||
permissions: mapRoleToActions(
|
||||
workspacePermission?.type,
|
||||
permission?.type
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ResolveField(() => GrantedDocUsersConnection, {
|
||||
description: 'Page granted users list',
|
||||
complexity: 4,
|
||||
})
|
||||
async pageGrantedUsersList(
|
||||
@Parent() workspace: WorkspaceType,
|
||||
@Args('pageId') pageId: string,
|
||||
@Args('pageGrantedUsersInput')
|
||||
pageGrantedUsersInput: PageGrantedUsersInput
|
||||
): Promise<GrantedDocUsersConnection> {
|
||||
const docId = new DocID(pageId, workspace.id);
|
||||
const [permissions, totalCount] = await this.prisma.$transaction(tx => {
|
||||
return Promise.all([
|
||||
tx.workspacePageUserPermission.findMany({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
pageId: docId.guid,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
take: pageGrantedUsersInput.first,
|
||||
skip: pageGrantedUsersInput.offset,
|
||||
cursor: pageGrantedUsersInput.after
|
||||
? {
|
||||
id: pageGrantedUsersInput.after,
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
tx.workspacePageUserPermission.count({
|
||||
where: {
|
||||
workspaceId: workspace.id,
|
||||
pageId: docId.guid,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
return {
|
||||
totalCount,
|
||||
edges: permissions.map(permission => ({
|
||||
user: {
|
||||
user: {
|
||||
id: permission.user.id,
|
||||
name: permission.user.name,
|
||||
email: permission.user.email,
|
||||
avatarUrl: permission.user.avatarUrl,
|
||||
emailVerified: permission.user.emailVerifiedAt !== null,
|
||||
hasPassword: permission.user.password !== null,
|
||||
},
|
||||
role: permission.type,
|
||||
},
|
||||
cursor: permission.id,
|
||||
})),
|
||||
pageInfo: {
|
||||
startCursor: permissions.at(0)?.id,
|
||||
endCursor: permissions.at(-1)?.id,
|
||||
hasNextPage: totalCount > pageGrantedUsersInput.first,
|
||||
hasPreviousPage:
|
||||
pageGrantedUsersInput.offset !== undefined &&
|
||||
pageGrantedUsersInput.offset > 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@@ -134,15 +385,26 @@ export class PagePermissionResolver {
|
||||
const docId = new DocID(pageId, workspaceId);
|
||||
|
||||
if (docId.isWorkspace) {
|
||||
this.logger.error('Expect to publish page, but it is a workspace', {
|
||||
workspaceId,
|
||||
pageId,
|
||||
});
|
||||
throw new ExpectToPublishPage();
|
||||
}
|
||||
|
||||
await this.permission.checkWorkspace(
|
||||
await this.permission.checkPagePermission(
|
||||
docId.workspace,
|
||||
user.id,
|
||||
Permission.Write
|
||||
docId.guid,
|
||||
'Doc_Publish',
|
||||
user.id
|
||||
);
|
||||
|
||||
this.logger.log('Publish page', {
|
||||
workspaceId,
|
||||
pageId,
|
||||
mode,
|
||||
});
|
||||
|
||||
return this.permission.publishPage(docId.workspace, docId.guid, mode);
|
||||
}
|
||||
|
||||
@@ -171,13 +433,18 @@ export class PagePermissionResolver {
|
||||
const docId = new DocID(pageId, workspaceId);
|
||||
|
||||
if (docId.isWorkspace) {
|
||||
this.logger.error('Expect to revoke public page, but it is a workspace', {
|
||||
workspaceId,
|
||||
pageId,
|
||||
});
|
||||
throw new ExpectToRevokePublicPage('Expect page not to be workspace');
|
||||
}
|
||||
|
||||
await this.permission.checkWorkspace(
|
||||
await this.permission.checkPagePermission(
|
||||
docId.workspace,
|
||||
user.id,
|
||||
Permission.Write
|
||||
docId.guid,
|
||||
'Doc_Publish',
|
||||
user.id
|
||||
);
|
||||
|
||||
const isPublic = await this.permission.isPublicPage(
|
||||
@@ -186,9 +453,148 @@ export class PagePermissionResolver {
|
||||
);
|
||||
|
||||
if (!isPublic) {
|
||||
this.logger.log('Expect to revoke public page, but it is not public', {
|
||||
workspaceId,
|
||||
pageId,
|
||||
});
|
||||
throw new PageIsNotPublic('Page is not public');
|
||||
}
|
||||
|
||||
this.logger.log('Revoke public page', {
|
||||
workspaceId,
|
||||
pageId,
|
||||
});
|
||||
|
||||
return this.permission.revokePublicPage(docId.workspace, docId.guid);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async grantDocUserRoles(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('input') input: GrantDocUserRolesInput
|
||||
): Promise<boolean> {
|
||||
const doc = new DocID(input.docId, input.workspaceId);
|
||||
const pairs = {
|
||||
spaceId: input.workspaceId,
|
||||
docId: input.docId,
|
||||
};
|
||||
if (doc.isWorkspace) {
|
||||
this.logger.error(
|
||||
'Expect to grant doc user roles, but it is a workspace',
|
||||
pairs
|
||||
);
|
||||
throw new ExpectToGrantDocUserRoles(
|
||||
pairs,
|
||||
'Expect doc not to be workspace'
|
||||
);
|
||||
}
|
||||
await this.permission.checkPagePermission(
|
||||
doc.workspace,
|
||||
doc.guid,
|
||||
'Doc_Users_Manage',
|
||||
user.id
|
||||
);
|
||||
await this.permission.grantPagePermission(
|
||||
doc.workspace,
|
||||
doc.guid,
|
||||
input.userIds,
|
||||
input.role
|
||||
);
|
||||
this.logger.log('Grant doc user roles', {
|
||||
...pairs,
|
||||
userIds: input.userIds,
|
||||
role: input.role,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async revokeDocUserRoles(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('docId') docId: string,
|
||||
@Args('userIds', { type: () => [String] }) userIds: string[]
|
||||
): Promise<boolean> {
|
||||
const doc = new DocID(docId);
|
||||
const pairs = {
|
||||
spaceId: doc.workspace,
|
||||
docId: doc.guid,
|
||||
};
|
||||
if (doc.isWorkspace) {
|
||||
this.logger.error(
|
||||
'Expect to revoke doc user roles, but it is a workspace',
|
||||
pairs
|
||||
);
|
||||
throw new ExpectToRevokeDocUserRoles(
|
||||
pairs,
|
||||
'Expect doc not to be workspace'
|
||||
);
|
||||
}
|
||||
await this.permission.checkWorkspace(
|
||||
doc.workspace,
|
||||
user.id,
|
||||
WorkspaceRole.Collaborator
|
||||
);
|
||||
await this.permission.revokePage(doc.workspace, doc.guid, userIds);
|
||||
this.logger.log('Revoke doc user roles', {
|
||||
...pairs,
|
||||
userIds: userIds,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async updateDocUserRole(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('docId') docId: string,
|
||||
@Args('userId') userId: string,
|
||||
@Args('role', { type: () => DocRole }) role: DocRole
|
||||
): Promise<boolean> {
|
||||
const doc = new DocID(docId);
|
||||
const pairs = {
|
||||
spaceId: doc.workspace,
|
||||
docId: doc.guid,
|
||||
};
|
||||
if (doc.isWorkspace) {
|
||||
this.logger.error(
|
||||
'Expect to update doc user role, but it is a workspace',
|
||||
pairs
|
||||
);
|
||||
throw new ExpectToUpdateDocUserRole(
|
||||
pairs,
|
||||
'Expect doc not to be workspace'
|
||||
);
|
||||
}
|
||||
await this.permission.checkWorkspace(
|
||||
doc.workspace,
|
||||
user.id,
|
||||
WorkspaceRole.Collaborator
|
||||
);
|
||||
if (role === DocRole.Owner) {
|
||||
const ret = await this.permission.grantPagePermission(
|
||||
doc.workspace,
|
||||
doc.guid,
|
||||
[userId],
|
||||
role
|
||||
);
|
||||
this.logger.log('Transfer doc owner', {
|
||||
...pairs,
|
||||
userId: userId,
|
||||
role: role,
|
||||
});
|
||||
return ret.length > 0;
|
||||
} else {
|
||||
await this.permission.updatePagePermission(
|
||||
doc.workspace,
|
||||
doc.guid,
|
||||
userId,
|
||||
role
|
||||
);
|
||||
this.logger.log('Update doc user role', {
|
||||
...pairs,
|
||||
userId: userId,
|
||||
role: role,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../base';
|
||||
import { Models } from '../../../models';
|
||||
import { DocContentService } from '../../doc-renderer';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||
import { WorkspaceBlobStorage } from '../../storage';
|
||||
|
||||
export const defaultWorkspaceAvatar =
|
||||
@@ -221,14 +221,14 @@ export class WorkspaceService {
|
||||
|
||||
async sendRoleChangedEmail(
|
||||
userId: string,
|
||||
ws: { id: string; role: Permission }
|
||||
ws: { id: string; role: WorkspaceRole }
|
||||
) {
|
||||
const user = await this.models.user.getPublicUser(userId);
|
||||
if (!user) throw new UserNotFound();
|
||||
|
||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||
|
||||
if (ws.role === Permission.Admin) {
|
||||
if (ws.role === WorkspaceRole.Admin) {
|
||||
await this.mailer.sendTeamBecomeAdminMail(user.email, {
|
||||
workspace,
|
||||
url: this.url.link(`/workspace/${workspace.id}`),
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '../../../base';
|
||||
import { Models } from '../../../models';
|
||||
import { CurrentUser } from '../../auth';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||
import { QuotaManagementService } from '../../quota';
|
||||
import {
|
||||
InviteLink,
|
||||
@@ -71,7 +71,7 @@ export class TeamWorkspaceResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
|
||||
if (emails.length > 512) {
|
||||
@@ -113,7 +113,7 @@ export class TeamWorkspaceResolver {
|
||||
ret.inviteId = await this.permissions.grant(
|
||||
workspaceId,
|
||||
target.id,
|
||||
Permission.Write,
|
||||
WorkspaceRole.Collaborator,
|
||||
needMoreSeat
|
||||
? WorkspaceMemberStatus.NeedMoreSeat
|
||||
: WorkspaceMemberStatus.Pending
|
||||
@@ -159,7 +159,7 @@ export class TeamWorkspaceResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspace.id,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
|
||||
const cacheId = `workspace:inviteLink:${workspace.id}`;
|
||||
@@ -186,7 +186,7 @@ export class TeamWorkspaceResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
const cacheWorkspaceId = `workspace:inviteLink:${workspaceId}`;
|
||||
const invite = await this.cache.get<{ inviteId: string }>(cacheWorkspaceId);
|
||||
@@ -222,7 +222,7 @@ export class TeamWorkspaceResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
const cacheId = `workspace:inviteLink:${workspaceId}`;
|
||||
return await this.cache.delete(cacheId);
|
||||
@@ -237,7 +237,7 @@ export class TeamWorkspaceResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -257,7 +257,7 @@ export class TeamWorkspaceResolver {
|
||||
const result = await this.permissions.grant(
|
||||
workspaceId,
|
||||
userId,
|
||||
Permission.Write,
|
||||
WorkspaceRole.Collaborator,
|
||||
WorkspaceMemberStatus.Accepted
|
||||
);
|
||||
|
||||
@@ -283,12 +283,12 @@ export class TeamWorkspaceResolver {
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceId') workspaceId: string,
|
||||
@Args('userId') userId: string,
|
||||
@Args('permission', { type: () => Permission }) permission: Permission
|
||||
@Args('permission', { type: () => WorkspaceRole }) permission: WorkspaceRole
|
||||
) {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Owner
|
||||
WorkspaceRole.Owner
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -311,7 +311,7 @@ export class TeamWorkspaceResolver {
|
||||
);
|
||||
|
||||
if (result) {
|
||||
if (permission === Permission.Owner) {
|
||||
if (permission === WorkspaceRole.Owner) {
|
||||
this.event.emit('workspace.members.ownershipTransferred', {
|
||||
workspaceId,
|
||||
from: user.id,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import {
|
||||
Args,
|
||||
Field,
|
||||
@@ -15,6 +14,7 @@ import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
|
||||
import type { FileUpload } from '../../../base';
|
||||
import {
|
||||
AFFiNELogger,
|
||||
AlreadyInSpace,
|
||||
Cache,
|
||||
DocNotFound,
|
||||
@@ -33,7 +33,11 @@ import {
|
||||
import { Models } from '../../../models';
|
||||
import { CurrentUser, Public } from '../../auth';
|
||||
import { type Editor, PgWorkspaceDocStorageAdapter } from '../../doc';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { PermissionService, WorkspaceRole } from '../../permission';
|
||||
import {
|
||||
mapWorkspaceRoleToWorkspaceActions,
|
||||
WorkspacePermissionsList,
|
||||
} from '../../permission/types';
|
||||
import { QuotaManagementService, QuotaQueryType } from '../../quota';
|
||||
import { UserType } from '../../user';
|
||||
import {
|
||||
@@ -68,6 +72,45 @@ class WorkspacePageMeta {
|
||||
updatedBy!: EditorType | null;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class WorkspacePermissions implements WorkspacePermissionsList {
|
||||
@Field()
|
||||
Workspace_Organize_Read!: boolean;
|
||||
@Field()
|
||||
Workspace_Sync!: boolean;
|
||||
@Field()
|
||||
Workspace_CreateDoc!: boolean;
|
||||
@Field()
|
||||
Workspace_Users_Read!: boolean;
|
||||
@Field()
|
||||
Workspace_Properties_Read!: boolean;
|
||||
@Field()
|
||||
Workspace_Settings_Read!: boolean;
|
||||
@Field()
|
||||
Workspace_Users_Manage!: boolean;
|
||||
@Field()
|
||||
Workspace_Settings_Update!: boolean;
|
||||
@Field()
|
||||
Workspace_Properties_Create!: boolean;
|
||||
@Field()
|
||||
Workspace_Properties_Update!: boolean;
|
||||
@Field()
|
||||
Workspace_Properties_Delete!: boolean;
|
||||
@Field()
|
||||
Workspace_Delete!: boolean;
|
||||
@Field()
|
||||
Workspace_TransferOwner!: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class WorkspaceRolePermissions {
|
||||
@Field(() => WorkspaceRole)
|
||||
role!: WorkspaceRole;
|
||||
|
||||
@Field(() => WorkspacePermissions)
|
||||
permissions!: WorkspacePermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workspace resolver
|
||||
* Public apis rate limit: 10 req/m
|
||||
@@ -75,8 +118,6 @@ class WorkspacePageMeta {
|
||||
*/
|
||||
@Resolver(() => WorkspaceType)
|
||||
export class WorkspaceResolver {
|
||||
private readonly logger = new Logger(WorkspaceResolver.name);
|
||||
|
||||
constructor(
|
||||
private readonly cache: Cache,
|
||||
private readonly prisma: PrismaClient,
|
||||
@@ -86,29 +127,32 @@ export class WorkspaceResolver {
|
||||
private readonly event: EventBus,
|
||||
private readonly mutex: RequestMutex,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly workspaceStorage: PgWorkspaceDocStorageAdapter
|
||||
) {}
|
||||
private readonly workspaceStorage: PgWorkspaceDocStorageAdapter,
|
||||
private readonly logger: AFFiNELogger
|
||||
) {
|
||||
logger.setContext(WorkspaceResolver.name);
|
||||
}
|
||||
|
||||
@ResolveField(() => Permission, {
|
||||
description: 'Permission of current signed in user in workspace',
|
||||
@ResolveField(() => WorkspaceRole, {
|
||||
description: 'Role of current signed in user in workspace',
|
||||
complexity: 2,
|
||||
})
|
||||
async permission(
|
||||
async role(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Parent() workspace: WorkspaceType
|
||||
) {
|
||||
// may applied in workspaces query
|
||||
if ('permission' in workspace) {
|
||||
return workspace.permission;
|
||||
if ('role' in workspace) {
|
||||
return workspace.role;
|
||||
}
|
||||
|
||||
const permission = await this.permissions.get(workspace.id, user.id);
|
||||
const role = await this.permissions.get(workspace.id, user.id);
|
||||
|
||||
if (!permission) {
|
||||
if (!role) {
|
||||
throw new SpaceAccessDenied({ spaceId: workspace.id });
|
||||
}
|
||||
|
||||
return permission;
|
||||
return role;
|
||||
}
|
||||
|
||||
@ResolveField(() => Int, {
|
||||
@@ -249,7 +293,7 @@ export class WorkspaceResolver {
|
||||
return this.permissions.tryCheckWorkspaceIs(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
}
|
||||
|
||||
@@ -279,6 +323,7 @@ export class WorkspaceResolver {
|
||||
return {
|
||||
...workspace,
|
||||
permission: type,
|
||||
role: type,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -297,6 +342,25 @@ export class WorkspaceResolver {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@Query(() => WorkspaceRolePermissions, {
|
||||
description: 'Get workspace role permissions',
|
||||
})
|
||||
async workspaceRolePermissions(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('id') id: string
|
||||
) {
|
||||
const workspace = await this.prisma.workspaceUserPermission.findFirst({
|
||||
where: { workspaceId: id, userId: user.id },
|
||||
});
|
||||
if (!workspace) {
|
||||
throw new SpaceAccessDenied({ spaceId: id });
|
||||
}
|
||||
return {
|
||||
role: workspace.type,
|
||||
permissions: mapWorkspaceRoleToWorkspaceActions(workspace.type),
|
||||
};
|
||||
}
|
||||
|
||||
@Mutation(() => WorkspaceType, {
|
||||
description: 'Create a new workspace',
|
||||
})
|
||||
@@ -312,7 +376,7 @@ export class WorkspaceResolver {
|
||||
public: false,
|
||||
permissions: {
|
||||
create: {
|
||||
type: Permission.Owner,
|
||||
type: WorkspaceRole.Owner,
|
||||
userId: user.id,
|
||||
accepted: true,
|
||||
status: WorkspaceMemberStatus.Accepted,
|
||||
@@ -323,21 +387,18 @@ export class WorkspaceResolver {
|
||||
|
||||
if (init) {
|
||||
// convert stream to buffer
|
||||
const buffer = await new Promise<Buffer>(resolve => {
|
||||
const stream = init.createReadStream();
|
||||
const chunks: Uint8Array[] = [];
|
||||
stream.on('data', chunk => {
|
||||
const chunks: Uint8Array[] = [];
|
||||
try {
|
||||
for await (const chunk of init.createReadStream()) {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
stream.on('error', () => {
|
||||
resolve(Buffer.from([]));
|
||||
});
|
||||
stream.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('Failed to get file content from upload stream', e);
|
||||
chunks.length = 0;
|
||||
}
|
||||
const buffer = chunks.length ? Buffer.concat(chunks) : null;
|
||||
|
||||
if (buffer.length) {
|
||||
if (buffer) {
|
||||
await this.prisma.snapshot.create({
|
||||
data: {
|
||||
id: workspace.id,
|
||||
@@ -364,7 +425,7 @@ export class WorkspaceResolver {
|
||||
await this.permissions.checkWorkspace(
|
||||
id,
|
||||
user.id,
|
||||
isTeam ? Permission.Owner : Permission.Admin
|
||||
isTeam ? WorkspaceRole.Owner : WorkspaceRole.Admin
|
||||
);
|
||||
|
||||
return this.prisma.workspace.update({
|
||||
@@ -380,7 +441,7 @@ export class WorkspaceResolver {
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('id') id: string
|
||||
) {
|
||||
await this.permissions.checkWorkspace(id, user.id, Permission.Owner);
|
||||
await this.permissions.checkWorkspace(id, user.id, WorkspaceRole.Owner);
|
||||
|
||||
await this.prisma.workspace.delete({
|
||||
where: {
|
||||
@@ -401,16 +462,16 @@ export class WorkspaceResolver {
|
||||
@Args('email') email: string,
|
||||
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean,
|
||||
@Args('permission', {
|
||||
type: () => Permission,
|
||||
type: () => WorkspaceRole,
|
||||
nullable: true,
|
||||
deprecationReason: 'never used',
|
||||
})
|
||||
_permission?: Permission
|
||||
_permission?: WorkspaceRole
|
||||
) {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -418,7 +479,7 @@ export class WorkspaceResolver {
|
||||
const lockFlag = `invite:${workspaceId}`;
|
||||
await using lock = await this.mutex.acquire(lockFlag);
|
||||
if (!lock) {
|
||||
return new TooManyRequest();
|
||||
throw new TooManyRequest();
|
||||
}
|
||||
|
||||
// member limit check
|
||||
@@ -445,7 +506,7 @@ export class WorkspaceResolver {
|
||||
const inviteId = await this.permissions.grant(
|
||||
workspaceId,
|
||||
target.id,
|
||||
Permission.Write
|
||||
WorkspaceRole.Collaborator
|
||||
);
|
||||
if (sendInviteMail) {
|
||||
try {
|
||||
@@ -474,10 +535,10 @@ export class WorkspaceResolver {
|
||||
} catch (e) {
|
||||
// pass through user friendly error
|
||||
if (e instanceof UserFriendlyError) {
|
||||
return e;
|
||||
throw e;
|
||||
}
|
||||
this.logger.error('failed to invite user', e);
|
||||
return new TooManyRequest();
|
||||
throw new TooManyRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,20 +573,20 @@ export class WorkspaceResolver {
|
||||
const isAdmin = await this.permissions.tryCheckWorkspaceIs(
|
||||
workspaceId,
|
||||
userId,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
if (isTeam && isAdmin) {
|
||||
// only owner can revoke team workspace admin
|
||||
await this.permissions.checkWorkspaceIs(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Owner
|
||||
WorkspaceRole.Owner
|
||||
);
|
||||
} else {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Admin
|
||||
WorkspaceRole.Admin
|
||||
);
|
||||
}
|
||||
|
||||
@@ -543,7 +604,7 @@ export class WorkspaceResolver {
|
||||
const lockFlag = `invite:${workspaceId}`;
|
||||
await using lock = await this.mutex.acquire(lockFlag);
|
||||
if (!lock) {
|
||||
return new TooManyRequest();
|
||||
throw new TooManyRequest();
|
||||
}
|
||||
|
||||
const isTeam = await this.quota.isTeamWorkspace(workspaceId);
|
||||
@@ -553,7 +614,7 @@ export class WorkspaceResolver {
|
||||
user.id
|
||||
);
|
||||
if (status === WorkspaceMemberStatus.Accepted) {
|
||||
return new AlreadyInSpace({ spaceId: workspaceId });
|
||||
throw new AlreadyInSpace({ spaceId: workspaceId });
|
||||
}
|
||||
|
||||
// invite link
|
||||
@@ -568,7 +629,7 @@ export class WorkspaceResolver {
|
||||
await this.permissions.grant(
|
||||
workspaceId,
|
||||
user.id,
|
||||
Permission.Write,
|
||||
WorkspaceRole.Collaborator,
|
||||
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
||||
);
|
||||
const memberCount =
|
||||
@@ -579,7 +640,7 @@ export class WorkspaceResolver {
|
||||
});
|
||||
return true;
|
||||
} else if (!status) {
|
||||
return new MemberQuotaExceeded();
|
||||
throw new MemberQuotaExceeded();
|
||||
}
|
||||
} else {
|
||||
const inviteId = await this.permissions.grant(workspaceId, user.id);
|
||||
|
||||
@@ -8,17 +8,28 @@ import {
|
||||
PickType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
import { Workspace, WorkspaceMemberStatus } from '@prisma/client';
|
||||
import { WorkspaceMemberStatus } from '@prisma/client';
|
||||
import { SafeIntResolver } from 'graphql-scalars';
|
||||
|
||||
import { Permission } from '../permission';
|
||||
import { DocRole, WorkspaceRole } from '../permission';
|
||||
import { UserType } from '../user/types';
|
||||
|
||||
registerEnumType(Permission, {
|
||||
registerEnumType(WorkspaceRole, {
|
||||
name: 'WorkspaceRole',
|
||||
description: 'User role in workspace',
|
||||
});
|
||||
|
||||
// @deprecated
|
||||
registerEnumType(WorkspaceRole, {
|
||||
name: 'Permission',
|
||||
description: 'User permission in workspace',
|
||||
});
|
||||
|
||||
registerEnumType(DocRole, {
|
||||
name: 'DocRole',
|
||||
description: 'User permission in doc',
|
||||
});
|
||||
|
||||
registerEnumType(WorkspaceMemberStatus, {
|
||||
name: 'WorkspaceMemberStatus',
|
||||
description: 'Member invite status in workspace',
|
||||
@@ -33,8 +44,14 @@ export class InviteUserType extends OmitType(
|
||||
@Field(() => ID)
|
||||
id!: string;
|
||||
|
||||
@Field(() => Permission, { description: 'User permission in workspace' })
|
||||
permission!: Permission;
|
||||
@Field(() => WorkspaceRole, {
|
||||
deprecationReason: 'Use role instead',
|
||||
description: 'User permission in workspace',
|
||||
})
|
||||
permission!: WorkspaceRole;
|
||||
|
||||
@Field(() => WorkspaceRole, { description: 'User role in workspace' })
|
||||
role!: WorkspaceRole;
|
||||
|
||||
@Field({ description: 'Invite id' })
|
||||
inviteId!: string;
|
||||
@@ -52,22 +69,25 @@ export class InviteUserType extends OmitType(
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class WorkspaceType implements Partial<Workspace> {
|
||||
export class WorkspaceFeatureType {
|
||||
@Field(() => ID)
|
||||
id!: string;
|
||||
|
||||
@Field({ description: 'is Public workspace' })
|
||||
public!: boolean;
|
||||
|
||||
@Field({ description: 'Workspace created date' })
|
||||
createdAt!: Date;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class WorkspaceType extends WorkspaceFeatureType {
|
||||
@Field({ description: 'Enable AI' })
|
||||
enableAi!: boolean;
|
||||
|
||||
@Field({ description: 'Enable url previous when sharing' })
|
||||
enableUrlPreview!: boolean;
|
||||
|
||||
@Field({ description: 'Workspace created date' })
|
||||
createdAt!: Date;
|
||||
|
||||
@Field(() => [InviteUserType], {
|
||||
description: 'Members of workspace',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user