Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
49c7e79
sort of working
jdettmannnava Dec 26, 2025
b38beaa
working except in tests
jdettmannnava Dec 26, 2025
653e4fb
Merge branch 'main' into jd/undevise
jdettmannnava Dec 29, 2025
a48e722
working without devise
jdettmannnava Dec 29, 2025
cc1342f
better for eval
jdettmannnava Dec 29, 2025
f32edc0
cleanup
jdettmannnava Dec 29, 2025
892b051
cleanup
jdettmannnava Dec 29, 2025
0099dc4
test invitation flow
jdettmannnava Dec 30, 2025
359f685
Merge branch 'main' into jd/undevise
jdettmannnava Dec 30, 2025
e452d81
Merge branch 'main' into jd/undevise
jdettmannnava Dec 31, 2025
08c230c
Merge branch 'main' into jd/undevise
jdettmannnava Dec 31, 2025
9629895
Merge branch 'main' into jd/undevise
jdettmannnava Jan 12, 2026
5f640e4
Merge branch 'main' into jd/undevise
jdettmannnava Jan 13, 2026
fb592c0
Merge branch 'main' into jd/undevise
jdettmannnava Jan 14, 2026
7807302
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 21, 2026
1216ecf
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 21, 2026
2a50289
Merge branch 'jd/dpc-5127-multiple-oidc' of github.com:CMSgov/dpc-app…
jdettmannnava Jan 21, 2026
a02ccc5
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 22, 2026
386e178
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 23, 2026
ec7bf0e
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 23, 2026
8652201
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 26, 2026
8c705e4
fix user create, accessibility tests
jdettmannnava Jan 26, 2026
a5c3806
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 27, 2026
478f77e
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 27, 2026
6778849
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Jan 29, 2026
08fb743
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 2, 2026
cb182dc
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 3, 2026
faf2875
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 4, 2026
3cdd9d0
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 5, 2026
8a435c8
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 9, 2026
9158dbd
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 10, 2026
6ec0ee1
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 12, 2026
9c5119c
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 12, 2026
db43ba6
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 12, 2026
729c0ce
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 13, 2026
187f0c3
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 17, 2026
be910bd
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 18, 2026
3b3f35d
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 19, 2026
be58ca3
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 19, 2026
72b19b3
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 20, 2026
764b99a
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 23, 2026
e75d6fb
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 24, 2026
72c6c9b
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 26, 2026
3a675d3
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 27, 2026
4f7021d
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 27, 2026
ec9a82f
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Feb 27, 2026
8947504
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 2, 2026
64054d6
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 5, 2026
57cf436
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 5, 2026
d3dcbcb
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 10, 2026
1fb7d12
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 11, 2026
741a3e8
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 12, 2026
9420bd7
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 13, 2026
5d0877c
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 16, 2026
819dc41
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 23, 2026
9010481
force merge dpc-admin and dpc-web
jdettmannnava Mar 24, 2026
4397aa2
merge main
jdettmannnava Mar 30, 2026
fa1bb09
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 30, 2026
1cb0d21
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 30, 2026
baef2c5
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Mar 30, 2026
4803ec7
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 3, 2026
4d5e8e0
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 3, 2026
f858656
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 6, 2026
1bfeaf3
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 6, 2026
47b7fe5
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 7, 2026
a90696d
undevise javascript test
jdettmannnava Apr 7, 2026
c1a30f0
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 7, 2026
bc799a2
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 8, 2026
20e90ff
add login support to integration tests
jdettmannnava Apr 8, 2026
a44ba73
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 9, 2026
aa768e4
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 15, 2026
d93a6c2
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 16, 2026
dae0674
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 20, 2026
3c54bee
Merge branch 'main' into jd/dpc-5127-multiple-oidc
jdettmannnava Apr 22, 2026
250e311
Resolving merge conflicts with main
Jose-verdance Apr 29, 2026
74dd2cf
DPC-5159 multi csp user POC (#2896)
jdettmannnava May 5, 2026
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
6 changes: 3 additions & 3 deletions dpc-load-testing/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1258,9 +1258,9 @@ flat-cache@^4.0.0:
keyv "^4.5.4"

flatted@^3.2.9:
version "3.4.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726"
integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==
version "3.3.3"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==

fs.realpath@^1.0.0:
version "1.0.0"
Expand Down
3 changes: 0 additions & 3 deletions dpc-portal/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ gem 'auto-session-timeout'
gem 'aws-sdk-cloudwatch'
gem 'bootsnap', '>= 1.4.2', require: false
gem 'bundler', '>= 1.15.0'
gem 'devise', '>= 5.0.3'
gem 'devise-async'
gem 'devise-security'
gem 'dotenv-rails', groups: %i[development test]
gem 'fhir_models'
gem 'health_check'
Expand Down
3 changes: 1 addition & 2 deletions dpc-portal/app/components/core/table/header_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ module Table
class HeaderComponent < ViewComponent::Base
Column = Struct.new(
:label,
:sortable,
keyword_init: true
:sortable
)
attr_reader :caption, :columns

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
<p><%=raw t(key=@text, org_name: @org_name) %></p>
<% 'have to put statement here, as do not have route helper in ViewComponent'
if @reason == :fail_to_proof %>
<%= link_to 'Go to DPC Portal', new_user_session_path, class: 'usa-button margin-bottom-3' %>
<%= link_to 'Go to DPC Portal', sign_in_path, class: 'usa-button margin-bottom-3' %>
<% end %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
<% end %>
</ul>
<% end %>
<%= link_to 'Go to DPC Portal', new_user_session_path, class: 'usa-button margin-right-0' %>
<%= link_to 'Go to DPC Portal', sign_in_path, class: 'usa-button margin-right-0' %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
disabled: @invitation&.renewed?,
destination: renew_organization_invitation_path(@invitation.provider_organization, @invitation)) %>
<% when :ao_accepted %>
<%= link_to new_user_session_path, class: 'usa-button', data: { turbo: false } do %>
<%= link_to sign_in_path, class: 'usa-button', data: { turbo: false } do %>
Sign in with <span class="login-button__logo">Login.gov</span>
<% end %>
<% when :cd_accepted %>
Expand All @@ -30,8 +30,8 @@
destination: login_dot_gov_logout_path,
method: :delete) %>
<% when :login_gov_signin_cancel %>
<%= link_to 'Back to portal home', new_user_session_path, class: 'usa-button usa-button--outline', data: { turbo: false }%>
<%= link_to 'Back to portal home', sign_in_path, class: 'usa-button usa-button--outline', data: { turbo: false }%>
<% when :login_gov_signin_fail %>
<%= link_to 'Back to portal home', new_user_session_path, class: 'usa-button usa-button--outline', data: { turbo: false }%>
<%= link_to 'Back to portal home', sign_in_path, class: 'usa-button usa-button--outline', data: { turbo: false }%>
<% end %>
</div>
22 changes: 21 additions & 1 deletion dpc-portal/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ class ApplicationController < ActionController::Base

