tpt-poa-registry-nz

Go

Digital Power of Attorney registry for New Zealand, compliant with the Protection of Personal and Property Rights Act 1988. RealMe Verified identity, attorney acceptance workflow, institution verification API, HMAC-signed webhooks, and public QR-code verification. Built with Go + Next.js.

0 stars0 forks0 watchersMIT License
golanglegal-technew-zealandnextjsopen-sourcepostgresqlpower-of-attorneyrealmeregistrysaml

Languages

Go67.6%TypeScript29.3%PLpgSQL1.7%CSS1.0%Makefile0.2%Dockerfile0.1%
README

TPT POA Registry NZ

A digital registry for managing Power of Attorney (POA) instruments under the New Zealand Protection of Personal and Property Rights Act 1988. Both the granter and attorney must hold a RealMe Verified identity.

License: MIT Go Next.js


Features

FeatureDescription
Digital POA GrantingCreate enduring, general, property, personal/welfare, or specific powers of attorney
RealMe Verified IdentityGranter and attorney identities verified via RealMe SAML 2.0
Attorney Acceptance WorkflowPOAs start as pending_acceptance; attorneys receive a one-time link to accept or decline
QR Code VerificationActive POAs generate a scannable QR code linking to the public verification page
Public Verification PageAnyone can verify a POA at /verify/:registryId — no login required
Full Audit TrailEvery lifecycle event (grant, accept, decline, amend, revoke, view) is logged with timestamp and actor
Institution Verification APIBanks and institutions verify POA validity via REST API with API-key auth and rate limiting
Bulk VerificationVerify up to 100 registry IDs in a single API call
Webhook NotificationsHMAC-SHA256-signed webhook delivery to subscribed institutions
Automatic ExpiryBackground job marks POAs expired hourly when their expiry date passes
Human-Readable IDsSequential registry identifiers — e.g. POA-2026-00042

Architecture

