Pass the redirect url through the forms
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2025-10-24 11:36:11 +11:00
parent 9be6ef09ff
commit ad70841689
4 changed files with 42 additions and 33 deletions

View File

@@ -1,15 +1,15 @@
module Api
class ForwardAuthController < ApplicationController
# ForwardAuth endpoints don't use sessions or CSRF
# ForwardAuth endpoints need session storage for return URL
allow_unauthenticated_access
skip_before_action :verify_authenticity_token
skip_before_action :verify_request_origin
# GET /api/verify
# This endpoint is called by reverse proxies (Traefik, Caddy, nginx)
# to verify if a user is authenticated and authorized to access an application
# to verify if a user is authenticated and authorized to access a domain
def verify
# Get the application slug from query params or X-Forwarded-Host header
app_slug = params[:app] || extract_app_from_headers
# Note: app_slug parameter is no longer used - we match domains directly with ForwardAuthRule
# Get the session from cookie
session_id = extract_session_id
@@ -40,24 +40,28 @@ module Api
return render_unauthorized("User account is not active")
end
# If an application is specified, check authorization
if app_slug.present?
application = Application.find_by(slug: app_slug, app_type: "trusted_header", active: true)
# Check for forward auth rule authorization
# Get the forwarded host for domain matching
forwarded_host = request.headers["X-Forwarded-Host"] || request.headers["Host"]
unless application
Rails.logger.warn "ForwardAuth: Application not found or not configured for trusted_header: #{app_slug}"
return render_forbidden("Application not found or not configured")
if forwarded_host.present?
# Find matching forward auth rule for this domain
rule = ForwardAuthRule.active.find { |r| r.matches_domain?(forwarded_host) }
unless rule
Rails.logger.warn "ForwardAuth: No rule found for domain: #{forwarded_host}"
return render_forbidden("No authentication rule configured for this domain")
end
# Check if user is allowed to access this application
unless application.user_allowed?(user)
Rails.logger.info "ForwardAuth: User #{user.email_address} denied access to #{app_slug}"
return render_forbidden("You do not have permission to access this application")
# Check if user is allowed by this rule
unless rule.user_allowed?(user)
Rails.logger.info "ForwardAuth: User #{user.email_address} denied access to #{forwarded_host} by rule #{rule.domain_pattern}"
return render_forbidden("You do not have permission to access this domain")
end
Rails.logger.info "ForwardAuth: User #{user.email_address} granted access to #{app_slug}"
Rails.logger.info "ForwardAuth: User #{user.email_address} granted access to #{forwarded_host} by rule #{rule.domain_pattern} (policy: #{rule.policy_for_user(user)})"
else
Rails.logger.info "ForwardAuth: User #{user.email_address} authenticated (no app specified)"
Rails.logger.info "ForwardAuth: User #{user.email_address} authenticated (no domain specified)"
end
# User is authenticated and authorized
@@ -87,22 +91,8 @@ module Api
end
def extract_app_from_headers
# Try to extract application slug from forwarded headers
# This is useful when the proxy doesn't pass ?app= param
# X-Forwarded-Host might contain the hostname
host = request.headers["X-Forwarded-Host"] || request.headers["Host"]
# Try to match hostname to application
# Format: app-slug.domain.com -> app-slug
if host.present?
# Extract subdomain as potential app slug
parts = host.split(".")
if parts.length >= 2
return parts.first if parts.first != "www"
end
end
# This method is deprecated since we now use ForwardAuthRule domain matching
# Keeping it for backward compatibility but it's no longer used
nil
end

View File

@@ -16,6 +16,11 @@ class SessionsController < ApplicationController
return
end
# Store the redirect URL from forward auth if present
if params[:rd].present?
session[:return_to_after_authenticating] = params[:rd]
end
# Check if user is active
unless user.active?
redirect_to signin_path, alert: "Your account is not active. Please contact an administrator."
@@ -26,7 +31,11 @@ class SessionsController < ApplicationController
if user.totp_enabled?
# Store user ID in session temporarily for TOTP verification
session[:pending_totp_user_id] = user.id
redirect_to totp_verification_path
# Preserve the redirect URL through TOTP verification
if params[:rd].present?
session[:totp_redirect_url] = params[:rd]
end
redirect_to totp_verification_path(rd: params[:rd])
return
end
@@ -57,6 +66,10 @@ class SessionsController < ApplicationController
# Try TOTP verification first
if user.verify_totp(code)
session.delete(:pending_totp_user_id)
# Restore redirect URL if it was preserved
if session[:totp_redirect_url].present?
session[:return_to_after_authenticating] = session.delete(:totp_redirect_url)
end
start_new_session_for user
redirect_to after_authentication_url, notice: "Signed in successfully."
return
@@ -65,6 +78,10 @@ class SessionsController < ApplicationController
# Try backup code verification
if user.verify_backup_code(code)
session.delete(:pending_totp_user_id)
# Restore redirect URL if it was preserved
if session[:totp_redirect_url].present?
session[:return_to_after_authenticating] = session.delete(:totp_redirect_url)
end
start_new_session_for user
redirect_to after_authentication_url, notice: "Signed in successfully using backup code."
return

View File

@@ -4,6 +4,7 @@
</div>
<%= form_with url: signin_path, class: "contents" do |form| %>
<%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
<div class="my-5">
<%= form.label :email_address, "Email Address", class: "block font-medium text-sm text-gray-700" %>
<%= form.email_field :email_address,

View File

@@ -8,6 +8,7 @@
</div>
<%= form_with url: totp_verification_path, method: :post, class: "space-y-6" do |form| %>
<%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
<div>
<%= label_tag :code, "Verification Code", class: "block text-sm font-medium text-gray-700" %>
<%= text_field_tag :code,