auto_session_timeout User.timeout_in

def active_url
'/active'
end

def current_user
@current_user ||= User.where(id: session['user']).first
end

def authenticate_user!
return if current_user

flash[:alert] = t('devise.failure.unauthenticated')
session[:user_return_to] = request.path
redirect_to sign_in_path
end

def sign_in(user)
session['user'] = user.id
end

private

def check_user_verification
Expand All @@ -37,7 +57,7 @@ def url_for_login_dot_gov_logout
URI::HTTPS.build(host: IDP_HOST,
path: '/openid_connect/logout',
query: { client_id: IDP_CLIENT_ID,
post_logout_redirect_uri: "#{root_url}users/auth/logged_out",
post_logout_redirect_uri: "#{root_url}auth/logged_out",
state: }.to_query)
end

Expand Down
39 changes: 33 additions & 6 deletions dpc-portal/app/controllers/invitations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def register
return unless create_link

session.delete("invitation_status_#{@invitation.id}")
sign_in(:user, @user)
sign_in(@user)
Rails.logger.info(['User logged in',
{ actionContext: LoggingConstants::ActionContext::Registration,
actionType: LoggingConstants::ActionType::UserLoggedIn,
Expand All @@ -82,7 +82,7 @@ def login
path: '/openid_connect/authorize',
query: { acr_values: 'http://idmanagement.gov/ns/assurance/ial/2',
client_id: IDP_CLIENT_ID,
redirect_uri: "#{my_protocol_host}/users/auth/openid_connect/callback",
redirect_uri: "#{my_protocol_host}/users/auth/login_dot_gov/callback",
response_type: 'code',
scope: 'openid email all_emails profile social_security_number',
nonce: @nonce,
Expand Down Expand Up @@ -179,6 +179,12 @@ def create_link
status: :unprocessable_entity)
false
end
rescue MultiUserMatchError => e
logger.error(['User matches too many existing users',
{ actionContext: LoggingConstants::ActionContext::Registration, error: e.message }])

render(Page::Utility::ErrorComponent.new(@invitation, 'multi_user_match'))
nil
end

def create_cd_org_link
Expand All @@ -203,14 +209,33 @@ def create_ao_org_link

def user
user_info = UserInfoService.new.user_info(session)
@user = User.find_or_create_by!(provider: :openid_connect, uid: user_info['sub']) do |user_to_create|
assign_user_attributes(user_to_create, user_info)
log_create_user
end
# Unique PacIds only available in prod
@user = if @invitation.authorized_official? && (ENV['ENV'] == 'prod' || Rails.env.test?)
ao_user(user_info)
else
User.find_or_create_by(email: @invitation.invited_email) do |user_to_create|
assign_user_attributes(user_to_create, user_info)
log_create_user
end
end
IdpUid.find_or_create_by!(user: @user, provider: :login_dot_gov, uid: user_info['sub'])
update_user(user_info)
@user
end

def ao_user(user_info)
matching_users = User.where('email = ? OR pac_id = ?', @invitation.invited_email, session[:user_pac_id])
raise MultiUserMatchError, "too many matching users | pac_id: #{session[:user_pac_id]}" if matching_users.size > 1

return matching_users.first if matching_users.present?

user = User.new
assign_user_attributes(user, user_info)
user.save!
log_create_user
user
end

def assign_user_attributes(user_to_create, user_info)
user_to_create.email = @invitation.invited_email
user_to_create.given_name = user_info['given_name']
Expand Down Expand Up @@ -357,4 +382,6 @@ def log_waivers(role_and_waivers)
actionType: LoggingConstants::ActionType::AoHasWaiver,
invitation: @invitation.id }])
end

