DEV: Remove redundant admin_login route, share with email_login

This commit is contained in:
Martin Brennan 2020-01-13 12:10:07 +10:00
parent d50eb82d51
commit 9e399b42b9
25 changed files with 55 additions and 318 deletions

View File

@ -1,46 +0,0 @@
import { getWebauthnCredential } from "discourse/lib/webauthn";
export default function() {
document.getElementById(
"activate-security-key-alternative"
).onclick = function() {
document.getElementById("second-factor-forms").style.display = "block";
document.getElementById("primary-security-key-form").style.display = "none";
};
document.getElementById("submit-security-key").onclick = function(e) {
e.preventDefault();
getWebauthnCredential(
document.getElementById("security-key-challenge").value,
document
.getElementById("security-key-allowed-credential-ids")
.value.split(","),
credentialData => {
document.getElementById(
"security-key-credential"
).value = JSON.stringify(credentialData);
e.target.parentElement.submit();
},
errorMessage => {
document.getElementById("security-key-error").innerText = errorMessage;
}
);
};
const useTotp = I18n.t("login.second_factor_toggle.totp");
const useBackup = I18n.t("login.second_factor_toggle.backup_code");
const backupForm = document.getElementById("backup-second-factor-form");
const primaryForm = document.getElementById("primary-second-factor-form");
document.getElementById("toggle-form").onclick = function(event) {
event.preventDefault();
if (backupForm.style.display === "none") {
backupForm.style.display = "block";
primaryForm.style.display = "none";
document.getElementById("toggle-form").innerHTML = useTotp;
} else {
backupForm.style.display = "none";
primaryForm.style.display = "block";
document.getElementById("toggle-form").innerHTML = useBackup;
}
};
}

View File

@ -1 +0,0 @@
require("admin-login/admin-login").default();

View File

