feat: move to V2
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-10-29 11:21:52 +01:00
parent 6342b8451e
commit 314717ba3a
244 changed files with 11615 additions and 15292 deletions
+28 -50
View File
@@ -1,60 +1,38 @@
# Security
## Authentication
## Dashboard sessions
### Device Tokens
- Format: `dev_` + 48 random bytes, base64url-encoded
- Stored as SHA-256 hash in database
- Raw token returned only once during registration
- Expire after 90 days
- Revocation immediately invalidates token
- Passwords hashed with bcrypt (cost 12), at least 8 characters; emails stored lowercased.
- Sign-in sets two httpOnly, SameSite=Lax cookies (Secure in production): a JWT access token for 15 minutes, and a
refresh token for 30 days, sent only to `/api/v1/auth`. No script can read either.
- A refresh token works once: refreshing revokes it and issues a new pair. Signing out revokes it.
- Every request looks the user up: a deleted user, a changed role or a forced sign-out (`sessionsRevokedAt`) applies at
once instead of when the access token expires.
- Sign-in answers the same, in the same time, for an unknown email and a wrong password.
### Client Authentication
- Passwords hashed with bcrypt (10 rounds)
- JWT access tokens (15 minute expiration)
- Refresh tokens (30 days) with rotation and revocation; a refresh token works only once
- The dashboard keeps both tokens in `SameSite=Lax` cookies (`Secure` over HTTPS). They are readable by
the dashboard's JavaScript, so an XSS bug would expose them; the short access token lifetime limits the damage
## Terminals
## Authorization
- Paired with a single-use registration code that expires after 10 minutes.
- Token: `kt_` + 48 random bytes; only its SHA-256 hash is stored, and it is shown once. It expires after 90 days.
- Unpairing on the dashboard stops the token at once.
### Role-Based Access
- `admin`: Full access, including users and pairing or unpairing terminals
- `manager` (the people running the venue): products and their pictures, stock, tabs, planned customers and
reminders. Accounts and tokens with the former role `viewer` are treated as managers
- `superadmin` (KoolTap itself): everything an admin may do, in every venue, plus the KoolTap Admin page (`/admin`
endpoints). Only a superadmin can make someone a superadmin; the first one is made with
`pnpm --filter @kooltap/api run superadmin <email>`. A superadmin viewing a venue is recorded in that venue's audit log,
and every admin action is audited
- Signing a user out from the admin page sets `sessionsRevokedAt`: access tokens issued before it are refused at once
- Product pictures are public by id (`GET /images/:imageId`); the id is a SHA-256 hash, so it can't be guessed
## Roles
### Venue Isolation
- Every user and device is scoped to exactly one venue; only a superadmin can reach other venues
- All service methods validate venue context from authentication
- Cross-venue access is blocked at the service layer
- `manager` runs the venue: tabs, customers, reminders, products and stock.
- `admin` also manages the venue's users and terminals.
- `superadmin` runs KoolTap and belongs to no venue: the admin page (`/admin` endpoints), and any venue they view, where
they act as its admin. The database enforces that superadmins, and only they, have no venue. Only a superadmin can make
someone a superadmin; the first one is made with `node dist/cli/superadmin.js <email> --create "<name>" <password>`
(or by promoting a user). Viewing a venue is recorded in that venue's audit log, and every admin action is audited.
## API Security
Every venue route checks that the user belongs to the venue in the URL (or is a superadmin). Product pictures are
public by id, a SHA-256 hash that can't be guessed.
### Headers
- Helmet for security headers
- Strict CORS (dashboard origin only)
- Body size limits enforced
## API
### Rate Limiting
- Login and device registration: 10 requests per minute per client
- General: 100 requests per minute per client
- Device sync and command acks are not rate limited (they are authenticated by the device token)
### Error Handling
- Consistent error format never exposes internals
- No secrets in error messages
- No stack traces in production
## Audit Trail
All sensitive operations are logged:
- Command creation, acknowledgement, expiration
- Device registration and revocation
- Snapshot conflicts
- No sensitive data (passwords, tokens) in audit logs
- Every body and query is validated with a zod schema; unknown fields are dropped.
- Helmet headers; CORS only for the dashboard's origin.
- Rate limits: 10 sign-ins or pairings per minute per client, 100 requests per minute otherwise. Terminal syncs are
not limited (they are authenticated by the device token).
- Errors are `{ error: { code, message } }`; internal errors are logged and never shown.
- The realtime socket authenticates with the session cookie and only carries topic names, never data.