2 Commits

Author SHA1 Message Date
Dan Milne
9663110938 Bump version to 0.10.2
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / scan_container (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled
2026-05-26 18:32:25 +10:00
Dan Milne
0bca1d2bac Allow OAuth redirect_uri host in form-action CSP on sign-in pages
Safari enforces form-action against every hop in a form submission's
redirect chain. When a user signed in (with TOTP, or through a
skip_consent OIDC app), the chain /signin or /totp-verification ->
/oauth/authorize -> external client got blocked at the cross-origin
hop because form-action was 'self'. The existing dynamic CSP widening
in OidcController#authorize only ran when the consent page rendered,
so skip_consent and pre-consented flows had no widening at all.

Add allow_oauth_redirect_in_csp on the sign-in and TOTP pages, which
pulls the OAuth redirect_uri out of session[:return_to_after_authenticating]
and appends its host to form-action for the rendered page.
2026-05-23 11:03:32 +10:00
3 changed files with 31 additions and 1 deletions

View File

@@ -43,6 +43,32 @@ module Authentication
session.delete(:return_to_after_authenticating) || root_url
end
# When a sign-in form will eventually redirect through /oauth/authorize to an
# external client, Safari enforces CSP form-action against every hop in the
# redirect chain. With the default form-action 'self', the final cross-origin
# hop to the OAuth client's redirect_uri gets blocked. Add the redirect_uri
# host to form-action so the chain completes.
def allow_oauth_redirect_in_csp
stored = session[:return_to_after_authenticating]
return if stored.blank?
uri = URI.parse(stored)
return unless uri.path&.start_with?("/oauth/")
redirect_uri = Rack::Utils.parse_query(uri.query.to_s)["redirect_uri"]
return if redirect_uri.blank?
redirect_host = URI.parse(redirect_uri).host
return if redirect_host.blank?
csp = request.content_security_policy
return unless csp&.respond_to?(:form_action) && csp.form_action.respond_to?(:<<)
csp.form_action << "https://#{redirect_host}"
rescue URI::InvalidURIError
nil
end
def start_new_session_for(user, acr: "1", remember_me: false)
user.update!(last_sign_in_at: Time.current)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip, acr: acr, remember_me: remember_me).tap do |session|

View File

@@ -28,6 +28,8 @@ class SessionsController < ApplicationController
end
end
allow_oauth_redirect_in_csp
respond_to do |format|
format.html # render HTML login page
format.json { render json: {error: "Authentication required"}, status: :unauthorized }
@@ -154,6 +156,8 @@ class SessionsController < ApplicationController
@user_has_webauthn = user&.can_authenticate_with_webauthn?
@pending_email = user&.email_address
allow_oauth_redirect_in_csp
# Just render the form
end

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Clinch
VERSION = "0.10.1"
VERSION = "0.10.2"
end