Production-minded multi-tenant incident tracker backend scaffold with:
- TypeScript (
strict: true) + ESLint - Express 5 API
- Session-cookie auth + CSRF protection
- Argon2id password hashing
- Login lockout and password reset token flow
- Redis-backed session store (memory store in tests)
- PostgreSQL repositories for auth, tenancy, incidents, audit logs, API keys, and usage
- Tenant, membership, and invitation workflows
- PostgreSQL RLS policies for tenant-scoped membership and invite tables
- Centralized RBAC policy engine (
authorize(action, resource, ctx)) - Append-only audit logging for sensitive auth/tenant/incident mutations
- Service accounts + scoped API keys (
read/write) - Usage metering and daily write quota enforcement
- OpenTelemetry hooks for HTTP/DB traces and metrics
- OpenAPI 3.1 contract in
openapi.yaml - Integration tests for auth/session, tenancy, incidents, API keys, and quota
- Primary auth is cookie-based sessions + CSRF.
- OIDC/PKCE is not in MVP scope.
- Login is email/password only.
- Install dependencies:
pnpm install- Start dependencies:
docker compose up -d redis postgres mailpit-
Create
.envfrom.env.exampleand setSESSION_SECRET. -
Run the API:
pnpm devpnpm lintpnpm typecheckpnpm testpnpm test:integrationpnpm test:integration:postgrespnpm db:migratepnpm openapi:validate
- ID-based endpoints are leak-resistant by default:
404when an object is missing or inaccessible in active tenant context403only when object existence is established and permission is denied
GET /v1/auth/mePOST /v1/auth/signupPOST /v1/auth/loginPOST /v1/auth/logoutPOST /v1/auth/password/forgotPOST /v1/auth/password/reset
Use GET /v1/auth/me first to establish a session and get csrfToken.
Send x-csrf-token for all state-changing endpoints.
For API-key-authenticated automation requests, send x-api-key and skip CSRF.
GET /v1/tenantsPOST /v1/tenantsPOST /v1/tenants/:tenantId/switchPOST /v1/tenants/:tenantId/invitesPOST /v1/tenants/invites/accept
GET /v1/incidentsPOST /v1/incidentsGET /v1/incidents/:incidentIdPATCH /v1/incidents/:incidentIdGET /v1/incidents/:incidentId/timeline-eventsPOST /v1/incidents/:incidentId/timeline-eventsGET /v1/incidents/:incidentId/tasksPOST /v1/incidents/:incidentId/tasksPATCH /v1/incidents/:incidentId/tasks/:taskIdGET /v1/incidents/:incidentId/status-updatesPOST /v1/incidents/:incidentId/status-updates
Incident routes accept either:
- Session cookie auth (CSRF required on mutating requests)
- API key auth via
x-api-key(scope-checked; CSRF not required)
GET /v1/tenants/:tenantId/service-accountsPOST /v1/tenants/:tenantId/service-accountsGET /v1/tenants/:tenantId/api-keysPOST /v1/tenants/:tenantId/api-keysPOST /v1/tenants/:tenantId/api-keys/:apiKeyId/revoke
These management endpoints require session auth and api_keys.manage.
GET /v1/audit-logs(requiresaudit_log.read)GET /v1/usage(requiresbilling.read)
- Session cookies are
httpOnlyandsameSite=lax. securecookies are enabled in production.- Passwords are hashed with Argon2id.
- Login attempts are lockout-protected.
- Sensitive actions write audit log entries (
audit_log_eventsin Postgres). - Write-heavy incident mutations are quota-protected and return
429 QUOTA_EXCEEDEDwhen over limit. - Error format is consistent:
{
"code": "AUTH_INVALID_CREDENTIALS",
"message": "Invalid email or password.",
"traceId": "...",
"details": {}
}- GitHub Actions workflow (
.github/workflows/ci.yml) runs:pnpm lintpnpm typecheckpnpm testpnpm openapi:validate- Postgres service +
pnpm db:migrate+pnpm test:integration:postgres
This repo includes render.yaml for Blueprint deploys.
- Push this repo to GitHub.
- In Render, choose
New->Blueprintand select the repo. - Confirm the
incident-tracker-apiweb service fromrender.yaml. - Set
DATABASE_URLandREDIS_URLin Render (both are markedsync: false). - Deploy.
Notes:
- Build command compiles TypeScript.
preDeployCommandrunspnpm db:migratebefore each deploy.- Health check uses
GET /health. - In production, the app trusts one reverse-proxy hop so secure session cookies work on Render.