From dbcf54de9190520ac8b5e9195c8f4ec2eefa84a9 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Wed, 28 Jan 2026 11:59:46 -0500 Subject: [PATCH 1/4] saas: Set lifetime of console1984 sessions to 60 days --- saas/lib/fizzy/saas/engine.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/saas/lib/fizzy/saas/engine.rb b/saas/lib/fizzy/saas/engine.rb index 40b5b49d0..fc4ff891e 100644 --- a/saas/lib/fizzy/saas/engine.rb +++ b/saas/lib/fizzy/saas/engine.rb @@ -124,6 +124,7 @@ class Engine < ::Rails::Engine config.console1984.protected_environments = %i[ production beta staging ] config.console1984.ask_for_username_if_empty = true config.console1984.base_record_class = "::SaasRecord" + config.console1984.incinerate_after = 60.days config.audits1984.base_controller_class = "::SaasAdminController" config.audits1984.auditor_class = "::Identity" From 3b13c8573e554376917bc7005151d09af8b6161d Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 27 Jan 2026 10:50:09 -0500 Subject: [PATCH 2/4] saas: Backfill tests for admin audit endpoints --- .../admin/audits_controller_test.rb | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 saas/test/controllers/admin/audits_controller_test.rb diff --git a/saas/test/controllers/admin/audits_controller_test.rb b/saas/test/controllers/admin/audits_controller_test.rb new file mode 100644 index 000000000..4fdbe638e --- /dev/null +++ b/saas/test/controllers/admin/audits_controller_test.rb @@ -0,0 +1,41 @@ +require "test_helper" + +class Admin::AuditsControllerTest < ActionDispatch::IntegrationTest + # Test authentication via the Audits1984::SessionsController#index endpoint, + # which inherits from Admin::AuditsController through Audits1984::ApplicationController. + + test "unauthenticated access is forbidden" do + untenanted do + get saas.admin_audits1984_path + assert_redirected_to new_session_path + end + end + + test "logged-in non-staff access is forbidden" do + sign_in_as :jz + + untenanted do + get saas.admin_audits1984_path + end + + assert_response :forbidden + end + + test "logged-in staff access is allowed" do + sign_in_as :david + + untenanted do + get saas.admin_audits1984_path + end + + assert_response :success + end + + test "invalid bearer token is forbidden" do + untenanted do + get saas.admin_audits1984_path, headers: { "Authorization" => "Bearer invalid_token" } + end + + assert_response :unauthorized + end +end From 4e61b64d9a74262f25fe7c65168021a2ad7a53bd Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 27 Jan 2026 12:38:15 -0500 Subject: [PATCH 3/4] Update to feature branch of audits1984 --- Gemfile.saas | 2 +- Gemfile.saas.lock | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Gemfile.saas b/Gemfile.saas index 05e6bbabd..e022ab6b2 100644 --- a/Gemfile.saas +++ b/Gemfile.saas @@ -8,7 +8,7 @@ gem "stripe", "~> 18.0" gem "queenbee", bc: "queenbee-plugin" gem "fizzy-saas", path: "saas" gem "console1984", bc: "console1984" -gem "audits1984", bc: "audits1984" +gem "audits1984", bc: "audits1984", branch: "flavorjones/coworker-api" # Telemetry gem "rails_structured_logging", bc: "rails-structured-logging" diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 6dcb7b9ee..1c45027ec 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -1,10 +1,12 @@ GIT remote: https://github.com/basecamp/audits1984 - revision: c790eaec0716c8df56b3d0ab5bf5d75e3e1b0ed0 + revision: 422b8ff25820c828b7cf09aff4923fd8cc2c6795 + branch: flavorjones/coworker-api specs: audits1984 (0.1.7) console1984 importmap-rails (>= 1.2.1) + jbuilder rinku rouge turbo-rails From 89d2f2d0fcc304888cc3b92bd9ee037d9348f5ae Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 27 Jan 2026 12:37:47 -0500 Subject: [PATCH 4/4] saas: Add bearer token authentication for audit console - Rename SaasAdminController to Admin::AuditController - Override require_authentication to support bearer tokens alongside session auth, allowing API access to audit console - Add migration for audits1984_auditor_tokens table - Add controller tests for authentication scenarios including staff validation on token-based access Co-Authored-By: Claude Opus 4.5 --- .../controllers/admin/audits_controller.rb | 17 ++++++++++ saas/app/controllers/saas_admin_controller.rb | 6 ---- ...230838_create_auditor_tokens.audits1984.rb | 14 ++++++++ saas/db/saas_schema.rb | 12 ++++++- saas/lib/fizzy/saas/engine.rb | 2 +- .../admin/audits_controller_test.rb | 34 +++++++++++++++++++ 6 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 saas/app/controllers/admin/audits_controller.rb delete mode 100644 saas/app/controllers/saas_admin_controller.rb create mode 100644 saas/db/migrate/20260126230838_create_auditor_tokens.audits1984.rb diff --git a/saas/app/controllers/admin/audits_controller.rb b/saas/app/controllers/admin/audits_controller.rb new file mode 100644 index 000000000..9f9f13fb7 --- /dev/null +++ b/saas/app/controllers/admin/audits_controller.rb @@ -0,0 +1,17 @@ +class Admin::AuditsController < ::AdminController + private + # Extend Fizzy's authentication to support auditor bearer tokens. + def require_authentication + authenticate_by_audit_bearer_token || super + end + + def authenticate_by_audit_bearer_token + if auditor = auditor_from_bearer_token + Current.identity = auditor + end + end + + def find_current_auditor + Current.identity + end +end diff --git a/saas/app/controllers/saas_admin_controller.rb b/saas/app/controllers/saas_admin_controller.rb deleted file mode 100644 index f68ca492e..000000000 --- a/saas/app/controllers/saas_admin_controller.rb +++ /dev/null @@ -1,6 +0,0 @@ -class SaasAdminController < ::AdminController - private - def find_current_auditor - Current.identity - end -end diff --git a/saas/db/migrate/20260126230838_create_auditor_tokens.audits1984.rb b/saas/db/migrate/20260126230838_create_auditor_tokens.audits1984.rb new file mode 100644 index 000000000..3747a6863 --- /dev/null +++ b/saas/db/migrate/20260126230838_create_auditor_tokens.audits1984.rb @@ -0,0 +1,14 @@ +# This migration comes from audits1984 (originally 20260126000000) +class CreateAuditorTokens < ActiveRecord::Migration[7.0] + def change + create_table :audits1984_auditor_tokens do |t| + t.uuid :auditor_id, null: false, index: { unique: true } + t.string :token_digest, null: false + t.datetime :expires_at, null: false + + t.timestamps + + t.index :token_digest, unique: true + end + end +end diff --git a/saas/db/saas_schema.rb b/saas/db/saas_schema.rb index 7e9b80b92..980e952ac 100644 --- a/saas/db/saas_schema.rb +++ b/saas/db/saas_schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.2].define(version: 2025_12_16_000000) do +ActiveRecord::Schema[8.2].define(version: 2026_01_26_230838) do create_table "account_billing_waivers", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.uuid "account_id", null: false t.datetime "created_at", null: false @@ -43,6 +43,16 @@ t.index ["stripe_subscription_id"], name: "index_account_subscriptions_on_stripe_subscription_id", unique: true end + create_table "audits1984_auditor_tokens", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.uuid "auditor_id", null: false + t.datetime "created_at", null: false + t.datetime "expires_at", null: false + t.string "token_digest", null: false + t.datetime "updated_at", null: false + t.index ["auditor_id"], name: "index_audits1984_auditor_tokens_on_auditor_id", unique: true + t.index ["token_digest"], name: "index_audits1984_auditor_tokens_on_token_digest", unique: true + end + create_table "audits1984_audits", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.uuid "auditor_id", null: false t.datetime "created_at", null: false diff --git a/saas/lib/fizzy/saas/engine.rb b/saas/lib/fizzy/saas/engine.rb index fc4ff891e..47b0480e1 100644 --- a/saas/lib/fizzy/saas/engine.rb +++ b/saas/lib/fizzy/saas/engine.rb @@ -126,7 +126,7 @@ class Engine < ::Rails::Engine config.console1984.base_record_class = "::SaasRecord" config.console1984.incinerate_after = 60.days - config.audits1984.base_controller_class = "::SaasAdminController" + config.audits1984.base_controller_class = "::Admin::AuditsController" config.audits1984.auditor_class = "::Identity" config.audits1984.auditor_name_attribute = :email_address diff --git a/saas/test/controllers/admin/audits_controller_test.rb b/saas/test/controllers/admin/audits_controller_test.rb index 4fdbe638e..6aa94ef7a 100644 --- a/saas/test/controllers/admin/audits_controller_test.rb +++ b/saas/test/controllers/admin/audits_controller_test.rb @@ -38,4 +38,38 @@ class Admin::AuditsControllerTest < ActionDispatch::IntegrationTest assert_response :unauthorized end + + test "valid bearer token is allowed" do + token = Audits1984::AuditorToken.generate_for(identities(:david)) + + untenanted do + get saas.admin_audits1984_path, headers: { "Authorization" => "Bearer #{token}" } + end + + assert_response :success + end + + test "expired bearer token is forbidden" do + token = Audits1984::AuditorToken.generate_for(identities(:david)) + Audits1984::AuditorToken.update_all(expires_at: 1.day.ago) + + untenanted do + get saas.admin_audits1984_path, headers: { "Authorization" => "Bearer #{token}" } + end + + assert_response :unauthorized + end + + test "bearer token for non-staff user is forbidden" do + # Even with a valid token, non-staff users should be denied access. + # This handles the case where a user's staff privileges are revoked + # after a token was issued. + token = Audits1984::AuditorToken.generate_for(identities(:jz)) + + untenanted do + get saas.admin_audits1984_path, headers: { "Authorization" => "Bearer #{token}" } + end + + assert_response :forbidden + end end