@ -345,11 +345,14 @@ class SessionController < ApplicationController
end
def email_login_info
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
token = params[:token]
matched_token = EmailToken.confirmable(token)
if !SiteSetting.enable_local_logins_via_email &&
!matched_token.user.admin? # admin-login uses this route, so allow them even if disabled
raise Discourse::NotFound
end
if matched_token
response = {
can_login: true,
@ -382,13 +385,17 @@ class SessionController < ApplicationController
end
def email_login
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
second_factor_token = params[:second_factor_token]
second_factor_method = params[:second_factor_method].to_i
security_key_credential = params[:security_key_credential]
token = params[:token]
matched_token = EmailToken.confirmable(token)
if !SiteSetting.enable_local_logins_via_email &&
!matched_token&.user&.admin? # admin-login uses this route, so allow them even if disabled
raise Discourse::NotFound
end
if security_key_credential.present?
if matched_token&.user&.security_keys_enabled?
security_key_valid = ::Webauthn::SecurityKeyAuthenticationService.new(

View File

@ -684,89 +684,12 @@ class UsersController < ApplicationController
else
@message = I18n.t("admin_login.errors.unknown_email_address")
end
elsif (token = params[:token]).present?
valid_token = EmailToken.valid_token_format?(token)
if valid_token
if params[:second_factor_token].present?
RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
end
email_token_user = EmailToken.confirmable(token)&.user
totp_enabled = email_token_user&.totp_enabled?
security_keys_enabled = email_token_user&.security_keys_enabled?
second_factor_token = params[:second_factor_token]
second_factor_method = params[:second_factor_method].to_i
confirm_email = false
@security_key_required = security_keys_enabled
if security_keys_enabled && params[:security_key_credential].blank?
Webauthn.stage_challenge(email_token_user, secure_session)
challenge_and_credentials = Webauthn.allowed_credentials(email_token_user, secure_session)
@security_key_challenge = challenge_and_credentials[:challenge]
@security_key_allowed_credential_ids = challenge_and_credentials[:allowed_credential_ids].join(",")
end
if security_keys_enabled && params[:security_key_credential].present?
credential = JSON.parse(params[:security_key_credential]).with_indifferent_access
confirm_email = ::Webauthn::SecurityKeyAuthenticationService.new(
email_token_user,
credential,
challenge: Webauthn.challenge(email_token_user, secure_session),
rp_id: Webauthn.rp_id(email_token_user, secure_session),
origin: Discourse.base_url
).authenticate_security_key
@message = I18n.t('login.security_key_invalid') if !confirm_email
elsif security_keys_enabled && second_factor_token.blank?
confirm_email = false
@message = I18n.t("login.second_factor_title")
if totp_enabled
@second_factor_required = true
@backup_codes_enabled = true
end
else
confirm_email =
if totp_enabled
@second_factor_required = true
@backup_codes_enabled = true
@message = I18n.t("login.second_factor_title")
if second_factor_token.present?
if email_token_user.authenticate_second_factor(second_factor_token, second_factor_method)
true
else
@error = I18n.t("login.invalid_second_factor_code")
false
end
end
else
true
end
end
if confirm_email
@user = EmailToken.confirm(token)
if @user && @user.admin?
log_on_user(@user)
return redirect_to path("/")
else
@message = I18n.t("admin_login.errors.unknown_email_address")
end
end
else
@message = I18n.t("admin_login.errors.invalid_token")
end
end
render layout: 'no_ember'
rescue RateLimiter::LimitExceeded
@message = I18n.t("rate_limiter.slow_down")
render layout: 'no_ember'
rescue ::Webauthn::SecurityKeyError => err
@message = err.message
render layout: 'no_ember'
end
def email_login

View File

@ -1,61 +1,10 @@
<% if @message %>
<%= @message %>
<% if @error %><p><%= @error %></p><% end %>
<% if @security_key_required %>
<div id="primary-security-key-form">
<div id="security-key-error"></div>
<%= hidden_field_tag 'security_key_challenge', @security_key_challenge, id: 'security-key-challenge' %>
<%= hidden_field_tag 'security_key_allowed_credential_ids', @security_key_allowed_credential_ids, id: 'security-key-allowed-credential-ids' %>
<%=form_tag({}, method: :put) do %>
<p><strong><%= t('login.security_key_authenticate') %></strong></p>
<p><%= t('login.security_key_description') %></p>
<%= hidden_field_tag 'second_factor_method', '3' %>
<%= hidden_field_tag 'security_key_credential', nil, id: 'security-key-credential' %>
<% if @second_factor_required %>
<%= link_to t('login.security_key_alternative'), '#', id: 'activate-security-key-alternative' %><br/><br/>
<% end %>
<%= button_tag t('login.security_key_authenticate'), id: 'submit-security-key' %>
<% end %>
</div>
<% end %>
<% if @second_factor_required %>
<div id="second-factor-forms" style="<%= @security_key_required ? 'display: none' : '' %>">
<div id="primary-second-factor-form">
<%=form_tag({}, method: :put) do %>
<br/>
<%= label_tag(:second_factor_token, t('login.second_factor_description')) %>
<%= render 'common/second_factor_text_field' %><br><br>
<%= submit_tag t('submit')%>
<% end %>
</div>
<%if @backup_codes_enabled%>
<div id="backup-second-factor-form" style="display: none">
<%= form_tag({}, method: :put) do%>
<%= label_tag(:second_factor_token, t("login.second_factor_backup_description")) %>
<%= render 'common/second_factor_backup_input' %><br><br>
<%= submit_tag(t("submit")) %>
<%end%>
</div>
<a href id="toggle-form"><%=t "login.second_factor_backup" %></a>
<%end%>
</div>
<% end %>
<% else %>
<%=form_tag({}, method: :put) do %>
<%= label_tag(:email, t('admin_login.email_input')) %>
<%= text_field_tag(:email, nil, autofocus: true) %><br><br>
<%= submit_tag t('admin_login.submit_button'), class: "btn btn-primary" %>
<% end %>
<% end %>
<%= preload_script "ember_jquery" %>
<%= preload_script "locales/#{I18n.locale}" %>
<%= preload_script "locales/i18n" %>
<%= preload_script "discourse/lib/webauthn" %>
<%= preload_script "admin-login/admin-login" %>
<%= preload_script "admin-login/admin-login.no-module" %>
<% end %>

View File

@ -2761,7 +2761,7 @@ ca:
admin_login:
title: "Inicia sessió d'administració"
subject_template: "[%{email_prefix}] Inici de sessió"
text_body_template: "Algú ha demanat iniciar sessió en el vostre compte en [%{site_name}](%{base_url}). \n\nSi no heu fet aquesta sol·licitud, podeu ignorar aquest correu de manera segura. \n\nFeu clic en l'enllaç següent per a iniciar la sessió: \n%{base_url}/u/admin-login/%{email_token}\n"
text_body_template: "Algú ha demanat iniciar sessió en el vostre compte en [%{site_name}](%{base_url}). \n\nSi no heu fet aquesta sol·licitud, podeu ignorar aquest correu de manera segura. \n\nFeu clic en l'enllaç següent per a iniciar la sessió: \n%{base_url}/session/email-login/%{email_token}\n"
account_created:
title: "Compte creat"
subject_template: "[%{email_prefix}] El vostre compte nou"

View File

@ -3008,7 +3008,7 @@ de:
Wenn du diese Anfrage nicht gestellt hast, kannst du diese E-Mail einfach ignorieren.
Folge diesem Link, um dich anzumelden:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Konto erstellt"
subject_template: "[%{email_prefix}] Dein neues Konto"

View File

@ -2063,7 +2063,7 @@ el:
Κάντε κλικ στον παρακάτω σύνδεσμο για να συνδεθείτε:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Ο Λογαριασμός Δημιουργήθηκε"
subject_template: "[%{email_prefix}] Ο νέος σας Λογαριασμός"

View File

@ -3563,7 +3563,7 @@ en:
If you did not make this request, you can safely ignore this email.
Click the following link to login:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Account Created"

View File

@ -3166,7 +3166,7 @@ es:
Si tú no realizaste esta petición, puedes ignorar este email.
Haz clic en el siguiente enlace para iniciar sesión:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Cuenta creada"
subject_template: "[%{email_prefix}] Tu nueva cuenta"

View File

@ -1877,7 +1877,7 @@ fa_IR:
اگر شما این درخواست را نداده‌اید، می‌توانید این ایمیل را نادیده بگیرید.
برای ورود روی لینک کلیک کنید:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "حساب کاربری ایجاد شد"
subject_template: "[%{email_prefix}] حساب کاربری جدید شما"

View File

@ -2895,7 +2895,7 @@ fi:
Jos et ole pyynnön taustalla, voit huoletta jättää tämän viestin huomiotta.
Kirjaudu sisään klikkaamalla linkkiä:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Tili luotu"
subject_template: "[%{email_prefix}] Uusi käyttäjätilisi"

View File

@ -3071,7 +3071,7 @@ fr:
Cliquez sur le lien ci-dessous pour vous identifier :
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Compte Créé"
subject_template: "[%{email_prefix}] Votre Nouveau Compte"

View File

@ -3278,7 +3278,7 @@ he:
אם לא אתם ביקשתם זאת, אתם יכולים פשוט להתעלם ממייל זה.
לחצו על הקישור הבא כדי להתחבר:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "חשבון נוצר"
subject_template: "[%{email_prefix}] החשבון החדש שלך"

View File

@ -2681,7 +2681,7 @@ hy:
Եթե Դուք չեք խնդրել, Դուք հանգիստ կարող եք անտեսել այս նամակը:
Սեղմեք հետևյալ հղմանը՝ մուտք գործելու համար՝
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Ստեղծված Հաշիվ"
subject_template: "[%{email_prefix}] Ձեր Նոր Հաշիվը"

View File

@ -2845,7 +2845,7 @@ it:
Se non sei stato tu a fare questa richiesta, puoi tranquillamente ignorare questa email.
Clicca sul seguente collegamento per connetterti:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Account Creato"
subject_template: "[%{email_prefix}] Il Tuo Nuovo Account"

View File

@ -2347,7 +2347,7 @@ pl_PL:
Jeśli to nie byłeś ty, możesz po prostu zignorować tą wiadomość.
Naciśnij następujący link, aby się zalogować:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Utworzono Konto"
subject_template: "[%{site_name}] Twoje Nowe Konto"

View File

@ -2914,7 +2914,7 @@ pt_BR:
Se você não fez essa solicitação, pode ignorar esse e-mail com segurança.
Clique no link a seguir para fazer o login:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Conta Criada"
subject_template: "[%{email_prefix}] Sua Nova Conta"

View File

@ -1641,7 +1641,7 @@ sl:
Če niste vi podali te zahteve, lahko zanemarite to e-sporočilo.
Za prijavo sledite povezavi:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "Račun ustvarjen"
subject_template: "[%{email_prefix}] Vaš nov uporabniški račun"

View File

@ -3013,7 +3013,7 @@ ur:
اگر آپ نے یہ درخواست نہیں کی، تو آپ اِس ای میل کو آرام سے نظر انداز کر سکتے ہیں۔
لاگ اِن کرنے کیلئے مندرجہ ذیل لِنک پر کلِک کریں:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "اکاؤنٹ تشکیل"
subject_template: "[%{email_prefix}] آپ کا نیا اکاؤنٹ"

View File

@ -3019,7 +3019,7 @@ zh_CN:
如果你没有提出此请求,你可以安全地忽略此邮件。
点击以下链接登录:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "账户已创建"
subject_template: "[%{email_prefix}] 你的新账户"

View File

@ -2790,7 +2790,7 @@ zh_TW:
如果你沒有做此請求,你可以直接忽略本郵件。
點擊下面的連結以登入:
%{base_url}/u/admin-login/%{email_token}
%{base_url}/session/email-login/%{email_token}
account_created:
title: "建立使用者"
subject_template: "[%{email_prefix}] 你的新帳號"

View File

@ -393,8 +393,6 @@ Discourse::Application.routes.draw do
post "#{root_path}/email-login" => "users#email_login"
get "#{root_path}/admin-login" => "users#admin_login"
put "#{root_path}/admin-login" => "users#admin_login"
get "#{root_path}/admin-login/:token" => "users#admin_login"
put "#{root_path}/admin-login/:token" => "users#admin_login"
post "#{root_path}/toggle-anon" => "users#toggle_anon"
post "#{root_path}/read-faq" => "users#read_faq"
get "#{root_path}/search/users" => "users#search_users"

View File

@ -20,6 +20,19 @@ RSpec.describe SessionController do
SiteSetting.enable_local_logins_via_email = true
end
context "when local logins via email disabled" do
before { SiteSetting.enable_local_logins_via_email = false }
it "only works for admins" do
get "/session/email-login/#{email_token.token}.json"
expect(response.status).to eq(404)
user.update(admin: true)
get "/session/email-login/#{email_token.token}.json"
expect(response.status).to eq(200)
end
end
context 'missing token' do
it 'returns the right response' do
get "/session/email-login"
@ -93,6 +106,20 @@ RSpec.describe SessionController do
SiteSetting.enable_local_logins_via_email = true
end
context "when local logins via email disabled" do
before { SiteSetting.enable_local_logins_via_email = false }
it "only works for admins" do
post "/session/email-login/#{email_token.token}.json"
expect(response.status).to eq(404)
user.update(admin: true)
post "/session/email-login/#{email_token.token}.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(user.id)
end
end
context 'missing token' do
it 'returns the right response' do
post "/session/email-login"

View File

@ -537,126 +537,6 @@ describe UsersController do
expect(response_body).to_not match(I18n.t("login.second_factor_description"))
end
end
context 'logs in admin' do
it 'does not log in admin with invalid token' do
SiteSetting.sso_url = "https://www.example.com/sso"
SiteSetting.enable_sso = true
get "/u/admin-login/invalid"
expect(session[:current_user_id]).to be_blank
end
context 'valid token' do
it 'does log in admin with SSO disabled' do
SiteSetting.enable_sso = false
token = admin.email_tokens.create(email: admin.email).token
get "/u/admin-login/#{token}"
expect(response).to redirect_to('/')
expect(session[:current_user_id]).to eq(admin.id)
end
it 'logs in admin with SSO enabled' do
SiteSetting.sso_url = "https://www.example.com/sso"
SiteSetting.enable_sso = true
token = admin.email_tokens.create(email: admin.email).token
get "/u/admin-login/#{token}"
expect(response).to redirect_to('/')
expect(session[:current_user_id]).to eq(admin.id)
end
end
describe 'when 2 factor authentication is enabled' do
fab!(:second_factor) { Fabricate(:user_second_factor_totp, user: admin) }
fab!(:email_token) { Fabricate(:email_token, user: admin) }
it 'does not log in when token required' do
second_factor
get "/u/admin-login/#{email_token.token}"
expect(response).not_to redirect_to('/')
expect(session[:current_user_id]).not_to eq(admin.id)
expect(response.body).to include(I18n.t('login.second_factor_description'))
end
describe 'invalid 2 factor token' do
it 'should display the right error' do
second_factor
put "/u/admin-login/#{email_token.token}", params: {
second_factor_token: '13213',
second_factor_method: UserSecondFactor.methods[:totp]
}
expect(response.status).to eq(200)
expect(response.body).to include(I18n.t('login.second_factor_description'))
expect(response.body).to include(I18n.t('login.invalid_second_factor_code'))
end
end
it 'logs in when a valid 2-factor token is given' do
put "/u/admin-login/#{email_token.token}", params: {
second_factor_token: ROTP::TOTP.new(second_factor.data).now,
second_factor_method: UserSecondFactor.methods[:totp]
}
expect(response).to redirect_to('/')
expect(session[:current_user_id]).to eq(admin.id)
end
end
describe 'when security key authentication required' do
fab!(:email_token) { Fabricate(:email_token, user: admin) }
let!(:security_key) do
Fabricate(
:user_security_key,
user: admin,
credential_id: valid_security_key_data[:credential_id],
public_key: valid_security_key_data[:public_key]
)
end
before do
simulate_localhost_webauthn_challenge
# store challenge in secure session by visiting the admin login page
get "/u/admin-login/#{email_token.token}"
end
it 'does not log in when token required' do
expect(response).not_to redirect_to('/')
expect(session[:current_user_id]).not_to eq(admin.id)
expect(response.body).to include(I18n.t('login.security_key_authenticate'))
end
describe 'invalid security key' do
it 'should display the right error' do
put "/u/admin-login/#{email_token.token}", params: {
security_key_credential: {
signature: 'bad',
clientData: 'bad',
authenticatorData: 'bad',
credentialId: 'bad'
}.to_json,
second_factor_method: UserSecondFactor.methods[:security_key]
}
expect(response.status).to eq(200)
expect(response.body).to include(I18n.t('webauthn.validation.not_found_error'))
end
end
it 'logs in when a valid security key is given' do
put "/u/admin-login/#{email_token.token}", params: {
security_key_credential: valid_security_key_auth_post_data.to_json,
second_factor_method: UserSecondFactor.methods[:security_key]
}
expect(response).to redirect_to('/')
expect(session[:current_user_id]).to eq(admin.id)
end
end
end
end
describe '#toggle_anon' do