Harden auth and security controls with session auth and docs
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
// @ts-expect-error Node strip-types runtime requires explicit .ts extension in ESM imports.
|
||||
import { API_TOKEN_RUNTIME_GLOBAL_KEY, downloadDocumentContentMarkdown, downloadDocumentFile, getDocumentPreviewBlob, getDocumentThumbnailBlob, setApiTokenResolver, setRuntimeApiToken, updateDocumentMetadata } from './api.ts';
|
||||
// @ts-ignore Node strip-types runtime requires explicit .ts extension in ESM imports.
|
||||
import {
|
||||
downloadDocumentContentMarkdown,
|
||||
downloadDocumentFile,
|
||||
getCurrentAuthSession,
|
||||
getDocumentPreviewBlob,
|
||||
getDocumentThumbnailBlob,
|
||||
getRuntimeApiToken,
|
||||
loginWithPassword,
|
||||
logoutCurrentSession,
|
||||
setRuntimeApiToken,
|
||||
updateDocumentMetadata,
|
||||
} from './api.ts';
|
||||
|
||||
/**
|
||||
* Throws when a test condition is false.
|
||||
@@ -65,12 +76,10 @@ function createMemorySessionStorage(): Storage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs API helper tests for authenticated media and download flows.
|
||||
* Runs API helper tests for authenticated media and auth session workflows.
|
||||
*/
|
||||
async function runApiTests(): Promise<void> {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const runtimeGlobalSource = globalThis as typeof globalThis & Record<string, unknown>;
|
||||
const originalRuntimeGlobalToken = runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
||||
const sessionStorageDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'sessionStorage');
|
||||
|
||||
try {
|
||||
@@ -79,9 +88,7 @@ async function runApiTests(): Promise<void> {
|
||||
writable: true,
|
||||
value: createMemorySessionStorage(),
|
||||
});
|
||||
setApiTokenResolver(null);
|
||||
setRuntimeApiToken(null);
|
||||
delete runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
||||
|
||||
const requestUrls: string[] = [];
|
||||
const requestAuthHeaders: Array<string | null> = [];
|
||||
@@ -108,6 +115,7 @@ async function runApiTests(): Promise<void> {
|
||||
assert(requestAuthHeaders[1] === null, `Expected no auth header for preview request, got "${requestAuthHeaders[1]}"`);
|
||||
|
||||
setRuntimeApiToken('session-user-token');
|
||||
assert(getRuntimeApiToken() === 'session-user-token', 'Expected session token readback to match persisted token');
|
||||
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const authHeader = new Headers(init?.headers).get('Authorization');
|
||||
assert(authHeader === 'Bearer session-user-token', `Expected session token auth header, got "${authHeader}"`);
|
||||
@@ -115,16 +123,6 @@ async function runApiTests(): Promise<void> {
|
||||
}) as typeof fetch;
|
||||
await getDocumentPreviewBlob('doc-session-auth');
|
||||
|
||||
setRuntimeApiToken('session-user-token');
|
||||
runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY] = 'runtime-global-token';
|
||||
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const authHeader = new Headers(init?.headers).get('Authorization');
|
||||
assert(authHeader === 'Bearer runtime-global-token', `Expected global runtime token auth header, got "${authHeader}"`);
|
||||
return new Response('preview-bytes', { status: 200 });
|
||||
}) as typeof fetch;
|
||||
await getDocumentPreviewBlob('doc-global-auth');
|
||||
|
||||
setApiTokenResolver(() => 'resolver-token');
|
||||
let mergedContentType: string | null = null;
|
||||
let mergedAuthorization: string | null = null;
|
||||
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
@@ -135,19 +133,47 @@ async function runApiTests(): Promise<void> {
|
||||
}) as typeof fetch;
|
||||
await updateDocumentMetadata('doc-headers', { original_filename: 'renamed.pdf' });
|
||||
assert(mergedContentType === 'application/json', `Expected JSON content type to be preserved, got "${mergedContentType}"`);
|
||||
assert(mergedAuthorization === 'Bearer resolver-token', `Expected resolver token auth header, got "${mergedAuthorization}"`);
|
||||
assert(mergedAuthorization === 'Bearer session-user-token', `Expected auth header, got "${mergedAuthorization}"`);
|
||||
|
||||
setApiTokenResolver(() => ' ');
|
||||
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const authHeader = new Headers(init?.headers).get('Authorization');
|
||||
assert(authHeader === 'Bearer runtime-global-token', `Expected fallback runtime global token auth header, got "${authHeader}"`);
|
||||
return new Response('preview-bytes', { status: 200 });
|
||||
globalThis.fetch = (async (): Promise<Response> => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
access_token: 'issued-session-token',
|
||||
token_type: 'bearer',
|
||||
expires_at: '2026-03-01T10:30:00Z',
|
||||
user: {
|
||||
id: '3a42f5e0-b1ad-4f68-b2f4-3fa8c2fb31c9',
|
||||
username: 'admin',
|
||||
role: 'admin',
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
||||
);
|
||||
}) as typeof fetch;
|
||||
await getDocumentPreviewBlob('doc-resolver-fallback');
|
||||
const loginPayload = await loginWithPassword('admin', 'password');
|
||||
assert(loginPayload.access_token === 'issued-session-token', 'Unexpected issued session token in login payload');
|
||||
assert(loginPayload.user.username === 'admin', 'Unexpected login user payload');
|
||||
|
||||
setApiTokenResolver(null);
|
||||
setRuntimeApiToken(null);
|
||||
delete runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
||||
globalThis.fetch = (async (): Promise<Response> => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
expires_at: '2026-03-01T10:30:00Z',
|
||||
user: {
|
||||
id: '3a42f5e0-b1ad-4f68-b2f4-3fa8c2fb31c9',
|
||||
username: 'admin',
|
||||
role: 'admin',
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
||||
);
|
||||
}) as typeof fetch;
|
||||
const sessionPayload = await getCurrentAuthSession();
|
||||
assert(sessionPayload.user.role === 'admin', 'Expected admin role from auth session payload');
|
||||
|
||||
globalThis.fetch = (async (): Promise<Response> => {
|
||||
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||
}) as typeof fetch;
|
||||
await logoutCurrentSession();
|
||||
|
||||
globalThis.fetch = (async (): Promise<Response> => {
|
||||
return new Response('file-bytes', {
|
||||
@@ -176,13 +202,7 @@ async function runApiTests(): Promise<void> {
|
||||
|
||||
await assertRejects(async () => downloadDocumentContentMarkdown('doc-4'), 'Failed to download document markdown');
|
||||
} finally {
|
||||
setApiTokenResolver(null);
|
||||
setRuntimeApiToken(null);
|
||||
if (originalRuntimeGlobalToken === undefined) {
|
||||
delete runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
||||
} else {
|
||||
runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY] = originalRuntimeGlobalToken;
|
||||
}
|
||||
if (sessionStorageDescriptor) {
|
||||
Object.defineProperty(globalThis, 'sessionStorage', sessionStorageDescriptor);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user