admin: user accounts and session login
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
This commit is contained in:
parent
c2020bf903
commit
9bf40690e5
13 changed files with 436 additions and 59 deletions
|
|
@ -10,6 +10,8 @@ ENV['MAIL_FROM'] ||= 'Test <test@example.com>'
|
|||
ENV['MAIL_TO'] ||= 'owner@example.com'
|
||||
ENV['ADMIN_USER'] ||= 'admin'
|
||||
ENV['ADMIN_PASSWORD'] ||= 'test-password'
|
||||
ENV['SCRYPT_LOG_N'] ||= '10' # cheap hashes in tests
|
||||
ENV['LOGIN_RATE_LIMIT'] ||= '10000'
|
||||
|
||||
# DB-backed tests run only against an explicit test database (bin/test
|
||||
# provisions an ephemeral one). Never fall through to a developer DATABASE_URL.
|
||||
|
|
@ -33,6 +35,26 @@ require 'rack/test'
|
|||
|
||||
Mail.defaults { delivery_method :test }
|
||||
|
||||
# Logs the rack-test session in through the real form (CSRF included).
|
||||
# `admin_env` keeps the old call sites working: it logs in once per test
|
||||
# and returns extra env to merge.
|
||||
module AdminLogin
|
||||
def admin_login!(username: ENV['ADMIN_USER'], password: ENV['ADMIN_PASSWORD'])
|
||||
User[username: username] ||
|
||||
User.create(username: username, superuser: true, password: password)
|
||||
get '/admin/login'
|
||||
token = last_response.body[/name="authenticity_token" value="([^"]+)"/, 1]
|
||||
post '/admin/login', { authenticity_token: token, username: username, password: password }
|
||||
raise "login failed: #{last_response.status}" unless last_response.redirect?
|
||||
@admin_logged_in = true
|
||||
end
|
||||
|
||||
def admin_env(extra = {})
|
||||
admin_login! unless @admin_logged_in
|
||||
extra
|
||||
end
|
||||
end
|
||||
|
||||
module TestDb
|
||||
def self.available?
|
||||
!ENV['DATABASE_URL'].nil?
|
||||
|
|
@ -41,4 +63,8 @@ module TestDb
|
|||
def self.clean!
|
||||
DB.connection[:invoices].delete
|
||||
end
|
||||
|
||||
def self.clean_users!
|
||||
DB.connection[:users].delete
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue