Skip to content

Commit a384e2d

Browse files
authored
Improve login-time error message for invalid Login.gov account (#2076)
1 parent 45020e8 commit a384e2d

5 files changed

Lines changed: 294 additions & 17 deletions

File tree

.env.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ AWS_SES_REGION=
77

88
INDEX_URL=/index
99

10-
LOGIN_GOV_CLIENT_ID=
11-
LOGIN_GOV_IDP_BASE_URL=
10+
LOGIN_GOV_CLIENT_ID=test
11+
LOGIN_GOV_IDP_BASE_URL=https://idp.int.identitysandbox.gov
1212
LOGIN_GOV_PRIVATE_KEY=
13-
LOGIN_GOV_REDIRECT_URI=
13+
LOGIN_GOV_REDIRECT_URI=http://localhost:3000/users/auth/login_dot_gov/callback
1414

1515
NEW_RELIC_KEY=
1616

app/controllers/users/omniauth_callbacks_controller.rb

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ module Users
44
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
55
def login_dot_gov
66
@kind = 'Login.gov'
7-
all_emails = auth_hash['info']['all_emails']
8-
if all_emails.present?
9-
@email = auth_hash['info']['all_emails'].find { |email| email.end_with?(".gov") || email.end_with?(".mil") } if auth_hash && auth_hash['info']['email_verified']
10-
else
11-
@email = auth_hash['info']['email'] if auth_hash && auth_hash['info']['email_verified']
7+
if auth_hash['info']['all_emails'].present?
8+
@email = auth_hash['info']['all_emails'].find { |email| email.end_with?('.gov', '.mil') } if auth_hash['info']['email_verified']
9+
elsif auth_hash['info']['email'].present?
10+
@email = auth_hash['info']['email'] if auth_hash['info']['email'].end_with?('.gov', '.mil') && auth_hash['info']['email_verified']
1211
end
12+
13+
if @email.blank?
14+
Event.log_event(Event.names[:user_authentication_failure], 'Event::Generic', 1, "Login.gov account with no verified .gov/.mil email attempted to log in on #{Date.today}")
15+
redirect_to index_path, alert: t('home.invalid_login_account') and return
16+
end
17+
1318
login
1419
end
1520

@@ -34,15 +39,9 @@ def auth_hash
3439
def login
3540
Event.log_event(Event.names[:user_authentication_attempt], 'Event::Generic', 1, "Email #{@email} attempted to authenticate on #{Date.today}")
3641

37-
@user = User.from_omniauth(auth_hash, @email) if @email.present?
42+
@user = User.from_omniauth(auth_hash, @email)
3843

39-
# If user exists
40-
# Else, if valid email and no user, we create an account.
41-
if @user.blank?
42-
message = "Email #{@email} failed to authenticate on #{Date.today} via #{@kind}"
43-
Event.log_event(Event.names[:user_authentication_failure], 'Event::Generic', 1, message)
44-
redirect_to index_path, alert: message
45-
elsif @user.errors.blank?
44+
if @user.errors.blank?
4645
Event.log_event(Event.names[:user_authentication_successful], 'User', @user.id, "User #{@user.email} successfully authenticated on #{Date.today}", @user.id)
4746
sign_in_and_redirect(:user, @user)
4847
elsif @user.errors.present?

config/initializers/omniauth.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
client_id: ENV.fetch('LOGIN_GOV_CLIENT_ID'),
2424
idp_base_url: ENV.fetch('LOGIN_GOV_IDP_BASE_URL'),
2525
ial: 1,
26-
private_key: OpenSSL::PKey::RSA.new(ENV.fetch('LOGIN_GOV_PRIVATE_KEY').gsub('\\n', "\n")),
26+
private_key: Rails.env.test? ? 'dummy_key' : OpenSSL::PKey::RSA.new(ENV.fetch('LOGIN_GOV_PRIVATE_KEY').gsub('\\n', "\n")),
2727
redirect_uri: ENV.fetch('LOGIN_GOV_REDIRECT_URI'),
2828
}
2929
end

