Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.saas
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion Gemfile.saas.lock
Original file line number Diff line number Diff line change
@@ -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
Expand Down
17 changes: 17 additions & 0 deletions saas/app/controllers/admin/audits_controller.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 0 additions & 6 deletions saas/app/controllers/saas_admin_controller.rb

This file was deleted.

14 changes: 14 additions & 0 deletions saas/db/migrate/20260126230838_create_auditor_tokens.audits1984.rb
Original file line number Diff line number Diff line change
@@ -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
12 changes: 11 additions & 1 deletion saas/db/saas_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion saas/lib/fizzy/saas/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ 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.base_controller_class = "::Admin::AuditsController"
config.audits1984.auditor_class = "::Identity"
config.audits1984.auditor_name_attribute = :email_address

Expand Down
75 changes: 75 additions & 0 deletions saas/test/controllers/admin/audits_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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

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