generated from bisco/codex-bootstrap
docs: define staff check-in flow
This commit is contained in:
29
docs/adr/0006-staff-check-in-with-token-validation.md
Normal file
29
docs/adr/0006-staff-check-in-with-token-validation.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ADR-0006: Staff Check-In with Token Validation
|
||||||
|
|
||||||
|
Date: 2026-04-28
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Confirmed reservations need a simple entrance check-in process. Staff should be able to use a mobile-friendly web page to scan a visitor's QR code or enter the token manually.
|
||||||
|
|
||||||
|
QR codes may be shown on smartphones, printed, forwarded, or photographed, so they must not expose personal data. Check-in must be restricted to authenticated staff or admin users and must prevent duplicate entrance.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Use staff-only Django REST Framework endpoints for QR verification preview and check-in confirmation.
|
||||||
|
|
||||||
|
The QR code will contain only an opaque verification token or URL. The backend will validate the token server-side, require a confirmed reservation, reject duplicate check-ins, and create or update a `CheckIn` record with timestamp and staff user on successful confirmation.
|
||||||
|
|
||||||
|
No Celery, Redis, or separate check-in service is required.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Staff can use a simple mobile web page for scanning or manual token entry.
|
||||||
|
- QR codes remain privacy-preserving because they do not contain personal data.
|
||||||
|
- Check-in decisions stay centralized in the Django backend and PostgreSQL state.
|
||||||
|
- The entrance workflow depends on backend availability at the venue.
|
||||||
|
- Duplicate check-ins can be blocked with application checks and database constraints.
|
||||||
@@ -243,13 +243,58 @@ Status codes:
|
|||||||
|
|
||||||
## Check-In
|
## Check-In
|
||||||
|
|
||||||
### Verify QR Code
|
Check-in endpoints are for authenticated staff or admin users. Staff use a mobile-friendly Angular page to scan the QR code with a device camera or enter the token manually.
|
||||||
|
|
||||||
|
The QR code must contain only an opaque verification token or a verification URL containing that token. The backend resolves and validates the token server-side.
|
||||||
|
|
||||||
|
### QR Verification Preview
|
||||||
|
|
||||||
```http
|
```http
|
||||||
POST /api/check-ins/verify/
|
POST /api/check-ins/preview/
|
||||||
```
|
```
|
||||||
|
|
||||||
Validates a QR token and records check-in. This endpoint is for authenticated staff or an authenticated scanning interface.
|
Validates a QR token and returns a preview before staff confirms entrance. This endpoint must not create a successful check-in.
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "opaque-check-in-token"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response `200 OK`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "valid",
|
||||||
|
"reservation_id": 123,
|
||||||
|
"performance_id": 10,
|
||||||
|
"show_title": "The Open Stage",
|
||||||
|
"starts_at": "2026-05-15T20:30:00+02:00",
|
||||||
|
"party_size": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Status codes:
|
||||||
|
|
||||||
|
- `200 OK`: token is valid and reservation can be checked in;
|
||||||
|
- `400 Bad Request`: token is missing or malformed;
|
||||||
|
- `401 Unauthorized`: staff authentication is missing;
|
||||||
|
- `403 Forbidden`: authenticated user cannot preview 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 preview response should include only the minimum information staff need to validate the party. It must not expose unnecessary reservation personal data.
|
||||||
|
|
||||||
|
### Check-In Confirmation
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/check-ins/confirm/
|
||||||
|
```
|
||||||
|
|
||||||
|
Validates the token again and records successful entrance.
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
|
|
||||||
@@ -267,7 +312,8 @@ Response `200 OK`:
|
|||||||
"reservation_id": 123,
|
"reservation_id": 123,
|
||||||
"performance_id": 10,
|
"performance_id": 10,
|
||||||
"party_size": 2,
|
"party_size": 2,
|
||||||
"checked_in_at": "2026-05-15T19:55:00+02:00"
|
"checked_in_at": "2026-05-15T19:55:00+02:00",
|
||||||
|
"checked_in_by": 7
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -276,12 +322,32 @@ Status codes:
|
|||||||
- `200 OK`: reservation checked in;
|
- `200 OK`: reservation checked in;
|
||||||
- `400 Bad Request`: token is missing or malformed;
|
- `400 Bad Request`: token is missing or malformed;
|
||||||
- `401 Unauthorized`: staff authentication is missing;
|
- `401 Unauthorized`: staff authentication is missing;
|
||||||
- `403 Forbidden`: authenticated user cannot perform check-in;
|
- `403 Forbidden`: authenticated user cannot confirm check-in;
|
||||||
- `404 Not Found`: token is unknown;
|
- `404 Not Found`: token is unknown;
|
||||||
- `409 Conflict`: reservation is not confirmed or was already checked in;
|
- `409 Conflict`: reservation is not confirmed or was already checked in;
|
||||||
- `410 Gone`: token is expired.
|
- `410 Gone`: token is expired.
|
||||||
|
|
||||||
The response should include only the minimum information staff need to admit the party.
|
Successful confirmation creates a `CheckIn` record, or updates an existing incomplete check-in record for the reservation. A reservation cannot have two successful check-ins.
|
||||||
|
|
||||||
|
Error responses should use clear machine-readable states so the staff interface can show simple messages.
|
||||||
|
|
||||||
|
Example `409 Conflict` for duplicate check-in:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "already_checked_in",
|
||||||
|
"detail": "This reservation has already been checked in."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `409 Conflict` for unconfirmed reservation:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "reservation_not_confirmed",
|
||||||
|
"detail": "This reservation is not confirmed."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Administration
|
## Administration
|
||||||
|
|
||||||
|
|||||||
@@ -79,19 +79,27 @@ The QR code must not contain:
|
|||||||
|
|
||||||
## 5. Entrance Check-In
|
## 5. Entrance Check-In
|
||||||
|
|
||||||
1. Staff opens the authenticated check-in interface.
|
1. Staff signs in with an account that has check-in permission.
|
||||||
2. Staff scans the visitor's QR code.
|
2. Staff opens a mobile-friendly web page for entrance check-in.
|
||||||
3. The frontend submits the scanned token to the backend.
|
3. Staff scans the visitor's QR code with the device camera or enters the QR token manually.
|
||||||
4. The backend validates:
|
4. The QR code provides only an opaque check-in token or verification URL.
|
||||||
|
5. The frontend submits the token to the backend for a verification preview.
|
||||||
|
6. The backend validates:
|
||||||
- staff authentication and permission;
|
- staff authentication and permission;
|
||||||
- token exists and has the `check_in` purpose;
|
- token exists and has the `check_in` purpose;
|
||||||
- reservation exists;
|
- reservation exists;
|
||||||
- reservation is confirmed;
|
- reservation is confirmed;
|
||||||
- token is valid for the performance check-in window if such a window is configured;
|
- token is valid for the performance check-in window if such a window is configured;
|
||||||
- reservation has not already been checked in.
|
- reservation has not already been checked in.
|
||||||
5. The backend creates a `CheckIn` record.
|
7. The backend returns a preview with only the minimum information needed for admission, such as performance, party size, and check-in state.
|
||||||
6. The backend returns a successful check-in response.
|
8. Staff confirms check-in in the mobile web page.
|
||||||
7. Staff admits the visitor party.
|
9. The backend validates the token again server-side.
|
||||||
|
10. The backend creates a `CheckIn` record, or updates an existing incomplete check-in record for the same reservation.
|
||||||
|
11. The backend stores the check-in timestamp and authenticated staff user.
|
||||||
|
12. The backend returns a successful check-in response.
|
||||||
|
13. Staff admits the visitor party.
|
||||||
|
|
||||||
|
The token remains opaque throughout the flow. The QR code must not expose visitor name, email address, phone number, notes, or other personal data.
|
||||||
|
|
||||||
## Duplicate Check-In
|
## Duplicate Check-In
|
||||||
|
|
||||||
@@ -103,6 +111,21 @@ If the same QR code is scanned again:
|
|||||||
|
|
||||||
The response should be clear enough for staff to understand that the reservation was already used.
|
The response should be clear enough for staff to understand that the reservation was already used.
|
||||||
|
|
||||||
|
## Check-In Failure States
|
||||||
|
|
||||||
|
Failed validation must return clear error states without creating a successful check-in.
|
||||||
|
|
||||||
|
Expected check-in failures include:
|
||||||
|
|
||||||
|
- missing or malformed token;
|
||||||
|
- unknown token;
|
||||||
|
- expired token;
|
||||||
|
- staff user is not authenticated;
|
||||||
|
- staff user does not have check-in permission;
|
||||||
|
- reservation is not confirmed;
|
||||||
|
- reservation was already checked in;
|
||||||
|
- token is not valid for the selected performance or check-in window.
|
||||||
|
|
||||||
## Capacity Handling
|
## Capacity Handling
|
||||||
|
|
||||||
Capacity is calculated as:
|
Capacity is calculated as:
|
||||||
|
|||||||
@@ -157,9 +157,10 @@ Suggested fields:
|
|||||||
- `id`: internal identifier;
|
- `id`: internal identifier;
|
||||||
- `reservation`: required unique reference to `Reservation`;
|
- `reservation`: required unique reference to `Reservation`;
|
||||||
- `checked_in_at`: timestamp of successful check-in;
|
- `checked_in_at`: timestamp of successful check-in;
|
||||||
- `checked_in_by`: optional authenticated staff user reference;
|
- `checked_in_by`: required authenticated staff user reference for successful check-in;
|
||||||
- `source`: optional source such as `qr_scan` or `manual`;
|
- `source`: optional source such as `qr_scan` or `manual`;
|
||||||
- `created_at`: creation timestamp.
|
- `created_at`: creation timestamp;
|
||||||
|
- `updated_at`: last update timestamp.
|
||||||
|
|
||||||
Relationships:
|
Relationships:
|
||||||
|
|
||||||
@@ -168,8 +169,14 @@ Relationships:
|
|||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
|
- check-in is performed only by authenticated staff or admin users;
|
||||||
|
- staff use a mobile-friendly web page to scan the QR code or enter the token manually;
|
||||||
|
- the QR code contains only an opaque verification token or URL;
|
||||||
|
- the backend validates the token server-side;
|
||||||
- only confirmed reservations can be checked in;
|
- only confirmed reservations can be checked in;
|
||||||
- a reservation cannot be checked in twice;
|
- a reservation cannot be checked in twice;
|
||||||
|
- successful check-in creates a `CheckIn` record, or updates an existing incomplete check-in record for the same reservation;
|
||||||
|
- successful check-in records must include `checked_in_at` and `checked_in_by`;
|
||||||
- failed check-in attempts should return a clear status without changing successful check-in state;
|
- 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.
|
- check-in must not expose unnecessary personal data to scanning clients.
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Controls:
|
|||||||
- collect only data required to manage the reservation;
|
- collect only data required to manage the reservation;
|
||||||
- do not expose reservation personal data through public APIs;
|
- do not expose reservation personal data through public APIs;
|
||||||
- do not include personal data in QR codes;
|
- do not include personal data in QR codes;
|
||||||
|
- keep check-in preview and confirmation responses limited to operational admission data;
|
||||||
- avoid logging request bodies from booking and confirmation endpoints;
|
- avoid logging request bodies from booking and confirmation endpoints;
|
||||||
- avoid logging raw tokens;
|
- avoid logging raw tokens;
|
||||||
- restrict admin access to staff users who need it.
|
- restrict admin access to staff users who need it.
|
||||||
@@ -56,12 +57,27 @@ QR codes must not contain:
|
|||||||
|
|
||||||
The check-in endpoint resolves the token server-side and returns only the minimum information staff need.
|
The check-in endpoint resolves the token server-side and returns only the minimum information staff need.
|
||||||
|
|
||||||
|
## Check-In Security
|
||||||
|
|
||||||
|
Check-in is performed by authenticated staff or admin users through a mobile-friendly web page.
|
||||||
|
|
||||||
|
Controls:
|
||||||
|
|
||||||
|
- require staff authentication for QR verification preview and check-in confirmation;
|
||||||
|
- allow QR scanning and manual token entry in the staff interface;
|
||||||
|
- validate every token server-side;
|
||||||
|
- require the reservation to be confirmed before check-in;
|
||||||
|
- reject duplicate check-in attempts;
|
||||||
|
- store successful check-in timestamp and staff user;
|
||||||
|
- return clear validation states without exposing unnecessary personal data;
|
||||||
|
- do not log raw QR tokens.
|
||||||
|
|
||||||
## Authentication and Authorization
|
## Authentication and Authorization
|
||||||
|
|
||||||
Required controls:
|
Required controls:
|
||||||
|
|
||||||
- Django admin requires authenticated staff accounts;
|
- Django admin requires authenticated staff accounts;
|
||||||
- check-in verification requires authenticated staff or an authenticated scanning client;
|
- check-in verification preview and confirmation require authenticated staff or admin users;
|
||||||
- staff permissions should separate content management from operational check-in when practical;
|
- 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.
|
- public APIs must not allow clients to set protected fields such as reservation status, token values, or check-in state.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user