An AT Protocol service that adds role-based access control to group-governed repositories on a Personal Data Server (PDS). CGS lets multiple users collaboratively manage a single atproto repository with fine-grained permissions, full audit logging, and secure credential management.
CGS acts as an authenticated proxy between clients and a group's PDS:
- Client sends a request with a signed JWT (
Authorization: Bearer <token>) - AuthVerifier validates the JWT signature against the caller's DID document, checks the audience against the group registry, and enforces nonce-based replay prevention
- RbacChecker looks up the caller's role in the group and verifies they have permission for the requested operation
- PDS proxy forwards the request to the group's backing PDS using securely stored credentials
- AuditLogger records every action (permitted or denied) for compliance and debugging
| Role | Level | Capabilities |
|---|---|---|
| member | 0 | Create/edit any record, delete own records, upload blobs, list members |
| admin | 1 | All member permissions + delete any record, edit group profile, manage members, query audit log |
| owner | 2 | All admin permissions + set roles (promote/demote members) |
- Global SQLite database — group registry and nonce cache
- Per-group SQLite databases — members, record authorship tracking, audit log
- All databases use WAL mode for concurrent read performance
- Node.js 22+
- pnpm
# Clone the repository
git clone https://github.com/your-org/certified-group-service.git
cd certified-group-service
# Install dependencies
pnpm install
# Configure environment
cp .env.example .env
# Edit .env — at minimum set ENCRYPTION_KEY and SERVICE_URL
# Generate an encryption key
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Build
pnpm build
# Start (migrations run automatically on startup)
pnpm startFor development with hot reload:
pnpm dev| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 3000 |
HTTP server listen port |
SERVICE_URL |
Yes | — | Public URL of this service (e.g. https://group-service.example.com). Written into group DID documents for atproto-proxy resolution. |
DATA_DIR |
No | ./data |
Directory for SQLite databases |
ENCRYPTION_KEY |
Yes | — | 32-byte hex key for AES-256-GCM encryption of stored PDS credentials. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
PLC_URL |
No | https://plc.directory |
PLC directory URL for DID resolution |
DID_CACHE_TTL_MS |
No | 600000 |
DID document cache TTL in milliseconds (10 min) |
MAX_BLOB_SIZE |
No | 5242880 |
Maximum blob upload size in bytes (5 MB) |
LOG_LEVEL |
No | info |
Log level: trace, debug, info, warn, error, fatal |
pnpm testTests use Vitest with supertest for HTTP integration testing and in-memory SQLite databases.
Build and run with Docker:
docker build -t group-service .
docker run -p 3000:3000 \
-e SERVICE_URL=https://group-service.example.com \
-e ENCRYPTION_KEY=<your-64-char-hex-key> \
-v $(pwd)/data:/app/data \
group-serviceThe Dockerfile uses a multi-stage build with node:22-slim for a minimal production image.
See docs/deployment.md for deployment guides, including Railway.
- Integration Guide — step-by-step guide to integrating the group service into your app
- Architecture — authentication flow, RBAC model, data model, PDS proxy internals
- API Reference — complete endpoint documentation with examples
- Deployment — production deployment guides
MIT