Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix username enumeration vulnerability #2184

Merged
merged 10 commits into from
Jan 28, 2025
24 changes: 20 additions & 4 deletions app/controllers/api/v1/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def create
begin
duplicate_user = User.get_duplicate_user(resource.email, [])[:user]
user = resource
error = [
{
message: I18n.t(:email_exists)
}
]
if !duplicate_user.nil? && duplicate_user.invited_to_sign_up?
duplicate_user.accept_invitation_or_confirm
duplicate_user.password = resource.password
Expand All @@ -25,13 +30,24 @@ def create
resource.last_accepted_terms_at = Time.now
resource.save!
end

User.current = user
sign_up(resource_name, user)
render_success 'user', user
render_success user, 'user', 401, error
rescue ActiveRecord::RecordInvalid => e
clean_up_passwords resource
set_minimum_password_length
render_error e.message.gsub(/^Validation failed: Email /, ''), 'INVALID_VALUE'
# Check if the error is specifically related to the email being taken
if resource.errors.details[:email].any? { |email_error| email_error[:error] == :taken } && resource.errors.details.except(:email).empty?
# Treat as successful sign-up if only the email is taken
duplicate_user = User.get_duplicate_user(resource.email, [])[:user]
User.current = duplicate_user if duplicate_user
sign_up(resource_name, duplicate_user)
render_success nil, 'user', 401, error
else
# For other errors, show the error message in the form
clean_up_passwords resource
set_minimum_password_length
render_error e.message.gsub("Email #{I18n.t(:email_exists)}<br />", '').strip, 'INVALID_VALUE', 401
end
end
end

Expand Down
7 changes: 4 additions & 3 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ def add_info_to_trace

private

def render_success(type = 'success', object = nil)
def render_success(type = 'success', object = nil, status = 200, errors = nil)
json = { type: type }
json[:data] = object unless object.nil?
logger.info message: json, status: 200
render json: json, status: 200
json[:errors] = errors unless errors.nil?
logger.info message: json, status: status
render json: json, status: status
end

def render_error(message, code, status = 400)
Expand Down
2 changes: 1 addition & 1 deletion config/locales/ar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ ar:
account_exists: هذا الحساب موجود بالفعل
media_exists: هذا العنصر موجود بالفعل
source_exists: هذا المصدر موجود بالفعل
email_exists: ' مستخدم بالفعل'
email_exists: 'يرجى التحقق من بريدك الإلكتروني. إذا كان لا يوجد حساب مرتبط بهذا البريد الإلكتروني، فستتلقى رسالة تأكيد. إذا لم تستلم رسالة التأكيد، حاول إعادة تعيين كلمة المرور أو التواصل مع دعمنا.'
banned_user: عذراً ، لقد تم حظرك من %{app_name}. يرجى التواصل مع فريق الدعم إن كان هناك خطأ.
devise:
mailer:
Expand Down
8 changes: 6 additions & 2 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ en:
attributes:
slug:
slug_format: accepts only letters, numbers and hyphens
user:
attributes:
email:
taken: Please check your email. If an account with that email doesn’t exist, you should have received a confirmation email. If you don’t receive a confirmation e-mail, try to reset your password or get in touch with our support.
messages:
record_invalid: "%{errors}"
improbable_phone: is an invalid number
Expand Down Expand Up @@ -308,7 +312,7 @@ en:
account_exists: This account already exists
media_exists: This item already exists
source_exists: This source already exists
email_exists: has already been taken
email_exists: Please check your email. If an account with that email doesn’t exist, you should have received a confirmation email. If you don’t receive a confirmation e-mail, try to reset your password or get in touch with our support.
error_password_not_strong: "Complexity requirement not met. Length should be 8-70 characters and include at least: 1 uppercase, 1 lowercase, 1 digit and 1 special character"
banned_user: Sorry, your account has been banned from %{app_name}. Please contact
the support team if you think this is an error.
Expand All @@ -335,7 +339,7 @@ en:
ignore: If you don't want to accept the invitation, please ignore this email.
app_team: "%{app} Workspace"
failure:
unconfirmed: Please check your email to verify your account.
unconfirmed: Please check your email. If an account with that email doesn’t exist, you should have received a confirmation email. If you don’t receive a confirmation e-mail, try to reset your password or get in touch with our support.
user_invitation:
invited: "%{email} was already invited to this workspace."
member: "%{email} is already a member."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ es:
account_exists: Esta cuenta ya existe
media_exists: Este ítem ya existe
source_exists: Esta fuente ya existe
email_exists: ya está en uso
email_exists: Por favor, revise su correo electrónico. Si no existe una cuenta con ese correo, debería haber recibido un correo de confirmación. Si no recibe un correo de confirmación, intente restablecer su contraseña o póngase en contacto con nuestro soporte.
banned_user: Lo sentimos, tu cuenta ha sido suspendida de %{app_name}. Por favor comunícate con nuestro equipo de soporte técnico si crees que ha sido un error.
devise:
mailer:
Expand Down
2 changes: 1 addition & 1 deletion config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ fr:
account_exists: Ce compte existe déjà
media_exists: Cet élément existe déjà
source_exists: Cette source existe déjà
email_exists: est déjà utilisée
email_exists: Veuillez vérifier votre courriel. Si aucun compte n'existe avec ce courriel, vous devriez avoir reçu un courriel de confirmation. Si vous ne recevez pas de courriel de confirmation, essayez de réinitialiser votre mot de passe ou contactez notre support.
banned_user: Désolé, votre compte a été banni de %{app_name}. Contactez l’équipe d’assistance si vous pensez que c’est une erreur.
devise:
mailer:
Expand Down
2 changes: 1 addition & 1 deletion config/locales/id.yml
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ id:
account_exists: Akun ini sudah ada
media_exists: Perihal ini sudah ada
source_exists: Sumber ini sudah ada
email_exists: sudah diambil
email_exists: Silakan periksa email Anda. Jika tidak ada akun dengan email tersebut, Anda seharusnya menerima email konfirmasi. Jika Anda tidak menerima email konfirmasi, coba atur ulang kata sandi Anda atau hubungi dukungan kami.
banned_user: 'Maaf, akun Anda telah dilarang dari %{app_name}. Mohon hubungi tim dukungan jika Anda merasa ini adalah sebuah kesalahan. '
devise:
mailer:
Expand Down
2 changes: 1 addition & 1 deletion config/locales/mk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ mk:
account_exists: Оваа сметка веќе постои
media_exists: Оваа статија веќе постои
source_exists: Овој извор веќе постои
email_exists: веќе е во употреба
email_exists: Ве молиме проверете ја вашата е-пошта. Ако не постои сметка со таа е-пошта, треба да добиете потврден е-маил. Ако не добиете потврден е-маил, обидете се да ја ресетирате вашата лозинка или контактирајте ја нашата поддршка.
banned_user: За жал, Вашата сметка има забрана за %{app_name}. Стапете во контакт со тимот за поддршка доколку сметате дека станува збор за грешка.
devise:
mailer:
Expand Down
2 changes: 1 addition & 1 deletion config/locales/pt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ pt:
account_exists: Esta conta já existe
media_exists: Esse item já existe
source_exists: Essa fonte já existe
email_exists: já está em uso
email_exists: Por favor, verifique seu e-mail. Se não existir uma conta com esse e-mail, você deve ter recebido um e-mail de confirmação. Se você não receber um e-mail de confirmação, tente redefinir sua senha ou entre em contato com nosso suporte.
banned_user: Desculpe, sua conta foi banida do %{app_name}. Por favor, entre em contato com a equipe de suporte se você achar que isso é um erro.
devise:
mailer:
Expand Down
2 changes: 1 addition & 1 deletion doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ Use this method in order to create a new user account
{
"errors": [
{
"message": "Please check your email to verify your account.",
"message": "Please check your email. If an account with that email doesn’t exist, you should have received a confirmation email. If you don’t receive a confirmation e-mail, try to reset your password or get in touch with our support.",
"code": 1,
"data": {
}
Expand Down
35 changes: 28 additions & 7 deletions test/controllers/registrations_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def teardown
p1 = random_complex_password
assert_no_difference 'User.count' do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: email, login: 'test', name: 'Test' } }
assert_response :success
assert_response 401
end
end

Expand All @@ -45,7 +45,7 @@ def teardown
User.any_instance.stubs(:confirmation_required?).returns(false)
assert_difference 'User.count' do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: '[email protected]', login: 'test', name: 'Test' } }
assert_response :success
assert_response 401
end
User.any_instance.unstub(:confirmation_required?)
end
Expand All @@ -54,31 +54,31 @@ def teardown
p1 = random_complex_password
assert_no_difference 'User.count' do
post :create, params: { api_user: { password_confirmation: p1, email: '[email protected]', login: 'test', name: 'Test' } }
assert_response 400
assert_response 401
end
end

