@kehto/acl
v0.2.0
Published
Pure, WASM-ready ACL module for the napplet protocol — zero dependencies, zero side effects
Maintainers
Readme
@kehto/acl
Pure, WASM-ready ACL module for the napplet protocol — zero dependencies, zero side effects.
Install
pnpm add @kehto/aclOverview
@kehto/acl is the authoritative access-control core for kehto. It owns an immutable AclState keyed on the NIP-5D 2-segment identity (dTag, hash) — the v1.2 canonical shape (the pre-v1.2 (pubkey, dTag, hash) triple is dropped; migrateAclState ships for legacy persistence readers).
Every function is pure: state in, state out. No I/O, no timers, no globals — the module is trivially compilable to WASM and is the single source of truth for capability decisions.
The module exposes two parallel capability surfaces:
- Bitfield constants (
CAP_RELAY_READ,CAP_RELAY_WRITE,CAP_STATE_READ,CAP_STATE_WRITE, …) — the compact per-entry representation used insideAclState.entries[*].caps. - Canonical v1.2 NIP-5D 8-domain capability strings (
CAP_IDENTITY_READ,CAP_KEYS_BIND,CAP_KEYS_FORWARD,CAP_MEDIA_CONTROL,CAP_NOTIFY_SEND,CAP_NOTIFY_CHANNEL,CAP_THEME_READ) — plus the retainedrelay:*,cache:*,hotkey:forward, andstate:*literals. These strings are whatresolveCapabilitiesNub()returns and what@kehto/runtime's enforce gate grants/revokes against. The v1.1sign:event/sign:nip04/sign:nip44entries were intentionally removed — canonical NIP-5D does not expose napplet-visible signing.
Quick Start
import {
createState,
grant,
check,
block,
unblock,
CAP_RELAY_WRITE,
CAP_NOTIFY_SEND,
} from '@kehto/acl';
// 1. Start with a restrictive state — unknown identities are denied everything.
let state = createState('restrictive');
const id = { dTag: 'chat', hash: 'ff00aa11' };
// 2. Grant the two canonical v1.2 capabilities this napplet needs.
state = grant(state, id, CAP_RELAY_WRITE);
state = grant(state, id, CAP_NOTIFY_SEND);
check(state, id, CAP_RELAY_WRITE); // true
check(state, id, CAP_NOTIFY_SEND); // true
// 3. Block the identity — all checks fail until unblocked, caps are preserved.
state = block(state, id);
check(state, id, CAP_RELAY_WRITE); // false (blocked)
state = unblock(state, id);
check(state, id, CAP_RELAY_WRITE); // true (restored)Public API
Types
AclState— immutable ACL state containerAclEntry— per-identity entry (caps,blocked,quota)Identity—{ dTag, hash }pair (NIP-5D 2-segment identity)Capability— union of every canonical capability stringCapabilityResolution—{ senderCap, recipientCap }returned byresolveCapabilitiesNubNubMessage— minimal shape consumed byresolveCapabilitiesNub({ type: string })
Constants — bit flags
CAP_RELAY_READ,CAP_RELAY_WRITE,CAP_CACHE_READ,CAP_CACHE_WRITECAP_HOTKEY_FORWARD,CAP_SIGN_EVENT,CAP_SIGN_NIP04,CAP_SIGN_NIP44CAP_STATE_READ,CAP_STATE_WRITE,CAP_ALL,CAP_NONEDEFAULT_QUOTA
Constants — canonical NIP-5D capability strings (v1.2)
ALL_CAPABILITIES— readonly tuple of every recognized capability stringCAP_IDENTITY_READ,CAP_KEYS_BIND,CAP_KEYS_FORWARDCAP_MEDIA_CONTROL,CAP_NOTIFY_SEND,CAP_NOTIFY_CHANNEL,CAP_THEME_READ
State mutations
createState— create an empty AclStategrant,revoke— add/remove capability bitsblock,unblock— toggle the block flagsetQuota,getQuota— per-identity state storage quotaserialize,deserialize— JSON round-trip for persistence
Capability resolution
check— evaluate identity + capability against statetoKey— compute thedTag:hashcomposite keyresolveCapabilitiesNub— map a NIP-5D NUB envelope type to the required sender/recipient capabilities across the 8 canonical domains
Migration
migrateAclState— one-shot migration from the legacy 3-segmentpubkey:dTag:hashkeys to the v1.2 2-segmentdTag:hashkeys; idempotent (returns the same reference when nothing to migrate)
API Reference
Full API reference: docs/api/@kehto/acl/ (generated via pnpm docs:api).
License
MIT
