Skip to content

Commit

Permalink
Merge pull request #67 from MITLibraries/tco-29-add-devise
Browse files Browse the repository at this point in the history
Implement authentication
  • Loading branch information
jazairi authored Aug 2, 2024
2 parents eec3780 + 1d29f7d commit 0ed43fa
Show file tree
Hide file tree
Showing 18 changed files with 536 additions and 5 deletions.
11 changes: 10 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ end
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false

# Ruby GraphQL implememntation [https://github.com/rmosolgo/graphql-ruby]
# Use Devise for authentication
gem 'devise'

# Ruby GraphQL implementation [https://github.com/rmosolgo/graphql-ruby]
gem 'graphql'

# HTTP is an easy-to-use client library for making requests from Ruby [https://github.com/httprb/http]
Expand All @@ -23,6 +26,11 @@ gem 'jbuilder'

gem 'mitlibraries-theme', git: 'https://github.com/mitlibraries/mitlibraries-theme', tag: 'v1.4'

# Use OmniAuth as Touchstone middleware and include the OIDC strategy and CSRF protection gems
gem 'omniauth'
gem 'omniauth_openid_connect'
gem 'omniauth-rails_csrf_protection'

# Use the Puma web server [https://github.com/puma/puma]
gem 'puma', '>= 5.0'