test "should not create user if password is too short" do
p1 = '1234'
assert_no_difference 'User.count' do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: '[email protected]', login: 'test', name: 'Test' } }
assert_response 400
assert_response 401
end
end

test "should not create user if password don't match" do
p1 = random_complex_password
assert_no_difference 'User.count' do
post :create, params: { api_user: { password: random_complex_password, password_confirmation: random_complex_password, email: '[email protected]', login: 'test', name: 'Test' } }
assert_response 400
assert_response 401
end
end

test "should not create user if email is not present" do
p1 = random_complex_password
assert_no_difference 'User.count' do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: '', login: 'test', name: 'Test' } }
assert_response 400
assert_response 401
end
end

Expand All @@ -94,7 +94,7 @@ def teardown
p1 = random_complex_password
assert_no_difference 'User.count' do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: '[email protected]', login: 'test', name: '' } }
assert_response 400
assert_response 401
end
end

Expand Down Expand Up @@ -143,4 +143,25 @@ def teardown
end
assert_response 401
end

test "should return generic response in case of error when registering using an existing email" do
existing_user = create_user(email: '[email protected]')
p1 = random_complex_password

assert_no_difference 'User.count' do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: existing_user.email, login: 'test', name: 'Test' } }
assert_response 401
assert_equal 'Please check your email. If an account with that email doesn’t exist, you should have received a confirmation email. If you don’t receive a confirmation e-mail, try to reset your password or get in touch with our support.', response.parsed_body.dig("errors", 0, "message")
end
end

test "should return generic response when registering with non-existing email" do
p1 = random_complex_password

assert_difference 'User.count', 1 do
post :create, params: { api_user: { password: p1, password_confirmation: p1, email: '[email protected]', login: 'test', name: 'Test' } }
assert_response 401
assert_equal 'Please check your email. If an account with that email doesn’t exist, you should have received a confirmation email. If you don’t receive a confirmation e-mail, try to reset your password or get in touch with our support.', JSON.parse(response.parsed_body).dig("errors", 0, "message")
end
end
end
Loading