All files / realm/blong-access/adapter/db accessAclAssert.ts

40% Statements 24/60
100% Branches 1/1
0% Functions 0/1
40% Lines 24/60

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 611x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x                                                                         7x  
import {type IMeta, handler} from '@feasibleone/blong';
 
import {type AclCheckVerdict, type AclHost} from './accessModel.ts';
 
/**
 * `access.acl.assert` — gate one record (or the scope a new record is created
 * in) against the effective ACL, throwing the realm's `acl.*` error family.
 *
 * The adapter owns the SQL (`aclCheck`, shared with the generic CRUD) and this
 * handler owns the realm semantics: which predicate refuses with 404 rather than
 * 403, and which error family is raised.  Realm handlers call it through the
 * handler proxy before touching a guarded record:
 *
 * ```ts
 * await handler.accessAclAssert({recordId: params.invoiceId, predicate: 'edit'}, $meta);
 * ```
 *
 * `guarded: false` in the result means the entity does not opt into the ACL (or
 * the action is not RBAC-managed) — the caller is free to proceed, which is why
 * the verdict is returned rather than only thrown on.
 */
export default handler(({errors}) => ({
    async accessAclAssert(
        params: {
            /** Guarded entity as `subject.object` — defaults to the entity of the action. */
            entity?: string;
            /** The action to check — defaults to `$meta.method`. */
            action?: string;
            /** Binary record key (base64/hex) for a single-record check. */
            recordId?: string;
            /** Scope ids (base64/hex) for an `add` check. */
            scopeIds?: string[];
            /** The predicate being performed — `get` reports a denial as not-found. */
            predicate?: string;
        },
        $meta: IMeta,
    ): Promise<AclCheckVerdict> {
        const adapter = this as unknown as AclHost;
        const verdict = await adapter.aclCheck(
            {
                entity: params.entity,
                method: params.action ?? $meta.method,
                recordId: params.recordId,
                scopeIds: params.scopeIds,
            },
            $meta,
        );
        if (verdict.allowed) return verdict;
        // A denied single-record read reports "not found", so the caller cannot
        // probe for the existence of records it may not see; lists simply omit
        // them.  Everything else is an explicit refusal.
        if (params.predicate === 'get') {
            throw errors.aclNotFound({params: {reason: 'notFound'}});
        }
        if (params.scopeIds?.length) {
            throw errors.aclScopeDenied({params: {reason: 'scopeDenied'}});
        }
        throw errors.aclDenied({params: {reason: 'denied'}});
    },
}));