Skip to content

Commit

Permalink
add permission checks
Browse files Browse the repository at this point in the history
  • Loading branch information
nikgraf committed Jan 18, 2024
1 parent edf3b9d commit 24bd032
Show file tree
Hide file tree
Showing 25 changed files with 390 additions and 102 deletions.
6 changes: 5 additions & 1 deletion apps/app/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type Comment = {
signature: Scalars['String']['output'];
snapshotId: Scalars['String']['output'];
subkeyId: Scalars['String']['output'];
workspaceMemberDevicesProofHash: Scalars['String']['output'];
};

export type CommentConnection = {
Expand Down Expand Up @@ -126,6 +127,7 @@ export type CommentReply = {
signature: Scalars['String']['output'];
snapshotId: Scalars['String']['output'];
subkeyId: Scalars['String']['output'];
workspaceMemberDevicesProofHash: Scalars['String']['output'];
};

export type CreateCommentInput = {
Expand Down Expand Up @@ -1726,7 +1728,7 @@ export type CommentsByDocumentIdQueryVariables = Exact<{
}>;


export type CommentsByDocumentIdQuery = { __typename?: 'Query', commentsByDocumentId?: { __typename?: 'CommentConnection', nodes?: Array<{ __typename?: 'Comment', id: string, documentId: string, snapshotId: string, subkeyId: string, contentCiphertext: string, contentNonce: string, signature: string, createdAt: any, creatorDevice: { __typename?: 'CreatorDevice', signingPublicKey: string, encryptionPublicKey: string, encryptionPublicKeySignature: string }, commentReplies?: Array<{ __typename?: 'CommentReply', id: string, snapshotId: string, subkeyId: string, contentCiphertext: string, contentNonce: string, signature: string, createdAt: any, creatorDevice: { __typename?: 'CreatorDevice', signingPublicKey: string, encryptionPublicKey: string, encryptionPublicKeySignature: string } } | null> | null } | null> | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } | null };
export type CommentsByDocumentIdQuery = { __typename?: 'Query', commentsByDocumentId?: { __typename?: 'CommentConnection', nodes?: Array<{ __typename?: 'Comment', id: string, documentId: string, snapshotId: string, subkeyId: string, contentCiphertext: string, contentNonce: string, signature: string, workspaceMemberDevicesProofHash: string, createdAt: any, creatorDevice: { __typename?: 'CreatorDevice', signingPublicKey: string, encryptionPublicKey: string, encryptionPublicKeySignature: string }, commentReplies?: Array<{ __typename?: 'CommentReply', id: string, snapshotId: string, subkeyId: string, contentCiphertext: string, contentNonce: string, signature: string, workspaceMemberDevicesProofHash: string, createdAt: any, creatorDevice: { __typename?: 'CreatorDevice', signingPublicKey: string, encryptionPublicKey: string, encryptionPublicKeySignature: string } } | null> | null } | null> | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } | null };

export type DevicesQueryVariables = Exact<{
onlyNotExpired: Scalars['Boolean']['input'];
Expand Down Expand Up @@ -2505,6 +2507,7 @@ export const CommentsByDocumentIdDocument = gql`
contentCiphertext
contentNonce
signature
workspaceMemberDevicesProofHash
createdAt
creatorDevice {
signingPublicKey
Expand All @@ -2518,6 +2521,7 @@ export const CommentsByDocumentIdDocument = gql`
contentCiphertext
contentNonce
signature
workspaceMemberDevicesProofHash
createdAt
creatorDevice {
signingPublicKey
Expand Down
2 changes: 2 additions & 0 deletions apps/app/graphql/queries/commentsByDocumentId.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ query commentsByDocumentId(
contentCiphertext
contentNonce
signature
workspaceMemberDevicesProofHash
createdAt
creatorDevice {
signingPublicKey
Expand All @@ -31,6 +32,7 @@ query commentsByDocumentId(
contentCiphertext
contentNonce
signature
workspaceMemberDevicesProofHash
createdAt
creatorDevice {
signingPublicKey
Expand Down
177 changes: 128 additions & 49 deletions apps/app/machines/commentsMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ import {
runDeleteCommentRepliesMutation,
runDeleteCommentsMutation,
} from "../generated/graphql";
import {
getLocalOrLoadRemoteWorkspaceMemberDevicesProofQueryByHash,
loadRemoteWorkspaceMemberDevicesProofQuery,
} from "../store/workspaceMemberDevicesProofStore";
import { isValidDeviceSigningPublicKey } from "../utils/isValidDeviceSigningPublicKey/isValidDeviceSigningPublicKey";
import { showToast } from "../utils/toast/showToast";

type Params = {
// these won't change
workspaceId: string;
pageId: string;
shareLinkToken?: string;
activeDevice: LocalDevice | null;
Expand Down Expand Up @@ -109,6 +115,7 @@ export const commentsMachine =
params: {
pageId: "",
activeDevice: null,
workspaceId: "",
},
commentsByDocumentIdQueryError: false,
decryptedComments: [],
Expand Down Expand Up @@ -411,10 +418,10 @@ export const commentsMachine =
// console.log("commentReplyId", commentId, key);
// });

const decryptedComments =
const decryptedComments = await Promise.all(
context.commentsByDocumentIdQueryResult.data.commentsByDocumentId.nodes
.filter(notNull)
.map((encryptedComment) => {
.map(async (encryptedComment) => {
let commentKey: string;
const maybeCommentKey = context.yCommentKeys.get(
encryptedComment.id
Expand Down Expand Up @@ -442,6 +449,32 @@ export const commentsMachine =
commentKey = sodium.to_base64(maybeCommentKey);
}

const workspaceMemberDevicesProof =
await getLocalOrLoadRemoteWorkspaceMemberDevicesProofQueryByHash(
{
workspaceId: context.params.workspaceId,
hash: encryptedComment.workspaceMemberDevicesProofHash,
}
);
if (!workspaceMemberDevicesProof) {
throw new Error(
"workspaceMemberDevicesProof for decrypting a comment is not found"
);
}

const isValid = isValidDeviceSigningPublicKey({
signingPublicKey:
encryptedComment.creatorDevice.signingPublicKey,
workspaceMemberDevicesProofEntry: workspaceMemberDevicesProof,
workspaceId: context.params.workspaceId,
minimumRole: "COMMENTER",
});
if (!isValid) {
throw new Error(
"Invalid signing public key for the workspaceMemberDevicesProof"
);
}

const decryptedComment = verifyAndDecryptComment({
commentId: encryptedComment.id,
key: commentKey,
Expand All @@ -453,60 +486,93 @@ export const commentsMachine =
signature: encryptedComment.signature,
snapshotId: encryptedComment.snapshotId,
subkeyId: encryptedComment.subkeyId,
workspaceMemberDevicesProof:
workspaceMemberDevicesProof.proof,
});

const replies = encryptedComment.commentReplies
? encryptedComment.commentReplies
.filter(notNull)
.map((encryptedReply) => {
let replyKey: string;
const maybeReplyKey = context.yCommentReplyKeys.get(
encryptedReply.id
);
if (encryptedReply.snapshotId === activeSnapshot.id) {
const recreatedReplyKey = recreateCommentKey({
snapshotKey: activeSnapshot.key,
subkeyId: encryptedReply.subkeyId,
});
replyKey = recreatedReplyKey.key;
// key is missing in the yjs document so we add it
// this is the case in case the comment was produced by
// a user with the role commenter who doesn't have write
// access to the document
if (!maybeReplyKey) {
context.yCommentReplyKeys.set(
encryptedReply.id,
sodium.from_base64(replyKey)
? await Promise.all(
encryptedComment.commentReplies
.filter(notNull)
.map(async (encryptedReply) => {
let replyKey: string;
const maybeReplyKey = context.yCommentReplyKeys.get(
encryptedReply.id
);
if (encryptedReply.snapshotId === activeSnapshot.id) {
const recreatedReplyKey = recreateCommentKey({
snapshotKey: activeSnapshot.key,
subkeyId: encryptedReply.subkeyId,
});
replyKey = recreatedReplyKey.key;
// key is missing in the yjs document so we add it
// this is the case in case the comment was produced by
// a user with the role commenter who doesn't have write
// access to the document
if (!maybeReplyKey) {
context.yCommentReplyKeys.set(
encryptedReply.id,
sodium.from_base64(replyKey)
);
}
} else {
if (!maybeReplyKey) {
throw new Error("No comment reply key found.");
}
replyKey = sodium.to_base64(maybeReplyKey);
}

const workspaceMemberDevicesProof =
await getLocalOrLoadRemoteWorkspaceMemberDevicesProofQueryByHash(
{
workspaceId: context.params.workspaceId,
hash: encryptedReply.workspaceMemberDevicesProofHash,
}
);
if (!workspaceMemberDevicesProof) {
throw new Error(
"workspaceMemberDevicesProof for decrypting a comment is not found"
);
}
} else {
if (!maybeReplyKey) {
throw new Error("No comment reply key found.");

const isValid = isValidDeviceSigningPublicKey({
signingPublicKey:
encryptedReply.creatorDevice.signingPublicKey,
workspaceMemberDevicesProofEntry:
workspaceMemberDevicesProof,
workspaceId: context.params.workspaceId,
minimumRole: "COMMENTER",
});
if (!isValid) {
throw new Error(
"Invalid signing public key for the workspaceMemberDevicesProof"
);
}
replyKey = sodium.to_base64(maybeReplyKey);
}

const decryptedReply = verifyAndDecryptCommentReply({
key: replyKey,
ciphertext: encryptedReply.contentCiphertext,
nonce: encryptedReply.contentNonce,
commentId: encryptedComment.id,
commentReplyId: encryptedReply.id,
authorSigningPublicKey:
encryptedReply.creatorDevice.signingPublicKey,
documentId: context.params.pageId,
signature: encryptedReply.signature,
snapshotId: encryptedReply.snapshotId,
subkeyId: encryptedReply.subkeyId,
});
const decryptedReply = verifyAndDecryptCommentReply({
key: replyKey,
ciphertext: encryptedReply.contentCiphertext,
nonce: encryptedReply.contentNonce,
commentId: encryptedComment.id,
commentReplyId: encryptedReply.id,
authorSigningPublicKey:
encryptedReply.creatorDevice.signingPublicKey,
documentId: context.params.pageId,
signature: encryptedReply.signature,
snapshotId: encryptedReply.snapshotId,
subkeyId: encryptedReply.subkeyId,
workspaceMemberDevicesProof:
workspaceMemberDevicesProof.proof,
});

return {
...decryptedReply,
id: encryptedReply.id,
createdAt: encryptedReply.createdAt,
creatorDevice: encryptedReply.creatorDevice,
};
})
return {
...decryptedReply,
id: encryptedReply.id,
createdAt: encryptedReply.createdAt,
creatorDevice: encryptedReply.creatorDevice,
};
})
)
: [];

return {
Expand All @@ -516,7 +582,8 @@ export const commentsMachine =
replies,
creatorDevice: encryptedComment.creatorDevice,
};
});
})
);

return decryptedComments;
},
Expand All @@ -533,6 +600,11 @@ export const commentsMachine =
snapshotKey: activeSnapshot.key,
});

const workspaceMemberDevicesProof =
await loadRemoteWorkspaceMemberDevicesProofQuery({
workspaceId: context.params.workspaceId,
});

const result = encryptAndSignComment({
key: commentKey.key,
text: event.text,
Expand All @@ -542,6 +614,7 @@ export const commentsMachine =
documentId: context.params.pageId,
snapshotId: activeSnapshot.id,
subkeyId: commentKey.subkeyId,
workspaceMemberDevicesProof: workspaceMemberDevicesProof.proof,
});

const createCommentMutationResult = await runCreateCommentMutation({
Expand Down Expand Up @@ -577,6 +650,11 @@ export const commentsMachine =
snapshotKey: activeSnapshot.key,
});

const workspaceMemberDevicesProof =
await loadRemoteWorkspaceMemberDevicesProofQuery({
workspaceId: context.params.workspaceId,
});

const result = encryptAndSignCommentReply({
key: replyKey.key,
commentId: event.commentId,
Expand All @@ -585,6 +663,7 @@ export const commentsMachine =
documentId: context.params.pageId,
snapshotId: activeSnapshot.id,
subkeyId: replyKey.subkeyId,
workspaceMemberDevicesProof: workspaceMemberDevicesProof.proof,
});
const createCommentReplyMutation =
await runCreateCommentReplyMutation({
Expand Down
1 change: 1 addition & 0 deletions apps/app/navigation/screens/pageScreen/PageScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const ActualPageScreen = (
const commentsService = useInterpret(commentsMachine, {
context: {
params: {
workspaceId,
pageId: props.route.params.pageId,
activeDevice: activeDevice as LocalDevice,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const SharePageContainer: React.FC<SharePageContainerProps> = ({
const commentsService = useInterpret(commentsMachine, {
context: {
params: {
workspaceId,
pageId: route.params.pageId,
shareLinkToken: route.params.token,
activeDevice: shareDevice,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Comment" ADD COLUMN "workspaceMemberDevicesProofHash" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "CommentReply" ADD COLUMN "workspaceMemberDevicesProofHash" TEXT;
Loading

0 comments on commit 24bd032

Please sign in to comment.