Extract permissions client key helpers

master
dev 5 months ago
parent 6e2b3995c3
commit 1aba0c3c91

@ -21,6 +21,7 @@ Estado al cierre:
- `arts/sess/engine-session.ts` delega la clasificacion `none/anonymous/identified` en `session-identity.ts`.
- `arts/sess/engine-session.ts` delega snapshot, generation, dispatch, persistencia y commits en `session-state.ts`.
- `arts/sess/engine-session.ts` delega la resolucion de revoke local/global/degradado en `session-revoke.ts`.
- `arts/perm/client.ts` delega claves/scope de cache en `client-keys.ts` y lectura de snapshot en `client-snapshot.ts`.
- `arts/conn/connection.ts` delega la programacion de reconnect y exhaustion en `connection-reconnect-runtime.ts`.
- `arts/conn/connection.ts` delega decode/routing de frames entrantes en `connection-message-router.ts`.
- `arts/conn/connection.ts` delega el intento open/auth/flush/join en `connection-connect.ts`.

@ -0,0 +1,59 @@
import {
PERMISSION_CLIENT_KEY_SEPARATOR,
PERMISSION_CLIENT_SCOPE_PREFIX
} from './consts.ts';
import { permissionDecisionKey, stablePermissionStringify } from '$libs/svrs/perm';
import type {
PermissionClientCheckInput,
PermissionClientOptions,
PermissionSnapshot
} from './types.ts';
export interface PermissionClientKeyRuntime {
remoteDecisionKey(input: PermissionClientCheckInput): string;
resolveScopeKey(): string | undefined;
decisionKeyForScope(input: PermissionClientCheckInput, scope: string | undefined): string;
decisionKey(input: PermissionClientCheckInput): string;
scopedKeyPrefix(scope: string): string;
}
export function createPermissionClientKeyRuntime(
options: PermissionClientOptions,
readSnapshot: () => PermissionSnapshot
): PermissionClientKeyRuntime {
function remoteDecisionKey(input: PermissionClientCheckInput): string {
return permissionDecisionKey(input);
}
function resolveScopeKey(): string | undefined {
const configured =
typeof options.scopeKey === 'function' ? options.scopeKey() : options.scopeKey;
if (configured !== undefined && configured.length > 0) return configured;
const actor = readSnapshot().actor;
if (actor === undefined) return undefined;
return stablePermissionStringify(actor);
}
function decisionKeyForScope(
input: PermissionClientCheckInput,
scope: string | undefined
): string {
const base = remoteDecisionKey(input);
if (scope === undefined) return base;
return [PERMISSION_CLIENT_SCOPE_PREFIX, stablePermissionStringify(scope), base].join(
PERMISSION_CLIENT_KEY_SEPARATOR
);
}
function decisionKey(input: PermissionClientCheckInput): string {
return decisionKeyForScope(input, resolveScopeKey());
}
function scopedKeyPrefix(scope: string): string {
return [PERMISSION_CLIENT_SCOPE_PREFIX, stablePermissionStringify(scope), ''].join(
PERMISSION_CLIENT_KEY_SEPARATOR
);
}
return { remoteDecisionKey, resolveScopeKey, decisionKeyForScope, decisionKey, scopedKeyPrefix };
}

