An open-source security trust center platform for The Open GRC ecosystem, available under theopengrc.com. Built with Next.js 15, Docker, and Supabase, featuring organization-level document approval, magic link access, and comprehensive admin management.
🎯 Try the Live Demo → Explore the full Trust Center experience — public site and admin panel — with sample data. No setup required. Demo credentials:
[email protected]/demo123
- Public Security Documentation: Display compliance certifications, security updates, and public documents
- Document Request System: Users can request access to restricted documents without creating accounts
- Magic Link Access: Time-limited access links sent via email (no login required)
- Organization Whitelisting: Track approved documents per organization for granular access control
- Auto-Approval: Organizations with previously approved documents get instant access
- Admin Console: Full-featured admin panel for document management, request approval, and customization
- Support Tickets (optional): Built-in contact/support ticket system with message threads, status and priority management — toggleable per trust center
- Document Expiry Tracking: Set expiration dates on documents with dashboard widget showing upcoming expirations, color-coded badges, and date picker in editor
- Admin Email Notifications: Configurable email alerts when new document requests or support tickets are submitted
- Feature Toggles: Enable/disable support tickets and admin notifications from Settings → Features
- Salesforce Integration (OAuth): Connect Salesforce and sync customer/account status to automatically manage organization access
- Trust Center Customization: Customize branding, colors, hero section, and content
- Multi-Layer Security: Role-based access control with database, API, frontend, and storage protection
- Frontend: Next.js 15 (App Router), React 19, TypeScript, TailwindCSS
- Backend: Node.js with Express, TypeScript
- Database: PostgreSQL (via Supabase)
- Auth: Supabase Auth (admin-only), Magic links for document access
- Storage: Supabase Storage
- Containerization: Docker + Docker Compose
- CI/CD: GitHub Actions (CI build/lint/test, Trivy security scan, API smoke tests)
- Testing: Jest + Supertest (backend), ESLint (frontend + backend)
- Email: Resend API (production) / Mailpit (development) for secure email delivery with attachments
- Docker and Docker Compose installed
- Node.js 20+ (for local development)
- Git
-
Clone the repository
git clone https://github.com/kutcode/trust-center.git cd trust-center -
Set up environment variables
cp .env.example .env # Edit .env with your configuration (optional for local development) -
Start all services
docker-compose up
This will:
- Start Supabase local stack (PostgreSQL, Auth, Storage)
- Run database migrations
- Start backend API on port 4000
- Start frontend on port 3000
-
Access the application
- Frontend: http://localhost:3000
- Backend API: http://localhost:4000
- Supabase Studio: http://localhost:54323
- Mailpit (email testing): http://localhost:8025
This project supports multiple email providers through EMAIL_PROVIDER:
mailpitfor local development/testing (default)resendfor production/live delivery (recommended)smtpandsendgridas optional alternatives
-
For Development (emails captured locally):
- No configuration needed: emails are captured by Mailpit
- View emails at http://localhost:8025
-
For Production (real email delivery):
- Sign up at Resend.com (free tier: 3,000 emails/month)
- Get your API key from the dashboard
- Add to
.env:EMAIL_PROVIDER=resend RESEND_API_KEY=re_your_api_key_here SMTP_FROM=[email protected]
- Restart backend:
docker-compose up -d --force-recreate backend
📖 Full Email Setup Guide: See docs/EMAIL_SETUP.md for detailed instructions, custom domain setup, security best practices, and troubleshooting.
- Access Supabase Studio at http://localhost:54323
- Go to Authentication → Users → Add User
- Create a user with email and password
- Note the user ID
- Insert into
admin_userstable:INSERT INTO admin_users (id, email, full_name, role) VALUES ('<user-id>', '[email protected]', 'Admin User', 'admin');
The migrations include seed data for:
- Document categories (SOC 2 Reports, Privacy Policies, etc.)
- Default trust center settings
trust-center/
├── docker-compose.yml # Development Docker Compose
├── docker-compose.prod.yml # Production Docker Compose
├── frontend/ # Next.js frontend application
│ ├── src/
│ │ ├── app/ # App router pages
│ │ ├── components/ # React components
│ │ ├── lib/ # Utilities and Supabase clients
│ │ └── types/ # TypeScript types
│ ├── Dockerfile # Production Dockerfile (multi-stage)
│ └── Dockerfile.dev # Development Dockerfile
├── backend/ # Express backend API
│ ├── src/
│ │ ├── routes/ # API route handlers
│ │ │ └── admin/ # Admin routes (modular)
│ │ ├── middleware/ # Auth, rate limiting, validation
│ │ │ └── __tests__/ # Middleware tests
│ │ ├── schemas/ # Zod validation schemas (auth, document)
│ │ │ └── __tests__/ # Schema tests
│ │ ├── services/ # Email, Salesforce, webhooks
│ │ └── utils/ # Utility functions (safePath, adminNotifications)
│ │ └── __tests__/ # Utility tests
│ ├── Dockerfile # Production Dockerfile
│ └── Dockerfile.dev # Development Dockerfile
├── supabase/ # Supabase configuration
│ ├── migrations/ # Database migrations
│ ├── config.toml # Supabase local config
│ └── kong.yml # Kong API gateway config
├── scripts/ # Operational scripts
└── .github/workflows/ # CI/CD pipelines
See .env.example for all available environment variables:
JWT_SECRET: Secret for JWT tokensANON_KEY: Supabase anonymous keySERVICE_KEY: Supabase service role keyEMAIL_PROVIDER: Email backend (mailpit,resend,smtp,sendgrid)RESEND_API_KEY: Required whenEMAIL_PROVIDER=resendSMTP_*: SMTP configuration (used bysmtp, and host/port for mailpit capture)FRONTEND_URL: Frontend URLALLOW_ADMIN_SIGNUP: Keepfalseby default; temporary bootstrap onlyADMIN_SIGNUP_TOKEN: Optional bootstrap token for admin signup endpointENABLE_TEST_EMAIL: Keepfalseunless explicitly testing admin-only test endpointSALESFORCE_CLIENT_ID,SALESFORCE_CLIENT_SECRET,SALESFORCE_REDIRECT_URI: Salesforce OAuth Connected App configSALESFORCE_STATUS_FIELD,SALESFORCE_ALLOWED_STATUSES,SALESFORCE_DOMAIN_FIELD: Customer status -> organization access mappingDOCUMENT_UPLOADS_ENABLED: Global kill switch for document upload/replace endpointsDOCUMENT_UPLOAD_ADMIN_EMAIL_ALLOWLIST: Optional comma-separated list of admin emails allowed to upload documentsDOCUMENT_UPLOADS_PER_MINUTE: Per-admin upload rate limit to reduce abuseDOCUMENT_UPLOAD_MAX_MB: Max document upload size in MBDEMO_ALLOW_DOCUMENT_UPLOADS: Explicitly allow uploads in demo mode (defaults to false)
The project now includes an admin integration for Salesforce using OAuth 2.0 Connected Apps.
- Salesforce does not provide a durable "API key only" integration for this workflow
- OAuth with refresh tokens is the standard, secure, long-lived approach
- Supports secure token rotation and revocation
- Admin saves Salesforce credentials in
Admin -> Integrations - Admin calls
GET /api/admin/integrations/salesforce/connect-url - Admin authorizes in Salesforce
- Salesforce redirects to
SALESFORCE_REDIRECT_URI - Backend stores active Salesforce connection
- Admin runs
POST /api/admin/integrations/salesforce/sync
- Pulls
AccountandContactrecords from Salesforce - Maps accounts to Trust Center organizations using related
Contactemail domains (domain-based access) - Requires at least one related Contact with a usable business email domain per Account
- Accounts without related Contact email domains are skipped (they do not update/create organizations)
- Uses account status field (
SALESFORCE_STATUS_FIELD, defaultType) - If status is in
SALESFORCE_ALLOWED_STATUSES, organization is set towhitelisted - Otherwise organization is set to
no_access - Admin Integrations page shows connected Salesforce user/org identity and sync audit logs for debugging
Important:
Contacts Queried (Org-wide)is org-wide for the connected user and can be much higher than account count- Current access mapping uses Contact email domains, not
Account.Websitefallback
Guides:
- Customer admin onboarding (short):
docs/SALESFORCE_ONBOARDING_ADMIN.md - Full technical setup + troubleshooting:
docs/SALESFORCE_SETUP.md
Warning
The default JWT secret and Supabase keys in docker-compose.yml are development-only demo values.
For production, you must set unique secrets via .env or a secrets manager.
Never use the default keys in any environment accessible from the internet.
Both frontend and backend support hot reload:
- Frontend: Changes to
frontend/srcautomatically reload - Backend: Changes to
backend/srcautomatically reload via nodemon
Migrations run automatically on container startup. To run manually:
docker-compose exec supabase-db psql -U postgres -d postgres -f /docker-entrypoint-initdb.d/001_initial_schema.sqldocker-compose exec supabase-db psql -U postgres -d postgres# Run backend tests
cd backend && npm test
# Run tests in watch mode
cd backend && npm run test:watchTests cover: auth middleware, rate limiting, magic link generation, email validation, Zod schema validation (auth + document schemas), input validation middleware, and path traversal prevention.
The email rate limiter uses an in-memory store, which is suitable for single-instance deployments.
For horizontal scaling with multiple backend instances, consider replacing with a Redis-backed store
(e.g., express-rate-limit with rate-limit-redis).
Use the operational scripts for fast validation and incident triage:
# End-to-end smoke checks (HTTP + DNS + TLS)
./scripts/ops-smoke-test.sh
# Container log triage
./scripts/log-triage.sh --since 30m
# API contract smoke checks (requires hurl)
./scripts/run-api-smoke.sh
# Backup and restore verification drill
./scripts/db-backup.sh
./scripts/db-restore-verify.sh backups/<backup-file>.dump
# Targeted DNS/TCP/HTTP/TLS diagnostics
./scripts/network-debug.sh localhost 4000 httpDetailed runbook: docs/OPERATIONS_RUNBOOK.md
CI security scans (Trivy) run on push/PR via:
.github/workflows/security-scan.yml
Deployed API smoke tests (Hurl) run via:
.github/workflows/api-smoke-deployed.yml(manual + scheduled)- Configure target with workflow input
api_urlor repo variable/secretSMOKE_API_URL
GET /api/documents- List published documentsGET /api/certifications- List active certificationsGET /api/security-updates- List published security updatesGET /api/settings- Get trust center settingsPOST /api/document-requests- Submit document requestPOST /api/contact- Submit contact formGET /api/access/:token- Validate magic linkGET /api/access/:token/download/:document_id- Download via magic link
POST /api/admin/auth/login- Admin loginGET /api/admin/document-requests- List all requestsPATCH /api/admin/document-requests/:id/approve- Approve requestPATCH /api/admin/document-requests/:id/deny- Deny requestPOST /api/documents- Upload documentGET /api/organizations- List organizationsPATCH /api/admin/settings- Update trust center settingsGET /api/admin/integrations/salesforce/status- Salesforce integration statusGET /api/admin/integrations/salesforce/config- Read Salesforce integration config (admin)PUT /api/admin/integrations/salesforce/config- Save Salesforce integration config (admin)GET /api/admin/integrations/salesforce/connect-url- Generate Salesforce OAuth URLPOST /api/admin/integrations/salesforce/sync- Sync Salesforce customer data to organizations
See the plan document for complete API documentation.
- User browses documents and clicks "Request Access"
- User fills out form (name, email, company, reason)
- System extracts email domain and checks organization
- If organization has approved documents: Auto-approve, send magic link instantly
- If organization not approved: Send to admin queue
- Admin reviews and approves → Documents added to org's approved list
- Magic link sent via email with time-limited access (7 days)
- Each organization tracks which documents they've been approved for
- First request from an org → Admin reviews
- Admin approves → Documents added to org's approved list
- Future requests for same documents → Auto-approved instantly
- New documents → Still require admin approval
- 4-Layer Protection: Database RLS, API middleware, Frontend routes, Storage policies
- Admin-Only Operations: All admin actions require JWT authentication
- Magic Link Security: 256-bit random tokens, time-limited, email-verified
- Input Validation: Zod schema validation on all mutation routes (auth, documents)
- Path Traversal Prevention:
resolveUploadPath()ensures file operations stay within the uploads directory - XSS Prevention: DOMPurify for rendered HTML content,
escapeHtml()for email templates - Error Leakage Prevention: Generic 500 responses — internal error details never exposed to clients
- Docker Hardening: Non-root container user, environment variable validation at startup
- Database Indexes: Optimized indexes on frequently queried columns for performance
- Audit Logging: All admin actions tracked for compliance
- Environment Variables: Set all production values in
.env - HTTPS: Use reverse proxy (nginx/traefik) for HTTPS
- Database: Use managed PostgreSQL or Supabase Cloud
- Storage: Configure Supabase Storage for production
- Email: Configure production email delivery provider
- Recommended for this project:
EMAIL_PROVIDER=resend - Keep
EMAIL_PROVIDER=mailpitin local/dev
- Recommended for this project:
- Secrets: Use secret management (Docker secrets, Kubernetes secrets, etc.)
- Rate Limiting: For multi-instance deployments, replace in-memory rate limiter with Redis
The production compose file (docker-compose.prod.yml) builds optimized images for both services.
The frontend uses a multi-stage build (deps → build → standalone Next.js) for minimal image size.
# Build production images
docker-compose -f docker-compose.prod.yml build
# Run production stack
docker-compose -f docker-compose.prod.yml up -dSee deployment/kubernetes/ for Kubernetes manifests (create if needed).
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License — you're free to use, modify, and distribute it for any purpose.
See the LICENSE file for full details.
We follow the Contributor Covenant with project-specific coding standards. Please read our Code of Conduct before contributing.
Key expectations:
- Follow TypeScript/React best practices and existing codebase conventions
- Write clean, secure, and accessible code
- Keep PRs focused and ensure
npm run buildpasses - Report security vulnerabilities privately — never in public issues
For issues and questions:
- 🐛 Bug Reports: GitHub Issues
- 💡 Feature Requests: GitHub Issues
- 📖 Documentation: docs/
- Email template customization
- Advanced analytics and reporting
- Multi-language support
- Redis-backed rate limiting for horizontal scaling
- OpenAPI/Swagger documentation
- Advanced document versioning UI
- Frontend test coverage (React Testing Library)
- Document expiry alerts (email reminders before documents expire)
- Public-facing FAQ/knowledge base