Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 3 additions & 8 deletions app/frontend/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,13 @@
color: #374151; /* gray-700 */
}

/* Hide Chrome / Edge / Safari (macOS + iOS) password reveal icon */
input[type="password"]::-webkit-textfield-decoration-container {
visibility: hidden;
pointer-events: none;
width: 0;
height: 0;
/* Hide Chrome / Edge / Safari built-in password reveal icon (we use our own toggle) */
input[type="password"]::-ms-reveal {
display: none;
}

input[type="password"]::-webkit-credentials-auto-fill-button {
display: none !important;
visibility: hidden;
pointer-events: none;
}

/* Safari fix: Ensure password field has proper height */
Expand Down
2 changes: 1 addition & 1 deletion app/views/users/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<!-- Confirmed -->
<td class="px-4 py-2 text-center">
<% if user.confirmed_at.present? %>
<span class="fa fa-check-circle text-green-500" title="Confirmed <%= l(user.confirmed_at, format: :long) %>"></span>
<span class="fa fa-check-circle text-gray-400" title="Confirmed <%= l(user.confirmed_at, format: :long) %>"></span>
<% elsif user.welcome_instructions_sent_at.present? %>
<% sent_info = "Confirmation instructions sent #{l(user.welcome_instructions_sent_at, format: :long)}"
sent_info += " by #{user.updated_by.name}" if user.updated_by.present? %>
Expand Down
2 changes: 1 addition & 1 deletion config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
config.unlock_in = nil

# Warn on the last attempt before the account is locked.
# config.last_attempt_warning = true
config.last_attempt_warning = true

# ==> Configuration for :recoverable
#
Expand Down
8 changes: 4 additions & 4 deletions config/locales/devise.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ en:
send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
failure:
already_authenticated: "You are already signed in."
inactive: "Your account is currently inactive. Please email %{organization_name} to find out more."
invalid: "Invalid %{authentication_keys} or password."
locked: "Your account is locked."
inactive: "Invalid email or password. Please contact us for assistance."
invalid: "Invalid email or password. Please contact us for assistance."
locked: "Please contact us for assistance."
last_attempt: "You have one more attempt before your account is locked."
not_found_in_database: "Invalid %{authentication_keys} or password."
not_found_in_database: "Invalid email or password. Please contact us for assistance."
timeout: "Your session expired. Please sign in again to continue."
unauthenticated: "You need to sign in or sign up before continuing."
unconfirmed: "You have to confirm your email address before continuing."
Expand Down
106 changes: 80 additions & 26 deletions spec/system/login_spec.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,80 @@
# require "rails_helper"
#
# RSpec.describe "User login", type: :system do
# let(:user) { create(:user) }
#
# it "shows default avatar when logged out" do
# visit root_path
# expect(page).to_not have_css("#avatar")
# expect(page).to_not have_css("img[src*='missing.png']")
# end
#
# scenario "User login shows avatar only after login" do
# visit root_path
#
# # Logged out state
# expect(page).not_to have_css("#avatar")
#
# # Log in
# sign_in user
# visit root_path
#
# # Logged in state
# expect(page).to have_css("#avatar")
# expect(page).to have_css("#avatar-image")
# end
# end
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "User login", type: :system do
let(:password) { "MyString" }
let(:generic_error) { "Invalid email or password. Please contact us for assistance." }

def fill_in_login(email, password)
visit new_user_session_path
fill_in "user_email", with: email
fill_in "user_password", with: password
click_button "Log in"
end

context "when user is locked" do
let(:user) { create(:user, :locked, password: password) }

it "does not allow login and shows locked message" do
fill_in_login(user.email, password)

expect(page).to have_current_path(new_user_session_path)
expect(page).to have_content("Please contact us for assistance.")
end
end

context "when user is inactive" do
let(:user) { create(:user, password: password, inactive: true) }

it "does not allow login and shows generic error" do
fill_in_login(user.email, password)

expect(page).to have_current_path(new_user_session_path)
expect(page).to have_content(generic_error)
end
end

context "when user exceeds maximum failed login attempts" do
let(:user) { create(:user, password: password) }
let(:last_attempt_warning) { "You have one more attempt before your account is locked" }

it "warns on the last attempt then locks the account" do
9.times do
fill_in_login(user.email, "wrong_password")
end

expect(page).to have_content(last_attempt_warning)

fill_in_login(user.email, "wrong_password")

fill_in_login(user.email, password)

expect(page).to have_current_path(new_user_session_path)
expect(page).to have_content("Please contact us for assistance.")
expect(user.reload.locked_at).to be_present
end
end

context "when credentials are wrong" do
let(:user) { create(:user, password: password) }

it "shows generic error" do
fill_in_login(user.email, "wrong_password")

expect(page).to have_current_path(new_user_session_path)
expect(page).to have_content(generic_error)
end
end

context "when user is active and unlocked" do
let(:user) { create(:user, password: password) }

it "allows login successfully" do
fill_in_login(user.email, password)

expect(page).not_to have_content(generic_error)
expect(page).to have_css("#avatar")
end
end
end