fed8b2eed7
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Has been cancelled
Backend release / release (push) Has been cancelled
Bandit Security Scan / bandit_scan (push) Has been cancelled
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Has been cancelled
Build and push multi-arch DocsGPT Docker image / manifest (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / manifest (push) Has been cancelled
Python linting / ruff (push) Has been cancelled
Run python tests with pytest / Run tests and count coverage (3.12) (push) Has been cancelled
React Widget Build / build (push) Has been cancelled
94 lines
3.6 KiB
TypeScript
94 lines
3.6 KiB
TypeScript
/**
|
|
* P1-C · cross-user isolation.
|
|
*
|
|
* Covers the cross-user isolation acceptance criterion: two
|
|
* freshly-minted users (A and B) must not see each other's writes.
|
|
* User A creates a prompt; user B's
|
|
* `/api/get_prompts` must not include it, and the DB must report zero
|
|
* `prompts` rows for user B.
|
|
*
|
|
* This spec is mode-agnostic — it drives the API directly with signed
|
|
* tokens and doesn't touch the JWT modal — so it runs under both
|
|
* `AUTH_TYPE=session_jwt` and `AUTH_TYPE=simple_jwt` (though simple_jwt
|
|
* would in practice reject distinct subs: the server still accepts any
|
|
* HS256-signed token with the shared secret, per application/auth.py).
|
|
*/
|
|
|
|
import * as playwright from '@playwright/test';
|
|
const { expect, test } = playwright;
|
|
|
|
import { authedRequest } from '../../helpers/api.js';
|
|
import { signJwt } from '../../helpers/auth.js';
|
|
import { countRows } from '../../helpers/db.js';
|
|
import { resetDb } from '../../helpers/reset.js';
|
|
|
|
const USER_A_SUB = 'e2e-isolation-a';
|
|
const USER_B_SUB = 'e2e-isolation-b';
|
|
|
|
type Prompt = { id: string; name: string; type: 'public' | 'private' };
|
|
|
|
test.describe('auth · cross-user isolation', () => {
|
|
test.beforeEach(async () => {
|
|
await resetDb();
|
|
});
|
|
|
|
test("isolation: user A's prompt is invisible to user B across API and DB", async () => {
|
|
const tokenA = signJwt(USER_A_SUB);
|
|
const tokenB = signJwt(USER_B_SUB);
|
|
|
|
const apiA = await authedRequest(playwright, tokenA);
|
|
const apiB = await authedRequest(playwright, tokenB);
|
|
|
|
try {
|
|
// User A creates a prompt. Contract per
|
|
// application/api/user/prompts/routes.py:19-50 — POST JSON body with
|
|
// `name` + `content`, returns `{id}` on 200.
|
|
const promptName = 'e2e-isolation-prompt';
|
|
const promptContent = 'Isolation check — only user A should see me.';
|
|
|
|
const createRes = await apiA.post('/api/create_prompt', {
|
|
data: { name: promptName, content: promptContent },
|
|
});
|
|
expect(createRes.status()).toBe(200);
|
|
const createBody = (await createRes.json()) as { id: string };
|
|
expect(createBody.id).toBeTruthy();
|
|
|
|
// User A can see the prompt in their list.
|
|
const listARes = await apiA.get('/api/get_prompts');
|
|
expect(listARes.status()).toBe(200);
|
|
const listA = (await listARes.json()) as Prompt[];
|
|
const privateAPrompts = listA.filter((p) => p.type === 'private');
|
|
expect(privateAPrompts).toHaveLength(1);
|
|
expect(privateAPrompts[0].name).toBe(promptName);
|
|
expect(privateAPrompts[0].id).toBe(createBody.id);
|
|
|
|
// User B cannot see it. The three built-in public prompts
|
|
// (default/creative/strict) are always returned by /get_prompts —
|
|
// the isolation assertion is on the `private` slice.
|
|
const listBRes = await apiB.get('/api/get_prompts');
|
|
expect(listBRes.status()).toBe(200);
|
|
const listB = (await listBRes.json()) as Prompt[];
|
|
const privateBPrompts = listB.filter((p) => p.type === 'private');
|
|
expect(privateBPrompts).toHaveLength(0);
|
|
expect(listB.find((p) => p.id === createBody.id)).toBeUndefined();
|
|
|
|
// DB-level assertion — UI/API view can lie (bugs like missing
|
|
// WHERE user_id clauses) but the underlying row count cannot.
|
|
const aRowCount = await countRows('prompts', {
|
|
sql: 'user_id = $1',
|
|
params: [USER_A_SUB],
|
|
});
|
|
expect(aRowCount).toBe(1);
|
|
|
|
const bRowCount = await countRows('prompts', {
|
|
sql: 'user_id = $1',
|
|
params: [USER_B_SUB],
|
|
});
|
|
expect(bRowCount).toBe(0);
|
|
} finally {
|
|
await apiA.dispose();
|
|
await apiB.dispose();
|
|
}
|
|
});
|
|
});
|