mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-29 17:04:04 +09:00
Remove push subscriptions on a password change, and post them again
A push subscription belongs to the user, not a session, so a device
signed out by a password change kept getting notifications. Changing
the password now removes every subscription along with the sessions,
in the same transaction.
A browser still signed in keeps its own subscription in its service
worker, so the notifications controller posts it once per session: the
bell names the session by a digest of its id, never its token, and the
browser remembers the last one it posted for. That post never
unsubscribes the browser when the server refuses it.
A request that authenticated just before the change could put a
subscription back after it. The create action now checks the session
still exists and saves in one immediate transaction, after validating
the endpoint outside it.
From #371.
(cherry picked from commit 6cab924097)
This commit is contained in:
1 parent
417f087964
commit
93718f7408
9 files changed
+201
-16
No files matched your search
@@ -0,0 +1,71 @@
|
||||
require "application_system_test_case"
|
||||
|
||||
class PushSubscriptionResyncTest < ApplicationSystemTestCase
|
||||
ENDPOINT = "https://fcm.googleapis.com/fcm/send/system-test"
|
||||
|
||||
setup do
|
||||
stub_web_push_dns_resolution
|
||||
fake_a_browser_push_subscription
|
||||
end
|
||||
|
||||
teardown do
|
||||
page.execute_script("localStorage.clear()") if page.current_url.start_with?("http")
|
||||
end
|
||||
|
||||
test "a browser posts its subscription again once per session, so it survives a password change" do
|
||||
sign_in "david@37signals.com"
|
||||
assert_subscription_registered
|
||||
first_sync = subscription.updated_at
|
||||
|
||||
join_room rooms(:hq)
|
||||
assert_equal first_sync, subscription.reload.updated_at, "posted again in the same session"
|
||||
|
||||
change_password
|
||||
assert_nil Push::Subscription.find_by(endpoint: ENDPOINT)
|
||||
|
||||
join_room rooms(:hq)
|
||||
assert_subscription_registered
|
||||
assert_not page.evaluate_script("localStorage.getItem('unsubscribed-for-test')")
|
||||
end
|
||||
|
||||
test "a subscription the server refuses stays in the browser" do
|
||||
stub_dns_resolution("169.254.169.254")
|
||||
|
||||
sign_in "david@37signals.com"
|
||||
Timeout.timeout(5) { sleep 0.1 until page.evaluate_script("localStorage.getItem('push-subscription-synced-session')") }
|
||||
join_room rooms(:hq)
|
||||
|
||||
assert_not Push::Subscription.exists?(endpoint: ENDPOINT)
|
||||
assert_not page.evaluate_script("localStorage.getItem('unsubscribed-for-test')")
|
||||
end
|
||||
|
||||
private
|
||||
# Headless Chrome has no push service, so stand in for the subscription the service worker would hold.
|
||||
def fake_a_browser_push_subscription
|
||||
page.driver.browser.execute_cdp "Page.addScriptToEvaluateOnNewDocument", source: <<~JS
|
||||
Object.defineProperty(Notification, "permission", { get: () => "granted" })
|
||||
const subscription = {
|
||||
toJSON: () => ({ endpoint: "#{ENDPOINT}", keys: { p256dh: "p256dh-key", auth: "auth-key" } }),
|
||||
unsubscribe: async () => { localStorage.setItem("unsubscribed-for-test", "true") }
|
||||
}
|
||||
navigator.serviceWorker.getRegistration = async () => ({ pushManager: { getSubscription: async () => subscription } })
|
||||
JS
|
||||
end
|
||||
|
||||
def change_password
|
||||
visit user_profile_url
|
||||
find("input[name='user[password_challenge]']").fill_in with: "secret123456"
|
||||
find("input[name='user[password]']").fill_in with: "a new secret123456"
|
||||
find("input[name='user[password]']").send_keys :enter
|
||||
assert_selector ".flash", text: "✓"
|
||||
end
|
||||
|
||||
def assert_subscription_registered
|
||||
assert_selector "[data-controller~=notifications]", visible: :all
|
||||
Timeout.timeout(5) { sleep 0.1 until Push::Subscription.exists?(endpoint: ENDPOINT, user: users(:david)) }
|
||||
end
|
||||
|
||||
def subscription
|
||||
Push::Subscription.find_by!(endpoint: ENDPOINT)
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user