Commit graph

3 commits

Author SHA1 Message Date
14d48ca0f9 fix(acl): hard-reject a lapsed token binding instead of prompting (#36)
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
A request that finds no live token grant exited `checkIfPubkeyAllowed`
at `undefined` regardless of *why* — whether the binding never existed
or had simply lapsed (expired / token-revoked). `undefined` routes the
caller into the admin-prompt path, which for an unattended client (an
ATM spire) means the request hangs until a BunkerTimeoutError.

The Sintra smoke proved the divergence directly: a KeyUser-level revoke
exits at step 2 with `false` and the spire sees a clean BunkerRejected
("Pairing Required"), but a TTL expiry fell through to `undefined` and
the spire saw a BunkerTimeout ("Signer Unreachable") — same operator
intent ("this pairing is over"), two different, one-broken outcomes.

Classify the no-live-grant case before returning: if a token bound to
this KeyUser *would* have granted the request (its policy carries a
matching rule; for `connect`, any bound token) but is now expired or
token-revoked, return `false` so the client re-pairs immediately. Only
a genuinely never-granted (method/kind) request stays `undefined` so an
admin can still approve new permission out-of-band.

Usage-cap exhaustion is left at `undefined` deliberately: a windowed
cap is a temporary rate-limit that refills as the window rolls, not a
permanent lapse, so it must not be reclassed as the re-pair signal. A
dedicated rate-limit reply is a separate follow-up.

Tests: the #24 expired-token and token-revoke guards now assert `false`;
added connect-lapse, and two distinction cases proving a never-granted
method (live token, or a method the lapsed token never covered) stays
`undefined`.
2026-06-21 12:44:36 +02:00
c76bbf2791 test(acl)(#28): integration cases for windowed + stacked usage caps
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
9 cases: under/at limit, signings outside the window excluded, uncapped,
lifetime (null window) all-time count, kind-specific counting, both
stacked-cap directions (hourly binds vs daily binds), and the
record->count->deny loop via recordSigning. 22 integration + 7 unit green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:29:51 +02:00
0b9ffe8ca6 test(acl)(#29): DB-backed integration tests for checkIfPubkeyAllowed
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
Closes the gap flagged in #27 review: the wiring that actually closes
#24 (step-4 Token join filtered by liveWhere) was untested — only the
pure predicate was. Now covered end-to-end against a throwaway SQLite DB
+ the real prisma client.

Harness (no new dependency; pnpm add is blocked by the nix node_modules
hoist pattern):
- tests/register-ts.cjs: ts-node (transpile-only) + a CommonJS resolver
  that maps the app's '.js' ESM-style specifiers to their '.ts' sources.
- node:test temp DB via 'prisma db push'; a before() guard refuses to run
  unless DATABASE_URL points at tests/.tmp/ (never truncates a real DB).
- npm run test:integration / test:all.

13 cases incl. the #24 regression guard (expired token -> denied),
revoke, connect-off-live-token, override expiry/revoke ignored,
deny-beats-grant, kind mismatch, no-KeyUser.

Also: acl/index.ts NDK import -> 'import type' (NostrEvent/NIP46Method are
type-only) so the ACL module no longer pulls ESM-only NDK at runtime —
required for the CommonJS test import, and a correct cleanup besides.

Requires the prisma engine env (CI/nix ok; devShell pending #30).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:09:41 +00:00