┌─────────────────────────┐     ┌──────────────────────┐
│  Next.js Web Frontend   │     │  Institution Portal  │
│  (RealMe via backend)   │     │  (API Key auth)      │
└───────────┬─────────────┘     └──────────┬───────────┘
            │ HTTP (proxy /api/*)           │ HTTP
            ▼                              ▼
┌─────────────────────────────────────────────────────────┐
│                  Go API Server (:8082)                  │
│  ┌──────────┐ ┌────────────┐ ┌────────────────────────┐ │
│  │  Auth    │ │ POA CRUD + │ │  Institution Registry  │ │
│  │ Handlers │ │ Acceptance │ │  Bulk Verify + Webhook │ │
│  └────┬─────┘ └─────┬──────┘ └───────────┬────────────┘ │
│       │             │                     │              │
│  ┌────▼─────────────▼─────────────────────▼──────────┐  │
│  │                 Services Layer                     │  │
│  │    POAService · WebhookService · ExpiryJob        │  │
│  └────────────────────────┬───────────────────────────┘  │
│                           │                              │
│  ┌────────────────────────▼───────────────────────────┐  │
│  │              Repository Layer (pgx/pgxpool)        │  │
│  └────────────────────────┬───────────────────────────┘  │
└───────────────────────────┼──────────────────────────────┘
                            │
                     ┌──────▼──────┐
                     │ PostgreSQL  │
                     └─────────────┘

API Endpoints

RealMe Authentication

MethodPathDescription
GET/auth/loginInitiate RealMe login
GET/auth/callbackRealMe ACS callback
GET/auth/logoutClear session
GET/auth/metadataSP metadata XML
GET/auth/statusAuth status (requires login)

POA Management (RealMe Verified required)

MethodPathDescription
POST/poa/grantCreate new POA (returns acceptance token)
GET/poa/grantedList POAs where user is granter
GET/poa/heldList POAs where user is attorney
GET/poa/search?q=...Search POA records
GET/poa/registry/{registryId}Get POA by registry ID (e.g. POA-2026-00042)
GET/poa/{id}Get POA by UUID
PUT/poa/{id}/amendAmend POA scope/details
POST/poa/{id}/revokeRevoke POA
GET/poa/{id}/auditAudit log for POA
GET/poa/{id}/acceptance-linkGet active acceptance link (granter only)

Attorney Acceptance (public — no auth)

MethodPathDescription
GET/poa/accept/{token}View POA invitation details
POST/poa/accept/{token}Accept POA — transitions status to active
POST/poa/decline/{token}Decline POA (with reason)

Institution Verification (API key required — X-API-Key header)

Rate limited to 100 requests / minute per API key.

MethodPathDescription
GET/api/v1/registry/lookup/{registryId}Look up POA details
GET/api/v1/registry/verify?registryId=...&granterName=...Verify POA validity
POST/api/v1/registry/verify/batchBulk verify up to 100 registry IDs

Webhook Management (API key required)

MethodPathDescription
POST/webhook/subscribeCreate webhook subscription
GET/webhook/subscriptions?institutionId=...List subscriptions

POA Lifecycle

[Grant]
   │
   ▼
pending_acceptance  ──accept──▶  active  ──amend──▶  amended
   │                               │                    │
   └──decline──▶  declined         └──revoke──▶  revoked
                                   │
                               (expiry date passes)
                                   │
                                   ▼
                                expired
StatusDescription
pending_acceptancePOA granted; awaiting attorney acceptance
activeAttorney accepted; POA is valid
amendedGranter modified scope/details
declinedAttorney declined the appointment
revokedGranter revoked a revocable POA
expiredPast the specified expiry date (set automatically)
suspendedTemporarily suspended by admin

Webhook Events

Webhooks deliver HMAC-SHA256-signed payloads to subscribed institution URLs via the X-TPT-Signature header.

EventTrigger
poa.grantedNew POA created (status: pending_acceptance)
poa.acceptedAttorney accepted — POA now active
poa.declinedAttorney declined
poa.amendedGranter amended scope or details
poa.revokedGranter revoked the POA
*Subscribe to all events

Getting Started

Prerequisites

1 — Clone and configure

git clone https://github.com/tpt-nz/tpt-poa-registry-nz.git
cd tpt-poa-registry-nz
cp .env.example .env
# Edit .env with your values

2 — Database

# Start PostgreSQL (or use Docker: make dev)
createdb tptnz

# Run migrations
psql "$DATABASE_URL" -f migrations/001_init.sql
psql "$DATABASE_URL" -f migrations/002_attorney_acceptance.sql

3 — Start the Go API server

go mod tidy
go run ./cmd/server
# Listening on :8082

4 — Start the mock IdP (for local RealMe)

cd packages/realme-go
go run ./testenv/ -addr :8081

5 — Start the Next.js frontend

pnpm install
pnpm --filter web dev
# Listening on :3000

Docker (all-in-one)

make dev       # docker compose up -d (PostgreSQL + app)
make stop      # docker compose down
make migrate   # run Atlas migrations

Environment Variables

Go API Server

VariableDefaultDescription
LISTEN_ADDR:8082HTTP listen address
DATABASE_URLpostgres://tptnz:tptnz_dev@localhost:5432/tptnz?sslmode=disablePostgreSQL DSN
REALME_ENVIRONMENTmtsRealMe environment (mts | ite | production)
REALME_CERT_FILEcerts/sp.crtSP certificate path
REALME_KEY_FILEcerts/sp.keySP private key path
REALME_ENTITY_IDhttp://localhost:8082/auth/metadataSP entity ID
REALME_ACS_URLhttp://localhost:8082/auth/callbackACS callback URL
REALME_IDP_METADATA_URLhttp://localhost:8081/metadataIdP metadata URL
POA_API_KEYdev-api-keyAPI key for institution endpoints

Next.js Frontend

VariableDescription
BACKEND_URLBase URL of the Go API — server-side only, never sent to the browser
BACKEND_API_KEYAPI key for the server-side public verification page (matches POA_API_KEY)
NEXT_PUBLIC_SITE_URLPublic URL of the frontend — used for generating absolute QR code URLs

RealMe Registration

MTS (Development / Mock IdP)

  1. Generate a self-signed SP certificate:

    mkdir -p certs
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
      -keyout certs/sp.key -out certs/sp.crt \
      -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"
    
  2. Start the mock IdP bundled in this repo:

    cd packages/realme-go
    go run ./testenv/ -addr :8081
    
  3. Set REALME_IDP_METADATA_URL=http://localhost:8081/metadata in your .env.

ITE (Pre-Production)

  1. Register at the RealMe Developer Portal.
  2. Submit your SP metadata (available at GET /auth/metadata) to DIA.
  3. DIA will provide the ITE IdP metadata URL.
  4. Use a properly named certificate: ite.{service-name}.{org-domain}.nz
  5. Set REALME_ENVIRONMENT=ite and update REALME_IDP_METADATA_URL.

Production

Same as ITE with REALME_ENVIRONMENT=production and the production IdP metadata URL from DIA.


Development

make test        # go test ./...
make test-race   # go test -race ./...
make lint        # golangci-lint run ./...
make vet         # go vet ./...
make build       # build binary to bin/
make tools       # install air, atlas, golangci-lint

Contributing

See CONTRIBUTING.md.


License

MIT — see LICENSE.