Nullify token to auth-code FK on delete so cleanup job can purge codes

The FK added in b7fa499 defaulted to ON DELETE RESTRICT, which means
OidcTokenCleanupJob#perform would fail when deleting auth codes older
than 7 days if any refresh token (whose expiry is days-to-weeks) still
referenced them. Switch both token FKs to ON DELETE SET NULL so token
rows survive the code deletion with a NULL FK, preserving the audit
trail the cleanup job deliberately keeps.

Add a regression test covering the exact scenario: a 10-day-old auth
code with a token still pointing at it -> cleanup deletes the code,
token survives, token FK is nulled.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Dan Milne
2026-04-20 18:12:03 +10:00
parent 2068675173
commit 93d8381214
3 changed files with 70 additions and 6 deletions

View File

@@ -1,7 +1,51 @@
require "test_helper"
class OidcTokenCleanupJobTest < ActiveJob::TestCase
# test "the truth" do
# assert true
# end
include ActiveSupport::Testing::TimeHelpers
# Regression: deleting an old authorization code while a descendant token
# still references it must not blow up on the FK. We rely on ON DELETE
# SET NULL so the token row survives (audit trail) with a NULL FK.
test "deletes old authorization codes whose descendant tokens still reference them" do
user = User.create!(email_address: "cleanup_test@example.com", password: "password123")
application = Application.create!(
name: "Cleanup Test App",
slug: "cleanup-test-app",
app_type: "oidc",
redirect_uris: ["http://localhost/cb"].to_json,
active: true
)
auth_code = nil
travel_to(10.days.ago) do
auth_code = OidcAuthorizationCode.create!(
application: application,
user: user,
redirect_uri: "http://localhost/cb",
scope: "openid"
)
end
token = OidcAccessToken.create!(
application: application,
user: user,
scope: "openid",
oidc_authorization_code: auth_code
)
OidcTokenCleanupJob.new.perform
assert_not OidcAuthorizationCode.exists?(auth_code.id),
"old authorization code should be deleted"
assert OidcAccessToken.exists?(token.id),
"token row should survive for audit trail"
assert_nil token.reload.oidc_authorization_code_id,
"token FK should be nullified by ON DELETE SET NULL"
ensure
OidcRefreshToken.where(application: application).delete_all if application
OidcAccessToken.where(application: application).delete_all if application
OidcAuthorizationCode.where(application: application).delete_all if application
user&.destroy
application&.destroy
end
end