@ -0,0 +1,38 @@
import {
PERMISSION_DECISION_CODE_SNAPSHOT_DENIED,
PERMISSION_EFFECT_ALLOW,
PERMISSION_EFFECT_DENY
} from '$libs/perm';
import { PERMISSION_SNAPSHOT_GLOBAL_POLICY } from './consts.ts';
import type { PermissionClientKeyRuntime } from './client-keys.ts';
import type { PermissionDecision } from '$libs/perm';
import type { PermissionClientCheckInput, PermissionSnapshot } from './types.ts';
export function isPermissionSnapshotValid(snapshot: PermissionSnapshot, now: number): boolean {
return snapshot.expiresAt === undefined || Date.parse(snapshot.expiresAt) > now;
}
export function readPermissionSnapshotDecision(
input: PermissionClientCheckInput,
snapshot: PermissionSnapshot,
now: number,
keys: PermissionClientKeyRuntime
): PermissionDecision | undefined {
if (!isPermissionSnapshotValid(snapshot, now)) return undefined;
const key = keys.decisionKey(input);
const direct = snapshot.decisions?.[key];
if (direct) return direct;
const remote = snapshot.decisions?.[keys.remoteDecisionKey(input)];
if (remote) return remote;
const global = snapshot.global?.[input.action];
if (typeof global === 'boolean') {
return global
? { effect: PERMISSION_EFFECT_ALLOW, policy: PERMISSION_SNAPSHOT_GLOBAL_POLICY }
: {
effect: PERMISSION_EFFECT_DENY,
code: PERMISSION_DECISION_CODE_SNAPSHOT_DENIED,
reason: PERMISSION_SNAPSHOT_GLOBAL_POLICY
};
}
return global;
}