class MultiUserMatchError < StandardError; end
end
11 changes: 3 additions & 8 deletions dpc-portal/app/controllers/login_dot_gov_controller.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# frozen_string_literal: true

# Handles interactions with login.gov
class LoginDotGovController < Devise::OmniauthCallbacksController
class LoginDotGovController < ApplicationController
skip_before_action :verify_authenticity_token, only: :openid_connect

def openid_connect
auth = request.env['omniauth.auth']

user = User.find_by(provider: auth.provider, uid: auth.uid)
user = IdpUid.find_by(provider: auth.provider, uid: auth.uid)&.user
if user
sign_in(:user, user)
sign_in(user)
session[:logged_in_at] = Time.now
Rails.logger.info(['User logged in',
{ actionContext: LoggingConstants::ActionContext::Authentication,
Expand Down Expand Up @@ -48,11 +48,6 @@ def logout
redirect_to url_for_login_dot_gov_logout, allow_other_host: true
end

# Return from login.gov
def logged_out
redirect_to session.delete(:user_return_to) || new_user_session_path
end

private

def handle_invitation_flow_failure(invitation_id)
Expand Down
2 changes: 1 addition & 1 deletion dpc-portal/app/controllers/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class OrganizationsController < ApplicationController

def index
@links = current_user.provider_links
ao_or_cd = @links.any? { |link| link.is_a?(AoOrgLink) }
ao_or_cd = @links.any?(AoOrgLink)
render(Page::Organization::OrganizationListComponent.new(ao_or_cd:, links: @links))
end

Expand Down
10 changes: 7 additions & 3 deletions dpc-portal/app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# frozen_string_literal: true

module Users
# Adds functionality to devise session controller
class SessionsController < Devise::SessionsController
# Handles session destruction
class SessionsController < ApplicationController
auto_session_timeout_actions

def destroy
Rails.logger.info(['User logged out',
{ actionContext: LoggingConstants::ActionContext::Authentication,
actionType: LoggingConstants::ActionType::UserLoggedOut }])
sign_out(current_user)
session.delete('user')
redirect_to url_for_login_dot_gov_logout, allow_other_host: true
end

def logged_out
redirect_to session.delete(:user_return_to) || sign_in_path
end
end
end
12 changes: 12 additions & 0 deletions dpc-portal/app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

# Utility methods for views
module ApplicationHelper
def current_user
@current_user
end

def omniauth_authorize_path(service)
"/portal/auth/#{service}"
end
end
6 changes: 6 additions & 0 deletions dpc-portal/app/models/idp_uid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

# Simple class for holding OIDC information linked to user
class IdpUid < ApplicationRecord
belongs_to :user
end
2 changes: 1 addition & 1 deletion dpc-portal/app/models/invitation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class Invitation < ApplicationRecord
validates :invited_by, :invited_given_name, :invited_family_name, presence: true, if: :needs_validation?
validates :invited_email, :invited_email_confirmation, presence: true, if: :new_record?
validates :invited_email, format: Devise.email_regexp, confirmation: true, if: :new_record?
validates :invited_email, format: URI::MailTo::EMAIL_REGEXP, confirmation: true, if: :new_record?
validates :invitation_type, presence: true
validate :cannot_cancel_accepted
validate :check_if_duplicate, if: :new_record?
Expand Down
20 changes: 10 additions & 10 deletions dpc-portal/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@

# Base user class
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, and :trackable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:timeoutable, :omniauthable, omniauth_providers: [:openid_connect]

audited only: %i[verification_reason verification_status], on: :update

validates :verification_reason, allow_nil: true, allow_blank: true,
Expand All @@ -21,10 +15,16 @@ class User < ApplicationRecord
enum :verification_reason, %i[ao_med_sanction_waived ao_med_sanctions]
enum :verification_status, %i[approved rejected]

before_validation(on: :create) do
# Assign random, acceptable password to keep Devise happy.
# User should log in only through IdP
self.password = Devise.friendly_token[0, 20] unless password.present?
def self.remember_for
12.hours
end

def self.timeout_in
30.minutes
end

def timeout_in
self.class.timeout_in
end

def provider_links
Expand Down
2 changes: 1 addition & 1 deletion dpc-portal/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</div>
</div>
</div>
<% if user_signed_in? %>
<% if !!current_user %>
<%= auto_session_timeout_js %>
<% end %>
</body>
Expand Down
2 changes: 1 addition & 1 deletion dpc-portal/app/views/login_dot_gov/failure.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p><%= @message %> <%= link_to 'Try again?', new_user_session_path %></p>
<p><%= @message %> <%= link_to 'Try again?', sign_in_path %></p>
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<p>You logged in, <%= current_user.given_name %> <%= current_user.family_name %>!</p>
<p><%= link_to 'Try again?', new_user_session_path %></p>
<p><%= link_to 'Try again?', sign_in_path %></p>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<%= render(Page::Session::LoginComponent.new(omniauth_authorize_path(:user, :openid_connect))) %>
<%= render(Page::Session::LoginComponent.new(omniauth_authorize_path(:login_dot_gov))) %>
1 change: 1 addition & 0 deletions dpc-portal/config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
config.colorize_logging = false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need to change this config setting

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary, but I found that it was difficult to analyze the logs because of their colorization, so I would prefer to leave this change in.

# Settings specified here will take precedence over those in config/application.rb.

# Turn false under Spring and add config.action_view.cache_template_loading = true.
Expand Down
Loading
Loading