Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ gem 'jbuilder'
# Use Redis adapter to run Action Cable in production
gem 'redis', '~> 4.0'

# Enable CORS for Chrome extension support
gem 'rack-cors'

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ GEM
activesupport (>= 3.0.0)
racc (1.8.1)
rack (3.1.18)
rack-cors (3.0.0)
logger
rack (>= 3.0.14)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
Expand Down Expand Up @@ -562,6 +565,7 @@ DEPENDENCIES
pgvector
puma (~> 6.4)
pundit (~> 2.3)
rack-cors
rails (~> 7.2.0)
rails-controller-testing
redcarpet
Expand Down
49 changes: 49 additions & 0 deletions app/controllers/auth_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

class AuthController < ApplicationController
skip_before_action :verify_authenticity_token, only: %i[get_token validate_token]
skip_before_action :require_login, only: %i[get_token validate_token]

# GET /auth/get_token
# Return user email for authenticated SSO user (for Chrome extensions)
# The extension's content script reads the token from the page DOM
def get_token
Rails.logger.info '=== AUTH GET_TOKEN CALLED ==='
Rails.logger.info "Session user_id: #{session[:user_id]}"
Rails.logger.info "Current user: #{current_user&.email || 'NOT AUTHENTICATED'}"
Rails.logger.info "Request path: #{request.fullpath}"

# User is authenticated via SSO session, render page with token
if current_user
Rails.logger.info "✅ User authenticated successfully: #{current_user.email}"
# Render the page with current_user available (extension content script will capture it)
else
Rails.logger.info '❌ User not authenticated, redirecting to login'
# User not authenticated, redirect to login with return URL
redirect_to new_session_path(redirect_to: request.fullpath)
end
rescue StandardError => e
Rails.logger.error "Auth error: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
redirect_to root_path, alert: 'Authentication failed. Please try again.'
end

# GET /auth/validate
# Validate current token and return user info
def validate_token
if current_user
render json: {
valid: true,
user: {
id: current_user.id,
email: current_user.email
}
}
else
render json: {
valid: false,
error: 'Invalid or expired token'
}, status: :unauthorized
end
end
end
12 changes: 10 additions & 2 deletions app/controllers/saml_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
class SamlController < ApplicationController
skip_before_action :verify_authenticity_token
def init
# Store redirect_to parameter in session since SAML redirects lose URL parameters
session[:saml_redirect_to] = params[:redirect_to] if params[:redirect_to].present?

request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings), allow_other_host: true)
end
Expand Down Expand Up @@ -39,11 +42,16 @@ def consume
# Setting the session logs the user in. Need to make some methods for this.
if user
login_user(user)

# Use stored redirect_to parameter if available, otherwise fallback to root
redirect_url = session[:saml_redirect_to].presence || root_path
session.delete(:saml_redirect_to) # Clean up the session

redirect_to redirect_url, notice:
else
notice = 'Login failed. Please contact an admin for help.'
redirect_to root_path, notice:
end

redirect_to root_path, notice:
else
redirect_to(request.create(saml_settings))
end
Expand Down
20 changes: 17 additions & 3 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
class SessionsController < ApplicationController
skip_before_action :require_login

def new; end
def new
@redirect_to = params[:redirect_to]
end

def create
user = User.find_by_email(params[:session][:email])

if user&.authenticate(params[:session][:password])
login_user(user)
redirect_back(fallback_location: root_path)
Rails.logger.info "User logged in: #{user.email}"

# Use redirect_to parameter if provided, otherwise fallback to previous page or root
if params[:redirect_to].present?
Rails.logger.info "Redirecting to: #{params[:redirect_to]}"
redirect_to params[:redirect_to]
else
redirect_to(root_path)
end
else
redirect_to new_session_url, notice: 'Error logging in.'
Rails.logger.info "Login failed for: #{params[:session][:email]}"
# Preserve redirect_to parameter on failed login
redirect_params = params[:redirect_to].present? ? { redirect_to: params[:redirect_to] } : {}
redirect_to new_session_url(redirect_params), notice: 'Error logging in.'
end
end

Expand Down
53 changes: 53 additions & 0 deletions app/views/auth/get_token.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<% content_for :title, "Chrome Extension Token" %>

<div class="flex items-center justify-center py-12 px-4">
<div class="bg-white p-10 rounded-xl shadow-lg text-center max-w-lg w-full">
<% if params[:redirect_uri].present? %>
<!-- Modern flow: automatic redirect via chrome.identity.launchWebAuthFlow -->
<div class="text-5xl text-blue-500 mb-5 animate-pulse">⟳</div>
<h1 class="text-gray-800 mb-5 text-2xl font-semibold">Redirecting...</h1>
<p class="text-gray-600">
Authentication successful! Redirecting back to your extension...<br>
<span class="text-sm mt-2 inline-block">This window will close automatically.</span>
</p>
<% else %>
<!-- Legacy flow: content script captures token -->
<div class="text-5xl text-green-500 mb-5">✓</div>
<h1 class="text-gray-800 mb-5 text-2xl font-semibold">Authentication Successful</h1>

<p class="mb-5">Your user email for the Chrome extension:</p>

<div class="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg p-5 my-5 break-all font-mono text-sm min-h-15 flex items-center justify-center">
<div id="token-display" data-token="<%= current_user&.email %>" class="text-sky-600 font-semibold">
<%= current_user&.email || 'No email available. Please try again.' %>
</div>
</div>

<div class="text-gray-600 text-sm leading-relaxed mt-5">
<strong>Instructions:</strong><br>
Your email above will be automatically captured by your Chrome extension.<br>
You can safely close this window after the email is captured.
</div>
<% end %>

<!-- Debug info (remove in production) -->
<div class="bg-gray-100 border border-gray-300 rounded p-3 mt-3 text-xs text-gray-600">
<strong>Debug:</strong> User: <%= current_user&.email || 'NOT AUTHENTICATED' %> |
Session: <%= session[:user_id] || 'NO SESSION' %> |
Redirect URI: <%= params[:redirect_uri].present? ? 'Yes' : 'No' %>
</div>
</div>
</div>

<% unless params[:redirect_uri].present? %>
<script>
console.log('Auth page loaded - email should be captured by Chrome extension content script');
console.log('Email in data-token:', document.getElementById('token-display')?.getAttribute('data-token'));

// Redirect to root after extension captures token (5 seconds)
setTimeout(() => {
console.log('Redirecting to root...');
window.location.href = '/';
}, 5000);
</script>
<% end %>
6 changes: 5 additions & 1 deletion app/views/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<% if ENV['DISABLE_PASSWORD_LOGIN'] != "true" %>
<%= form_for :session, url: sessions_path, method: :post, class: "mt-8 space-y-6" do |f| %>
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
<% if @redirect_to.present? %>
<input type="hidden" name="redirect_to" value="<%= @redirect_to %>">
<% end %>
<div class="rounded-md shadow-sm -space-y-px">
<div>
<%= f.label :email, class: "sr-only" %>
Expand All @@ -41,7 +44,8 @@
<% end %>
<% end %>
<div>
<%= link_to "Login with SSO", "/auth/saml/init", class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-sky-800 hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500" %>
<% sso_url = @redirect_to.present? ? "/auth/saml/init?redirect_to=#{CGI.escape(@redirect_to)}" : "/auth/saml/init" %>
<%= link_to "Login with SSO", sso_url, class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-sky-800 hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500" %>
</div>
</div>
</div>
Loading