refactor: strengthen client boundaries and quality gates
This commit is contained in:
+17
-13
@@ -2,7 +2,6 @@ import {
|
||||
clearSession,
|
||||
currentRouteForPortal,
|
||||
dispatchSessionEvent,
|
||||
getSessionTenantId,
|
||||
hasRecentUserActivity,
|
||||
portalFromPath,
|
||||
readSession,
|
||||
@@ -19,7 +18,6 @@ type RequestOptions = RequestInit & {
|
||||
suppressSessionRedirect?: boolean;
|
||||
};
|
||||
|
||||
|
||||
type ApiErrorBody = { message?: string | string[]; error?: string; code?: string };
|
||||
|
||||
export async function readErrorBody(response: Response): Promise<ApiErrorBody> {
|
||||
@@ -32,8 +30,14 @@ export async function readErrorBody(response: Response): Promise<ApiErrorBody> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type SessionTiming = Pick<LoginSession, 'idleTimeoutSeconds' | 'lockRecoverySeconds' | 'absoluteExpiresAt' | 'lastActivityAt' | 'recentAuthenticationExpiresAt'>;
|
||||
export type SessionTiming = Pick<
|
||||
LoginSession,
|
||||
| 'idleTimeoutSeconds'
|
||||
| 'lockRecoverySeconds'
|
||||
| 'absoluteExpiresAt'
|
||||
| 'lastActivityAt'
|
||||
| 'recentAuthenticationExpiresAt'
|
||||
>;
|
||||
|
||||
// Authentication failures are handled centrally so every domain API keeps the
|
||||
// same lock, recovery and redirect behavior as the original adminApi facade.
|
||||
@@ -87,9 +91,8 @@ export async function request<T>(path: string, options: RequestOptions = {}): Pr
|
||||
const portal = requestPortal(path);
|
||||
const session = portal ? readSession(portal) : null;
|
||||
if (session && hasRecentUserActivity()) headers.set('x-session-activity', 'user');
|
||||
const tenantId = options.tenantId ?? (path.startsWith('/client') ? getSessionTenantId() : undefined);
|
||||
if (tenantId) {
|
||||
headers.set('x-tenant-id', tenantId);
|
||||
if (options.tenantId && !path.startsWith('/client')) {
|
||||
headers.set('x-tenant-id', options.tenantId);
|
||||
}
|
||||
const response = await fetch(`/api${path}`, { ...options, headers, credentials: 'same-origin' });
|
||||
const isLoginAttempt = path === '/admin/auth/login' || path === '/client/auth/login';
|
||||
@@ -114,9 +117,8 @@ export async function requestBlob(path: string, options: RequestOptions = {}): P
|
||||
const portal = requestPortal(path);
|
||||
const session = portal ? readSession(portal) : null;
|
||||
if (session && hasRecentUserActivity()) headers.set('x-session-activity', 'user');
|
||||
const tenantId = options.tenantId ?? (path.startsWith('/client') ? getSessionTenantId() : undefined);
|
||||
if (tenantId) {
|
||||
headers.set('x-tenant-id', tenantId);
|
||||
if (options.tenantId && !path.startsWith('/client')) {
|
||||
headers.set('x-tenant-id', options.tenantId);
|
||||
}
|
||||
const response = await fetch(`/api${path}`, { ...options, headers, credentials: 'same-origin' });
|
||||
if (response.status === 401) {
|
||||
@@ -156,7 +158,6 @@ export async function requestForm<T>(path: string, form: FormData, reauthenticat
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
|
||||
export function withQuery(path: string, query: Record<string, string | number | undefined>) {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(query).forEach(([key, value]) => {
|
||||
@@ -168,7 +169,10 @@ export function withQuery(path: string, query: Record<string, string | number |
|
||||
return `${path}${suffix}`;
|
||||
}
|
||||
|
||||
|
||||
export function fileDownloadUrl(fileObjectId: string, disposition: 'attachment' | 'inline' = 'attachment', portal: Portal = 'admin') {
|
||||
export function fileDownloadUrl(
|
||||
fileObjectId: string,
|
||||
disposition: 'attachment' | 'inline' = 'attachment',
|
||||
portal: Portal = 'admin',
|
||||
) {
|
||||
return `/api/${portal}/files/${encodeURIComponent(fileObjectId)}/download?disposition=${disposition}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user