@ -1,7 +1,5 @@
import {
PERMISSION_DECISION_CODE_SNAPSHOT_DENIED,
PERMISSION_EFFECT_ALLOW,
PERMISSION_EFFECT_DENY,
PERMISSION_EFFECT_INDETERMINATE,
PERMISSION_FALLBACK_DENY
} from '$libs/perm';
@ -17,8 +15,6 @@ import {
PERMISSION_CLIENT_PATH_CHECK,
PERMISSION_CLIENT_PATH_EXPLAIN,
PERMISSION_CLIENT_PATH_WHAT,
PERMISSION_CLIENT_KEY_SEPARATOR,
PERMISSION_CLIENT_SCOPE_PREFIX,
PERMISSION_DECISION_REASON_REMOTE_BATCH_FAILED,
PERMISSION_DECISION_REASON_REMOTE_CHECK_FAILED,
PERMISSION_METHOD_DECISION_KEY,
@ -32,17 +28,17 @@ import {
PERMISSION_REQUEST_FIELD_CONTEXT,
PERMISSION_REQUEST_FIELD_RESOURCE,
PERMISSION_RESPONSE_FIELD_ACTIONS,
PERMISSION_RESPONSE_FIELD_DECISIONS,
PERMISSION_SNAPSHOT_GLOBAL_POLICY
PERMISSION_RESPONSE_FIELD_DECISIONS
} from './consts.ts';
import { postPermissionJson } from './client-http.ts';
import { createPermissionClientKeyRuntime } from './client-keys.ts';
import { readPermissionSnapshotDecision } from './client-snapshot.ts';
import {
createPermissionClientDiagnostics,
emitPermissionClientDiagnostic
} from './diagnostics.ts';
import { PermDisposedError } from './errors.ts';
import { disposedPermissionsMessage } from './helpers.ts';
import { permissionDecisionKey, stablePermissionStringify } from '$libs/svrs/perm';
import type {
PermissionClient,
PermissionClientBatchInput,
@ -72,6 +68,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
let currentSnapshot: PermissionSnapshot = options.initialSnapshot ?? { decisions: {} };
let generation = 0;
let disposed = false;
const keys = createPermissionClientKeyRuntime(options, () => currentSnapshot);
function now(): number {
return clock.now();
@ -85,55 +82,8 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
for (const listener of listeners) listener(currentSnapshot);
}
function remoteDecisionKey(input: PermissionClientCheckInput): string {
return permissionDecisionKey(input);
}
function resolveScopeKey(): string | undefined {
const configured =
typeof options.scopeKey === 'function' ? options.scopeKey() : options.scopeKey;
if (configured !== undefined && configured.length > 0) return configured;
if (currentSnapshot.actor === undefined) return undefined;
return stablePermissionStringify(currentSnapshot.actor);
}
function decisionKeyForScope(
input: PermissionClientCheckInput,
scope: string | undefined
): string {
const base = remoteDecisionKey(input);
if (scope === undefined) return base;
return [PERMISSION_CLIENT_SCOPE_PREFIX, stablePermissionStringify(scope), base].join(
PERMISSION_CLIENT_KEY_SEPARATOR
);
}
function decisionKey(input: PermissionClientCheckInput): string {
return decisionKeyForScope(input, resolveScopeKey());
}
function snapshotStillValid(snapshot: PermissionSnapshot): boolean {
return snapshot.expiresAt === undefined || Date.parse(snapshot.expiresAt) > now();
}
function readSnapshotDecision(input: PermissionClientCheckInput): PermissionDecision | undefined {
if (!snapshotStillValid(currentSnapshot)) return undefined;
const key = decisionKey(input);
const direct = currentSnapshot.decisions?.[key];
if (direct) return direct;
const remote = currentSnapshot.decisions?.[remoteDecisionKey(input)];
if (remote) return remote;
const global = currentSnapshot.global?.[input.action];
if (typeof global === 'boolean') {
return global
? { effect: PERMISSION_EFFECT_ALLOW, policy: PERMISSION_SNAPSHOT_GLOBAL_POLICY }
: {
effect: PERMISSION_EFFECT_DENY,
code: PERMISSION_DECISION_CODE_SNAPSHOT_DENIED,
reason: PERMISSION_SNAPSHOT_GLOBAL_POLICY
};
}
return global;
return readPermissionSnapshotDecision(input, currentSnapshot, now(), keys);
}
function setCached(
@ -173,7 +123,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
async function check(input: PermissionClientCheckInput): Promise<PermissionDecision> {
ensureLive(PERMISSION_METHOD_CHECK);
const key = decisionKey(input);
const key = keys.decisionKey(input);
const requestGeneration = generation;
const cached = cache.get(key);
if (cached && cached.expiresAt > now()) return cached.decision;
@ -225,8 +175,8 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
ensureLive(PERMISSION_ACTIVE_EVENT_BATCH);
const requestGeneration = generation;
const checks = input.checks.map((item) => ({
remoteKey: remoteDecisionKey(item),
localKey: decisionKey(item)
remoteKey: keys.remoteDecisionKey(item),
localKey: keys.decisionKey(item)
}));
try {
const result = await postPermissionJson<{ decisions: Record<string, PermissionDecision> }>(
@ -268,7 +218,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
readonly context?: PermissionClientCheckInput['context'];
}): Promise<Record<string, PermissionDecision>> {
ensureLive(PERMISSION_METHOD_WHAT);
const scope = resolveScopeKey();
const scope = keys.resolveScopeKey();
const requestGeneration = generation;
try {
const result = await postPermissionJson<{ actions: Record<string, PermissionDecision> }>(
@ -278,7 +228,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
);
for (const [action, decision] of Object.entries(result[PERMISSION_RESPONSE_FIELD_ACTIONS])) {
setCached(
decisionKeyForScope({ action, resource: input.resource, context: input.context }, scope),
keys.decisionKeyForScope({ action, resource: input.resource, context: input.context }, scope),
decision,
requestGeneration
);
@ -330,7 +280,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
emit();
return;
}
const prefix = scopedKeyPrefix(scope);
const prefix = keys.scopedKeyPrefix(scope);
for (const key of [...cache.keys()]) if (key.startsWith(prefix)) cache.delete(key);
for (const key of [...failures.keys()]) if (key.startsWith(prefix)) failures.delete(key);
for (const key of [...pending.keys()]) if (key.startsWith(prefix)) pending.delete(key);
@ -340,12 +290,6 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
emit();
}
function scopedKeyPrefix(scope: string): string {
return [PERMISSION_CLIENT_SCOPE_PREFIX, stablePermissionStringify(scope), ''].join(
PERMISSION_CLIENT_KEY_SEPARATOR
);
}
return {
check,
async can(input) {
@ -366,7 +310,7 @@ export function createPermissionClient(options: PermissionClientOptions): Permis
},
decisionKey(input) {
ensureLive(PERMISSION_METHOD_DECISION_KEY);
return decisionKey(input);
return keys.decisionKey(input);
},
dispose() {
if (disposed) return;

Loading…
Cancel
Save

Powered by TurnKey Linux.