Give each user independent read, write and delete flags per area
(invoices, bingo, users), so someone can e.g. review and remove bingo
incidents without being able to add any. The request method and path
decide the flag needed: GET is read except the new/duplicate forms, POST
to .../delete is delete, any other POST is write. Unmapped admin paths
are superuser-only, and a test enumerates every registered admin route
so a new one cannot ship without a rule.
Pages follow the same flags: nav and dashboard tiles show readable areas
only, write forms and delete buttons are hidden, and the datacenter form
is shown read-only.
Assisted-by: Claude:opus-5
The admin was one shared HTTP Basic login: no way to give someone
partial access, revoke one person, or tell who changed what. Add a users
table and a session login form. Passwords are scrypt hashes from Ruby's
OpenSSL, so no native gem is needed. Sessions carry a per-user version
that is bumped on password change, which logs that user out everywhere.
ADMIN_USER/ADMIN_PASSWORD now bootstrap the first superuser on an empty
table and afterwards only work while no active superuser exists, as a
recovery path that re-creates one. Until per-area permissions land,
non-superusers only reach the dashboard and their own password page.
Assisted-by: Claude:opus-5
start_with?('/admin') also matched /admin-invoice-form.js, returning 401
for the public JS asset that the New Invoice form depends on. Without it,
the "+ add line" button did nothing.
Require either the bare /admin path or a /admin/-prefixed one so sibling
assets in public/ that happen to share the prefix stay reachable.
S3 module wraps aws-sdk-s3 against MinIO. Two clients on purpose:
- The bytes-mover client points at S3_ENDPOINT (internal minio:9000 over
the storage Docker network).
- The presign-only client points at S3_PUBLIC_ENDPOINT (https://s3.asxp.io
via Traefik) so the URLs it mints resolve from a browser.
force_path_style on both because subdomain-style URLs would require wildcard
DNS under s3.asxp.io.
AdminAuth is a tiny Rack middleware that 401s any request under /admin
unless HTTP Basic credentials match ADMIN_USER / ADMIN_PASSWORD from env.
Fails closed if either env var is missing.