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

| Feature | Description | |---------|-------------| | Digital POA Granting | Create enduring, general, property, personal/welfare, or specific powers of attorney | | RealMe Verified Identity | Granter and attorney identities verified via RealMe SAML 2.0 | | Attorney Acceptance Workflow | POAs start as pending_acceptance; attorneys receive a one-time link to accept or decline | | QR Code Verification | Active POAs generate a scannable QR code linking to the public verification page | | Public Verification Page | Anyone can verify a POA at /verify/:registryId — no login required | | Full Audit Trail | Every lifecycle event (grant, accept, decline, amend, revoke, view) is logged with timestamp and actor | | Institution Verification API | Banks and institutions verify POA validity via REST API with API-key auth and rate limiting | | Bulk Verification | Verify up to 100 registry IDs in a single API call | | Webhook Notifications | HMAC-SHA256-signed webhook delivery to subscribed institutions | | Automatic Expiry | Background job marks POAs expired hourly when their expiry date passes | | Human-Readable IDs | Sequential 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

| Method | Path | Description | |--------|------|-------------| | GET | /auth/login | Initiate RealMe login | | GET | /auth/callback | RealMe ACS callback | | GET | /auth/logout | Clear session | | GET | /auth/metadata | SP metadata XML | | GET | /auth/status | Auth status (requires login) |

POA Management (RealMe Verified required)

| Method | Path | Description | |--------|------|-------------| | POST | /poa/grant | Create new POA (returns acceptance token) | | GET | /poa/granted | List POAs where user is granter | | GET | /poa/held | List 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}/amend | Amend POA scope/details | | POST | /poa/{id}/revoke | Revoke POA | | GET | /poa/{id}/audit | Audit log for POA | | GET | /poa/{id}/acceptance-link | Get active acceptance link (granter only) |

Attorney Acceptance (public — no auth)

| Method | Path | Description | |--------|------|-------------| | 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.

| Method | Path | Description | |--------|------|-------------| | 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/batch | Bulk verify up to 100 registry IDs |

Webhook Management (API key required)

| Method | Path | Description | |--------|------|-------------| | POST | /webhook/subscribe | Create 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

| Status | Description | |--------|-------------| | pending_acceptance | POA granted; awaiting attorney acceptance | | active | Attorney accepted; POA is valid | | amended | Granter modified scope/details | | declined | Attorney declined the appointment | | revoked | Granter revoked a revocable POA | | expired | Past the specified expiry date (set automatically) | | suspended | Temporarily suspended by admin |


Webhook Events

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

| Event | Trigger | |-------|---------| | poa.granted | New POA created (status: pending_acceptance) | | poa.accepted | Attorney accepted — POA now active | | poa.declined | Attorney declined | | poa.amended | Granter amended scope or details | | poa.revoked | Granter 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

| Variable | Default | Description | |----------|---------|-------------| | LISTEN_ADDR | :8082 | HTTP listen address | | DATABASE_URL | postgres://tptnz:tptnz_dev@localhost:5432/tptnz?sslmode=disable | PostgreSQL DSN | | REALME_ENVIRONMENT | mts | RealMe environment (mts | ite | production) | | REALME_CERT_FILE | certs/sp.crt | SP certificate path | | REALME_KEY_FILE | certs/sp.key | SP private key path | | REALME_ENTITY_ID | http://localhost:8082/auth/metadata | SP entity ID | | REALME_ACS_URL | http://localhost:8082/auth/callback | ACS callback URL | | REALME_IDP_METADATA_URL | http://localhost:8081/metadata | IdP metadata URL | | POA_API_KEY | dev-api-key | API key for institution endpoints |

Next.js Frontend

| Variable | Description | |----------|-------------| | BACKEND_URL | Base URL of the Go API — server-side only, never sent to the browser | | BACKEND_API_KEY | API key for the server-side public verification page (matches POA_API_KEY) | | NEXT_PUBLIC_SITE_URL | Public 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.