+28
-50
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user