Expand Down Expand Up @@ -93,6 +101,7 @@ end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem 'capybara'
gem 'climate_control'
gem 'selenium-webdriver'
gem 'simplecov'
gem 'simplecov-lcov'
Expand Down
91 changes: 89 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,16 @@ GEM
tzinfo (~> 2.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
aes_key_wrap (1.1.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_required (1.0.2)
base64 (0.2.0)
bcrypt (3.1.20)
bigdecimal (3.1.8)
bindata (2.5.0)
bindex (0.8.1)
bootsnap (1.18.3)
msgpack (~> 1.2)
Expand All @@ -105,6 +109,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
climate_control (1.2.0)
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
crack (1.0.0)
Expand All @@ -115,14 +120,29 @@ GEM
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
docile (1.4.1)
devise (4.9.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
docile (1.4.0)
domain_name (0.6.20240107)
dotenv (3.1.2)
dotenv-rails (3.1.2)
dotenv (= 3.1.2)
railties (>= 6.1)
drb (2.2.1)
email_validator (2.2.4)
activemodel
erubi (1.13.0)
faraday (2.10.0)
faraday-net_http (>= 2.0, < 3.2)
logger
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-net_http (3.1.0)
net-http
ffi (1.17.0-arm64-darwin)
ffi (1.17.0-x86_64-darwin)
ffi (1.17.0-x86_64-linux-gnu)
Expand All @@ -134,6 +154,7 @@ GEM
graphql (2.3.10)
base64
hashdiff (1.1.0)
hashie (5.0.0)
http (5.2.0)
addressable (~> 2.8)
base64 (~> 0.1)
Expand All @@ -157,6 +178,13 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.7.2)
json-jwt (1.16.6)
activesupport (>= 4.2)
aes_key_wrap
base64
bindata
faraday (~> 2.0)
faraday-follow_redirects
language_server-protocol (3.17.0.3)
llhttp-ffi (0.5.0)
ffi-compiler (~> 1.0)
Expand All @@ -176,6 +204,8 @@ GEM
minitest (5.24.1)
msgpack (1.7.2)
mutex_m (0.2.0)
net-http (0.4.1)
uri
net-imap (0.4.14)
date
net-protocol
Expand All @@ -192,6 +222,30 @@ GEM
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
omniauth (2.1.2)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth_openid_connect (0.8.0)
omniauth (>= 1.9, < 3)
openid_connect (~> 2.2)
openid_connect (2.3.0)
activemodel
attr_required (>= 1.0.0)
email_validator
faraday (~> 2.0)
faraday-follow_redirects
json-jwt (>= 1.16)
mail
rack-oauth2 (~> 2.2)
swd (~> 2.0)
tzinfo
validate_url
webfinger (~> 2.0)
orm_adapter (0.5.0)
parallel (1.25.1)
parser (3.3.4.0)
ast (~> 2.4.1)
Expand All @@ -206,6 +260,16 @@ GEM
rack (3.1.7)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-oauth2 (2.2.1)
activesupport
attr_required
faraday (~> 2.0)
faraday-follow_redirects
json-jwt (>= 1.11.0)
rack (>= 2.1.0)
rack-protection (4.0.0)
base64 (>= 0.1.0)
rack (>= 3.0.0, < 4)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0)
Expand Down Expand Up @@ -249,7 +313,10 @@ GEM
regexp_parser (2.9.2)
reline (0.5.9)
io-console (~> 0.5)
rexml (3.3.2)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.3.1)
strscan
rubocop (1.65.0)
json (~> 2.3)
Expand Down Expand Up @@ -309,6 +376,11 @@ GEM
stringex (2.8.6)
stringio (3.1.1)
strscan (3.1.0)
swd (2.0.3)
activesupport (>= 3)
attr_required (>= 0.0.5)
faraday (~> 2.0)
faraday-follow_redirects
thor (1.3.1)
tilt (2.4.0)
timeout (0.4.1)
Expand All @@ -319,12 +391,22 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
uri (0.13.0)
validate_url (1.0.15)
activemodel (>= 3.0.0)
public_suffix
vcr (6.2.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webfinger (2.1.3)
activesupport
faraday (~> 2.0)
faraday-follow_redirects
webmock (3.23.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand All @@ -349,13 +431,18 @@ DEPENDENCIES
annotate
bootsnap
capybara
climate_control
debug
devise
dotenv-rails
graphql
http
importmap-rails
jbuilder
mitlibraries-theme!
omniauth
omniauth-rails_csrf_protection
omniauth_openid_connect
pg
puma (>= 5.0)
rack-cors
Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,34 @@ To see a current list of commands, run `make help`.

### Optional

`PLATFORM_NAME`: The value set is added to the header after the MIT Libraries logo. The logic and CSS for this comes from our theme gem.
`PLATFORM_NAME`: The value set is added to the header after the MIT Libraries logo. The logic and CSS for this comes
from our theme gem.

### Authentication

#### Required in all environments

Access to some of the config values below is limited. Please contact someone in the EngX team if you need help locating
them.

`BASE_URL`: The base url for the app. This is required for Omniauth config.
`OPENID_HOST`: The OID provider hostname, required for authentication. (Do not include URL prefix.)
`OPENID_SECRET_KEY`: The secret key for the OID client.
`OPENID_CLIENT_ID`: The identifier for the OID client.
`OPENID_ISSUER`: The URL for the OIDC issuer. This can be found in the Touchstone OpenID metadata.

#### Required in PR builds

The config below is needed to run Omniauth in developer mode in Heroku review apps. Rather than relying upon a single
ENV value, we use the `FakeAuthConfig` module to perform additional checks that confirm whether developer mode should
be enabled. This assures that developer mode is never enabled in staging or production apps.

`FAKE_AUTH_ENABLED`: Switches Omniauth to developer mode when set. If unset, PR builds will attempt to authenticate with
OIDC, which will fail as their domains are not registered with the provider. (Note: Developer mode is also enabled
whenever the app is started in the development environment.)
`HEROKU_APP_NAME`: Used by the FakeAuthConfig module to determine whether an app is a PR build. If this is set along
with `FAKE_AUTH_ENABLED`, then Omniauth will use Developer mode. Heroku sets this variable automatically for review
apps; it should never be manually set or overridden in any environment.

## Documentation

Expand Down
4 changes: 4 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
class ApplicationController < ActionController::Base
helper Mitlibraries::Theme::Engine.helpers

def new_session_path(_scope)
root_path
end
end
21 changes: 21 additions & 0 deletions app/controllers/users/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Handles authentication response from Omniauth. See
# [the Devise docs](https://www.rubydoc.info/gems/devise_token_auth/DeviseTokenAuth/OmniauthCallbacksController) for
# additional information about this controller.
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
include FakeAuthConfig

def openid_connect
@user = User.from_omniauth(request.env['omniauth.auth'])
sign_in_and_redirect @user, event: :authentication
flash[:notice] = "Welcome, #{@user.email}!"
end

# Developer authentication is used in local dev and PR builds.
def developer
raise 'Invalid Authentication' unless FakeAuthConfig.fake_auth_enabled?

@user = User.from_omniauth(request.env['omniauth.auth'])
sign_in_and_redirect @user, event: :authentication
flash[:notice] = "Welcome, #{@user.email}!"
end
end
24 changes: 24 additions & 0 deletions app/models/concerns/fake_auth_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module FakeAuthConfig
# Used in an initializer to determine if the application is configured and allowed to use fake authentication.
def self.fake_auth_enabled?
fake_auth_env? && app_name_pattern_match?
end

# Default to fake auth in development unless FAKE_AUTH_ENABLED=false. This allows rake tasks to run without loading
# ENV.
private_class_method def self.fake_auth_env?
if Rails.env.development? && ENV['FAKE_AUTH_ENABLED'].nil?
true
else
ENV['FAKE_AUTH_ENABLED'] == 'true'
end
end

# Check if the app is a PR build. This assures that fake auth is never enabled in staging or prod.
private_class_method def self.app_name_pattern_match?
return true if Rails.env.development?

review_app_pattern = /^tacos-api-pipeline-pr-\d+$/
review_app_pattern.match(ENV.fetch('HEROKU_APP_NAME', nil)).present?
end
end
40 changes: 40 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# uid :string not null
# email :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
class User < ApplicationRecord
include FakeAuthConfig

if FakeAuthConfig.fake_auth_enabled?
devise :omniauthable, omniauth_providers: [:developer]
else
devise :omniauthable, omniauth_providers: [:openid_connect]
end

validates :uid, presence: true
validates :email, presence: true

# Creating a user from Omniauth first requires us to determine whether fake_auth is enabled. If so, we need to
# traverse the response hash differently than with OIDC, as developer mode returns metadata in a different structure.
# @param auth Hash The authentication response from Omniauth.
def self.from_omniauth(auth)
if FakeAuthConfig.fake_auth_enabled?
uid = auth.uid
email = auth.info.email
else
uid = auth.extra.raw_info.preferred_username
email = auth.extra.raw_info.email
end

User.where(uid: uid).first_or_create do |user|
user.uid = uid
user.email = email
end
end
end
14 changes: 14 additions & 0 deletions app/views/layouts/_site_nav.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
<nav class="local-nav" aria-label="Main menu">
<%= nav_link_to("Home", root_path) %>
</nav>
<nav class="nav-user" aria-label="User menu">
<% if user_signed_in? %>
<%= button_to("Sign out", destroy_user_session_path, class: 'action-auth', id: "sign_in",
method: :delete) %>
<% else %>
<% if FakeAuthConfig.fake_auth_enabled? %>
<%= button_to("Sign in", user_developer_omniauth_authorize_path, id: "sign_in", class: 'action-auth',
method: :post) %>
<% else %>
<%= button_to("Sign in", user_openid_connect_omniauth_authorize_path, class: 'action-auth', id: "sign_in",
method: :post) %>
<% end %>
<% end %>
</nav>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 0ed43fa

Please sign in to comment.