@modularizer/plat-authority
v0.11.0
Published
Standalone authority/control-plane service for PLAT authority-mode CSS routing.
Downloads
124
Readme
PLAT Authority Mode — Getting Started
This README covers authority mode, the new centralized signaling path for PLAT. It complements the existing DMZ/MQTT path and enforces namespace ownership.
Current Status
Foundation (✅ Complete): Type contracts, validation, registration rules, and routing logic are implemented and tested.
Server (✅ Complete): HTTP /connect, host WebSocket /ws/host, and presence WebSocket /ws/presence are implemented.
Storage adapters (✅ Available): drizzle, memory, json, and yaml can be selected with STORAGE_TYPE.
Status: Runnable now via Docker Compose or local Node.
Quick Concept
Origin-Scoped Ownership
Authority treats namespace ownership as (origin, namespace).
- Owning
donkeyonapple.pear.commeans owning bothapple.pear.com/donkeyanddonkey.apple.pear.com - Subdomain and path forms are just routing views of the same ownership record
AUTHORITY_ALLOWED_ORIGINSdefines which marketplace origins participate in this modelapiis always reserved, andAUTHORITY_DISALLOWED_NAMESPACE_GLOBScan reserve additional namespace patterns
Dual Routing
if (serverName.startsWith('dmz/')) {
// Legacy MQTT path — no changes, works as before
// Example: css://dmz/my-room
} else {
// New authority path — centralized, ownership-enforced
// Example: css://team/alice/notebook
}Authority Mode vs DMZ
| Aspect | DMZ (dmz/*) | Authority (everything else) |
|--------|---------------|-----|
| Signaling | Public MQTT broker | Private control plane |
| Namespace | First-come, no ownership | PLAT enforces ownership |
| Registration | Implicit (announce) | Explicit with token |
| Auth Model | TOFU / challenge | Google + app-specific |
| Reliability | Best-effort | Guaranteed routing |
How to Use Authority Mode
For Clients
1. Use authority-mode server names
// ❌ DMZ (legacy)
const dmzClient = await plat.createClient('css://dmz/my-room')
// ✅ Authority (recommended)
const authClient = await plat.createClient('css://team/alice/notebook')
// or any non-dmz/* name2. That's it! The CSS transport plugin will automatically:
- Detect the server name is in authority mode
- Create a WebRTC offer
- Wait for ICE gathering
- Send
POST /connectto the authority server - Apply the answer
- Connect over the WebRTC data channel
No code changes needed. The routing is transparent.
For Hosts (Client-Side Servers)
1. Register with the authority on startup
import { PLATClientSideServer } from 'plat'
const server = new PLATClientSideServer({ /* your config */ })
// For authority-mode names, authenticate and register
const hostToken = await getGoogleAuthToken() // Your auth flow
await server.registerWithAuthority({
url: 'wss://authority.example.com/ws/host',
token: hostToken,
serverNames: [
{ server_name: 'team/alice/notebook', auth_mode: 'public' },
{ server_name: 'team/alice/whiteboard', auth_mode: 'private' },
],
})
// DMZ names continue to work as before (MQTT announce)
await server.announceOnMQTT({
serverName: 'dmz/legacy-room',
// ...
})2. Handle incoming connect requests
Authority will forward WebRTC offers from clients. Your server automatically handles them using the existing request/response machinery — no changes needed there either.
3. Optional: Suppress abusive clients
// If a client is misbehaving, tell authority to block them temporarily
await server.suppressClient({
serverName: 'team/alice/notebook',
clientKey: 'ip:192.0.2.1', // or 'google-sub:...' if authenticated
ttlSeconds: 3600, // 1 hour
reason: 'spam',
})Server Configuration
Run with Docker Compose:
cd /home/mod/Code/plat/authority
docker-compose upOr standalone:
cd plat/authority
npm ci
npm run devQuick Start (No Docker)
cd /home/mod/Code/plat/authority
npm ci
npm run build
npm startEnvironment variables:
# Google OAuth config (for host auth)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
# Database (Postgres for ownership, Redis for rate limits)
DATABASE_URL=postgresql://user:pass@postgres:5432/plat_authority
REDIS_URL=redis://redis:6379
# Server
PORT=3000
AUTHORITY_URL=wss://authority.example.com
ADMIN_TOKEN=replace-me
AUTHORITY_ALLOWED_ORIGINS=apple.pear.com,broswerver.com,browservable.com,coolsite.ai
AUTHORITY_DISALLOWED_NAMESPACE_GLOBS=admin-*,*-internal,staging
# Host auth
# insecure_token_sub (dev) | google_tokeninfo (recommended)
HOST_AUTH_MODE=google_tokeninfo
GOOGLE_CLIENT_ID=...
# Abuse controls
CONNECT_RATE_LIMIT_PER_30S=500
WS_HOST_MSG_RATE_LIMIT_PER_30S=300
WS_PRESENCE_MSG_RATE_LIMIT_PER_30S=300
OAUTH_RATE_LIMIT_PER_30S=30
# OAuth redirect framework (UI can live in another repo)
GOOGLE_OAUTH_CLIENT_SECRET=...
GOOGLE_OAUTH_REDIRECT_URI=https://authority.example.com/oauthCallback
OAUTH_ALLOWED_REDIRECT_ORIGINS=https://app.example.com,http://localhost:5173
# Cloudflare Tunnel (external ingress)
CLOUDFLARE_TUNNEL_ID=...
CLOUDFLARE_TUNNEL_TOKEN=...
# Storage adapter selection
# STORAGE_TYPE=drizzle|memory|json|yaml
# STORAGE_PATH=/app/data/servers.jsonSee docker-compose.yml for the full stack setup.
API Reference (Server Endpoints)
PLAT routes are flat and method-name based. The admin API methods are:
GET /pendingGET /historyGET /availabilityPOST /approvePOST /rejectPOST /request
HTTP Endpoints
POST /connect
Client sends this to initiate an authority-mode connection.
Request:
{
"server_name": "apple.pear.com/donkey/notebook",
"offer": { "type": "offer", "sdp": "..." },
"auth": { "mode": "public", "credentials": null },
"client": { "ip_hint": "...", "request_id": "..." }
}When AUTHORITY_ALLOWED_ORIGINS is configured, the same owned namespace can also be addressed in subdomain form, for example notes.donkey.apple.pear.com.
Response (success):
{
"ok": true,
"answer": { "type": "answer", "sdp": "..." }
}Response (failure):
{
"ok": false,
"error": "server_offline|unauthorized|rejected|timed_out|rate_limited|malformed"
}GET /healthz
Liveness check.
curl https://authority.example.com/healthz
# 200 OKGET /readyz
Readiness check (includes DB and Redis connectivity).
curl https://authority.example.com/readyz
# 200 OK if ready, 503 if notWebSocket Endpoints
wss://authority.example.com/ws/host
Host persistent connection for registration and signaling relay.
Host → Server:
{ "type": "hello", "token": "..." }
{ "type": "register_online", "servers": [...] }
{ "type": "register_offline", "server_names": [...] }
{ "type": "connect_answer", "connection_id": "...", "answer": {...} }
{ "type": "connect_reject", "connection_id": "...", "reason": "..." }
{ "type": "suppress_client", "server_name": "...", "client_key": "...", "ttl_seconds": 3600 }
{ "type": "ping" }Server → Host:
{ "type": "connect_request", "connection_id": "...", "server_name": "...", "offer": {...} }
{ "type": "pong" }wss://authority.example.com/ws/presence (optional)
Clients can subscribe to server online/offline events.
Client → Server:
{ "type": "subscribe", "server_names": ["team/alice/notebook"] }
{ "type": "unsubscribe", "server_names": ["team/alice/notebook"] }Server → Client:
{ "type": "presence_snapshot", "servers": [{ "server_name": "...", "online": true }] }
{ "type": "presence_update", "server_name": "...", "online": true }OAuth Redirect Endpoints (Flat)
GET /oauthStart(build + optional redirect to Google; accepts optionalrole=admin|user)GET /oauthCallback(Google callback; mints a signed authority session token)POST /oauthExchange(deprecated)POST /oauthAdminSession(deprecated)
Typical no-UI flow:
- Frontend/backend calls
GET /oauthStart?redirect_uri=https://app.example.com/callback. - Authority redirects user-agent to Google.
- Google redirects back to
GET /oauthCallbackon authority. - Authority redirects to app callback with
#session_token=...in the URL fragment. - App stores that token and sends it on future requests with
Authorization: Bearer <token>.
If the callback fails after authority has resolved the caller redirect, authority redirects back with #oauth_error=... and #oauth_error_description=... in the fragment instead of returning a silent browser 500.
Admin-specific flow (optional, for admin dashboards/tools):
- Start login with
GET /oauthStart?role=admin&redirect_uri=https://app.example.com/admin. - Authority verifies the Google account is allowed to act as admin.
- Authority redirects back with
#session_token=...,#google_sub=..., and role metadata in the fragment. - The admin app stores the token and uses
Authorization: Bearer <token>on admin routes.
Migration Path
Phase 1: Start New Projects in Authority Mode
- New server names default to authority mode (no
dmz/prefix) - Existing DMZ names continue to work unchanged
- Zero breaking changes
Phase 2: Move Existing Names (Optional)
- Migrate DMZ names to authority mode by re-registering without
dmz/prefix - Clients automatically route to the new authority path
- Can run both simultaneously for testing
Phase 3: Enhanced Features (Future)
- Trickle ICE for networks requiring it
- TURN servers for relay fallback
- Stronger identity federation
- Multi-instance authority with horizontal scaling
Troubleshooting
"server_offline"
The host for that server name is not currently connected to the authority. Check:
- Host auth token validity
- Host network connectivity
- Authority server is running
"rate_limited"
Too many connection requests from your IP/account. Normal limits:
- L0: 500 burst / 10k sustained per 10 min
- Scales down under load (L0 → L4)
- Very lenient on first attempt
"malformed"
Your connect request JSON is invalid. Check:
- Required fields:
server_name,offer - Field lengths (e.g.,
server_name< 255 chars,sdp< 48 KB) - No unknown JSON fields
"unauthorized"
Host rejected the connection based on auth payload or admission rules. Check:
- Your
authmode matches server's expectations - Your credentials are valid
- Host has not suppressed your client
Next Steps
Prepare your setup:
- Get Google OAuth credentials for host auth
- Plan your server naming: which names should be authority vs DMZ?
- Update your deployment config to include Postgres and Redis
Watch for HTTP/WS adapters:
- Next week, the full authority server launches
- Docker Compose file will be ready
- You can start testing immediately
Update your clients and hosts:
- Clients: Change server names to non-
dmz/*form - Hosts: Add authority registration on startup
- Tests: Add coverage for both dmz and authority routes
- Clients: Change server names to non-
Provide feedback:
- Error messages? Let us know.
- Rate limit too strict/lenient? Adjustable.
- Missing features? Tell us what matters most.
Examples
Example 1: Migrate a Chat Room from DMZ to Authority
Before (DMZ):
// Any client can claim any room
const chatClient = await plat.createClient('css://dmz/chat-room-42')
const host = new PLATClientSideServer()
await host.announceOnMQTT({ serverName: 'dmz/chat-room-42' })After (Authority):
// Only the owner can claim this room
const chatClient = await plat.createClient('css://acme-corp/chat-room-42')
const host = new PLATClientSideServer()
await host.registerWithAuthority({
url: 'wss://authority.example.com/ws/host',
token: googleAuthToken,
serverNames: [{ server_name: 'acme-corp/chat-room-42', auth_mode: 'public' }],
})That's it. Routing and handshake are automatic.
Example 2: Host with Multiple Authority-Mode Rooms
const host = new PLATClientSideServer()
await host.registerWithAuthority({
url: 'wss://authority.example.com/ws/host',
token: googleAuthToken,
serverNames: [
{ server_name: 'team/alice/notebook', auth_mode: 'public' }, // anyone can join
{ server_name: 'team/alice/whiteboard', auth_mode: 'private' }, // needs token
{ server_name: 'team/alice/files', auth_mode: 'private' }, // needs token
// Legacy MQTT name still works
{ server_name: 'dmz/shared-demo', auth_mode: 'public' }, // old path
],
})Support
- Docs: See
IMPLEMENTATION_STATUS.mdfor architecture details - Design:
authority/OVERVIEW.mdandTECHNICAL_IMPLEMENTATION.md - Issues: Report bugs or feature requests in the repo
Ready to try it? Start with a client connecting to an authority-mode server name. Once the HTTP/WS adapters are live, hosts can register and the full loop will work.
