Split permissions client http transport

master
dev 5 months ago
parent 2415f4942f
commit 34b682bab5

@ -12,6 +12,7 @@ Estado al cierre:
- `arts/conn/connection.ts` usa `createConnectionIdFactory(...)` desde helpers.
- `arts/conn/connection.ts` delega auth/request/ACK request-reply en `connection-requests.ts`.
- `svrs/auth/adapters/memory-store.ts` queda como composition root de 39 lineas; la logica se reparte en stores internos de credentials, flows, linked accounts, sessions/devices, refresh y state/snapshot.
- `arts/perm/client.ts` delega transporte HTTP/JSON en `client-http.ts`; el cliente queda centrado en cache, snapshot y fallback.
- Integracion total ampliada: `Auth.signOut()` valida anonimizacion, invalidacion de `Permissions` y evento `Cache.invalidate`.
- Tanda focalizada verde: `npx vitest run src/arts/conn src/libs/cach src/arts/cach src/svrs/cach src/arts/auth src/svrs/auth src/libs/auth src/arts/aapp/test/ecosystem.integration.test.ts` -> 19 archivos, 82 tests.
- `/test/ecosystem` revisado en navegador: carga sin errores de consola, `ar` cambia a `rtl`, Formats se actualiza por locale, Perm cambia con rol `viewer`, Cach re-scopea por locale y Conn loopback publica/recibe.