config/locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ en:
177177
sign_up_text: Any federal employee with a .gov or .mil email address can use Touchpoints. Your account will be automatically created the first time you sign in.
178178
sign_in_text: Touchpoints uses Login.gov to handle user accounts. Once you authenticate with Login.gov, you will be signed in and redirected back to Touchpoints.
179179
sign_in_with: Sign in with
180+
invalid_login_account: "This Login.gov account cannot be used to access Touchpoints because it is not associated with a verified .gov or .mil email address. If you are a government employee, please follow <a href='https://login.gov/help/manage-your-account/change-your-email-address/'>these instructions</a> to add your work email to this account, or sign in with a different Login.gov account that uses your work email."
180181
the_feedback_and_analytics_team: " the Feedback and Analytics Team"
181182
characters_allowed: "characters allowed"
182183
characters_left: "characters left"
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe Users::OmniauthCallbacksController, type: :controller do
6+
before do
7+
@request.env['devise.mapping'] = Devise.mappings[:user]
8+
OmniAuth.config.test_mode = true
9+
end
10+
11+
after do
12+
OmniAuth.config.test_mode = false
13+
end
14+
15+
let!(:example_organization_gov) { FactoryBot.create(:organization, domain: 'example.gov') }
16+
let!(:example_organization_mil) { FactoryBot.create(:organization, domain: 'army.mil') }
17+
18+
describe 'GET #login_dot_gov' do
19+
context 'with verified .gov email in all_emails array' do
20+
let(:auth_hash) do
21+
OmniAuth::AuthHash.new(
22+
provider: 'login_dot_gov',
23+
uid: '12345',
24+
info: {
25+
all_emails: ['user@example.gov', 'personal@gmail.com'],
26+
email_verified: true
27+
}
28+
)
29+
end
30+
31+
before do
32+
request.env['omniauth.auth'] = auth_hash
33+
session['user_return_to'] = '/admin/forms'
34+
end
35+
36+
it 'successfully authenticates the new user' do
37+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_attempt], 'Event::Generic', 1, anything)
38+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_successful], 'User', anything, anything, anything)
39+
40+
get :login_dot_gov
41+
42+
expect(response).to redirect_to('/admin/forms')
43+
user = User.find_by(email: 'user@example.gov')
44+
expect(controller.current_user).to eq(user)
45+
end
46+
end
47+
48+
context 'with verified .mil email in all_emails array' do
49+
let(:auth_hash) do
50+
OmniAuth::AuthHash.new(
51+
provider: 'login_dot_gov',
52+
uid: '12346',
53+
info: {
54+
all_emails: ['soldier@army.mil', 'personal@gmail.com'],
55+
email_verified: true
56+
}
57+
)
58+
end
59+
60+
before do
61+
request.env['omniauth.auth'] = auth_hash
62+
session['user_return_to'] = '/admin/forms'
63+
end
64+
65+
it 'successfully authenticates the new user' do
66+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_attempt], 'Event::Generic', 1, anything)
67+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_successful], 'User', anything, anything, anything)
68+
69+
get :login_dot_gov
70+
71+
expect(response).to redirect_to('/admin/forms')
72+
user = User.find_by(email: 'soldier@army.mil')
73+
expect(controller.current_user).to eq(user)
74+
end
75+
end
76+
77+
context 'with verified .gov email in email field' do
78+
let(:auth_hash) do
79+
OmniAuth::AuthHash.new(
80+
provider: 'login_dot_gov',
81+
uid: '12347',
82+
info: {
83+
email: 'user@example.gov',
84+
email_verified: true
85+
}
86+
)
87+
end
88+
89+
before do
90+
request.env['omniauth.auth'] = auth_hash
91+
session['user_return_to'] = '/profile'
92+
end
93+
94+
it 'successfully authenticates the new user' do
95+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_attempt], 'Event::Generic', 1, anything)
96+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_successful], 'User', anything, anything, anything)
97+
98+
get :login_dot_gov
99+
100+
expect(response).to redirect_to('/profile')
101+
user = User.find_by(email: 'user@example.gov')
102+
expect(controller.current_user).to eq(user)
103+
end
104+
end
105+
106+
context 'with no verified .gov or .mil email' do
107+
let(:auth_hash) do
108+
OmniAuth::AuthHash.new(
109+
provider: 'login_dot_gov',
110+
uid: '12348',
111+
info: {
112+
all_emails: ['personal@gmail.com'],
113+
email_verified: true
114+
}
115+
)
116+
end
117+
118+
before do
119+
request.env['omniauth.auth'] = auth_hash
120+
session['user_return_to'] = '/admin/forms'
121+
end
122+
123+
it 'redirects with error message' do
124+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_failure], 'Event::Generic', 1, anything)
125+
126+
get :login_dot_gov
127+
128+
expect(response).to redirect_to(index_path)
129+
expect(flash[:alert]).to include('This Login.gov account cannot be used')
130+
end
131+
end
132+
133+
context 'with unverified email' do
134+
let(:auth_hash) do
135+
OmniAuth::AuthHash.new(
136+
provider: 'login_dot_gov',
137+
uid: '12349',
138+
info: {
139+
all_emails: ['user@example.gov'],
140+
email_verified: false
141+
}
142+
)
143+
end
144+
145+
before do
146+
request.env['omniauth.auth'] = auth_hash
147+
session['user_return_to'] = '/admin/forms'
148+
end
149+
150+
it 'redirects with error message' do
151+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_failure], 'Event::Generic', 1, anything)
152+
153+
get :login_dot_gov
154+
155+
expect(response).to redirect_to(index_path)
156+
expect(flash[:alert]).to include('This Login.gov account cannot be used')
157+
end
158+
end
159+
160+
context 'with no email provided' do
161+
let(:auth_hash) do
162+
OmniAuth::AuthHash.new(
163+
provider: 'login_dot_gov',
164+
uid: '12350',
165+
info: {}
166+
)
167+
end
168+
169+
before do
170+
request.env['omniauth.auth'] = auth_hash
171+
session['user_return_to'] = '/admin/forms'
172+
end
173+
174+
it 'redirects with error message' do
175+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_failure], 'Event::Generic', 1, anything)
176+
177+
get :login_dot_gov
178+
179+
expect(response).to redirect_to(index_path)
180+
expect(flash[:alert]).to include('This Login.gov account cannot be used')
181+
end
182+
end
183+
184+
context 'when user creation fails' do
185+
let(:auth_hash) do
186+
OmniAuth::AuthHash.new(
187+
provider: 'login_dot_gov',
188+
uid: '12351',
189+
info: {
190+
email: 'user@agency.gov',
191+
email_verified: true
192+
}
193+
)
194+
end
195+
196+
before do
197+
request.env['omniauth.auth'] = auth_hash
198+
session['user_return_to'] = '/admin/forms'
199+
end
200+
201+
it 'redirects with error message' do
202+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_attempt], 'Event::Generic', 1, anything)
203+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_failure], 'Event::Generic', 1, anything)
204+
205+
get :login_dot_gov
206+
207+
expect(response).to redirect_to(index_path)
208+
expect(flash[:alert]).to include("Organization 'agency.gov' has not yet been configured for Touchpoints")
209+
end
210+
end
211+
212+
context 'with existing user migrating from legacy account' do
213+
let!(:existing_user) { FactoryBot.create(:user, email: 'existing@example.gov', provider: nil, uid: nil) }
214+
let(:auth_hash) do
215+
OmniAuth::AuthHash.new(
216+
provider: 'login_dot_gov',
217+
uid: '12352',
218+
info: {
219+
email: 'existing@example.gov',
220+
email_verified: true
221+
}
222+
)
223+
end
224+
225+
before do
226+
request.env['omniauth.auth'] = auth_hash
227+
session['user_return_to'] = '/admin/users'
228+
end
229+
230+
it 'updates the existing user with provider and uid' do
231+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_attempt], 'Event::Generic', 1, anything)
232+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_successful], 'User', anything, anything, anything)
233+
234+
get :login_dot_gov
235+
236+
expect(response).to redirect_to('/admin/users')
237+
existing_user.reload
238+
expect(existing_user.provider).to eq('login_dot_gov')
239+
expect(existing_user.uid).to eq('12352')
240+
expect(controller.current_user).to eq(existing_user)
241+
end
242+
end
243+
244+
context 'with existing user already having login.gov credentials' do
245+
let!(:existing_user) do
246+
FactoryBot.create(:user, email: 'returning@example.gov', provider: 'login_dot_gov', uid: '12353')
247+
end
248+
let(:auth_hash) do
249+
OmniAuth::AuthHash.new(
250+
provider: 'login_dot_gov',
251+
uid: '12353',
252+
info: {
253+
email: 'returning@example.gov',
254+
email_verified: true
255+
}
256+
)
257+
end
258+
259+
before do
260+
request.env['omniauth.auth'] = auth_hash
261+
session['user_return_to'] = '/admin/cx_collections'
262+
end
263+
264+
it 'successfully logs in the existing user without creating a new one' do
265+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_attempt], 'Event::Generic', 1, anything)
266+
expect(Event).to receive(:log_event).with(Event.names[:user_authentication_successful], 'User', existing_user.id, anything, existing_user.id)
267+
268+
expect do
269+
get :login_dot_gov
270+
end.not_to change(User, :count)
271+
272+
expect(response).to redirect_to('/admin/cx_collections')
273+
expect(controller.current_user).to eq(existing_user)
274+
end
275+
end
276+
end
277+
end

0 commit comments

Comments
 (0)