Revert "Merge pull request from GHSA-hv9p-jfm4-gpr9"
This reverts commit b8340c6c8e
.
This commit is contained in:
parent
b8340c6c8e
commit
5f6f707080
|
@ -1,29 +0,0 @@
|
||||||
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import DiscourseURL from "discourse/lib/url";
|
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
|
||||||
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
|
|
||||||
lockImageUrl: Discourse.getURL("/images/lock.svg"),
|
|
||||||
actions: {
|
|
||||||
finishLogin() {
|
|
||||||
ajax({
|
|
||||||
url: `/session/email-login/${this.model.token}`,
|
|
||||||
type: "POST",
|
|
||||||
data: {
|
|
||||||
second_factor_token: this.secondFactorToken,
|
|
||||||
second_factor_method: this.secondFactorMethod
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
if (result.success) {
|
|
||||||
DiscourseURL.redirectTo("/");
|
|
||||||
} else {
|
|
||||||
this.set("model.error", result.error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(popupAjaxError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -177,7 +177,6 @@ export default function() {
|
||||||
});
|
});
|
||||||
this.route("signup", { path: "/signup" });
|
this.route("signup", { path: "/signup" });
|
||||||
this.route("login", { path: "/login" });
|
this.route("login", { path: "/login" });
|
||||||
this.route("email-login", { path: "/session/email-login/:token" });
|
|
||||||
this.route("login-preferences");
|
this.route("login-preferences");
|
||||||
this.route("forgot-password", { path: "/password-reset" });
|
this.route("forgot-password", { path: "/password-reset" });
|
||||||
this.route("faq", { path: "/faq" });
|
this.route("faq", { path: "/faq" });
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
|
|
||||||
export default Discourse.Route.extend({
|
|
||||||
titleToken() {
|
|
||||||
return I18n.t("login.title");
|
|
||||||
},
|
|
||||||
|
|
||||||
model(params) {
|
|
||||||
return ajax(`/session/email-login/${params.token}`);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,33 +0,0 @@
|
||||||
<div class="container email-login clearfix">
|
|
||||||
<div class="pull-left col-image">
|
|
||||||
<img src={{lockImageUrl}} class="password-reset-img">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pull-left col-form">
|
|
||||||
<form>
|
|
||||||
{{#if model.error}}
|
|
||||||
<div class='alert alert-error'>
|
|
||||||
{{model.error}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if model.can_login}}
|
|
||||||
{{#if model.second_factor_required}}
|
|
||||||
{{#second-factor-form
|
|
||||||
secondFactorMethod=secondFactorMethod
|
|
||||||
secondFactorToken=secondFactorToken
|
|
||||||
backupEnabled=model.backup_codes_enabled
|
|
||||||
isLogin=true}}
|
|
||||||
{{second-factor-input value=secondFactorToken secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
|
|
||||||
{{/second-factor-form}}
|
|
||||||
{{else}}
|
|
||||||
<h2>{{i18n "email_login.confirm_title" site_name=siteSettings.title}}</h2>
|
|
||||||
<p>{{i18n "email_login.logging_in_as" email=model.token_email}}</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{d-button label="email_login.confirm_button" action=(action "finishLogin") class="btn-primary"}}
|
|
||||||
{{/if}}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -267,7 +267,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-reset,
|
.password-reset,
|
||||||
.email-login,
|
|
||||||
.invites-show {
|
.invites-show {
|
||||||
.col-form {
|
.col-form {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
@ -283,8 +282,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-reset,
|
.password-reset {
|
||||||
.email-login {
|
|
||||||
.col-form {
|
.col-form {
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-reset,
|
.password-reset,
|
||||||
.email-login,
|
|
||||||
.invites-show {
|
.invites-show {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
.col-image {
|
.col-image {
|
||||||
|
|
|
@ -11,10 +11,10 @@ class SessionController < ApplicationController
|
||||||
render body: nil, status: 500
|
render body: nil, status: 500
|
||||||
end
|
end
|
||||||
|
|
||||||
before_action :check_local_login_allowed, only: %i(create forgot_password email_login email_login_info)
|
before_action :check_local_login_allowed, only: %i(create forgot_password email_login)
|
||||||
before_action :rate_limit_login, only: %i(create email_login)
|
before_action :rate_limit_login, only: %i(create email_login)
|
||||||
skip_before_action :redirect_to_login_if_required
|
skip_before_action :redirect_to_login_if_required
|
||||||
skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login sso_provider destroy one_time_password)
|
skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login sso_provider destroy email_login one_time_password)
|
||||||
|
|
||||||
ACTIVATE_USER_KEY = "activate_user"
|
ACTIVATE_USER_KEY = "activate_user"
|
||||||
|
|
||||||
|
@ -305,79 +305,49 @@ class SessionController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_login_info
|
|
||||||
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
|
|
||||||
|
|
||||||
token = params[:token]
|
|
||||||
matched_token = EmailToken.confirmable(token)
|
|
||||||
|
|
||||||
if matched_token
|
|
||||||
response = {
|
|
||||||
can_login: true,
|
|
||||||
token: token,
|
|
||||||
token_email: matched_token.email
|
|
||||||
}
|
|
||||||
|
|
||||||
if matched_token.user&.totp_enabled?
|
|
||||||
response.merge!(
|
|
||||||
second_factor_required: true,
|
|
||||||
backup_codes_enabled: matched_token.user&.backup_codes_enabled?
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: response
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
can_login: false,
|
|
||||||
error: I18n.t('email_login.invalid_token')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def email_login
|
def email_login
|
||||||
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
|
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
|
||||||
second_factor_token = params[:second_factor_token]
|
second_factor_token = params[:second_factor_token]
|
||||||
second_factor_method = params[:second_factor_method].to_i
|
second_factor_method = params[:second_factor_method].to_i
|
||||||
token = params[:token]
|
token = params[:token]
|
||||||
matched_token = EmailToken.confirmable(token)
|
valid_token = !!EmailToken.valid_token_format?(token)
|
||||||
|
user = EmailToken.confirmable(token)&.user
|
||||||
|
|
||||||
if matched_token&.user&.totp_enabled?
|
if valid_token && user&.totp_enabled?
|
||||||
if !second_factor_token.present?
|
if !second_factor_token.present?
|
||||||
return render json: { error: I18n.t('login.invalid_second_factor_code') }
|
@second_factor_required = true
|
||||||
elsif !matched_token.user.authenticate_second_factor(second_factor_token, second_factor_method)
|
@backup_codes_enabled = true if user&.backup_codes_enabled?
|
||||||
|
return render layout: 'no_ember'
|
||||||
|
elsif !user.authenticate_second_factor(second_factor_token, second_factor_method)
|
||||||
RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
|
RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
|
||||||
return render json: { error: I18n.t('login.invalid_second_factor_code') }
|
@error = I18n.t('login.invalid_second_factor_code')
|
||||||
|
return render layout: 'no_ember'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if user = EmailToken.confirm(token)
|
if user = EmailToken.confirm(token)
|
||||||
if login_not_approved_for?(user)
|
if login_not_approved_for?(user)
|
||||||
return render json: login_not_approved
|
@error = login_not_approved[:error]
|
||||||
elsif payload = login_error_check(user)
|
elsif payload = login_error_check(user)
|
||||||
return render json: payload
|
@error = payload[:error]
|
||||||
else
|
else
|
||||||
log_on_user(user)
|
log_on_user(user)
|
||||||
return render json: success_json
|
return redirect_to path("/")
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
@error = I18n.t('email_login.invalid_token')
|
||||||
end
|
end
|
||||||
|
|
||||||
return render json: { error: I18n.t('email_login.invalid_token') }
|
render layout: 'no_ember'
|
||||||
end
|
end
|
||||||
|
|
||||||
def one_time_password
|
def one_time_password
|
||||||
@otp_username = otp_username = $redis.get "otp_#{params[:token]}"
|
otp_username = $redis.get "otp_#{params[:token]}"
|
||||||
|
|
||||||
if otp_username && user = User.find_by_username(otp_username)
|
if otp_username && user = User.find_by_username(otp_username)
|
||||||
if current_user&.username == otp_username
|
log_on_user(user)
|
||||||
$redis.del "otp_#{params[:token]}"
|
$redis.del "otp_#{params[:token]}"
|
||||||
return redirect_to path("/")
|
return redirect_to path("/")
|
||||||
elsif request.post?
|
|
||||||
log_on_user(user)
|
|
||||||
$redis.del "otp_#{params[:token]}"
|
|
||||||
return redirect_to path("/")
|
|
||||||
else
|
|
||||||
# Display the form
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
@error = I18n.t('user_api_key.invalid_token')
|
@error = I18n.t('user_api_key.invalid_token')
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<%if @error%>
|
||||||
|
<div class='alert alert-error'>
|
||||||
|
<%= @error %>
|
||||||
|
</div>
|
||||||
|
<%end%>
|
||||||
|
|
||||||
|
<%if @second_factor_required%>
|
||||||
|
<div id="simple-container">
|
||||||
|
<div id="primary-second-factor-form">
|
||||||
|
<%= form_tag(method: "post") do%>
|
||||||
|
<h2><%=t "login.second_factor_title" %></h2>
|
||||||
|
<%= label_tag(:second_factor_token, t("login.second_factor_description")) %>
|
||||||
|
<div><%= render 'common/second_factor_text_field' %></div>
|
||||||
|
<%= submit_tag(t("submit"), class: "btn btn-large btn-primary") %>
|
||||||
|
<%end%>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%if @backup_codes_enabled%>
|
||||||
|
<div id="backup-second-factor-form" style="display: none">
|
||||||
|
<%= form_tag(method: "post") do%>
|
||||||
|
<h2><%=t "login.second_factor_backup_title" %></h2>
|
||||||
|
<%= label_tag(:second_factor_token, t("login.second_factor_backup_description")) %>
|
||||||
|
<div><%= render 'common/second_factor_backup_input' %></div>
|
||||||
|
<%= submit_tag(t("submit"), class: "btn btn-large btn-primary") %>
|
||||||
|
<%end%>
|
||||||
|
</div>
|
||||||
|
<a href id="toggle-form"><%=t "login.second_factor_toggle.backup_code" %></a>
|
||||||
|
<%= render 'common/second_factor_form_script' %>
|
||||||
|
<%end%>
|
||||||
|
</div>
|
||||||
|
<%end%>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<% content_for :title do %><%=t "email_login.title" %><% end %>
|
||||||
|
|
||||||
|
<%- content_for(:no_ember_head) do %>
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<%= preload_script "ember_jquery" %>
|
||||||
|
<%= render_google_universal_analytics_code %>
|
||||||
|
<%- end %>
|
||||||
|
|
||||||
|
<%- content_for(:head) do %>
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<%- end %>
|
|
@ -2,10 +2,4 @@
|
||||||
<div class='alert alert-error'>
|
<div class='alert alert-error'>
|
||||||
<%= @error %>
|
<%= @error %>
|
||||||
</div>
|
</div>
|
||||||
<%else%>
|
|
||||||
<%= form_tag do%>
|
|
||||||
<h2><%= t("user_api_key.otp_confirmation.confirm_title", site_name: SiteSetting.title) %></h2>
|
|
||||||
<p><%= t("user_api_key.otp_confirmation.logging_in_as", username: @otp_username) %></p>
|
|
||||||
<%= submit_tag(t("user_api_key.otp_confirmation.confirm_button"), class: "btn btn-primary") %>
|
|
||||||
<%end%>
|
|
||||||
<%end%>
|
<%end%>
|
||||||
|
|
|
@ -1387,9 +1387,6 @@ en:
|
||||||
complete_email_found: "We found an account that matches <b>%{email}</b>, you should receive an email with a login link shortly."
|
complete_email_found: "We found an account that matches <b>%{email}</b>, you should receive an email with a login link shortly."
|
||||||
complete_username_not_found: "No account matches the username <b>%{username}</b>"
|
complete_username_not_found: "No account matches the username <b>%{username}</b>"
|
||||||
complete_email_not_found: "No account matches <b>%{email}</b>"
|
complete_email_not_found: "No account matches <b>%{email}</b>"
|
||||||
confirm_title: Continue to %{site_name}
|
|
||||||
logging_in_as: Logging in as %{email}
|
|
||||||
confirm_button: Finish Login
|
|
||||||
|
|
||||||
login:
|
login:
|
||||||
title: "Log In"
|
title: "Log In"
|
||||||
|
|
|
@ -939,10 +939,6 @@ en:
|
||||||
description: '"%{application_name}" is requesting the following access to your account:'
|
description: '"%{application_name}" is requesting the following access to your account:'
|
||||||
instructions: 'We just generated a new user API key for you to use with "%{application_name}", please paste the following key into your application:'
|
instructions: 'We just generated a new user API key for you to use with "%{application_name}", please paste the following key into your application:'
|
||||||
otp_description: 'Would you like to allow "%{application_name}" to access this site?'
|
otp_description: 'Would you like to allow "%{application_name}" to access this site?'
|
||||||
otp_confirmation:
|
|
||||||
confirm_title: Continue to %{site_name}
|
|
||||||
logging_in_as: Logging in as %{username}
|
|
||||||
confirm_button: Finish Login
|
|
||||||
no_trust_level: "Sorry, you do not have the required trust level to access the user API"
|
no_trust_level: "Sorry, you do not have the required trust level to access the user API"
|
||||||
generic_error: "Sorry, we are unable to issue user API keys, this feature may be disabled by the site admin"
|
generic_error: "Sorry, we are unable to issue user API keys, this feature may be disabled by the site admin"
|
||||||
scopes:
|
scopes:
|
||||||
|
|
|
@ -337,10 +337,9 @@ Discourse::Application.routes.draw do
|
||||||
get "session/sso_provider" => "session#sso_provider"
|
get "session/sso_provider" => "session#sso_provider"
|
||||||
get "session/current" => "session#current"
|
get "session/current" => "session#current"
|
||||||
get "session/csrf" => "session#csrf"
|
get "session/csrf" => "session#csrf"
|
||||||
get "session/email-login/:token" => "session#email_login_info"
|
get "session/email-login/:token" => "session#email_login"
|
||||||
post "session/email-login/:token" => "session#email_login"
|
post "session/email-login/:token" => "session#email_login"
|
||||||
get "session/otp/:token" => "session#one_time_password", constraints: { token: /[0-9a-f]+/ }
|
get "session/otp/:token" => "session#one_time_password", constraints: { token: /[0-9a-f]+/ }
|
||||||
post "session/otp/:token" => "session#one_time_password", constraints: { token: /[0-9a-f]+/ }
|
|
||||||
get "composer_messages" => "composer_messages#index"
|
get "composer_messages" => "composer_messages#index"
|
||||||
post "composer/parse_html" => "composer#parse_html"
|
post "composer/parse_html" => "composer#parse_html"
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#email_login_info' do
|
describe '#email_login' do
|
||||||
before do
|
before do
|
||||||
SiteSetting.enable_local_logins_via_email = true
|
SiteSetting.enable_local_logins_via_email = true
|
||||||
end
|
end
|
||||||
|
@ -26,66 +26,13 @@ RSpec.describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'valid token' do
|
|
||||||
it 'returns information' do
|
|
||||||
get "/session/email-login/#{email_token.token}.json"
|
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["can_login"]).to eq(true)
|
|
||||||
expect(JSON.parse(response.body)["second_factor_required"]).to eq(nil)
|
|
||||||
|
|
||||||
# Does not log in the user
|
|
||||||
expect(session[:current_user_id]).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'fails when local logins via email is disabled' do
|
|
||||||
SiteSetting.enable_local_logins_via_email = false
|
|
||||||
|
|
||||||
get "/session/email-login/#{email_token.token}.json"
|
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'fails when local logins is disabled' do
|
|
||||||
SiteSetting.enable_local_logins = false
|
|
||||||
|
|
||||||
get "/session/email-login/#{email_token.token}.json"
|
|
||||||
|
|
||||||
expect(response.status).to eq(500)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'user has 2-factor logins' do
|
|
||||||
let!(:user_second_factor) { Fabricate(:user_second_factor_totp, user: user) }
|
|
||||||
let!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user: user) }
|
|
||||||
|
|
||||||
it "includes that information in the response" do
|
|
||||||
get "/session/email-login/#{email_token.token}.json"
|
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["can_login"]).to eq(true)
|
|
||||||
expect(JSON.parse(response.body)["second_factor_required"]).to eq(true)
|
|
||||||
expect(JSON.parse(response.body)["backup_codes_enabled"]).to eq(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#email_login' do
|
|
||||||
before do
|
|
||||||
SiteSetting.enable_local_logins_via_email = true
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'missing token' do
|
|
||||||
it 'returns the right response' do
|
|
||||||
post "/session/email-login"
|
|
||||||
expect(response.status).to eq(404)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'invalid token' do
|
context 'invalid token' do
|
||||||
it 'returns the right response' do
|
it 'returns the right response' do
|
||||||
post "/session/email-login/adasdad.json"
|
get "/session/email-login/adasdad"
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to match(
|
||||||
I18n.t('email_login.invalid_token')
|
I18n.t('email_login.invalid_token')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -94,11 +41,11 @@ RSpec.describe SessionController do
|
||||||
it 'should return the right response' do
|
it 'should return the right response' do
|
||||||
email_token.update!(created_at: 999.years.ago)
|
email_token.update!(created_at: 999.years.ago)
|
||||||
|
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
expect(CGI.unescapeHTML(response.body)).to match(
|
||||||
I18n.t('email_login.invalid_token')
|
I18n.t('email_login.invalid_token')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -107,39 +54,37 @@ RSpec.describe SessionController do
|
||||||
|
|
||||||
context 'valid token' do
|
context 'valid token' do
|
||||||
it 'returns success' do
|
it 'returns success' do
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["success"]).to eq("OK")
|
expect(response).to redirect_to("/")
|
||||||
expect(session[:current_user_id]).to eq(user.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails when local logins via email is disabled' do
|
it 'fails when local logins via email is disabled' do
|
||||||
SiteSetting.enable_local_logins_via_email = false
|
SiteSetting.enable_local_logins_via_email = false
|
||||||
|
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails when local logins is disabled' do
|
it 'fails when local logins is disabled' do
|
||||||
SiteSetting.enable_local_logins = false
|
SiteSetting.enable_local_logins = false
|
||||||
|
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(500)
|
expect(response.status).to eq(500)
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't log in the user when not approved" do
|
it "doesn't log in the user when not approved" do
|
||||||
SiteSetting.must_approve_users = true
|
SiteSetting.must_approve_users = true
|
||||||
|
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(I18n.t("login.not_approved"))
|
expect(CGI.unescapeHTML(response.body)).to include(
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
I18n.t("login.not_approved")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when admin IP address is not valid" do
|
context "when admin IP address is not valid" do
|
||||||
|
@ -154,14 +99,13 @@ RSpec.describe SessionController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the right response' do
|
it 'returns the right response' do
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
expect(CGI.unescapeHTML(response.body)).to include(
|
||||||
I18n.t("login.admin_not_allowed_from_ip_address", username: user.username)
|
I18n.t("login.admin_not_allowed_from_ip_address", username: user.username)
|
||||||
)
|
)
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -178,14 +122,13 @@ RSpec.describe SessionController do
|
||||||
it 'returns the right response' do
|
it 'returns the right response' do
|
||||||
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(permitted_ip_address)
|
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(permitted_ip_address)
|
||||||
|
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
expect(CGI.unescapeHTML(response.body)).to include(
|
||||||
I18n.t("login.not_allowed_from_ip_address", username: user.username)
|
I18n.t("login.not_allowed_from_ip_address", username: user.username)
|
||||||
)
|
)
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -195,48 +138,63 @@ RSpec.describe SessionController do
|
||||||
suspended_at: Time.zone.now
|
suspended_at: Time.zone.now
|
||||||
)
|
)
|
||||||
|
|
||||||
post "/session/email-login/#{email_token.token}.json"
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
expect(CGI.unescapeHTML(response.body)).to include(I18n.t("login.suspended",
|
||||||
I18n.t("login.suspended", date: I18n.l(user.suspended_till, format: :date_only)
|
date: I18n.l(user.suspended_till, format: :date_only)
|
||||||
))
|
))
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'user has 2-factor logins' do
|
context 'user has 2-factor logins' do
|
||||||
let!(:user_second_factor) { Fabricate(:user_second_factor_totp, user: user) }
|
let!(:user_second_factor) { Fabricate(:user_second_factor_totp, user: user) }
|
||||||
let!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user: user) }
|
let!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user: user) }
|
||||||
|
|
||||||
|
describe 'requires second factor' do
|
||||||
|
it 'should return a second factor prompt' do
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
response_body = CGI.unescapeHTML(response.body)
|
||||||
|
|
||||||
|
expect(response_body).to include(I18n.t(
|
||||||
|
"login.second_factor_title"
|
||||||
|
))
|
||||||
|
|
||||||
|
expect(response_body).to_not include(I18n.t(
|
||||||
|
"login.invalid_second_factor_code"
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'errors on incorrect 2-factor' do
|
describe 'errors on incorrect 2-factor' do
|
||||||
context 'when using totp method' do
|
context 'when using totp method' do
|
||||||
it 'does not log in with incorrect two factor' do
|
it 'does not log in with incorrect two factor' do
|
||||||
post "/session/email-login/#{email_token.token}.json", params: {
|
post "/session/email-login/#{email_token.token}", params: {
|
||||||
second_factor_token: "0000",
|
second_factor_token: "0000",
|
||||||
second_factor_method: UserSecondFactor.methods[:totp]
|
second_factor_method: UserSecondFactor.methods[:totp]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
expect(CGI.unescapeHTML(response.body)).to include(I18n.t(
|
||||||
I18n.t("login.invalid_second_factor_code")
|
"login.invalid_second_factor_code"
|
||||||
)
|
))
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
context 'when using backup code method' do
|
context 'when using backup code method' do
|
||||||
it 'does not log in with incorrect backup code' do
|
it 'does not log in with incorrect backup code' do
|
||||||
post "/session/email-login/#{email_token.token}.json", params: {
|
post "/session/email-login/#{email_token.token}", params: {
|
||||||
second_factor_token: "0000",
|
second_factor_token: "0000",
|
||||||
second_factor_method: UserSecondFactor.methods[:backup_codes]
|
second_factor_method: UserSecondFactor.methods[:backup_codes]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(JSON.parse(response.body)["error"]).to eq(
|
expect(CGI.unescapeHTML(response.body)).to include(I18n.t(
|
||||||
I18n.t("login.invalid_second_factor_code")
|
"login.invalid_second_factor_code"
|
||||||
)
|
))
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -244,24 +202,22 @@ RSpec.describe SessionController do
|
||||||
describe 'allows successful 2-factor' do
|
describe 'allows successful 2-factor' do
|
||||||
context 'when using totp method' do
|
context 'when using totp method' do
|
||||||
it 'logs in correctly' do
|
it 'logs in correctly' do
|
||||||
post "/session/email-login/#{email_token.token}.json", params: {
|
post "/session/email-login/#{email_token.token}", params: {
|
||||||
second_factor_token: ROTP::TOTP.new(user_second_factor.data).now,
|
second_factor_token: ROTP::TOTP.new(user_second_factor.data).now,
|
||||||
second_factor_method: UserSecondFactor.methods[:totp]
|
second_factor_method: UserSecondFactor.methods[:totp]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["success"]).to eq("OK")
|
expect(response).to redirect_to("/")
|
||||||
expect(session[:current_user_id]).to eq(user.id)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
context 'when using backup code method' do
|
context 'when using backup code method' do
|
||||||
it 'logs in correctly' do
|
it 'logs in correctly' do
|
||||||
post "/session/email-login/#{email_token.token}.json", params: {
|
post "/session/email-login/#{email_token.token}", params: {
|
||||||
second_factor_token: "iAmValidBackupCode",
|
second_factor_token: "iAmValidBackupCode",
|
||||||
second_factor_method: UserSecondFactor.methods[:backup_codes]
|
second_factor_method: UserSecondFactor.methods[:backup_codes]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(JSON.parse(response.body)["success"]).to eq("OK")
|
expect(response).to redirect_to("/")
|
||||||
expect(session[:current_user_id]).to eq(user.id)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1369,40 +1325,9 @@ RSpec.describe SessionController do
|
||||||
get "/session/otp/asd1231dasd123"
|
get "/session/otp/asd1231dasd123"
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
|
|
||||||
post "/session/otp/asd1231dasd123"
|
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when token is valid' do
|
context 'when token is valid' do
|
||||||
it "should display the form for GET" do
|
|
||||||
token = SecureRandom.hex
|
|
||||||
$redis.setex "otp_#{token}", 10.minutes, user.username
|
|
||||||
|
|
||||||
get "/session/otp/#{token}"
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).to include(
|
|
||||||
I18n.t("user_api_key.otp_confirmation.logging_in_as", username: user.username)
|
|
||||||
)
|
|
||||||
expect($redis.get("otp_#{token}")).to eq(user.username)
|
|
||||||
|
|
||||||
expect(session[:current_user_id]).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should redirect on GET if already logged in" do
|
|
||||||
sign_in(user)
|
|
||||||
token = SecureRandom.hex
|
|
||||||
$redis.setex "otp_#{token}", 10.minutes, user.username
|
|
||||||
|
|
||||||
get "/session/otp/#{token}"
|
|
||||||
expect(response.status).to eq(302)
|
|
||||||
|
|
||||||
expect($redis.get("otp_#{token}")).to eq(nil)
|
|
||||||
expect(session[:current_user_id]).to eq(user.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should authenticate user and delete token' do
|
it 'should authenticate user and delete token' do
|
||||||
user = Fabricate(:user)
|
user = Fabricate(:user)
|
||||||
|
|
||||||
|
@ -1412,7 +1337,7 @@ RSpec.describe SessionController do
|
||||||
token = SecureRandom.hex
|
token = SecureRandom.hex
|
||||||
$redis.setex "otp_#{token}", 10.minutes, user.username
|
$redis.setex "otp_#{token}", 10.minutes, user.username
|
||||||
|
|
||||||
post "/session/otp/#{token}"
|
get "/session/otp/#{token}"
|
||||||
|
|
||||||
expect(response.status).to eq(302)
|
expect(response.status).to eq(302)
|
||||||
expect(response).to redirect_to("/")
|
expect(response).to redirect_to("/")
|
||||||
|
|
Loading…
Reference in New Issue