@ -0,0 +1,83 @@
import type { StandardSchemaV1 } from '$libs/standard-schema';
import { HTTP_CONTENT_TYPE_JSON, HTTP_HEADER_CONTENT_TYPE, HTTP_METHOD_POST } from '$libs/http';
import {
PERMISSION_ERROR_MSG_REQUEST_FAILED_PREFIX,
PERMISSION_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX,
PERMISSION_HTTP_CREDENTIALS_INCLUDE
} from './consts.ts';
import { PermRemoteRequestError } from './errors.ts';
import type { PermissionClientOptions } from './types.ts';
const PERMISSION_JSON_PASSTHROUGH_SCHEMA: StandardSchemaV1<unknown, unknown> = {
'~standard': {
version: 1,
vendor: 'active-perm',
validate(value) {
return { value };
}
}
};
function joinUrl(base: string, path: string): string {
return `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
}
export async function postPermissionJson<T>(
options: PermissionClientOptions,
path: string,
body: unknown
): Promise<T> {
const url = joinUrl(options.endpoint, path);
if (options.http) {
const response = await options.http.post(url, {
body: body as Record<string, unknown>,
schema: PERMISSION_JSON_PASSTHROUGH_SCHEMA
});
if (response.ok) return response.value as T;
const status = 'status' in response ? response.status : undefined;
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_REQUEST_FAILED_PREFIX}${status ?? url}`,
url,
status
);
}
const fetcher = options.fetcher ?? fetch.bind(globalThis);
const response = await fetcher(url, {
method: HTTP_METHOD_POST,
headers: { [HTTP_HEADER_CONTENT_TYPE]: HTTP_CONTENT_TYPE_JSON },
credentials: PERMISSION_HTTP_CREDENTIALS_INCLUDE,
body: JSON.stringify(body)
});
if (!response.ok) {
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_REQUEST_FAILED_PREFIX}${response.status} ${response.statusText}`,
url,
response.status
);
}
return readPermissionJson<T>(response, url);
}
async function readPermissionJson<T>(response: Response, url: string): Promise<T> {
const contentType = response.headers.get(HTTP_HEADER_CONTENT_TYPE);
if (contentType && !contentType.toLowerCase().includes(HTTP_CONTENT_TYPE_JSON)) {
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX}${url}`,
url,
response.status
);
}
try {
return (await response.json()) as T;
} catch (_error) {
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX}${url}`,
url,
response.status
);
}
}

@ -5,8 +5,6 @@ import {
PERMISSION_EFFECT_INDETERMINATE,
PERMISSION_FALLBACK_DENY
} from '$libs/perm';
import type { StandardSchemaV1 } from '$libs/standard-schema';
import { HTTP_CONTENT_TYPE_JSON, HTTP_HEADER_CONTENT_TYPE, HTTP_METHOD_POST } from '$libs/http';
import {
PERMISSION_ACTIVE_EVENT_BATCH,
PERMISSION_ACTIVE_EVENT_HYDRATE,
@ -23,9 +21,6 @@ import {
PERMISSION_CLIENT_SCOPE_PREFIX,
PERMISSION_DECISION_REASON_REMOTE_BATCH_FAILED,
PERMISSION_DECISION_REASON_REMOTE_CHECK_FAILED,
PERMISSION_ERROR_MSG_REQUEST_FAILED_PREFIX,
PERMISSION_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX,
PERMISSION_HTTP_CREDENTIALS_INCLUDE,
PERMISSION_METHOD_DECISION_KEY,
PERMISSION_METHOD_CHECK,
PERMISSION_METHOD_CAN,
@ -40,11 +35,12 @@ import {
PERMISSION_RESPONSE_FIELD_DECISIONS,
PERMISSION_SNAPSHOT_GLOBAL_POLICY
} from './consts.ts';
import { postPermissionJson } from './client-http.ts';
import {
createPermissionClientDiagnostics,
emitPermissionClientDiagnostic
} from './diagnostics.ts';
import { PermDisposedError, PermRemoteRequestError } from './errors.ts';
import { PermDisposedError } from './errors.ts';
import { disposedPermissionsMessage } from './helpers.ts';
import { permissionDecisionKey, stablePermissionStringify } from '$libs/svrs/perm';
import type {
@ -56,85 +52,11 @@ import type {
} from './types.ts';
import type { ExplainResult, PermissionDecision } from '$libs/perm';
const PERMISSION_JSON_PASSTHROUGH_SCHEMA: StandardSchemaV1<unknown, unknown> = {
'~standard': {
version: 1,
vendor: 'active-perm',
validate(value) {
return { value };
}
}
};
interface CacheEntry {
readonly decision: PermissionDecision;
readonly expiresAt: number;
}
function joinUrl(base: string, path: string): string {
return `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
}
async function postJson<T>(
options: PermissionClientOptions,
path: string,
body: unknown
): Promise<T> {
const url = joinUrl(options.endpoint, path);
if (options.http) {
const response = await options.http.post(url, {
body: body as Record<string, unknown>,
schema: PERMISSION_JSON_PASSTHROUGH_SCHEMA
});
if (response.ok) return response.value as T;
const status = 'status' in response ? response.status : undefined;
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_REQUEST_FAILED_PREFIX}${status ?? url}`,
url,
status
);
}
const fetcher = options.fetcher ?? fetch.bind(globalThis);
const response = await fetcher(url, {
method: HTTP_METHOD_POST,
headers: { [HTTP_HEADER_CONTENT_TYPE]: HTTP_CONTENT_TYPE_JSON },
credentials: PERMISSION_HTTP_CREDENTIALS_INCLUDE,
body: JSON.stringify(body)
});
if (!response.ok) {
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_REQUEST_FAILED_PREFIX}${response.status} ${response.statusText}`,
url,
response.status
);
}
return readPermissionJson<T>(response, url);
}
async function readPermissionJson<T>(response: Response, url: string): Promise<T> {
const contentType = response.headers.get(HTTP_HEADER_CONTENT_TYPE);
if (contentType && !contentType.toLowerCase().includes(HTTP_CONTENT_TYPE_JSON)) {
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX}${url}`,
url,
response.status
);
}
try {
return (await response.json()) as T;
} catch (_error) {
throw new PermRemoteRequestError(
`${PERMISSION_ERROR_MSG_RESPONSE_NOT_JSON_PREFIX}${url}`,
url,
response.status
);
}
}
export function createPermissionClient(options: PermissionClientOptions): PermissionClient {
const clock = options.clock ?? systemClock;
const diagnostics = createPermissionClientDiagnostics(options.logger);
@ -268,7 +190,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
const inFlight = pending.get(key);
if (inFlight) return inFlight;
const request = postJson<PermissionDecision>(options, PERMISSION_CLIENT_PATH_CHECK, {
const request = postPermissionJson<PermissionDecision>(options, PERMISSION_CLIENT_PATH_CHECK, {
[PERMISSION_REQUEST_FIELD_ACTION]: input.action,
[PERMISSION_REQUEST_FIELD_RESOURCE]: input.resource,
[PERMISSION_REQUEST_FIELD_CONTEXT]: input.context
@ -307,7 +229,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
localKey: decisionKey(item)
}));
try {
const result = await postJson<{ decisions: Record<string, PermissionDecision> }>(
const result = await postPermissionJson<{ decisions: Record<string, PermissionDecision> }>(
options,
PERMISSION_CLIENT_PATH_BATCH,
{ [PERMISSION_REQUEST_FIELD_CHECKS]: input.checks }
@ -349,7 +271,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
const scope = resolveScopeKey();
const requestGeneration = generation;
try {
const result = await postJson<{ actions: Record<string, PermissionDecision> }>(
const result = await postPermissionJson<{ actions: Record<string, PermissionDecision> }>(
options,
PERMISSION_CLIENT_PATH_WHAT,
input
@ -376,7 +298,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
async function explain(input: PermissionClientCheckInput): Promise<ExplainResult | null> {
ensureLive(PERMISSION_METHOD_EXPLAIN);
try {
return await postJson<ExplainResult>(options, PERMISSION_CLIENT_PATH_EXPLAIN, input);
return await postPermissionJson<ExplainResult>(options, PERMISSION_CLIENT_PATH_EXPLAIN, input);
} catch (error) {
options.onError?.(error);
return null;

Loading…
Cancel
Save

Powered by TurnKey Linux.