Skip to content

Commit

Permalink
Merge pull request #101 from GSA/22/user-can-login-via-rails
Browse files Browse the repository at this point in the history
[22 | 24] Users can login/logout via Rails app
  • Loading branch information
cpreisinger authored Aug 9, 2024
2 parents 6ade342 + 39af8b4 commit 43b47b9
Show file tree
Hide file tree
Showing 20 changed files with 472 additions and 127 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,6 @@ group :test do
gem "capybara"
gem "selenium-webdriver"
gem "rspec_junit_formatter"
gem "simplecov"
gem 'simplecov', '~> 0.17.0', require: false
gem "rails-controller-testing"
end
56 changes: 30 additions & 26 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ GEM
base64 (0.2.0)
bigdecimal (3.1.8)
bindex (0.8.1)
bootsnap (1.18.3)
bootsnap (1.18.4)
msgpack (~> 1.2)
builder (3.3.0)
capybara (3.40.0)
Expand All @@ -96,8 +96,8 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
codeclimate-test-reporter (1.0.9)
simplecov (<= 0.13)
codeclimate-test-reporter (1.0.7)
simplecov
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
crack (1.0.0)
Expand All @@ -111,18 +111,18 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.5.1)
docile (1.1.5)
docile (1.4.1)
drb (2.2.1)
erubi (1.13.0)
faraday (2.10.0)
faraday (2.10.1)
faraday-net_http (>= 2.0, < 3.2)
logger
faraday-net_http (3.1.0)
faraday-net_http (3.1.1)
net-http
foreman (0.88.1)
globalid (1.2.1)
activesupport (>= 6.1)
hashdiff (1.1.0)
hashdiff (1.1.1)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
io-console (0.7.2)
Expand Down Expand Up @@ -165,19 +165,23 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.6-aarch64-linux)
nokogiri (1.16.7-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.6-arm64-darwin)
nokogiri (1.16.7-arm-linux)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-darwin)
nokogiri (1.16.7-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
nokogiri (1.16.7-x86-linux)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
parallel (1.25.1)
parser (3.3.4.0)
ast (~> 2.4.1)
racc
pg (1.5.6)
pg (1.5.7)
prism (0.30.0)
propshaft (0.9.0)
actionpack (>= 7.0.0)
Expand All @@ -186,7 +190,7 @@ GEM
railties (>= 7.0.0)
psych (5.1.2)
stringio
public_suffix (6.0.0)
public_suffix (6.0.1)
puma (6.4.2)
nio4r (~> 2.0)
racc (1.8.1)
Expand Down Expand Up @@ -272,7 +276,7 @@ GEM
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
rubocop-ast (1.32.0)
parser (>= 3.3.1.0)
rubocop-performance (1.21.1)
rubocop (>= 1.48.1, < 2.0)
Expand All @@ -284,9 +288,9 @@ GEM
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-rspec (3.0.3)
rubocop-rspec (3.0.4)
rubocop (~> 1.61)
ruby-lsp (0.17.9)
ruby-lsp (0.17.11)
language_server-protocol (~> 3.17.0)
prism (>= 0.29.0, < 0.31)
rbs (>= 3, < 4)
Expand All @@ -299,12 +303,12 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
simplecov (0.13.0)
docile (~> 1.1.0)
simplecov (0.17.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
sorbet-runtime (0.5.11492)
sorbet-runtime (0.5.11511)
stimulus-rails (1.3.3)
railties (>= 6.0.0)
stringio (3.1.1)
Expand Down Expand Up @@ -335,14 +339,14 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.16)
zeitwerk (2.6.17)

PLATFORMS
aarch64-linux
arm64-darwin-21
arm64-darwin-22
arm64-darwin-23
x86_64-darwin-22
arm-linux
arm64-darwin
x86-linux
x86_64-darwin
x86_64-linux

DEPENDENCIES
Expand Down Expand Up @@ -371,7 +375,7 @@ DEPENDENCIES
rubocop-rspec
ruby-lsp
selenium-webdriver
simplecov
simplecov (~> 0.17.0)
stimulus-rails
turbo-rails
tzinfo-data
Expand All @@ -382,4 +386,4 @@ RUBY VERSION
ruby 3.2.4p170

BUNDLED WITH
2.4.6
2.5.9
10 changes: 7 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ Rails.application.load_tasks

namespace :cf do
desc "Only run on the first application instance"
task :on_first_instance do
instance_index = JSON.parse(ENV["VCAP_APPLICATION"])["instance_index"] rescue nil
exit(0) unless instance_index == 0
task on_first_instance: :environment do
instance_index = begin
JSON.parse(ENV.fetch("VCAP_APPLICATION", nil))["instance_index"]
rescue
nil
end
exit(0) unless instance_index.zero?
end
end
30 changes: 30 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
# frozen_string_literal: true

class ApplicationController < ActionController::Base
helper_method :current_user, :logged_in?

def current_user
return unless session[:userinfo]

user_token = session["userinfo"][0]["sub"]
@current_user ||= User.find_by(token: user_token) if user_token
end

def logged_in?
!!current_user
end

def sign_in(login_userinfo)
user = User.user_from_userinfo(login_userinfo)

@current_user = user
session[:userinfo] = login_userinfo
end

def sign_out
@current_user = nil
session.delete(:userinfo)
end

def redirect_if_logged_in(path = "/dashboard")
return unless logged_in?

redirect_to path, notice: I18n.t("already_logged_in_notice")
end
end
5 changes: 5 additions & 0 deletions app/controllers/dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class DashboardController < ApplicationController
def index; end
end
11 changes: 5 additions & 6 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ def create
redirect_to(login_gov.authorization_url, allow_other_host: true)
end

def delete
def destroy
login_gov = LoginGov.new
# TODO: update user session status, clear out JWT
# TODO: add session duration to the security log
# TODO: delete session locally and Phoenix
redirect_to(login_gov.logout_url)
sign_out
redirect_to(login_gov.logout_url, allow_other_host: true)
end

def result
# TODO: store the user_info in the session
# session[:user_info] = @login_userinfo
sign_in(@login_userinfo)
redirect_to dashboard_path
end

private
Expand Down
10 changes: 10 additions & 0 deletions app/models/application_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@

class ApplicationRecord < ActiveRecord::Base
primary_abstract_class

attribute :inserted_at, :datetime, precision: 6
attribute :updated_at, :datetime, precision: 6

# created_at timestamp is currently overridden to inserted_at due to shared Phoenix database
def self.timestamp_attributes_for_create
# only strings allowed here, symbols won't work, see below commit for more details
# https://github.com/rails/rails/commit/2b5dacb43dd92e98e1fd240a80c2a540ed380257
super << 'inserted_at'
end
end
52 changes: 50 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,55 @@ class User < ApplicationRecord

attribute :renewal_request, :string

attribute :updated_at, :datetime, precision: 6

validates :email, presence: true

# Finds, creates, or updates user from userinfo
# Find in case of user with existing token matching userinfo["sub"]
# Create in case of no token or email matching in userinfo
# Update in case of matching email to userinfo["email"] but no token set
# TODO: Add relevant security log tracking here?
def self.user_from_userinfo(userinfo)
email = userinfo[0]["email"]
token = userinfo[0]["sub"]

if (user = find_by(token:))
user
elsif (user = find_by(email:))
update_admin_added_user(user, userinfo)
else
create_user_from_userinfo(userinfo)
end
end

def self.update_admin_added_user(user, userinfo)
update(user.id, { token: userinfo[0]["sub"] })
end

def self.create_user_from_userinfo(userinfo)
email = userinfo[0]["email"]
token = userinfo[0]["sub"]

default_role, default_status = default_role_and_status_for_email(email)

create({
email:,
role: default_role,
token:,
terms_of_use: nil,
privacy_guidelines: nil,
status: default_status
})
end

def self.default_role_and_status_for_email(email)
if default_challenge_manager?(email)
%w[challenge_manager pending]
else
%w[solver active]
end
end

def self.default_challenge_manager?(email)
/\.(gov|mil)$/.match?(email)
end
end
9 changes: 9 additions & 0 deletions app/views/dashboard/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="usa-card__container custom-card col-md-6 rules-of-behavior">
<div class="usa-card__body">
<% if logged_in? %>
Logged In Dashboard Index
<% else %>
Logged Out Dashboard Index
<% end %>
</div>
</div>
18 changes: 18 additions & 0 deletions app/views/layouts/_header.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<header class="usa-header usa-header--basic margin-bottom-2">
<div class="usa-nav-container">
<div class="usa-navbar">
<div class="usa-logo">
<em class="usa-logo__text">
<a href="/" title="Challenge Platform">Challenge Platform</a>
</em>
</div>
</div>
<nav aria-label="Primary navigation" class="usa-nav">
<% if logged_in? %>
<%= button_to("Logout", session_path, method: :delete, class: "usa-button usa-nav-link", type: "button") %>
<% else %>
<%= button_to("Login", new_session_path, method: :get, class: "usa-button usa-nav-link", type: "button") %>
<% end %>
</nav>
</div>
</header>
1 change: 1 addition & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</head>

<body>
<%= render "layouts/header" %>
<%= yield %>
</body>
</html>
2 changes: 1 addition & 1 deletion config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@

# Raise error when a before_action's only/except options reference missing actions
config.action_controller.raise_on_missing_callback_actions = true
end
end
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ en:
hello: "Hello world"
please_try_again: "Please try again."
login_error: "There was an issue with logging in. Please try again."
already_logged_in_notice: "You are already logged in."
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
get 'auth/result', to: 'sessions#result'
resource 'session', only: [:new, :create, :destroy]

get '/', to: "dashboard#index"
get '/dashboard', to: "dashboard#index"

# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
Expand Down
Loading

0 comments on commit 43b47b9

Please sign in to comment.