Skip to content

Commit

Permalink
document title permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
nikgraf committed Jan 19, 2024
1 parent 9069f39 commit 40e03c1
Show file tree
Hide file tree
Showing 34 changed files with 560 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ export function CreateWorkspaceForm(props: CreateWorkspaceFormProps) {
},
workspaceId: event.transaction.id,
workspaceKeyId,
workspaceMemberDevicesProof,
documentId: createDocumentChainEvent.transaction.id,
});

const encryptedWorkspaceInfo = encryptWorkspaceInfo({
Expand Down Expand Up @@ -253,6 +255,7 @@ export function CreateWorkspaceForm(props: CreateWorkspaceFormProps) {
document: {
nameCiphertext: encryptedDocumentTitle.ciphertext,
nameNonce: encryptedDocumentTitle.nonce,
nameSignature: encryptedDocumentTitle.signature,
subkeyId: encryptedDocumentTitle.subkeyId,
snapshot,
serializedDocumentChainEvent: JSON.stringify(
Expand Down
53 changes: 46 additions & 7 deletions apps/app/components/page/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,17 @@ export default function Page({
snapshotId,
activeDevice,
});

const workspaceMemberDevicesProof =
await loadRemoteWorkspaceMemberDevicesProofQuery({ workspaceId });

snapshotInFlightDataRef.current = {
snapshotKey: {
keyDerivationTrace: snapshotKeyData.keyDerivationTrace,
key: sodium.from_base64(snapshotKeyData.key),
},
documentChainState: latestDocumentChainState,
workspaceMemberDevicesProofEntry: getLastWorkspaceMemberDevicesProof({
workspaceId,
}),
workspaceMemberDevicesProofEntry: workspaceMemberDevicesProof,
};

const workspace = await getWorkspace({
Expand All @@ -195,16 +197,44 @@ export default function Page({
throw new Error("Workspace or workspaceKeys not found");
}

const documentTitleWorkspaceMemberDevicesProof =
await getLocalOrLoadRemoteWorkspaceMemberDevicesProofQueryByHash({
workspaceId,
hash: document.nameWorkspaceMemberDevicesProofHash,
});
if (!documentTitleWorkspaceMemberDevicesProof) {
throw new Error(
"documentTitleWorkspaceMemberDevicesProof not found for document"
);
}

const isValid = isValidDeviceSigningPublicKey({
signingPublicKey: document.nameCreatorDeviceSigningPublicKey,
workspaceMemberDevicesProofEntry:
documentTitleWorkspaceMemberDevicesProof,
workspaceId,
minimumRole: "EDITOR",
});
if (!isValid) {
throw new Error(
"Invalid signing public key for the workspaceMemberDevicesProof for decryptDocumentTitleBasedOnSnapshotKey"
);
}

const documentTitle = decryptDocumentTitleBasedOnSnapshotKey({
snapshotKey: sodium.to_base64(snapshotKeyRef.current!.key),
ciphertext: document.nameCiphertext,
nonce: document.nameNonce,
subkeyId: document.subkeyId,
documentId: docId,
workspaceId,
workspaceMemberDevicesProof:
documentTitleWorkspaceMemberDevicesProof.proof,
signature: document.nameSignature,
creatorDeviceSigningPublicKey:
document.nameCreatorDeviceSigningPublicKey,
});

const workspaceMemberDevicesProof =
await loadRemoteWorkspaceMemberDevicesProofQuery({ workspaceId });

const documentTitleData = encryptDocumentTitle({
title: documentTitle,
activeDevice,
Expand All @@ -214,6 +244,8 @@ export default function Page({
workspaceKeyBox: workspace.currentWorkspaceKey.workspaceKeyBox!,
workspaceId,
workspaceKeyId: workspace.currentWorkspaceKey.id,
workspaceMemberDevicesProof: workspaceMemberDevicesProof.proof,
documentId: docId,
});

let documentShareLinkDeviceBoxes: DocumentShareLinkDeviceBox[] = [];
Expand Down Expand Up @@ -257,7 +289,14 @@ export default function Page({
documentChainEventHash: latestDocumentChainState.eventHash,
},
additionalServerData: {
documentTitleData,
documentTitleData: {
ciphertext: documentTitleData.ciphertext,
nonce: documentTitleData.nonce,
subkeyId: documentTitleData.subkeyId,
signature: documentTitleData.signature,
workspaceMemberDevicesProofHash:
workspaceMemberDevicesProof.proof.hash,
},
documentShareLinkDeviceBoxes,
},
};
Expand Down
6 changes: 6 additions & 0 deletions apps/app/components/sidebarFolder/SidebarFolder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ export default function SidebarFolder(props: Props) {
signatureKeyPair,
sodium
);

const documentNameData = encryptDocumentTitle({
title: documentName,
activeDevice,
Expand All @@ -421,12 +422,17 @@ export default function SidebarFolder(props: Props) {
workspaceKeyBox: workspace.currentWorkspaceKey.workspaceKeyBox!,
workspaceId: workspace.id,
workspaceKeyId: workspace.currentWorkspaceKey.id,
documentId,
workspaceMemberDevicesProof: workspaceMemberDevicesProof.proof,
});
const result = await runCreateDocumentMutation(
{
input: {
nameCiphertext: documentNameData.ciphertext,
nameNonce: documentNameData.nonce,
nameSignature: documentNameData.signature,
nameWorkspaceMemberDevicesProofHash:
workspaceMemberDevicesProof.proof.hash,
subkeyId: documentNameData.subkeyId,
workspaceId: props.workspaceId,
parentFolderId: props.folderId,
Expand Down
23 changes: 20 additions & 3 deletions apps/app/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ export type CreateCommentResult = {
export type CreateDocumentInput = {
nameCiphertext: Scalars['String']['input'];
nameNonce: Scalars['String']['input'];
nameSignature: Scalars['String']['input'];
nameWorkspaceMemberDevicesProofHash: Scalars['String']['input'];
parentFolderId: Scalars['String']['input'];
serializedDocumentChainEvent: Scalars['String']['input'];
snapshot: DocumentSnapshotInput;
Expand Down Expand Up @@ -210,6 +212,7 @@ export type CreateFolderResult = {
export type CreateInitialDocumentInput = {
nameCiphertext: Scalars['String']['input'];
nameNonce: Scalars['String']['input'];
nameSignature: Scalars['String']['input'];
serializedDocumentChainEvent: Scalars['String']['input'];
snapshot: DocumentSnapshotInput;
subkeyId: Scalars['String']['input'];
Expand Down Expand Up @@ -371,7 +374,10 @@ export type Document = {
__typename?: 'Document';
id: Scalars['String']['output'];
nameCiphertext: Scalars['String']['output'];
nameCreatorDeviceSigningPublicKey: Scalars['String']['output'];
nameNonce: Scalars['String']['output'];
nameSignature: Scalars['String']['output'];
nameWorkspaceMemberDevicesProofHash: Scalars['String']['output'];
parentFolderId?: Maybe<Scalars['String']['output']>;
rootFolderId?: Maybe<Scalars['String']['output']>;
subkeyId: Scalars['String']['output'];
Expand Down Expand Up @@ -1192,6 +1198,8 @@ export type UpdateDocumentNameInput = {
id: Scalars['String']['input'];
nameCiphertext: Scalars['String']['input'];
nameNonce: Scalars['String']['input'];
nameSignature: Scalars['String']['input'];
nameWorkspaceMemberDevicesProofHash: Scalars['String']['input'];
subkeyId: Scalars['String']['input'];
workspaceKeyId: Scalars['String']['input'];
};
Expand Down Expand Up @@ -1691,7 +1699,7 @@ export type UpdateDocumentNameMutationVariables = Exact<{
}>;


export type UpdateDocumentNameMutation = { __typename?: 'Mutation', updateDocumentName?: { __typename?: 'UpdateDocumentNameResult', document?: { __typename?: 'Document', id: string, nameCiphertext: string, nameNonce: string, parentFolderId?: string | null, workspaceId: string, subkeyId: string } | null } | null };
export type UpdateDocumentNameMutation = { __typename?: 'Mutation', updateDocumentName?: { __typename?: 'UpdateDocumentNameResult', document?: { __typename?: 'Document', id: string, nameCiphertext: string, nameNonce: string, nameSignature: string, nameWorkspaceMemberDevicesProofHash: string, nameCreatorDeviceSigningPublicKey: string, parentFolderId?: string | null, workspaceId: string, subkeyId: string } | null } | null };

export type UpdateFolderNameMutationVariables = Exact<{
input: UpdateFolderNameInput;
Expand Down Expand Up @@ -1745,7 +1753,7 @@ export type DocumentQueryVariables = Exact<{
}>;


export type DocumentQuery = { __typename?: 'Query', document?: { __typename?: 'Document', id: string, nameCiphertext: string, nameNonce: string, parentFolderId?: string | null, workspaceId: string, subkeyId: string } | null };
export type DocumentQuery = { __typename?: 'Query', document?: { __typename?: 'Document', id: string, nameCiphertext: string, nameNonce: string, nameSignature: string, nameWorkspaceMemberDevicesProofHash: string, nameCreatorDeviceSigningPublicKey: string, parentFolderId?: string | null, workspaceId: string, subkeyId: string } | null };

export type DocumentChainQueryVariables = Exact<{
documentId: Scalars['ID']['input'];
Expand Down Expand Up @@ -1793,7 +1801,7 @@ export type DocumentsQueryVariables = Exact<{
}>;


export type DocumentsQuery = { __typename?: 'Query', documents?: { __typename?: 'DocumentConnection', nodes?: Array<{ __typename?: 'Document', id: string, nameCiphertext: string, nameNonce: string, parentFolderId?: string | null, rootFolderId?: string | null, workspaceId: string, subkeyId: string } | null> | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } | null };
export type DocumentsQuery = { __typename?: 'Query', documents?: { __typename?: 'DocumentConnection', nodes?: Array<{ __typename?: 'Document', id: string, nameCiphertext: string, nameNonce: string, nameSignature: string, nameWorkspaceMemberDevicesProofHash: string, nameCreatorDeviceSigningPublicKey: string, parentFolderId?: string | null, rootFolderId?: string | null, workspaceId: string, subkeyId: string } | null> | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } | null };

export type EncryptedWebDeviceQueryVariables = Exact<{
accessToken: Scalars['String']['input'];
Expand Down Expand Up @@ -2416,6 +2424,9 @@ export const UpdateDocumentNameDocument = gql`
id
nameCiphertext
nameNonce
nameSignature
nameWorkspaceMemberDevicesProofHash
nameCreatorDeviceSigningPublicKey
parentFolderId
workspaceId
subkeyId
Expand Down Expand Up @@ -2570,6 +2581,9 @@ export const DocumentDocument = gql`
id
nameCiphertext
nameNonce
nameSignature
nameWorkspaceMemberDevicesProofHash
nameCreatorDeviceSigningPublicKey
parentFolderId
workspaceId
subkeyId
Expand Down Expand Up @@ -2691,6 +2705,9 @@ export const DocumentsDocument = gql`
id
nameCiphertext
nameNonce
nameSignature
nameWorkspaceMemberDevicesProofHash
nameCreatorDeviceSigningPublicKey
parentFolderId
rootFolderId
workspaceId
Expand Down
3 changes: 3 additions & 0 deletions apps/app/graphql/mutations/updateDocumentName.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ mutation updateDocumentName($input: UpdateDocumentNameInput!) {
id
nameCiphertext
nameNonce
nameSignature
nameWorkspaceMemberDevicesProofHash
nameCreatorDeviceSigningPublicKey
parentFolderId
workspaceId
subkeyId
Expand Down
3 changes: 3 additions & 0 deletions apps/app/graphql/queries/document.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ query document($id: ID!) {
id
nameCiphertext
nameNonce
nameSignature
nameWorkspaceMemberDevicesProofHash
nameCreatorDeviceSigningPublicKey
parentFolderId
workspaceId
subkeyId
Expand Down
3 changes: 3 additions & 0 deletions apps/app/graphql/queries/documents.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ query documents($parentFolderId: ID!, $first: Int! = 100, $after: String) {
id
nameCiphertext
nameNonce
nameSignature
nameWorkspaceMemberDevicesProofHash
nameCreatorDeviceSigningPublicKey
parentFolderId
rootFolderId
workspaceId
Expand Down
27 changes: 27 additions & 0 deletions apps/app/store/documentStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
runSnapshotQuery,
runWorkspaceQuery,
} from "../generated/graphql";
import { isValidDeviceSigningPublicKey } from "../utils/isValidDeviceSigningPublicKey/isValidDeviceSigningPublicKey";
import * as sql from "./sql/sql";
import { getLocalOrLoadRemoteWorkspaceMemberDevicesProofQueryByHash } from "./workspaceMemberDevicesProofStore";

export const table = "document_v2";

Expand Down Expand Up @@ -162,6 +164,27 @@ export const loadRemoteDocumentName = async ({
}
}

const workspaceMemberDevicesProof =
await getLocalOrLoadRemoteWorkspaceMemberDevicesProofQueryByHash({
workspaceId,
hash: document.nameWorkspaceMemberDevicesProofHash,
});
if (!workspaceMemberDevicesProof) {
throw new Error("workspaceMemberDevicesProof not found");
}

const isValid = isValidDeviceSigningPublicKey({
signingPublicKey: document.nameCreatorDeviceSigningPublicKey,
workspaceMemberDevicesProofEntry: workspaceMemberDevicesProof,
workspaceId,
minimumRole: "EDITOR",
});
if (!isValid) {
throw new Error(
"Invalid signing public key for the workspaceMemberDevicesProof for decryptDocumentTitle"
);
}

name = decryptDocumentTitle({
ciphertext: document.nameCiphertext,
nonce: document.nameNonce,
Expand All @@ -173,6 +196,10 @@ export const loadRemoteDocumentName = async ({
workspaceKeyBox: documentWorkspaceKey.workspaceKeyBox,
workspaceId,
workspaceKeyId: documentWorkspaceKey.id,
documentId,
workspaceMemberDevicesProof: workspaceMemberDevicesProof.proof,
signature: document.nameSignature,
creatorDeviceSigningPublicKey: document.nameCreatorDeviceSigningPublicKey,
});
createOrReplaceDocument({ documentId, name });
}
Expand Down
11 changes: 11 additions & 0 deletions apps/app/utils/document/updateDocumentName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
runSnapshotQuery,
runUpdateDocumentNameMutation,
} from "../../generated/graphql";
import { loadRemoteWorkspaceMemberDevicesProofQuery } from "../../store/workspaceMemberDevicesProofStore";
import { getWorkspace } from "../workspace/getWorkspace";

export type Props = {
Expand Down Expand Up @@ -32,13 +33,20 @@ export const updateDocumentName = async ({
throw new Error(snapshotResult.error?.message || "Could not get snapshot");
}

const workspaceMemberDevicesProof =
await loadRemoteWorkspaceMemberDevicesProofQuery({
workspaceId,
});

const encryptedDocumentTitle = encryptDocumentTitle({
title: name,
activeDevice,
workspaceKeyBox: workspace.currentWorkspaceKey.workspaceKeyBox!,
snapshot: snapshotResult.data.snapshot,
workspaceId,
workspaceKeyId: workspace.currentWorkspaceKey.id,
documentId,
workspaceMemberDevicesProof: workspaceMemberDevicesProof.proof,
});
const updateDocumentNameResult = await runUpdateDocumentNameMutation(
{
Expand All @@ -48,6 +56,9 @@ export const updateDocumentName = async ({
nameNonce: encryptedDocumentTitle.nonce,
workspaceKeyId: workspace?.currentWorkspaceKey?.id!,
subkeyId: encryptedDocumentTitle.subkeyId,
nameSignature: encryptedDocumentTitle.signature,
nameWorkspaceMemberDevicesProofHash:
workspaceMemberDevicesProof.proof.hash,
},
},
{}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Warnings:
- Added the required column `nameSignature` to the `Document` table without a default value. This is not possible if the table is not empty.
- Added the required column `nameWorkspaceMemberDevicesProofHash` to the `Document` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "nameSignature" TEXT NOT NULL,
ADD COLUMN "nameWorkspaceMemberDevicesProofHash" TEXT NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `nameCreatorDeviceSigningPublicKey` to the `Document` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "nameCreatorDeviceSigningPublicKey" TEXT NOT NULL;

-- AddForeignKey
ALTER TABLE "Document" ADD CONSTRAINT "Document_nameCreatorDeviceSigningPublicKey_fkey" FOREIGN KEY ("nameCreatorDeviceSigningPublicKey") REFERENCES "CreatorDevice"("signingPublicKey") ON DELETE SET DEFAULT ON UPDATE CASCADE;
Loading

0 comments on commit 40e03c1

Please sign in to comment.