WIP: Store per-device user agent for remoteStorage authorizations #257

Draft
raucao wants to merge 2 commits from feature/rs_device_tokens into feature/rs_auth_pkce
pull from: feature/rs_device_tokens
10 changed files with 36 additions and 7 deletions

No files matched your search

+3 -1
View File
@@ -104,6 +104,7 @@ class Rs::OauthController < ApplicationController
redirect_uri: redirect_uri,
permissions: permissions,
expire_at: expire_at,
user_agent: request.user_agent,
code_challenge: challenge,
code_challenge_method: method
)
@@ -116,7 +117,8 @@ class Rs::OauthController < ApplicationController
client_id: client_id,
redirect_uri: redirect_uri,
app_name: client_id,
expire_at: expire_at
expire_at: expire_at,
user_agent: request.user_agent
)
redirect_to url_with_state("#{redirect_uri}#access_token=#{auth.token}", state),
@@ -29,6 +29,8 @@ class Services::RsAuthsController < Services::BaseController
current_user.address
end
# Storage-first bearer token issuance (remoteStorage spec §11): the app
# receives the address and runs the application-first flow itself.
launch_url = "#{@auth.launch_url}#remotestorage=#{user_address}"
redirect_to launch_url, allow_other_host: true
@@ -25,7 +25,8 @@ module RsOauthManager
client_id: auth_code.client_id,
redirect_uri: auth_code.redirect_uri,
app_name: auth_code.client_id,
expire_at: auth_code.permission_expire_at
expire_at: auth_code.permission_expire_at,
user_agent: auth_code.user_agent
)
auth_code.consume!
+3 -1
View File
@@ -2,7 +2,7 @@ module RsOauthManager
class IssueCode < RsOauthManagerService
def initialize(user:, client_id:, redirect_uri:, permissions:,
code_challenge:, code_challenge_method: "S256",
expire_at: nil)
expire_at: nil, user_agent: nil)
@user = user
@client_id = client_id
@redirect_uri = redirect_uri
@@ -10,6 +10,7 @@ module RsOauthManager
@code_challenge = code_challenge
@code_challenge_method = code_challenge_method
@expire_at = expire_at
@user_agent = user_agent
end
def call
@@ -19,6 +20,7 @@ module RsOauthManager
redirect_uri: @redirect_uri,
permissions: @permissions,
permission_expire_at: @expire_at,
user_agent: @user_agent,
code_challenge: @code_challenge,
code_challenge_method: @code_challenge_method
)
@@ -0,0 +1,6 @@
class AddUserAgentToRsAuthorizations < ActiveRecord::Migration[8.1]
def change
add_column :remote_storage_authorizations, :user_agent, :string
add_column :rs_authorization_codes, :user_agent, :string
end
end
+3 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_11_02_120000) do
ActiveRecord::Schema[8.1].define(version: 2026_11_02_130000) do
create_table "active_storage_attachments", force: :cascade do |t|
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
@@ -110,6 +110,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_11_02_120000) do
t.string "redirect_uri"
t.string "token"
t.datetime "updated_at", null: false
t.string "user_agent"
t.integer "user_id", null: false
t.integer "web_app_id"
t.index ["permissions"], name: "index_remote_storage_authorizations_on_permissions"
@@ -128,6 +129,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_11_02_120000) do
t.text "permissions", default: "--- []\n"
t.string "redirect_uri", null: false
t.datetime "updated_at", null: false
t.string "user_agent"
t.integer "user_id", null: false
t.index ["code_digest"], name: "index_rs_authorization_codes_on_code_digest", unique: true
t.index ["user_id"], name: "index_rs_authorization_codes_on_user_id"
@@ -310,6 +310,7 @@ RSpec.describe Rs::OauthController, type: :controller do
context "when user is signed in" do
before do
sign_in user
request.user_agent = "RSpec Device"
end
after do
@@ -334,6 +335,10 @@ RSpec.describe Rs::OauthController, type: :controller do
expect(@auth.permissions).to eq(%w(documents photos contacts:rw videos:r tasks/work:r))
end
it "stores the device's user agent" do
expect(@auth.user_agent).to eq("RSpec Device")
end
it "redirects to the redirect_uri" do
expect(response).to redirect_to("https://example.com#access_token=#{@auth.token}&state=foobar123")
end
@@ -463,6 +468,7 @@ RSpec.describe Rs::OauthController, type: :controller do
expect(@code.permissions).to eq(%w(documents photos contacts:rw))
expect(@code.code_challenge).to eq("challenge123")
expect(@code.code_challenge_method).to eq("S256")
expect(@code.user_agent).to eq("RSpec Device")
end
it "does not create a remote storage authorization" do
@@ -11,7 +11,7 @@ RSpec.describe Services::RsAuthsController, type: :controller do
])
end
describe "GET /services/storage/rs_auths/:id/launch_app" do
describe "GET /services/storage/rs_auths/:id/launch_app (storage-first)" do
context "when user is signed in" do
before do
sign_in user
@@ -32,10 +32,14 @@ RSpec.describe Services::RsAuthsController, type: :controller do
@auth.destroy
end
it "redirects to the given URL with the correct RS URL fragment params" do
it "hands the app the user address in the URL fragment" do
launch_url = "https://app.example.com#remotestorage=#{user.address}"
expect(response).to redirect_to(launch_url)
end
it "does not hand over an access token" do
expect(response.location).not_to include("access_token")
end
end
context "when remoteStorage is not enabled for the user" do
@@ -20,6 +20,7 @@ RSpec.describe RsOauthManager::ExchangeCode do
permissions: %w(documents:rw),
code_challenge: code_challenge,
code_challenge_method: "S256",
user_agent: "RSpec Device",
**overrides
)
end
@@ -43,6 +44,7 @@ RSpec.describe RsOauthManager::ExchangeCode do
expect(auth.permissions).to eq(%w(documents:rw))
expect(auth.client_id).to eq("example.com")
expect(auth.token).to be_present
expect(auth.user_agent).to eq("RSpec Device")
expect(RsAuthorizationCode.exists?(code.id)).to be(false)
end
@@ -11,7 +11,8 @@ RSpec.describe RsOauthManager::IssueCode do
permissions: %w(documents:rw),
code_challenge: "challenge",
code_challenge_method: "S256",
expire_at: 1.month.from_now
expire_at: 1.month.from_now,
user_agent: "RSpec Device"
)
end
@@ -21,6 +22,7 @@ RSpec.describe RsOauthManager::IssueCode do
expect(subject.client_id).to eq("example.com")
expect(subject.permissions).to eq(%w(documents:rw))
expect(subject.code_challenge).to eq("challenge")
expect(subject.user_agent).to eq("RSpec Device")
end
it "does not create a remote storage authorization" do