JSON API for Atlas — Northeastern University Library's institutional digital repository.
Atlas is the system of record for the repository's content graph. It owns durable identifiers (NOIDs), MODS metadata, the resource hierarchy, and the binary stream of every file. It does not handle browser sessions or SSO — that's Cerberus's job.
┌─────────────────────────────────┐
Browser / SSO ──────▶│ Cerberus │
│ (Northeastern auth gateway) │
└─────────────────────────────────┘
│
│ uses (mints + signs the assertion)
▼
┌─────────────────────────────────┐
│ atlas_rb (canonical Ruby client)│
└─────────────────────────────────┘
│
│ HTTP · Bearer <signed assertion>
│ (ES256 JWT, iss=cerberus)
▼
any other OpenAPI ──▶┌─────────────────────────────────┐
consumer (curl, │ Atlas │ ← this repo
codegen, agents) │ Rails API · Postgres · Solr · │
│ Valkyrie · jbuilder │
└─────────────────────────────────┘
- Cerberus terminates user sessions and asserts identity. It never talks
to Atlas directly — it reaches it through atlas_rb, carrying a
short-lived signed assertion: an ES256 JWT whose proven
subis the acting user. The pre-shared system token +User: NUIDheader is scoped to SSO first-login provisioning of the:systemprincipal. - atlas_rb is the canonical
Ruby client and the only path Cerberus uses to reach Atlas; it reads
ATLAS_URL, authenticates by signing a relay assertion (or with a personal JWT viaATLAS_JWT), and wraps every endpoint as a class method. - Any other consumer (codegen, agents, curl) can drive Atlas straight from the OpenAPI document — see API documentation.
Community → Collection → Work
↓
FileSet
↓
Blob
| Resource | Represents |
|---|---|
| Community | Top-level org unit; may nest sub-Communities. |
| Collection | Holds Works; lives directly under a Community. |
| Work | Bibliographic unit (article, thesis, dataset…); MODS metadata lives here. |
| FileSet | Classified slot under a Work (e.g. primary, supplemental). |
| Blob | The binary bytes; supports byte-range streaming. |
Every resource has a NOID. GET /resources/:id resolves any NOID to its
typed endpoint (302 redirect to /works/:id, /collections/:id, etc.).
require_auth (app/controllers/application_controller.rb) resolves
@current_user from the bearer token before every action. Atlas accepts
four inbound credential shapes; endpoint authorization beyond identity is
enforced by cancancan.
| # | Credential | Headers | Resolves to |
|---|---|---|---|
| 1 | Guest | (no Authorization) |
The :guest user — read-only. |
| 2 | System token | Authorization: Bearer <system_token> + User: NUID <system NUID> |
The :system fixture only (SSO first-login provisioning). Missing/unknown NUID → 400; pairing the system token with any non-:system NUID → 401. |
| 3 | Devise-JWT user token | Authorization: Bearer <jwt> |
The real person named by the token's signed sub. The User: header is ignored on this path; :system / :anonymous are rejected (401). |
| 4 | Cerberus signed assertion | Authorization: Bearer <ES256 JWT> (iss == "cerberus") |
The real person named by the signed sub, verified against Cerberus's public keyset by kid (aud=atlas, exp with 30s leeway, ES256 only). This is the Cerberus relay path. |
A blank bearer is the guest path; anything that matches none of the above is 401. Read endpoints generally fall through to guest when no valid auth is supplied; write endpoints rely on the caller having been authorized upstream (by Cerberus or by holding a personal JWT).
Headers vs. signed identity. The User: header is only meaningful on the
system path — there it must be NUID <system NUID> and resolves the :system
fixture. On the JWT and assertion paths, identity comes from the signed token,
and the User: header is ignored.
Acting-as (an admin operator acting with a separate attribution target)
lives exclusively on the Cerberus-assertion path and rides a signed
obo claim — never a User: or On-Behalf-Of header. It is admin-only (a
signed obo from a non-admin → 403). An On-Behalf-Of header is never
honored: it is overwritten by the signed claim on the assertion path, and
rejected (403) on every other path.
Atlas reads these keys from Rails encrypted credentials (rails credentials:edit):
| Key | Purpose |
|---|---|
cerberus_signing_keys |
{ kid => PEM } map of Cerberus's public signing keys, used to verify relay assertions (ES256). Public keys only — safe at rest, nothing to rotate as a secret. Empty (the default until Cerberus is provisioned) leaves the assertion path inert. |
system_token |
Shared bearer secret for the system / provisioning path (paired with Cerberus's atlas_system_token). |
jwt_secret |
devise-jwt signing secret. Falls back to secret_key_base until provisioned; rotating it is a global token kill-switch that does not also invalidate sessions/cookies. |
secret_key_base |
Standard Rails secret. |
The contract is generated from request specs (rswag) and rendered two ways:
- Humans:
/docs— interactive reference via Scalar. - Machines (clients, agents, codegen):
/api-docs/openapi.yaml.
The committed openapi/openapi.yaml is the source of truth. To regenerate
after editing a spec or jbuilder partial:
bin/openapiCI runs the same generator and fails the build if the committed YAML
diverges from what the specs produce. Atlas's response shape lives in
app/views/**/*.jbuilder partials; the matching schemas live in
spec/support/openapi_schemas.rb. Strict schema validation in the request
specs catches drift between the two.
docker compose up -d # web, db (postgres), solr
open http://localhost:3000 # API root
open http://localhost:3000/docsThe dev image mounts ./ into the container (docker-compose.dev.yml), so
edits land live. The web service entrypoint runs db:create + db:migrate
on boot, so a fresh checkout is one command away from a working API.
docker compose exec web bundle exec rake # full suite
docker compose exec web bundle exec rspec spec/requests/ # contract specs (rswag)
docker compose exec web bundle exec rspec spec/integration/ # atlas_rb client round-trips
docker compose exec web bundle exec rake rswag:specs:swaggerize # regenerate openapi.yamlThere are three concentric test layers:
- Controller specs (
spec/controllers/) — fast, mock-friendly, per-action. - Request specs (
spec/requests/) — rswag DSL; double as the OpenAPI source. Strict schema validation flags response drift. - Integration specs (
spec/integration/) — boot a real Puma server via Capybara, drive Atlas throughatlas_rbover HTTP. This is the end-to-end contract test against the same client Cerberus uses in production.
- Ruby 3.0, Rails 7, Postgres 14, Solr (Blacklight image)
- Valkyrie for the metadata persistence
abstraction; a custom Valkyrie OCFL storage adapter
(
app/lib/valkyrie/storage/ocfl.rb) for binary storage - jbuilder partials per resource
(
app/views/{resource}/_{resource}.json.jbuilder) — single source of truth for response shapes - Devise + devise-jwt for the user-token path (the non-Cerberus side of auth)
- pagy for index-action pagination
- rswag for spec-driven OpenAPI
Internal Northeastern University Libraries project — contact the maintainers for licensing.