Skip to content

Commit

Permalink
fix attachment download for share links
Browse files Browse the repository at this point in the history
  • Loading branch information
nikgraf committed Mar 21, 2024
1 parent cb058a1 commit ad98c0a
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 51 deletions.
5 changes: 3 additions & 2 deletions apps/app/components/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default function Editor({
documentState,
canComment,
currentDeviceSigningPublicKey,
documentShareLinkToken,
}: EditorProps) {
const webViewRef = useRef<WebView>(null);
// leveraging a ref here since the injectedJavaScriptBeforeContentLoaded
Expand Down Expand Up @@ -123,10 +124,10 @@ export default function Editor({

const downloadAndDecryptFile = useMemo(() => {
return createDownloadAndDecryptFileFunction({
workspaceId,
documentShareLinkToken,
documentId,
});
}, [workspaceId, documentId]);
}, [documentId, documentShareLinkToken]);

const workspaceDevicesToUsernames = useWorkspaceMemberDevicesToUsernames({
workspaceId,
Expand Down
5 changes: 3 additions & 2 deletions apps/app/components/editor/Editor.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default function Editor({
editable,
documentState,
canComment,
documentShareLinkToken,
}: EditorProps) {
const [editorBottombarState, setEditorBottombarState] =
useState<EditorBottombarState>(initialEditorBottombarState);
Expand Down Expand Up @@ -184,10 +185,10 @@ export default function Editor({

const downloadAndDecryptFile = useMemo(() => {
return createDownloadAndDecryptFileFunction({
workspaceId,
documentId,
documentShareLinkToken,
});
}, [workspaceId, documentId]);
}, [documentId, documentShareLinkToken]);

if (!documentLoaded) {
return <EditorLoading />;
Expand Down
1 change: 1 addition & 0 deletions apps/app/components/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export type EditorProps = {
documentState: DocumentState;
canComment: boolean;
currentDeviceSigningPublicKey: string;
documentShareLinkToken?: string;
};
1 change: 1 addition & 0 deletions apps/app/components/sharePage/SharePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export const SharePage: React.FC<Props> = ({
currentDeviceSigningPublicKey={sodium.to_base64(
signatureKeyPair.publicKey
)}
documentShareLinkToken={token}
/>
</>
);
Expand Down
14 changes: 9 additions & 5 deletions apps/app/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,8 +967,8 @@ export type QueryEncryptedWebDeviceArgs = {

export type QueryFileUrlArgs = {
documentId: Scalars['ID']['input'];
documentShareLinkToken?: InputMaybe<Scalars['String']['input']>;
fileId: Scalars['ID']['input'];
workspaceId: Scalars['ID']['input'];
};


Expand Down Expand Up @@ -1814,8 +1814,8 @@ export type EncryptedWebDeviceQuery = { __typename?: 'Query', encryptedWebDevice

export type FileUrlQueryVariables = Exact<{
fileId: Scalars['ID']['input'];
workspaceId: Scalars['ID']['input'];
documentId: Scalars['ID']['input'];
documentShareLinkToken?: InputMaybe<Scalars['String']['input']>;
}>;


Expand Down Expand Up @@ -2507,7 +2507,7 @@ export function useVerifyRegistrationMutation() {
return Urql.useMutation<VerifyRegistrationMutation, VerifyRegistrationMutationVariables>(VerifyRegistrationDocument);
};
export const CommentsByDocumentIdDocument = gql`
query commentsByDocumentId($documentId: ID!, $documentShareLinkToken: String, $first: Int = 50, $after: String) {
query commentsByDocumentId($documentId: ID!, $documentShareLinkToken: String, $first: Int = 200, $after: String) {
commentsByDocumentId(
documentId: $documentId
documentShareLinkToken: $documentShareLinkToken
Expand Down Expand Up @@ -2741,8 +2741,12 @@ export function useEncryptedWebDeviceQuery(options: Omit<Urql.UseQueryArgs<Encry
return Urql.useQuery<EncryptedWebDeviceQuery, EncryptedWebDeviceQueryVariables>({ query: EncryptedWebDeviceDocument, ...options });
};
export const FileUrlDocument = gql`
query fileUrl($fileId: ID!, $workspaceId: ID!, $documentId: ID!) {
fileUrl(fileId: $fileId, workspaceId: $workspaceId, documentId: $documentId) {
query fileUrl($fileId: ID!, $documentId: ID!, $documentShareLinkToken: String) {
fileUrl(
fileId: $fileId
documentId: $documentId
documentShareLinkToken: $documentShareLinkToken
) {
id
downloadUrl
}
Expand Down
2 changes: 1 addition & 1 deletion apps/app/graphql/queries/commentsByDocumentId.graphql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
query commentsByDocumentId(
$documentId: ID!
$documentShareLinkToken: String
$first: Int = 50
$first: Int = 200
$after: String
) {
commentsByDocumentId(
Expand Down
8 changes: 6 additions & 2 deletions apps/app/graphql/queries/fileUrl.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
query fileUrl($fileId: ID!, $workspaceId: ID!, $documentId: ID!) {
fileUrl(fileId: $fileId, workspaceId: $workspaceId, documentId: $documentId) {
query fileUrl($fileId: ID!, $documentId: ID!, $documentShareLinkToken: String) {
fileUrl(
fileId: $fileId
documentId: $documentId
documentShareLinkToken: $documentShareLinkToken
) {
id
downloadUrl
}
Expand Down
6 changes: 3 additions & 3 deletions apps/app/utils/file/createDownloadAndDecryptFileFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { runFileUrlQuery } from "../../generated/graphql";
import { decryptFile } from "./decryptFile";

type CreateDownloadAndDecryptFileFunctionProps = {
workspaceId: string;
documentId: string;
documentShareLinkToken?: string;
};

export type Props = {
Expand All @@ -13,11 +13,11 @@ export type Props = {
};
export const createDownloadAndDecryptFileFunction = ({
documentId,
workspaceId,
documentShareLinkToken,
}: CreateDownloadAndDecryptFileFunctionProps) => {
return async ({ fileId, publicNonce, key }: Props): Promise<string> => {
const result = await runFileUrlQuery(
{ fileId, documentId, workspaceId },
{ fileId, documentId, documentShareLinkToken },
{}
);
if (!result.data?.fileUrl?.downloadUrl) {
Expand Down
44 changes: 31 additions & 13 deletions apps/backend/src/database/file/getFileUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,48 @@ export type Props = {
userId: string;
fileId: string;
documentId: string;
workspaceId: string;
documentShareLinkToken?: string | null | undefined;
};

export const getFileUrl = async ({
userId,
fileId,
documentId,
workspaceId,
documentShareLinkToken,
}: Props) => {
const user2Workspace = await prisma.usersToWorkspaces.findFirst({
where: { userId, workspaceId },
select: { role: true },
});
if (!user2Workspace) {
throw new ForbiddenError("Unauthorized");
}
// verify the document exists
const document = await prisma.document.findFirst({
where: { id: documentId, workspaceId },
select: { id: true },
where: { id: documentId },
});
if (!document) {
throw new UserInputError("Invalid documentId");
throw new ForbiddenError("Unauthorized");
}
// if the user has a documentShareLinkToken, verify it
let documentShareLink: any = null;
if (documentShareLinkToken) {
documentShareLink = await prisma.documentShareLink.findFirst({
where: {
token: documentShareLinkToken,
documentId,
},
});
if (!documentShareLink) {
throw new UserInputError("Invalid documentShareLinkToken");
}
} else {
// if no documentShareLinkToken, the user must have access to the workspace
const user2Workspace = await prisma.usersToWorkspaces.findFirst({
where: {
userId,
workspaceId: document.workspaceId,
},
});
if (!user2Workspace) {
throw new ForbiddenError("Unauthorized");
}
}
const fileName = `${workspaceId}/${documentId}/${fileId}.blob`;

const fileName = `${document.workspaceId}/${documentId}/${fileId}.blob`;
const downloadUrl = await getSignedUrl(
s3Client,
new GetObjectCommand({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ test("too many", async () => {
await commentsByDocumentId({
graphql,
documentId: userData1.document.id,
first: 51,
first: 201,
authorizationHeader: deriveSessionAuthorization({
sessionKey: userData1.sessionKey,
}).authorization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export const commentsByDocumentIdQuery = queryField((t) => {
documentShareLinkToken: stringArg(),
},
async nodes(root, args, context) {
if (args.first && args.first > 50) {
if (args.first && args.first > 200) {
throw new UserInputError(
"Requested too many devices. First value exceeds 50."
"Requested too many comments. First value exceeds 200."
);
}
if (!context.user && typeof args.documentShareLinkToken !== "string") {
Expand Down
20 changes: 5 additions & 15 deletions apps/backend/src/graphql/queries/file/fileUrl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,26 @@ beforeAll(async () => {
type Props = {
graphql: TestContext;
fileId: string;
workspaceId: string;
documentId: string;
authorization: string;
};
const getFileUrl = async ({
graphql,
fileId,
documentId,
workspaceId,
authorization,
}: Props) => {
const query = gql`
query fileUrl($fileId: ID!, $documentId: ID!, $workspaceId: ID!) {
fileUrl(
fileId: $fileId
documentId: $documentId
workspaceId: $workspaceId
) {
query fileUrl($fileId: ID!, $documentId: ID!) {
fileUrl(fileId: $fileId, documentId: $documentId) {
id
downloadUrl
}
}
`;
return graphql.client.request<any>(
query,
{ fileId, documentId, workspaceId },
{ fileId, documentId },
{ authorization }
);
};
Expand All @@ -72,7 +66,6 @@ test("get file url", async () => {
graphql,
fileId: fileUploadData.fileId,
documentId: userData.document.id,
workspaceId: userData.workspace.id,
authorization: deriveSessionAuthorization({
sessionKey: userData.sessionKey,
}).authorization,
Expand All @@ -95,7 +88,6 @@ test("invalid access", async () => {
graphql,
fileId: fileUploadData.fileId,
documentId: userData.document.id,
workspaceId: userData.workspace.id,
authorization: deriveSessionAuthorization({
sessionKey: otherUser.sessionKey,
}).authorization,
Expand All @@ -110,7 +102,6 @@ test("Unauthenticated", async () => {
graphql,
fileId: fileUploadData.fileId,
documentId: userData.document.id,
workspaceId: userData.workspace.id,
authorization: "invalid-session-key",
}))()
).rejects.toThrowError(/UNAUTHENTICATED/);
Expand Down Expand Up @@ -139,14 +130,13 @@ describe("Input errors", () => {
input: {
fileId: null,
documentId: userData.document.id,
workspaceId: userData.workspace.id,
},
},
{ authorization: userData.sessionKey }
))()
).rejects.toThrowError();
});
test("Invalid document id", async () => {
test("Invalid file id", async () => {
const userData = await createUserWithWorkspace({
username: `${generateId()}@example.com`,
password,
Expand All @@ -157,8 +147,8 @@ describe("Input errors", () => {
query,
{
input: {
fileId: null,
documentId: userData.document.id,
workspaceId: null,
},
},
{ authorization: userData.sessionKey }
Expand Down
10 changes: 5 additions & 5 deletions apps/backend/src/graphql/queries/file/fileUrl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuthenticationError } from "apollo-server-express";
import { idArg, nonNull, queryField } from "nexus";
import { idArg, nonNull, queryField, stringArg } from "nexus";
import { getFileUrl } from "../../../database/file/getFileUrl";
import { File } from "../../types/file";

Expand All @@ -9,17 +9,17 @@ export const fileUrlQuery = queryField((t) => {
args: {
fileId: nonNull(idArg()),
documentId: nonNull(idArg()),
workspaceId: nonNull(idArg()),
documentShareLinkToken: stringArg(),
},
async resolve(root, args, context) {
if (!context.user) {
if (!context.user && typeof args.documentShareLinkToken !== "string") {
throw new AuthenticationError("Not authenticated");
}
const downloadUrl = await getFileUrl({
userId: context.user.id,
userId: context.user?.id,
fileId: args.fileId,
documentId: args.documentId,
workspaceId: args.workspaceId,
documentShareLinkToken: args.documentShareLinkToken,
});
return {
id: args.fileId,
Expand Down

0 comments on commit ad98c0a

Please sign in to comment.