Help us shape the future of Go! Take the 2026 Go Developer Survey

pb

command
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

Portal Backend (PB)

The management REST API of OpenNDX: members, provider schemas, consumer applications, and the submissions that create them. It validates callers' tokens, enforces scope-based access, and keeps the Policy Decision Point and the identity provider in step with what it stores.

Naming. PB is planned to be renamed Management Server. Until then the code, image, service and environment names all say portal-backend or pb; the rename checklist lists every place the name appears.

Overview

flowchart LR
    AP[Admin and Member portals] -->|REST, Bearer token| PB
    CLI[ondx CLI] -->|REST, Bearer token| PB
    PB --> DB[(PostgreSQL: portal_backend)]
    PB -->|metadata and allow-list| PDP[Policy Decision Point]
    PB -->|create and delete app clients| IDP[ThunderID]
    PB -.->|verify tokens: JWKS| IDP
    PB -.->|audit events| A[Argus audit service]

PB is called by the ondx CLI and by the admin and member portals. The Consent Portal does not use it. PB never calls the Orchestration Engine or Consent Engine.

Quick start

With Docker Compose (from the repo root). PB needs the PDP at runtime, so start both:

docker compose up --build portal-backend policy-decision-point
curl http://localhost:8083/health

This also starts PostgreSQL and ThunderID, and registers PB's own client (PORTAL_BACKEND) in ThunderID.

Native, against your own PostgreSQL, PDP and identity provider:

cp cmd/pb/config.example.yaml cmd/pb/config.yaml    # once; config.yaml is git-ignored
export DB_PASSWORD=<password> IDP_CLIENT_SECRET=<secret>
make run portal-backend    # or: go run ./cmd/pb -config cmd/pb/config.yaml

config.example.yaml points at localhost:5432 as user postgres, a PDP on localhost:8082 and a ThunderID on localhost:8090, reads the two secrets from the environment, and creates PB's tables at startup.

Configuration

PB reads one YAML file: the -config flag, else CONFIG_PATH, else config.yaml in the working directory. There are no other flags or environment variables. The Docker image sets CONFIG_PATH=/app/config/config.yaml and has no config file of its own, so mount one there. Compose mounts config.docker.yaml.

A value written as "{{env:NAME}}" or "{{file:/path}}" is read from that environment variable or file at startup, so secrets stay out of the file. Required keys:

Key Notes
idp.jwksURL, idp.issuer, idp.audience Validate incoming tokens. audience is PB's resource-server identifier.
idp.management.baseURL, .clientID, .clientSecret PB's own client for ThunderID's management API.
pdp.url Policy Decision Point.
database.postgres.host, .user, .password

Every key, its default and the environment variable it replaces: CONFIGURATION.md.

API

All routes are under /api/v1 and need Authorization: Bearer <token>. Full schemas: openapi.yaml. Success responses are the bare resource, lists are {items, count}, and errors are {"error": "<message>"}.

Resource Endpoints Scope
Members GET /members, GET /members/{memberId}
POST /members
PUT /members/{memberId}
member:read
member:write-all
member:write
Schemas GET /schemas, GET /schemas/{schemaId}
POST /schemas, PUT /schemas/{schemaId}
schema:read
schema:write
Schema policy metadata GET /schemas/{schemaId}/policy-metadata
PATCH, DELETE on .../policy-metadata/{id}
DELETE .../policy-metadata/{id}/allowlist/{applicationId}
schema:read
schema:write
Schema submissions GET /schema-submissions, GET .../{submissionId}
POST /schema-submissions, PUT .../{submissionId}
schema-submission:read
schema-submission:write
Applications GET /applications, GET /applications/{applicationId}
POST /applications, PUT /applications/{applicationId}
PUT /applications/{applicationId}/policy
application:read
application:write
Application submissions GET /application-submissions, GET .../{submissionId}
POST /application-submissions, PUT .../{submissionId}
application-submission:read
application-submission:write

Outside /api/v1, with no authentication: GET /health (503 if the database is down), GET /debug and GET /debug/db. The authorization model, including the -all scopes and what each ThunderID role may do, is in AUTHORIZATION.md.

How it works

Creating an application touches three systems, so PB undoes earlier steps if a later one fails:

sequenceDiagram
    participant C as Caller
    participant PB
    participant IDP as ThunderID
    participant DB as PostgreSQL
    participant PDP
    C->>PB: POST /api/v1/applications
    PB->>IDP: create M2M client in the member's organization unit, assign role
    IDP-->>PB: client_id and client_secret
    PB->>DB: insert application
    PB->>PDP: update allow-list for client_id (30 days)
    PB-->>C: application and idpClientSecret (shown once)
  • The PDP allow-list is keyed by the IdP client_id, which is the identity the Orchestration Engine reads from consumer tokens.
  • POST /schemas stores the schema, then registers its policy metadata in the PDP, and removes the schema if that fails.
  • A submission is a request that becomes a schema or application when its status is set to approved (PUT .../{submissionId}); the creation steps above then run, and the status goes back to pending if they fail. Who may set that status is described in AUTHORIZATION.md.
  • A caller reaches its own member's records. Reaching other members' records needs the matching -all scope as well.

Security and deployment

Deployment security: network isolation. /health, /debug and /debug/db are not authenticated. Expose only /api/v1/* beyond the internal network. PB also calls the PDP without authentication, so the PDP must be reachable only from PB and the Orchestration Engine.

  • CORS defaults to http://localhost:5173. Compose sets *; set explicit origins for any shared deployment.
  • PB serves no /metrics endpoint and no /openapi.yaml; the spec is the file in this directory.

Development

make validate-test portal-backend      # tests with -race and coverage
go test ./cmd/pb/... ./internal/pb/... -count=1
make quality-check portal-backend

Tests run without external services. make validate-test does not cover internal/authn; run go test ./internal/authn/... for the token validation code. Docker image: docker build -t portal-backend -f cmd/pb/Dockerfile . from the repo root.

Code is in internal/pb: member, schema and application (one package per resource: model, service, handler), caller (scopes and the caller's member), policy (PDP client), idp/thunderid (ThunderID client), middleware (audit), database/dbtest (test databases). The configuration is in config.go. PB connects through the shared internal/database, which uses OpenNSW/core/database; CORS is OpenNSW/core/cors. Token validation is in internal/authn.

Rename note

When PB becomes Management Server, these identifiers embed the current name:

  • Compose service, container and image: portal-backend, pb-${ENVIRONMENT}, ghcr.io/openndx/portal-backend; Makefile service name and CI workflows (portal-backend-validate, portal-backend-publish)
  • Go packages internal/pb and internal/cli/pbclient; the "portal-backend" string in /health and /debug
  • Environment variables PB_PORT, DB_NAME_PB, PB_AUDIENCE, and the ondx flag and variable --pb-url / NDX_PB_URL
  • The token audience http://pb.openndx.local, which clients request as resource=
  • ThunderID seed data in thunderid/bootstrap: the PORTAL_BACKEND client, the PortalBackendSystem role and the "Portal Backend Resource Server"

More docs

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL