Files
documenso/packages/app-tests/e2e/api/v1/test-unauthorized-api-access.spec.ts
2025-07-10 12:56:46 +10:00

541 lines
16 KiB
TypeScript

import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
import { FieldType } from '@documenso/prisma/client';
import {
seedBlankDocument,
seedCompletedDocument,
seedDraftDocument,
seedPendingDocumentWithFullFields,
} from '@documenso/prisma/seed/documents';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
test.describe.configure({
mode: 'parallel',
});
test.describe('Document Access API V1', () => {
test('should block unauthorized access to documents not owned by the user', async ({
request,
}) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const documentA = await seedBlankDocument(userA, teamA.id);
// User B cannot access User A's document
const resB = await request.get(`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(404);
});
test('should block unauthorized access to document download endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const documentA = await seedCompletedDocument(userA, teamA.id, ['test@example.com'], {
createDocumentOptions: { title: 'Document 1 - Completed' },
});
const resB = await request.get(`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/download`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(500);
});
test('should block unauthorized access to document delete endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const documentA = await seedBlankDocument(userA, teamA.id);
const resB = await request.delete(`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(404);
});
test('should block unauthorized access to document send endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { document: documentA } = await seedPendingDocumentWithFullFields({
owner: userA,
recipients: ['test@example.com'],
teamId: teamA.id,
});
const resB = await request.post(`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/send`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {},
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(500);
});
test('should block unauthorized access to document resend endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { user: recipientUser } = await seedUser();
const { document: documentA, recipients } = await seedPendingDocumentWithFullFields({
owner: userA,
recipients: [recipientUser.email],
teamId: teamA.id,
});
const resB = await request.post(`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/resend`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
recipients: [recipients[0].id],
},
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(500);
});
test('should block unauthorized access to document recipients endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const documentA = await seedBlankDocument(userA, teamA.id);
const resB = await request.post(
`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/recipients`,
{
headers: { Authorization: `Bearer ${tokenB}` },
data: { name: 'Test', email: 'test@example.com' },
},
);
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(401);
});
test('should block unauthorized access to PATCH on recipients endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { user: userRecipient } = await seedUser();
const documentA = await seedDraftDocument(userA, teamA.id, [userRecipient.email]);
const recipient = await prisma.recipient.findFirst({
where: {
documentId: documentA.id,
email: userRecipient.email,
},
});
const patchRes = await request.patch(
`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/recipients/${recipient!.id}`,
{
headers: { Authorization: `Bearer ${tokenB}` },
data: {
name: 'New Name',
email: 'new@example.com',
role: 'SIGNER',
signingOrder: null,
authOptions: {
accessAuth: [],
actionAuth: [],
},
},
},
);
expect(patchRes.ok()).toBeFalsy();
expect(patchRes.status()).toBe(401);
});
test('should block unauthorized access to DELETE on recipients endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { user: userRecipient } = await seedUser();
const documentA = await seedDraftDocument(userA, teamA.id, [userRecipient.email]);
const recipient = await prisma.recipient.findFirst({
where: {
documentId: documentA.id,
email: userRecipient.email,
},
});
const deleteRes = await request.delete(
`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/recipients/${recipient!.id}`,
{
headers: { Authorization: `Bearer ${tokenB}` },
data: {},
},
);
expect(deleteRes.ok()).toBeFalsy();
expect(deleteRes.status()).toBe(401);
});
test('should block unauthorized access to document fields endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { user: recipientUser } = await seedUser();
const documentA = await seedDraftDocument(userA, teamA.id, [recipientUser.email]);
const documentRecipient = await prisma.recipient.findFirst({
where: {
documentId: documentA.id,
email: recipientUser.email,
},
});
const resB = await request.post(`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/fields`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
recipientId: documentRecipient!.id,
type: 'SIGNATURE',
pageNumber: 1,
pageX: 1,
pageY: 1,
pageWidth: 1,
pageHeight: 1,
},
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(404);
});
test('should block unauthorized access to template get endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const templateA = await seedBlankTemplate(userA, teamA.id);
const resB = await request.get(`${WEBAPP_BASE_URL}/api/v1/templates/${templateA.id}`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(404);
});
test('should block unauthorized access to template delete endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const templateA = await seedBlankTemplate(userA, teamA.id);
const resB = await request.delete(`${WEBAPP_BASE_URL}/api/v1/templates/${templateA.id}`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(404);
});
test('should block unauthorized access to PATCH on fields endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { user: userRecipient } = await seedUser();
const documentA = await seedDraftDocument(userA, teamA.id, [userRecipient.email]);
const recipient = await prisma.recipient.findFirst({
where: {
documentId: documentA.id,
email: userRecipient.email,
},
});
const field = await prisma.field.create({
data: {
documentId: documentA.id,
recipientId: recipient!.id,
type: FieldType.TEXT,
page: 1,
positionX: 5,
positionY: 5,
width: 10,
height: 5,
customText: '',
inserted: false,
fieldMeta: {
type: 'text',
label: 'Default Text Field',
},
},
});
const patchRes = await request.patch(
`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/fields/${field.id}`,
{
headers: { Authorization: `Bearer ${tokenB}` },
data: {
recipientId: recipient!.id,
type: FieldType.TEXT,
pageNumber: 1,
pageX: 99,
pageY: 99,
pageWidth: 99,
pageHeight: 99,
fieldMeta: {
type: 'text',
label: 'My new field',
},
},
},
);
expect(patchRes.ok()).toBeFalsy();
expect(patchRes.status()).toBe(401);
});
test('should block unauthorized access to DELETE on fields endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const { user: userRecipient } = await seedUser();
const documentA = await seedDraftDocument(userA, teamA.id, [userRecipient.email]);
const recipient = await prisma.recipient.findFirst({
where: {
documentId: documentA.id,
email: userRecipient.email,
},
});
const field = await prisma.field.create({
data: {
documentId: documentA.id,
recipientId: recipient!.id,
type: FieldType.NUMBER,
page: 1,
positionX: 5,
positionY: 5,
width: 10,
height: 5,
customText: '',
inserted: false,
fieldMeta: {
type: 'number',
label: 'Default Number Field',
},
},
});
const deleteRes = await request.delete(
`${WEBAPP_BASE_URL}/api/v1/documents/${documentA.id}/fields/${field.id}`,
{
headers: { Authorization: `Bearer ${tokenB}` },
data: {},
},
);
expect(deleteRes.ok()).toBeFalsy();
expect(deleteRes.status()).toBe(401);
});
test('should block unauthorized access to documents list endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
await seedBlankDocument(userA, teamA.id);
const resB = await request.get(`${WEBAPP_BASE_URL}/api/v1/documents`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
const reqData = await resB.json();
expect(resB.ok()).toBeTruthy();
expect(resB.status()).toBe(200);
expect(reqData.documents.every((doc: { userId: number }) => doc.userId !== userA.id)).toBe(
true,
);
expect(reqData.documents.length).toBe(0);
expect(reqData.totalPages).toBe(0);
});
test('should block unauthorized access to templates list endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
await seedBlankTemplate(userA, teamA.id);
const resB = await request.get(`${WEBAPP_BASE_URL}/api/v1/templates`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
const reqData = await resB.json();
expect(resB.ok()).toBeTruthy();
expect(resB.status()).toBe(200);
expect(reqData.templates.every((tpl: { userId: number }) => tpl.userId !== userA.id)).toBe(
true,
);
expect(reqData.templates.length).toBe(0);
expect(reqData.totalPages).toBe(0);
});
test('should block unauthorized access to create-document-from-template endpoint', async ({
request,
}) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
const { token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
});
const templateA = await seedBlankTemplate(userA, teamA.id);
const resB = await request.post(
`${WEBAPP_BASE_URL}/api/v1/templates/${templateA.id}/create-document`,
{
headers: {
Authorization: `Bearer ${tokenB}`,
'Content-Type': 'application/json',
},
data: {
title: 'Should not work',
recipients: [{ name: 'Test user', email: 'test@example.com' }],
meta: {
subject: 'Test',
message: 'Test',
timezone: 'UTC',
dateFormat: 'yyyy-MM-dd',
redirectUrl: 'https://example.com',
},
},
},
);
expect(resB.ok()).toBeFalsy();
expect(resB.status()).toBe(401);
});
});