Architecture & Controls
This page is for IT leads, accountants, and security officers who need a technical overview. If you are an operator and want the short version, see the overview page.
Principle
Section titled “Principle”CampOne is a multi-tenant SaaS application. Backend (Django 5.2, Python 3.12), admin frontend (React 19, Vite 6), and database (Supabase PostgreSQL, EU region) are three clearly separated components that communicate exclusively over encrypted connections (TLS 1.2+).
Authentication
Section titled “Authentication”| Element | Value |
|---|---|
| Token scheme | JWT (RFC 7519), HS256, Bearer header |
| Access-token lifetime | 30 minutes, in-memory only in the browser |
| Refresh-token lifetime | 7 days, in an HttpOnly; Secure; SameSite=None cookie |
| Rotation | Refresh tokens are exchanged on every use; the prior token is immediately blacklisted |
| Logout | Server-side blacklist of the refresh token plus cookie deletion |
| Password policy | Minimum length, similarity check against user attributes, common-password denylist, no purely numeric passwords |
| Password hashing | Django default pipeline (PBKDF2 primary, Argon2 available), cost factor rotatable |
| Password reset | Single-use HMAC token, default validity 3 days |
| Session limit | Per-tenant cap by plan (e.g. Starter 2, Professional 5) — oldest sessions are revoked when exceeded |
Multi-tenancy
Section titled “Multi-tenancy”Every request passes through a TenantMiddleware that resolves the tenant in this order:
- From the
tenant_idclaim in the JWT (for authenticated calls). - From the
X-Tenant-Slugheader (for unauthenticated public endpoints). - From the custom domain in the Origin header (for guest-facing booking sites).
A TenantManager then automatically applies the filter tenant=<current> to every database query. Cross-tenant access requires explicit, grep-able tenant-switching code.
Inactive tenants (is_active=False) are never resolved — deactivation acts as an immediate kill switch.
Roles & permissions
Section titled “Roles & permissions”| Role | Description |
|---|---|
| Superadmin | CampOne-internal support. No tenant assigned. Real-tenant access requires an approved support request. |
| Tenant admin | Full access within own tenant. Can create staff and assign modules. |
| Staff | Restricted access to assigned modules (e.g. reception only or POS only). |
| Customer | Guest with access to their own bookings via the guest portal. |
CampOne support can access a real tenant in one of three modes, every time recorded in the audit log:
- Mock: a shared synthetic sample tenant. No approval required.
- Redacted: your tenant, but PII is masked in API responses, and write operations are blocked server-side. No approval required.
- Real: your tenant with full access. Requires an approved support-access request. Token lifetime 30 minutes.
Transport & security headers
Section titled “Transport & security headers”| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload (1 year, preload list) |
Content-Security-Policy | with frame-ancestors 'none' and connect-src / img-src restricted to known origins |
X-Frame-Options | DENY |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | geolocation=(), microphone=(), camera=() |
All four shipped frontends (admin, marketing, docs, per-tenant booking SPAs) set these headers at the Vercel edge in addition to the backend configuration.
Data at rest
Section titled “Data at rest”| Data type | Storage | Protection |
|---|---|---|
| Database | Supabase PostgreSQL, AWS Frankfurt region (eu-west-1) | TLS, daily backups, point-in-time recovery |
| File uploads | Supabase S3, EU region | private ACL, tenant-scoped paths, signed URLs for every access |
| Tenant secrets (Stripe keys, SMTP passwords) | Database | Application-level encrypted fields, separate key per environment |
| Logs | Backend host volume | Rotation at 5 MB, max. 5 generations, no PII in query parameters |
Throttling & abuse prevention
Section titled “Throttling & abuse prevention”| Endpoint | Limit |
|---|---|
| Login | 10 attempts/minute per IP |
| Password reset | 5 attempts/hour per IP |
| Account deletion | 3 attempts/hour per account |
| Public availability search | 60 requests/minute per IP |
| AI assistant (anonymous) | 20 requests/hour per IP |
| Public API per key | configurable, default 1000/hour |
In addition, the application enforces a per-tenant concurrent-session cap — oldest refresh tokens are blacklisted when exceeded.
Public surface protection
Section titled “Public surface protection”- Public API (
/api/v1/public/): API-key authentication with formatck_<prefix><secret>; only the salted hash is stored in the database, the cleartext is unrecoverable after creation. Every call is logged inAPICallLogwith status, path, latency. Bodies are not logged — the audit trail does not contain personal data. - Webhooks: Stripe webhooks are signature-verified using the per-tenant webhook secret. The OTA webhook (Booking.com) authenticates via HTTP Basic over TLS.
- Lead form on the marketing site: validated client-side via
react-hook-formandzod, server-side throttled at 20 requests/hour per IP.
Audit trail
Section titled “Audit trail”Six separate, tenant-scoped, indexed tables record relevant events:
UserLoginLog— every successful login with IP and timestamp.BookingAuditLog— every booking change as a diff entry with actor and reason.SupportAuditEvent+SupportAccessSession— every CampOne support session into your tenant.APICallLog— every public-API call.MessageLog— every transactional email sent.ErrorLog— every unhandled backend error.
Detailed description on the audit-trail page.
Software currency
Section titled “Software currency”CampOne runs on the latest major versions:
| Component | Version |
|---|---|
| Django | 5.2 |
| Django REST Framework | 3.17 |
| SimpleJWT | 5.4 |
| React | 19 |
| Vite | 6 |
| Python | 3.12 |
| PostgreSQL | 15 (Supabase) |
There are no end-of-life components in production. Security patches roll into the next maintenance release; critical updates ship out of the regular release cycle.
Verifiability
Section titled “Verifiability”This page is a summary. For a technical audit, on request we provide:
- the internal security-posture document with file-level references for every control,
- the nDSG compliance audit report,
- the current state of the security roadmap with risk ratings,
- access to a test environment for independent verification.
Requests to security@campone.ch.