Link devices to sessions for automatic cleanup on logout

When a session is destroyed (user logs out), all devices registered
to that session are automatically destroyed, preventing push
notifications from being sent to logged-out devices.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Rosa Gutierrez
2026-01-21 20:02:14 +01:00
parent b5205ce6b2
commit ab6bc256eb
7 changed files with 63 additions and 4 deletions
@@ -0,0 +1,5 @@
class AddSessionToActionPushNativeDevices < ActiveRecord::Migration[8.2]
def change
add_reference :action_push_native_devices, :session, foreign_key: true, type: :uuid
end
end
Generated
+4
View File
@@ -75,10 +75,12 @@ ActiveRecord::Schema[8.2].define(version: 2026_02_18_120000) do
t.uuid "owner_id"
t.string "owner_type"
t.string "platform", null: false
t.uuid "session_id"
t.string "token", null: false
t.datetime "updated_at", null: false
t.index ["owner_type", "owner_id", "token"], name: "idx_on_owner_type_owner_id_token_95a4008c64", unique: true
t.index ["owner_type", "owner_id"], name: "index_action_push_native_devices_on_owner"
t.index ["session_id"], name: "index_action_push_native_devices_on_session_id"
end
create_table "action_text_rich_texts", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
@@ -852,4 +854,6 @@ ActiveRecord::Schema[8.2].define(version: 2026_02_18_120000) do
t.index ["account_id"], name: "index_webhooks_on_account_id"
t.index ["board_id", "subscribed_actions"], name: "index_webhooks_on_board_id_and_subscribed_actions", length: { subscribed_actions: 255 }
end
add_foreign_key "action_push_native_devices", "sessions"
end
+1 -1
View File
@@ -6,7 +6,7 @@ class DevicesController < ApplicationController
end
def create
ApplicationPushDevice.register(owner: Current.identity, **device_params)
ApplicationPushDevice.register(session: Current.session, **device_params)
head :created
end
+5 -3
View File
@@ -1,7 +1,9 @@
class ApplicationPushDevice < ActionPushNative::Device
def self.register(owner:, token:, platform:, name: nil)
owner.devices.find_or_initialize_by(token: token).tap do |device|
device.update!(platform: platform.downcase, name: name)
belongs_to :session, optional: true
def self.register(session:, token:, platform:, name: nil)
session.identity.devices.find_or_initialize_by(token: token).tap do |device|
device.update!(session: session, platform: platform.downcase, name: name)
end
end
end
+7
View File
@@ -0,0 +1,7 @@
module Session::Devices
extend ActiveSupport::Concern
included do
has_many :devices, class_name: "ApplicationPushDevice", dependent: :destroy
end
end
+1
View File
@@ -155,6 +155,7 @@ module Fizzy
::Account.include Account::Billing, Account::Limited
::User.include User::NotifiesAccountOfEmailChange
::Identity.include Authorization::Identity, Identity::Devices
::Session.include Session::Devices
::Signup.prepend Signup
ApplicationController.include Authorization::Controller
CardsController.include(Card::LimitedCreation)
+40
View File
@@ -0,0 +1,40 @@
require "test_helper"
class Session::DevicesTest < ActiveSupport::TestCase
setup do
@session = sessions(:david)
@identity = @session.identity
end
test "destroying session destroys associated devices" do
device = ApplicationPushDevice.register(
session: @session,
token: "test_token",
platform: "apple",
name: "Test iPhone"
)
assert_difference -> { ApplicationPushDevice.count }, -1 do
@session.destroy
end
assert_nil ApplicationPushDevice.find_by(id: device.id)
end
test "destroying session does not destroy devices from other sessions" do
other_session = sessions(:kevin)
device = ApplicationPushDevice.register(
session: other_session,
token: "other_token",
platform: "apple",
name: "Other iPhone"
)
assert_no_difference -> { ApplicationPushDevice.count } do
@session.destroy
end
assert ApplicationPushDevice.exists?(device.id)
end
end