Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
# `sharing/grants/grants.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`GrantManager` is the deterministic in-memory grant store. It holds `Grant` records keyed by `GrantId` and answers permission queries via `check(grantId, userId, action)`. The check is exhaustive: existence, non-revocation, non-expiry, grantee identity, and permission inclusion are all verified, in that order, with the first failure short-circuiting the result. Every check increments `share.grant.check_total`; denials additionally increment `share.grant.denied_total` with a `reason` attribute so dashboards can split denials by cause.
## Features
- Synchronous `check` — sub-millisecond by design (no IO, no crypto).
- Injectable `Clock` so expiry tests are deterministic.
- `EventBusLike` integration: `grant:created` and `grant:revoked` events carry full context but never key material.
- Hard cap on active grants (`GRANT_MANAGER_MAX_ENTRIES = 10_000_000`) per the spec scalability table.
- Filtered `list(query)` supporting `granteeId`, `partitionId`, and `active` filters.
- `revoke` is idempotent — already-revoked / missing grants are no-ops.
## Usage: Basic Example
```ts
import { GrantManager } from "@suaveplan/drive-sharing/sharing/grants";
import { partitionId, userId } from "@suaveplan/drive-sharing/types/common";
const grants = new GrantManager();
const grant = grants.create({
grantorId: userId("alice"),
granteeId: userId("bob"),
partitionId: partitionId("p"),
permissions: ["read"],
});
grants.check(grant.id, userId("bob"), "read"); // { allowed: true }
```
## Usage: Advanced Example
```ts
import { GrantManager } from "@suaveplan/drive-sharing/sharing/grants";
import { partitionId, userId } from "@suaveplan/drive-sharing/types/common";
let now = 0;
const events = new Map<string, unknown[]>();
const grants = new GrantManager({
clock: () => now,
events: {
on: () => () => {},
emit: (k, p) => {
const arr = events.get(k) ?? [];
arr.push(p);
events.set(k, arr);
},
},
});
const g = grants.create({
grantorId: userId("alice"),
granteeId: userId("bob"),
partitionId: partitionId("p"),
permissions: ["read"],
expiresAt: 1000,
});
now = 999;
console.log(grants.check(g.id, userId("bob"), "read")); // allowed
now = 1001;
console.log(grants.check(g.id, userId("bob"), "read")); // denied: expired
grants.revoke(g.id);
console.log(grants.list({ active: true })); // []
```
## API Reference
### `class GrantManager`
- `constructor(config?: GrantManagerConfig)`
- `create(options: CreateGrantOptions): Grant`
- `check(id, userId, action): GrantCheckResult`
- `revoke(id): void`
- `list(query?): Grant[]`
- `get(id): Grant | undefined`
### `interface CreateGrantOptions`
`{ grantorId, granteeId, partitionId, permissions, expiresAt? }`.
### `interface Grant`
`{ id, grantorId, granteeId, partitionId, permissions, createdAt, expiresAt?, revoked }`.
### `interface GrantCheckResult`
`{ allowed: boolean; reason?: string }` — `reason` is one of `grant_not_found`, `revoked`, `expired`, `grantee_mismatch`, `permission_missing`.
### `interface GrantQuery`
`{ granteeId?, partitionId?, active? }`.
### `interface GrantManagerEvents`
- `grant:created` — full grant
- `grant:revoked` — `{ id, partitionId, userId }`
## Implementation Notes
- The denial-reason strings are stable contract — dashboards may key on them. Renaming a reason is a major-version bump.
- `revoke` is intentionally idempotent so a duplicate "revoke this grant" message from a retry-loop does not trigger a second `grant:revoked` event.
- `list({ active: true })` reads the clock once per grant; for very large grant stores callers should prefer `list({ partitionId, active: true })` so the filter narrows before the active-check fires.
- The Zod schema (`CreateGrantOptionsSchema`) enforces non-empty `permissions`. An attacker who calls `create` with `permissions: []` is rejected at the boundary, never silently inserted.
- `check` runs the action verb through `permissions.includes(action as SharePermission)` — the cast is safe because every `PolicyAction` is also a valid `SharePermission` in the canonical list.