fix(security): separate booking and check-in tokens

This commit is contained in:
bisco
2026-04-29 21:49:21 +02:00
parent 5cad1871e7
commit 13a05f6d0d
10 changed files with 214 additions and 64 deletions

View File

@@ -204,7 +204,7 @@ Response `200 OK`:
"reservation_id": 123,
"status": "confirmed",
"party_size": 2,
"qr_code_url": "https://example.org/api/reservations/123/qr-code/"
"qr_code_url": "https://example.org/api/check-ins/preview/?token=opaque-check-in-token"
}
```
@@ -222,13 +222,14 @@ Status codes:
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.
Returns the generated QR code for a confirmed reservation. Access must be protected by a valid opaque `check_in` 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_url": "https://example.org/api/check-ins/preview/?token=opaque-check-in-token",
"qr_code_image": "data:image/png;base64,...",
"printable": true
}

View File

@@ -25,7 +25,7 @@ Availability shown to visitors is informational. The backend recalculates availa
- 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.
8. After the transaction commits successfully, 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.
@@ -45,8 +45,8 @@ The reservation is not confirmed at this stage.
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.
9. The backend creates a separate `check_in` token for QR verification.
10. The backend generates a QR code containing only the opaque check-in token or a verification URL built from that token.
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.
@@ -111,7 +111,7 @@ This operational flow should still follow the same backend rules as the public b
2. the backend validates booking availability and capacity;
3. the backend creates a `pending` reservation;
4. the backend creates the normal confirmation token;
5. the backend sends the standard confirmation email;
5. after the reservation transaction commits, the backend sends the standard confirmation email;
6. the guest still confirms through the email link before the reservation becomes confirmed and usable for check-in.
## Duplicate Check-In

View File

@@ -36,6 +36,9 @@ Rules:
- do not encode personal data in tokens;
- store token hashes when practical;
- treat raw tokens as secrets;
- keep confirmation tokens and check-in tokens separate by purpose;
- allow confirmation tokens only for reservation confirmation;
- allow check-in tokens only for QR retrieval and check-in validation;
- 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.
@@ -165,7 +168,7 @@ Do not log:
Initial residual risks:
- synchronous email can make booking responses depend on SMTP availability;
- synchronous email after commit can still add latency to booking requests even though it no longer runs inside the reservation transaction;
- 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.