diff --git a/app/controllers/api/forward_auth_controller.rb b/app/controllers/api/forward_auth_controller.rb index dc89b6f..53684c2 100644 --- a/app/controllers/api/forward_auth_controller.rb +++ b/app/controllers/api/forward_auth_controller.rb @@ -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 diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 15a426c..0853874 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -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 diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 11d170b..1161b6b 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -4,6 +4,7 @@ <%= form_with url: signin_path, class: "contents" do |form| %> + <%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
<%= form.label :email_address, "Email Address", class: "block font-medium text-sm text-gray-700" %> <%= form.email_field :email_address, diff --git a/app/views/sessions/verify_totp.html.erb b/app/views/sessions/verify_totp.html.erb index 012540b..b5720c2 100644 --- a/app/views/sessions/verify_totp.html.erb +++ b/app/views/sessions/verify_totp.html.erb @@ -8,6 +8,7 @@
<%= form_with url: totp_verification_path, method: :post, class: "space-y-6" do |form| %> + <%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
<%= label_tag :code, "Verification Code", class: "block text-sm font-medium text-gray-700" %> <%= text_field_tag :code,