@super-oar/authz
v0.0.4
Published
Track A FERPA/COPPA application-layer authorization resolver (PDP+PEP): principal resolution, capability merge (deny-precedence), scope+filter resolution, requireX/assertX guards, object-level BOLA guard, consent classification. ESM, pure-JS, DB-optional.
Readme
timeback-authz (monorepo)
This repository houses both implementations of the Track A authorization policy plus the shared parity suite:
| Path | What it is |
| --- | --- |
| repo root (src/, test/, package.json) | @super-oar/authz — the TypeScript package (source of truth). Published to GitHub Packages via .github/workflows/publish.yml on v* tag push. Unchanged location/name/paths — existing consumers and the publish flow are unaffected. |
| python/ | superbuilders-authz — the Python port (import superbuilders_authz) for the Python tier. Same gate order, same verdicts, same reasons. |
| conformance/ | Shared cross-language conformance vectors (vectors.json). Both engines load the same file: test/conformance.test.ts (TS) and python/tests/test_conformance.py (Python). |
Parity gate: CI (.github/workflows/ci.yml) runs both suites — including the conformance vectors — on every PR. Any decision-policy change must update conformance/vectors.json and pass both suites (see CONTRIBUTING.md). A vector passing in one language and failing in the other is a port-drift bug.
Versioning (lockstep convention): the TS package versions independently (npm version + tag → publish). The Python package tracks the TS minor line — when TS bumps its minor, bump the Python minor in python/pyproject.toml in the same PR so both engines' policy surface stays in lockstep.
@super-oar/authz
Track A (FERPA/COPPA) application-layer authorization resolver — the shared PDP+PEP every TimeBack TS app installs. Implements TA-PEP-03 / TA-PEP-10.
- ESM-only, pure-JS, no native deps, DB-optional → runs in Node, AWS Lambda, and Cloudflare Workers.
- Application-layer first.
decide()returns a decision + a filter spec and works with no database — because most consumers read student data over REST / MySQL / DynamoDB, not a Postgres the package can attach to. RLS/GUC enforcement is a later phase and is intentionally not in this package. - Decision core is swappable (ADR-11): SQL capability merge now → Cedar/OpenFGA later, same surface.
API
decide(input, deps) → { verdict, reason, filter?, maskFields? }— the core. Ordered gates (tenant → role active+window → read-only → capability+scope → sensitivity → consent → elevation); first-fail-denies;dual_read/shadowdowngrade towould_*(deploying denies nobody). Omitresourcefor a list read → returns afilter(allowed org/user/class ids) to constrain your query.- Guards (throw
AuthzError(403)in enforce, returnwould_*in shadow):authorize,assertParentChildAccess,assertTeacherClassAccess,assertSchoolAccess,assertCanRead(object-level BOLA),requireAdmin/requireTeacherOrAdmin/requireParentOrGuardian. - Capability merge:
mergeCapabilities(role_capabilities ⊕ user_capability_grants, deny-precedence, NULL tier → most-restrictive, M2Mclient_appkeys). - Consent:
classifyConsentBoundary,methodSatisfiesBasis/isVpc(COPPA under-13 VPC split). - Shadow:
shadowCompare(rundecide()beside your existing check, log-only, never blocks; optionally inject a realconsentOk/resolveScopeand setenv.sinkto a consent-gated sink so consent can produce awould_deny);shadowConsentCompare(consent-gate-only shadow for ingest sinks — e.g. "would this Caliper event be dropped for lack ofbehavioral_analyticsconsent?"). The[authz-shadow]log line is unchanged.
You inject your store + identity (the per-app glue)
const deps: DecideDeps = {
resolveScope: (scope, subject) => /* your enrollments / guardianship_links / orgs query → ScopeResult */,
consentOk: (child, purpose, tenant) => /* your consent_records lookup → boolean */,
}- Build
Principalfrom your already-verified identity (Cognito JWT / Clerk / Supabase / AppSync OIDC). Never trust an IdP group/claim/metadata for capabilities — resolve them from the authoritative store. Usesystem: truefor non-HTTP workers (BullMQ/cron),readOnly: truefor read-only service accounts.
Test / build
export PATH=/opt/homebrew/opt/node@20/bin:$PATH
npm install
npm test # vitest: capability merge + RC fail-closed regressions + guards
npm run typecheck
npm run build # tsup → dist/ (ESM + d.ts)Consume (other repos)
.npmrc:
@superbuilders:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}then npm i @super-oar/authz (yarn-berry: npmScopes in .yarnrc.yml; pnpm catalog repos: add a catalog entry).
Release
npm version <patch|minor|major> on a clean main → git push --follow-tags → .github/workflows/publish.yml publishes to GitHub Packages (mirrors @super-oar/timeback-tracelog).
Not covered (separate deliverable)
bridge's Python tier can't consume a JS package → a separate @super-oar/authz-python sibling (parallel to timeback-tracelog-python), built later.
