diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index f774f48..5e855f7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,7 +1,7 @@ # L4D Tools - Architecture & Implementation Plan -**Date**: January 18, 2026 -**Status**: MVP Implementation Complete +**Date**: January 18, 2026 +**Status**: MVP Implementation Complete **Framework**: Rails 8.1.2 with SQLite + Hotwire + Solid Queue --- @@ -84,7 +84,7 @@ Template Show → Click "Spawn Server" → Enter server name and port → (Optional) Override startup parameters → Click "Spawn Server" - + Background (SpawnServerJob): → Create /opt/l4d2/servers/{server_id}/ with subdirs → Generate server.cfg from template ConfigOptions @@ -280,8 +280,8 @@ WS /cable → ActionCable (LogChannel) ### Frontend (Slim + Hotwire) -**Templating**: All views use Slim (not ERB) -**Interactivity**: Hotwire (Turbo + Stimulus) for SPA-like experience +**Templating**: All views use Slim (not ERB) +**Interactivity**: Hotwire (Turbo + Stimulus) for SPA-like experience **Styling**: Basic CSS in `app/assets/stylesheets/application.css` Key Views: diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 7aec0b8..4f4bbc9 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,22 +1,89 @@ +require "net/http" + class SessionsController < ApplicationController - skip_before_action :authenticate_user! + skip_before_action :authenticate_user!, only: [ :auth_request, :steam_callback ] def auth_request - # Manually trigger OmniAuth Steam strategy - request.env['omniauth.strategy'] = OmniAuth::Strategies::Steam.new(nil) - auth = request.env['omniauth.strategy'].request_phase - redirect_to auth + # Build Steam OpenID URL + steam_openid_url = "https://steamcommunity.com/openid/login" + # Use the actual request host/protocol for both return_to and realm to avoid signature mismatch + base_url = request.base_url + return_url = "#{base_url}#{steam_callback_path}" + + Rails.logger.info("Steam auth_request return_url=#{return_url} realm=#{base_url}") + + params = { + "openid.ns" => "http://specs.openid.net/auth/2.0", + "openid.identity" => "http://specs.openid.net/auth/2.0/identifier_select", + "openid.claimed_id" => "http://specs.openid.net/auth/2.0/identifier_select", + "openid.mode" => "checkid_setup", + "openid.return_to" => return_url, + "openid.realm" => base_url, + "openid.ns.sreg" => "http://openid.net/extensions/sreg/1.1", + "openid.sreg.required" => "email" + } + + redirect_to "#{steam_openid_url}?#{params.to_query}", allow_other_host: true end def steam_callback - auth_hash = request.env["omniauth.auth"] + # Get the OpenID response + openid_response = request.params - if auth_hash - user = User.find_or_create_from_steam(auth_hash) - session[:user_id] = user.id - redirect_to dashboard_path, notice: "Logged in successfully!" + # Verify the response with Steam + if verify_steam_response(openid_response) + # Extract Steam ID from identity URL + # Format: http://steamcommunity.com/openid/id/[STEAMID] + identity_url = openid_response["openid.identity"] + + if identity_url && identity_url.include?("/id/") + steam_id = identity_url.split("/id/").last + + # Create mock auth_hash for compatibility with our User model + auth_hash = { + "uid" => steam_id, + "info" => { + "nickname" => "Steam User #{steam_id}" + } + } + + user = User.find_or_create_from_steam(auth_hash) + session[:user_id] = user.id + redirect_to dashboard_path, notice: "Logged in successfully!" + else + redirect_to root_path, alert: "Could not extract Steam ID from response." + end else - redirect_to root_path, alert: "Steam authentication failed." + redirect_to root_path, alert: "Steam authentication failed: Invalid response signature." + end + end + + private + + def verify_steam_response(response) + # Steam expects only openid.* keys and mode=check_auth for validation + openid_params = response.to_h.select { |k, _| k.to_s.start_with?("openid.") } + return false unless openid_params["openid.mode"] == "id_res" + + # Per OpenID 2.0 spec, the verification mode is "check_authentication" + verify_params = openid_params.merge("openid.mode" => "check_authentication") + + Rails.logger.info("Steam verify payload: #{verify_params.inspect}") + + uri = URI.parse("https://steamcommunity.com/openid/login") + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + + request = Net::HTTP::Post.new(uri.path) + request.set_form_data(verify_params) + + begin + response = http.request(request) + Rails.logger.info("Steam verify response body: #{response.body.inspect}") + response.body.include?("is_valid:true") + rescue StandardError => e + Rails.logger.error("Steam verification error: #{e.message}") + false end end @@ -24,4 +91,8 @@ class SessionsController < ApplicationController session[:user_id] = nil redirect_to root_path, notice: "Logged out successfully!" end + + def omniauth_failure + redirect_to root_path, alert: "Steam authentication failed: #{params[:message]}" + end end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index cc75fc8..d1453c2 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -24,6 +24,13 @@
+ <% if flash.any? %> + + <% end %> <%= yield %>