From 1eb6aca588fdbb117e5fe02497813c0f4f9164b2 Mon Sep 17 00:00:00 2001 From: bisco Date: Tue, 28 Apr 2026 10:45:37 +0200 Subject: [PATCH] docs: add initial architecture documentation --- docs/api-contract.md | 315 +++++++++++++++++++++++++++++++++++++++++ docs/architecture.md | 161 +++++++++++++++++++-- docs/booking-flow.md | 136 ++++++++++++++++++ docs/deployment.md | 174 +++++++++++++++++++++-- docs/domain-model.md | 186 ++++++++++++++++++++++++ docs/security-notes.md | 151 ++++++++++++++++++++ docs/security.md | 21 ++- 7 files changed, 1112 insertions(+), 32 deletions(-) create mode 100644 docs/api-contract.md create mode 100644 docs/booking-flow.md create mode 100644 docs/domain-model.md create mode 100644 docs/security-notes.md diff --git a/docs/api-contract.md b/docs/api-contract.md new file mode 100644 index 0000000..2db8e16 --- /dev/null +++ b/docs/api-contract.md @@ -0,0 +1,315 @@ +# API Contract + +This document proposes the initial REST API for AzioneLab. Endpoint names are intentionally explicit and small-project friendly. + +All examples use JSON unless noted otherwise. + +## Public Content + +### List Shows + +```http +GET /api/shows/ +``` + +Returns published shows. + +Response `200 OK`: + +```json +{ + "results": [ + { + "id": 1, + "title": "The Open Stage", + "slug": "the-open-stage", + "summary": "A contemporary theatre performance.", + "poster_image": "https://example.org/media/shows/open-stage.jpg" + } + ] +} +``` + +### Show Detail + +```http +GET /api/shows/{slug}/ +``` + +Response `200 OK`: + +```json +{ + "id": 1, + "title": "The Open Stage", + "slug": "the-open-stage", + "summary": "A contemporary theatre performance.", + "description": "Full public show description.", + "poster_image": "https://example.org/media/shows/open-stage.jpg", + "performances": [ + { + "id": 10, + "starts_at": "2026-05-15T20:30:00+02:00", + "venue": { + "name": "AzioneLab Theatre", + "city": "Rome" + }, + "booking_enabled": true, + "available_seats": 24 + } + ] +} +``` + +Status codes: + +- `200 OK`: show found; +- `404 Not Found`: show does not exist or is not published. + +### List Performances + +```http +GET /api/performances/ +``` + +Optional filters: + +- `show`: show slug; +- `from`: start date/time lower bound. + +Response `200 OK`: + +```json +{ + "results": [ + { + "id": 10, + "show": { + "title": "The Open Stage", + "slug": "the-open-stage" + }, + "venue": { + "name": "AzioneLab Theatre", + "city": "Rome" + }, + "starts_at": "2026-05-15T20:30:00+02:00", + "booking_enabled": true, + "available_seats": 24 + } + ] +} +``` + +### Performance Detail + +```http +GET /api/performances/{id}/ +``` + +Response `200 OK`: + +```json +{ + "id": 10, + "show": { + "title": "The Open Stage", + "slug": "the-open-stage", + "summary": "A contemporary theatre performance." + }, + "venue": { + "name": "AzioneLab Theatre", + "address": "Via Example 10", + "city": "Rome" + }, + "starts_at": "2026-05-15T20:30:00+02:00", + "booking_enabled": true, + "available_seats": 24 +} +``` + +Status codes: + +- `200 OK`: performance found; +- `404 Not Found`: performance does not exist or is not public. + +## Booking + +### Create Reservation + +```http +POST /api/performances/{id}/reservations/ +``` + +Creates a pending reservation and sends a confirmation email. + +Request: + +```json +{ + "name": "Maria Rossi", + "email": "maria.rossi@example.org", + "phone": "+390600000000", + "party_size": 2, + "notes": "We will arrive a few minutes early." +} +``` + +Response `201 Created`: + +```json +{ + "id": 123, + "status": "pending", + "performance": 10, + "party_size": 2, + "message": "Reservation created. Please check your email to confirm it." +} +``` + +Status codes: + +- `201 Created`: pending reservation created; +- `400 Bad Request`: invalid input; +- `404 Not Found`: performance does not exist or is not public; +- `409 Conflict`: booking is closed or capacity is already unavailable. + +Validation rules: + +- `name` is required; +- `email` must be a valid email address; +- `party_size` must be a positive integer; +- public clients must not set reservation status; +- the backend must validate booking availability server-side. + +### Confirm Reservation + +```http +POST /api/reservations/confirm/ +``` + +Confirms a pending reservation using the token from the email link. + +Request: + +```json +{ + "token": "opaque-confirmation-token" +} +``` + +Response `200 OK`: + +```json +{ + "reservation_id": 123, + "status": "confirmed", + "party_size": 2, + "qr_code_url": "https://example.org/api/reservations/123/qr-code/" +} +``` + +Status codes: + +- `200 OK`: reservation confirmed; +- `400 Bad Request`: token is missing or malformed; +- `404 Not Found`: token is unknown; +- `409 Conflict`: token already used, reservation already handled, or no capacity remains; +- `410 Gone`: token expired. + +### Retrieve QR Code + +```http +GET /api/reservations/{id}/qr-code/ +``` + +Returns the generated QR code for a confirmed reservation. Access must be protected by a valid QR token, signed URL, or equivalent control so that reservation IDs are not enough to retrieve QR codes. + +Response `200 OK`: + +```json +{ + "reservation_id": 123, + "qr_code_image": "data:image/png;base64,...", + "printable": true +} +``` + +Status codes: + +- `200 OK`: QR code available; +- `403 Forbidden`: caller is not allowed to access the QR code; +- `404 Not Found`: reservation not found; +- `409 Conflict`: reservation is not confirmed. + +## Check-In + +### Verify QR Code + +```http +POST /api/check-ins/verify/ +``` + +Validates a QR token and records check-in. This endpoint is for authenticated staff or an authenticated scanning interface. + +Request: + +```json +{ + "token": "opaque-check-in-token" +} +``` + +Response `200 OK`: + +```json +{ + "status": "checked_in", + "reservation_id": 123, + "performance_id": 10, + "party_size": 2, + "checked_in_at": "2026-05-15T19:55:00+02:00" +} +``` + +Status codes: + +- `200 OK`: reservation checked in; +- `400 Bad Request`: token is missing or malformed; +- `401 Unauthorized`: staff authentication is missing; +- `403 Forbidden`: authenticated user cannot perform check-in; +- `404 Not Found`: token is unknown; +- `409 Conflict`: reservation is not confirmed or was already checked in; +- `410 Gone`: token is expired. + +The response should include only the minimum information staff need to admit the party. + +## Administration + +The initial administration API is Django admin. + +Admin paths: + +```http +GET /admin/ +``` + +Admin users can manage shows, venues, performances, reservations, reservation tokens, and check-ins according to staff permissions. + +Status codes: + +- `302 Found`: unauthenticated browser redirected to admin login; +- `200 OK`: authenticated admin page; +- `403 Forbidden`: authenticated user lacks the required permission. + +## Error Format + +Use Django REST Framework's standard validation error format unless a project-specific envelope is introduced later. + +Example `400 Bad Request`: + +```json +{ + "email": ["Enter a valid email address."], + "party_size": ["Ensure this value is greater than or equal to 1."] +} +``` diff --git a/docs/architecture.md b/docs/architecture.md index 5e50cc6..eec4195 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,13 +1,156 @@ # Architecture -Describe the project architecture here. +AzioneLab is a public website for a theatre company with a small booking system for performances. -Include: +The architecture is intentionally simple: one Django backend, one Angular frontend, one PostgreSQL database, and nginx as the public reverse proxy. There is no Celery, Redis, message broker, or separate worker service at this stage. -- main components; -- runtime dependencies; -- data flow; -- persistence; -- external integrations; -- deployment topology; -- relevant ADRs. +## Components + +### Public frontend + +The public frontend is an Angular application using Angular Material. + +Responsibilities: + +- render descriptive pages for the theatre company; +- render the public list of shows; +- render public show and performance detail pages; +- provide the booking form for a selected performance; +- show reservation submission, confirmation, and check-in feedback states; +- call the backend through REST APIs. + +The frontend must not calculate authoritative availability. It may display availability returned by the backend, but the backend remains responsible for final capacity validation. + +### Backend API + +The backend is a Django 5.2 LTS application using Django REST Framework. + +Responsibilities: + +- expose public read APIs for shows, venues, and performances; +- expose public booking and reservation confirmation APIs; +- expose an authenticated check-in verification API; +- provide Django admin or an equivalent authenticated administration area; +- store reservations, tokens, and check-in state; +- calculate performance availability server-side; +- send reservation confirmation emails; +- generate QR codes after reservation confirmation. + +The backend runs with gunicorn in production. + +### Administration area + +The first administration area should use Django admin unless a custom Angular admin becomes necessary later. + +Administrators can manage: + +- shows; +- venues; +- performances; +- total room capacity; +- manually occupied seats; +- optional additional seats available during booking; +- reservation status and check-in records when operationally necessary. + +Admin functionality must require authenticated staff access. + +### Database + +PostgreSQL is the system of record. + +It stores: + +- show and venue content; +- performance scheduling and capacity configuration; +- reservations and explicit reservation status; +- reservation tokens used for confirmation and QR verification; +- check-in records. + +Capacity checks must happen inside database transactions to avoid overbooking when multiple users book at the same time. + +### Email + +The backend sends transactional emails for: + +- reservation confirmation link after booking submission; +- optional confirmation success email containing the QR code or QR verification link. + +Email delivery can use Django's email backend configuration. No asynchronous email worker is required initially; failures should be logged without exposing tokens or personal data. + +### QR code generation + +The backend generates QR codes using a small Python library such as `qrcode` or `segno`. + +QR codes must contain only an opaque token or verification URL. They must not contain names, email addresses, phone numbers, notes, or other personal data. + +### nginx + +nginx is the public entry point. + +Responsibilities: + +- terminate HTTP traffic, and TLS when configured; +- serve the built Angular static assets; +- reverse proxy API and admin requests to gunicorn; +- serve static and media files according to the deployment configuration. + +## Runtime Dependencies + +Required runtime dependencies: + +- Python; +- Django 5.2 LTS; +- Django REST Framework; +- gunicorn; +- PostgreSQL; +- Angular; +- Angular Material; +- nginx; +- Docker Compose; +- a Python QR code library such as `qrcode` or `segno`; +- an SMTP-compatible email provider or relay. + +Not included at this stage: + +- Celery; +- Redis; +- background workers; +- separate search, cache, or queue services. + +## Data Flow + +1. A visitor opens the website through nginx. +2. nginx serves the Angular frontend. +3. The frontend calls public backend API endpoints for shows and performances. +4. The visitor submits a booking form for a specific performance. +5. The backend validates input, checks capacity server-side, creates a pending reservation, creates a confirmation token, and sends a confirmation email. +6. The visitor opens the confirmation link. +7. The backend validates the token, confirms the reservation if capacity is still available, and generates a QR code token or QR code image. +8. The visitor presents the QR code at the venue using a smartphone or printed copy. +9. Staff scans the QR code. +10. The backend validates the token and records a check-in if the reservation is confirmed and not already checked in. + +## Deployment Topology + +The initial deployment uses Docker Compose with these services: + +- `nginx`: public reverse proxy and static frontend server; +- `frontend`: Angular build stage or static asset build source; +- `backend`: Django application served by gunicorn; +- `db`: PostgreSQL database. + +Only nginx should be publicly exposed. The backend and database should be reachable only on the internal Compose network. + +## Architectural Constraints + +- Keep the booking workflow synchronous and explicit. +- Keep all capacity validation on the backend. +- Store reservation status explicitly. +- Use opaque, random, non-guessable tokens. +- Do not place personal data in QR codes. +- Avoid optional infrastructure until the project needs it. +- Prefer Django admin for internal management before building custom admin UI. + +## Relevant ADRs + +No ADRs are recorded yet. The technology stack and initial constraints are documented here from the project request. diff --git a/docs/booking-flow.md b/docs/booking-flow.md new file mode 100644 index 0000000..a53a271 --- /dev/null +++ b/docs/booking-flow.md @@ -0,0 +1,136 @@ +# Booking Flow + +This document describes the full public booking, confirmation, QR generation, and entrance check-in flow. + +## 1. Public Discovery + +1. A visitor opens the public website. +2. The Angular frontend requests published shows and upcoming performances from the backend. +3. The visitor opens a show detail page. +4. The frontend displays upcoming public performances and availability returned by the backend. + +Availability shown to visitors is informational. The backend recalculates availability when reservation state changes. + +## 2. Booking Submission + +1. The visitor selects a performance. +2. The frontend displays the booking form for that performance. +3. The visitor enters contact details and party size. +4. The frontend submits the booking request to the backend. +5. The backend validates: + - required fields; + - email format; + - positive party size; + - performance exists and booking is open; + - requested seats do not exceed currently available seats. +6. The backend creates a `pending` reservation. +7. The backend creates a random opaque confirmation token. +8. The backend sends an email with a confirmation link. +9. The frontend tells the visitor to check their email. + +The reservation is not confirmed at this stage. + +## 3. Email Confirmation + +1. The visitor opens the confirmation link from the email. +2. The frontend or backend submits the confirmation token to the confirmation endpoint. +3. The backend validates that the token: + - exists; + - has the `confirmation` purpose; + - belongs to a pending reservation; + - has not expired; + - has not already been used. +4. The backend starts a database transaction. +5. The backend locks the related performance row. +6. The backend recalculates confirmed reservations for the performance. +7. The backend confirms the reservation only if enough seats remain. +8. The backend marks the confirmation token as used. +9. The backend creates a QR verification token. +10. The backend generates a QR code containing the opaque QR token or a verification URL. +11. The backend returns or sends the QR code to the visitor. + +If there is no longer enough capacity, the backend must not confirm the reservation. + +## 4. QR Code Delivery + +After confirmation, the visitor receives access to a QR code. + +The QR code may be delivered as: + +- an image displayed on the confirmation page; +- an image or link in a confirmation email; +- a printable page linked from the confirmation result. + +The QR code must be usable from a smartphone screen or printed copy. + +The QR code must contain: + +- an opaque check-in token; or +- a verification URL containing an opaque token. + +The QR code must not contain: + +- visitor name; +- email address; +- phone number; +- notes; +- party size if avoidable; +- any other personal data. + +## 5. Entrance Check-In + +1. Staff opens the authenticated check-in interface. +2. Staff scans the visitor's QR code. +3. The frontend submits the scanned token to the backend. +4. The backend validates: + - staff authentication and permission; + - token exists and has the `check_in` purpose; + - reservation exists; + - reservation is confirmed; + - token is valid for the performance check-in window if such a window is configured; + - reservation has not already been checked in. +5. The backend creates a `CheckIn` record. +6. The backend returns a successful check-in response. +7. Staff admits the visitor party. + +## Duplicate Check-In + +If the same QR code is scanned again: + +1. The backend detects an existing check-in for the reservation. +2. The backend returns `409 Conflict`. +3. The backend does not create a second check-in. + +The response should be clear enough for staff to understand that the reservation was already used. + +## Capacity Handling + +Capacity is calculated as: + +```text +room capacity + additional seats - manually occupied seats - confirmed reservations +``` + +Only confirmed reservations consume capacity. Pending reservations represent interest, not a guaranteed seat. + +To avoid overbooking, final confirmation must be transactional: + +1. lock the performance; +2. recalculate confirmed seats; +3. compare availability with requested party size; +4. confirm only when seats are available. + +## Failure States + +Expected failure states include: + +- invalid booking input; +- booking disabled for the performance; +- no capacity remaining; +- confirmation token expired; +- confirmation token already used; +- QR token invalid; +- reservation not confirmed; +- reservation already checked in. + +Each failure should return a clear status code and a concise user-facing message. diff --git a/docs/deployment.md b/docs/deployment.md index 24755d5..938e1b7 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,15 +1,167 @@ # Deployment -Describe how this project is deployed. +AzioneLab should deploy with a simple Docker Compose topology: -Include: +- `nginx`: public reverse proxy and static frontend server; +- `frontend`: Angular build source or build stage for static assets; +- `backend`: Django 5.2 LTS application served by gunicorn; +- `db`: PostgreSQL database. -- environments; -- Docker/Compose usage; -- required configuration; -- secrets handling; -- exposed ports; -- volumes; -- networks; -- deployment commands; -- rollback procedure. +Only nginx should expose public ports. The backend and database should stay on the internal Compose network. + +## Services + +### nginx + +Responsibilities: + +- listen on public HTTP and HTTPS ports; +- serve built Angular files; +- proxy `/api/` and `/admin/` requests to the backend; +- serve static and media files according to the selected storage layout; +- apply request size and timeout limits appropriate for booking and admin usage. + +Public ports: + +- `80` for HTTP; +- `443` for HTTPS in production. + +### frontend + +The frontend is an Angular application using Angular Material. + +Deployment options: + +- build the Angular app in a Docker build stage and copy static files into the nginx image; +- or run a one-shot build container that writes static files to a shared volume consumed by nginx. + +The first option is preferred for a simple production deployment because nginx can serve immutable built assets without a long-running Node process. + +### backend + +The backend is a Django application served by gunicorn. + +Responsibilities: + +- REST API; +- Django admin; +- booking, confirmation, QR generation, and check-in logic; +- transactional capacity validation; +- email sending. + +The backend should run database migrations before or during deployment through an explicit operational command, not as hidden startup magic unless that choice is documented later. + +### db + +PostgreSQL is the only database service. + +Responsibilities: + +- persistent application data; +- reservation and check-in state; +- transactional capacity enforcement. + +Use a named Docker volume for database data. + +## Networks + +Recommended Compose networks: + +- `public`: nginx-facing network when needed; +- `internal`: private network for nginx, backend, and db communication. + +The database should not be published to the host in production. + +## Volumes + +Recommended volumes: + +- `postgres_data`: PostgreSQL data directory; +- `media`: uploaded media and generated QR assets if stored on disk; +- `static`: collected Django static files if served by nginx from a shared volume. + +Generated QR codes may also be generated on demand instead of stored as files. If stored, they must not reveal personal data and access must remain controlled. + +## Configuration + +Required backend configuration: + +- `DJANGO_SECRET_KEY`; +- `DJANGO_ALLOWED_HOSTS`; +- `DJANGO_CSRF_TRUSTED_ORIGINS`; +- `DATABASE_URL` or equivalent database settings; +- email host, port, username, password, TLS settings, and sender address; +- public site URL used to build confirmation and QR verification links. + +Required database configuration: + +- database name; +- database user; +- database password; +- data volume path. + +Required nginx configuration: + +- upstream backend service name and port; +- static frontend root; +- proxy rules for `/api/` and `/admin/`; +- TLS certificate paths for production. + +Secrets must be provided through deployment-managed environment variables, Docker secrets, or another secret manager. Do not commit real secret values. + +## Example Request Routing + +```text +Visitor browser + -> nginx + -> Angular static files + -> /api/ requests proxied to backend:gunicorn + -> /admin/ requests proxied to backend:gunicorn +Backend + -> PostgreSQL + -> SMTP provider +``` + +## Deployment Commands + +The exact commands will be finalized when application code and Compose files are added. + +Expected production-style flow: + +```bash +docker compose build +docker compose run --rm backend python manage.py migrate +docker compose run --rm backend python manage.py collectstatic --noinput +docker compose up -d +``` + +Expected validation commands: + +```bash +docker compose config +docker compose run --rm backend python manage.py check --deploy +docker compose run --rm backend python manage.py test +``` + +The repository does not yet define the canonical Docker-based test command. + +## Rollback + +Rollback should be designed around immutable images and database backups. + +Basic rollback steps: + +1. identify the previous known-good image tags or Git commit; +2. stop the current Compose stack; +3. deploy the previous image tags or commit; +4. restore the database from backup only if a migration or data change requires it; +5. run smoke checks for public pages, booking creation, confirmation, and check-in. + +Database rollback needs special care once migrations exist. Down migrations or backup restore procedures should be documented before production use. + +## Operational Notes + +- Configure database backups before accepting real bookings. +- Monitor backend errors, email delivery failures, and check-in failures. +- Keep container images explicitly versioned; do not use `latest` tags. +- Keep the system small until operational needs justify additional services. diff --git a/docs/domain-model.md b/docs/domain-model.md new file mode 100644 index 0000000..88021e6 --- /dev/null +++ b/docs/domain-model.md @@ -0,0 +1,186 @@ +# Domain Model + +This document describes the core domain concepts for AzioneLab's theatre website and booking system. + +## Show + +A show is a theatrical production presented by the company. + +Suggested fields: + +- `id`: internal identifier; +- `title`: public show title; +- `slug`: stable public URL identifier; +- `summary`: short public description; +- `description`: full public description; +- `poster_image`: optional public image; +- `is_published`: controls public visibility; +- `created_at`: creation timestamp; +- `updated_at`: last update timestamp. + +Relationships: + +- one show can have many performances. + +Rules: + +- unpublished shows are not listed publicly; +- a public show detail page may include only published upcoming performances. + +## Venue + +A venue is the place where a performance happens. + +Suggested fields: + +- `id`: internal identifier; +- `name`: public venue name; +- `slug`: stable public URL identifier; +- `address`: public address; +- `city`: venue city; +- `notes`: optional public or internal venue notes; +- `created_at`: creation timestamp; +- `updated_at`: last update timestamp. + +Relationships: + +- one venue can host many performances. + +## Performance + +A performance is a scheduled presentation of one show at one venue. + +Suggested fields: + +- `id`: internal identifier; +- `show`: required reference to `Show`; +- `venue`: required reference to `Venue`; +- `starts_at`: performance date and time; +- `room_capacity`: configured total room capacity; +- `manually_occupied_seats`: seats unavailable because they are reserved outside the public booking system; +- `additional_seats`: optional extra seats made available during booking; +- `is_booking_enabled`: controls whether public booking is open; +- `created_at`: creation timestamp; +- `updated_at`: last update timestamp. + +Relationships: + +- each performance belongs to one show; +- each performance belongs to one venue; +- one performance can have many reservations. + +Availability formula: + +```text +available seats = + room capacity + + additional seats + - manually occupied seats + - confirmed reservations +``` + +Rules: + +- capacity values must not be negative; +- `manually_occupied_seats` must not exceed `room_capacity + additional_seats`; +- only confirmed reservations reduce public availability; +- pending reservations do not guarantee a seat until confirmation; +- final capacity validation must happen server-side when confirming a reservation; +- changes to capacity configuration must preserve existing confirmed reservations. + +## Reservation + +A reservation is a booking request for a specific performance. + +Suggested fields: + +- `id`: internal identifier; +- `performance`: required reference to `Performance`; +- `status`: explicit status such as `pending`, `confirmed`, `cancelled`, or `expired`; +- `name`: reservation contact name; +- `email`: reservation contact email; +- `phone`: optional reservation contact phone; +- `party_size`: number of requested seats; +- `notes`: optional visitor note; +- `confirmed_at`: timestamp set when the reservation is confirmed; +- `qr_code_generated_at`: timestamp set when the QR code is generated; +- `created_at`: creation timestamp; +- `updated_at`: last update timestamp. + +Relationships: + +- each reservation belongs to one performance; +- one reservation can have one or more reservation tokens for different purposes; +- one confirmed reservation can have at most one successful check-in. + +Rules: + +- a new reservation starts as `pending`; +- a reservation becomes `confirmed` only through a valid confirmation token; +- a confirmed reservation receives a QR code; +- `party_size` must be positive; +- the backend must reject confirmation if the requested seats would exceed availability; +- personal data must never be stored in QR codes. + +## ReservationToken + +A reservation token is an opaque token used for confirmation or QR verification. + +Suggested fields: + +- `id`: internal identifier; +- `reservation`: required reference to `Reservation`; +- `purpose`: token purpose, such as `confirmation` or `check_in`; +- `token_hash`: server-side hash of the opaque token; +- `expires_at`: optional expiration timestamp; +- `used_at`: timestamp set when a one-time token is consumed; +- `created_at`: creation timestamp. + +Relationships: + +- each token belongs to one reservation. + +Rules: + +- raw tokens must be random, non-guessable, and generated with a cryptographically secure generator; +- store only a hash of the token when practical; +- confirmation tokens should be single use; +- QR tokens may remain valid until the performance check-in window closes; +- tokens must not encode personal data. + +## CheckIn + +A check-in records entrance validation for a confirmed reservation. + +Suggested fields: + +- `id`: internal identifier; +- `reservation`: required unique reference to `Reservation`; +- `checked_in_at`: timestamp of successful check-in; +- `checked_in_by`: optional authenticated staff user reference; +- `source`: optional source such as `qr_scan` or `manual`; +- `created_at`: creation timestamp. + +Relationships: + +- each check-in belongs to one reservation; +- a reservation can have at most one successful check-in. + +Rules: + +- only confirmed reservations can be checked in; +- a reservation cannot be checked in twice; +- failed check-in attempts should return a clear status without changing successful check-in state; +- check-in must not expose unnecessary personal data to scanning clients. + +## Anti-Overbooking Rule + +The backend must enforce capacity inside a transaction when confirming reservations. + +Recommended approach: + +- lock the relevant `Performance` row during confirmation; +- count confirmed seats for that performance; +- compare requested seats with available seats; +- confirm only if enough seats remain; +- otherwise leave the reservation pending or mark it as expired/rejected according to the future product decision. diff --git a/docs/security-notes.md b/docs/security-notes.md new file mode 100644 index 0000000..ce7ea88 --- /dev/null +++ b/docs/security-notes.md @@ -0,0 +1,151 @@ +# Security Notes + +This document records security assumptions and controls for AzioneLab's initial architecture. + +## Assumptions + +- The public website is readable by anonymous visitors. +- Booking endpoints are public but must validate input strictly. +- Administration and check-in functions require authenticated staff users. +- HTTPS is expected in production. +- PostgreSQL is reachable only from trusted application containers. +- Email is sent through a configured SMTP provider or relay. + +## Personal Data + +Reservations contain personal data such as name, email address, optional phone number, and optional notes. + +Controls: + +- collect only data required to manage the reservation; +- do not expose reservation personal data through public APIs; +- do not include personal data in QR codes; +- avoid logging request bodies from booking and confirmation endpoints; +- avoid logging raw tokens; +- restrict admin access to staff users who need it. + +## Token Handling + +Reservation tokens are used for email confirmation and QR verification. + +Rules: + +- generate tokens with a cryptographically secure random generator; +- make tokens opaque and non-guessable; +- do not encode personal data in tokens; +- store token hashes when practical; +- treat raw tokens as secrets; +- mark one-time confirmation tokens as used after successful confirmation; +- expire confirmation tokens after a reasonable period; +- keep QR tokens valid only for the intended performance and check-in period where practical. + +## QR Codes + +QR codes must contain only: + +- an opaque verification token; or +- a verification URL containing an opaque token. + +QR codes must not contain: + +- name; +- email; +- phone; +- notes; +- reservation metadata that identifies the visitor. + +The check-in endpoint resolves the token server-side and returns only the minimum information staff need. + +## Authentication and Authorization + +Required controls: + +- Django admin requires authenticated staff accounts; +- check-in verification requires authenticated staff or an authenticated scanning client; +- staff permissions should separate content management from operational check-in when practical; +- public APIs must not allow clients to set protected fields such as reservation status, token values, or check-in state. + +## Input Validation + +Public booking endpoints must validate: + +- required fields; +- email format; +- maximum field lengths; +- positive party size; +- booking status for the selected performance; +- capacity availability; +- unexpected fields. + +Validation must happen server-side even when the frontend also validates input. + +## Anti-Overbooking + +The backend must enforce capacity server-side. + +Capacity calculation: + +```text +available seats = + room capacity + + additional seats + - manually occupied seats + - confirmed reservations +``` + +Controls: + +- use database transactions for confirmation; +- lock the performance row or use an equivalent consistency control; +- recalculate availability inside the transaction; +- confirm the reservation only when enough seats remain; +- ensure duplicate confirmation requests are idempotent or rejected safely; +- do not rely on frontend availability values. + +## Secrets + +Secrets must not be committed to the repository. + +Expected secret configuration: + +- Django `SECRET_KEY`; +- database password; +- SMTP credentials; +- TLS private keys or certificate automation credentials, if used. + +Use environment variables, Docker secrets, or deployment-managed secret injection. Documentation and example configuration should use placeholders only. + +## Deployment Security + +Deployment should follow least privilege: + +- expose only nginx publicly; +- keep backend and database on an internal Docker network; +- avoid privileged containers; +- use explicit image tags rather than `latest`; +- persist PostgreSQL data in a named volume; +- configure TLS for production; +- serve static and media files without exposing private files. + +## Logging + +Logs should help diagnose operational issues without exposing sensitive data. + +Do not log: + +- raw confirmation tokens; +- raw QR tokens; +- full booking payloads; +- passwords; +- session cookies; +- authorization headers; +- SMTP credentials. + +## Residual Risks + +Initial residual risks: + +- synchronous email can make booking responses depend on SMTP availability; +- QR codes can be copied, so duplicate check-in prevention must be reliable; +- staff account compromise would expose admin and check-in functionality; +- retention and deletion rules for personal data still need a project policy. diff --git a/docs/security.md b/docs/security.md index 0e855fa..3c8d751 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,16 +1,13 @@ # Security -Describe security assumptions and controls. +AzioneLab security assumptions and controls are documented in [security-notes.md](security-notes.md). -Include: +The initial security model covers: -- authentication; -- authorization; -- network exposure; -- TLS/certificates; -- secrets management; -- logging of sensitive data; -- container privileges; -- filesystem permissions; -- dependency management; -- relevant ADRs. +- public website access; +- authenticated administration and check-in; +- reservation privacy; +- opaque token handling; +- QR code privacy; +- server-side capacity validation; +- deployment and logging assumptions.