mirror of
https://github.com/discourse/discourse.git
synced 2025-04-02 09:15:57 +08:00
FIX: make it possible to use backup code everywhere where 2FA required (#7010)
This commit is contained in:
parent
e1d1073273
commit
6f427589b2
@ -16,17 +16,23 @@ export default Ember.Component.extend({
|
|||||||
: I18n.t("login.second_factor_backup_description");
|
: I18n.t("login.second_factor_backup_description");
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("secondFactorMethod")
|
@computed("secondFactorMethod", "isLogin")
|
||||||
linkText(secondFactorMethod) {
|
linkText(secondFactorMethod, isLogin) {
|
||||||
return secondFactorMethod === SECOND_FACTOR_METHODS.TOTP
|
if (isLogin) {
|
||||||
? "login.second_factor_backup"
|
return secondFactorMethod === SECOND_FACTOR_METHODS.TOTP
|
||||||
: "login.second_factor";
|
? "login.second_factor_backup"
|
||||||
|
: "login.second_factor";
|
||||||
|
} else {
|
||||||
|
return secondFactorMethod === SECOND_FACTOR_METHODS.TOTP
|
||||||
|
? "user.second_factor_backup.use"
|
||||||
|
: "user.second_factor.use";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleSecondFactorMethod() {
|
toggleSecondFactorMethod() {
|
||||||
const secondFactorMethod = this.get("secondFactorMethod");
|
const secondFactorMethod = this.get("secondFactorMethod");
|
||||||
this.set("loginSecondFactor", "");
|
this.set("secondFactorToken", "");
|
||||||
if (secondFactorMethod === SECOND_FACTOR_METHODS.TOTP) {
|
if (secondFactorMethod === SECOND_FACTOR_METHODS.TOTP) {
|
||||||
this.set("secondFactorMethod", SECOND_FACTOR_METHODS.BACKUP_CODE);
|
this.set("secondFactorMethod", SECOND_FACTOR_METHODS.BACKUP_CODE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -106,7 +106,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||||||
data: {
|
data: {
|
||||||
login: this.get("loginName"),
|
login: this.get("loginName"),
|
||||||
password: this.get("loginPassword"),
|
password: this.get("loginPassword"),
|
||||||
second_factor_token: this.get("loginSecondFactor"),
|
second_factor_token: this.get("secondFactorToken"),
|
||||||
second_factor_method: this.get("secondFactorMethod")
|
second_factor_method: this.get("secondFactorMethod")
|
||||||
}
|
}
|
||||||
}).then(
|
}).then(
|
||||||
|
@ -9,7 +9,7 @@ export default Ember.Controller.extend(PasswordValidation, {
|
|||||||
isDeveloper: Ember.computed.alias("model.is_developer"),
|
isDeveloper: Ember.computed.alias("model.is_developer"),
|
||||||
admin: Ember.computed.alias("model.admin"),
|
admin: Ember.computed.alias("model.admin"),
|
||||||
secondFactorRequired: Ember.computed.alias("model.second_factor_required"),
|
secondFactorRequired: Ember.computed.alias("model.second_factor_required"),
|
||||||
backupEnabled: Ember.computed.alias("model.second_factor_backup_enabled"),
|
backupEnabled: Ember.computed.alias("model.backup_enabled"),
|
||||||
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
|
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
|
||||||
passwordRequired: true,
|
passwordRequired: true,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
@ -38,7 +38,7 @@ export default Ember.Controller.extend(PasswordValidation, {
|
|||||||
type: "PUT",
|
type: "PUT",
|
||||||
data: {
|
data: {
|
||||||
password: this.get("accountPassword"),
|
password: this.get("accountPassword"),
|
||||||
second_factor_token: this.get("secondFactor"),
|
second_factor_token: this.get("secondFactorToken"),
|
||||||
second_factor_method: this.get("secondFactorMethod")
|
second_factor_method: this.get("secondFactorMethod")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||||
import { default as DiscourseURL, userPath } from "discourse/lib/url";
|
import { default as DiscourseURL, userPath } from "discourse/lib/url";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -11,10 +12,15 @@ export default Ember.Controller.extend({
|
|||||||
"model.second_factor_remaining_backup_codes"
|
"model.second_factor_remaining_backup_codes"
|
||||||
),
|
),
|
||||||
backupCodes: null,
|
backupCodes: null,
|
||||||
|
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
|
||||||
|
|
||||||
@computed("secondFactorToken")
|
@computed("secondFactorToken", "secondFactorMethod")
|
||||||
isValidSecondFactorToken(secondFactorToken) {
|
isValidSecondFactorToken(secondFactorToken, secondFactorMethod) {
|
||||||
return secondFactorToken && secondFactorToken.length === 6;
|
if (secondFactorMethod === SECOND_FACTOR_METHODS.TOTP) {
|
||||||
|
return secondFactorToken && secondFactorToken.length === 6;
|
||||||
|
} else if (secondFactorMethod === SECOND_FACTOR_METHODS.BACKUP_CODE) {
|
||||||
|
return secondFactorToken && secondFactorToken.length === 16;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("isValidSecondFactorToken", "backupEnabled", "loading")
|
@computed("isValidSecondFactorToken", "backupEnabled", "loading")
|
||||||
@ -59,7 +65,12 @@ export default Ember.Controller.extend({
|
|||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
|
|
||||||
this.get("model")
|
this.get("model")
|
||||||
.toggleSecondFactor(this.get("secondFactorToken"), false, 2)
|
.toggleSecondFactor(
|
||||||
|
this.get("secondFactorToken"),
|
||||||
|
this.get("secondFactorMethod"),
|
||||||
|
SECOND_FACTOR_METHODS.BACKUP_CODE,
|
||||||
|
false
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
this.set("errorMessage", response.error);
|
this.set("errorMessage", response.error);
|
||||||
@ -79,7 +90,10 @@ export default Ember.Controller.extend({
|
|||||||
if (!this.get("secondFactorToken")) return;
|
if (!this.get("secondFactorToken")) return;
|
||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
this.get("model")
|
this.get("model")
|
||||||
.generateSecondFactorCodes(this.get("secondFactorToken"))
|
.generateSecondFactorCodes(
|
||||||
|
this.get("secondFactorToken"),
|
||||||
|
this.get("secondFactorMethod")
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
this.set("errorMessage", response.error);
|
this.set("errorMessage", response.error);
|
||||||
|
@ -2,6 +2,7 @@ import { default as computed } from "ember-addons/ember-computed-decorators";
|
|||||||
import { default as DiscourseURL, userPath } from "discourse/lib/url";
|
import { default as DiscourseURL, userPath } from "discourse/lib/url";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { findAll } from "discourse/models/login-method";
|
import { findAll } from "discourse/models/login-method";
|
||||||
|
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -13,6 +14,8 @@ export default Ember.Controller.extend({
|
|||||||
showSecondFactorKey: false,
|
showSecondFactorKey: false,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
newUsername: null,
|
newUsername: null,
|
||||||
|
backupEnabled: Ember.computed.alias("model.second_factor_backup_enabled"),
|
||||||
|
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
|
||||||
|
|
||||||
loaded: Ember.computed.and("secondFactorImage", "secondFactorKey"),
|
loaded: Ember.computed.and("secondFactorImage", "secondFactorKey"),
|
||||||
|
|
||||||
@ -41,7 +44,12 @@ export default Ember.Controller.extend({
|
|||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
|
|
||||||
this.get("model")
|
this.get("model")
|
||||||
.toggleSecondFactor(this.get("secondFactorToken"), enable, 1)
|
.toggleSecondFactor(
|
||||||
|
this.get("secondFactorToken"),
|
||||||
|
this.get("secondFactorMethod"),
|
||||||
|
SECOND_FACTOR_METHODS.TOTP,
|
||||||
|
enable
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
this.set("errorMessage", response.error);
|
this.set("errorMessage", response.error);
|
||||||
|
@ -367,20 +367,24 @@ const User = RestModel.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSecondFactor(token, enable, method) {
|
toggleSecondFactor(authToken, authMethod, targetMethod, enable) {
|
||||||
return ajax("/u/second_factor.json", {
|
return ajax("/u/second_factor.json", {
|
||||||
data: {
|
data: {
|
||||||
second_factor_token: token,
|
second_factor_token: authToken,
|
||||||
second_factor_method: method,
|
second_factor_method: authMethod,
|
||||||
|
second_factor_target: targetMethod,
|
||||||
enable
|
enable
|
||||||
},
|
},
|
||||||
type: "PUT"
|
type: "PUT"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
generateSecondFactorCodes(token) {
|
generateSecondFactorCodes(authToken, authMethod) {
|
||||||
return ajax("/u/second_factors_backup.json", {
|
return ajax("/u/second_factors_backup.json", {
|
||||||
data: { second_factor_token: token },
|
data: {
|
||||||
|
second_factor_token: authToken,
|
||||||
|
second_factor_method: authMethod
|
||||||
|
},
|
||||||
type: "PUT"
|
type: "PUT"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<div id="second-factor">
|
<div id="second-factor">
|
||||||
<h3>{{secondFactorTitle}}</h3>
|
<h3>{{secondFactorTitle}}</h3>
|
||||||
|
{{#if optionalText}}
|
||||||
|
<p>{{{optionalText}}}</p>
|
||||||
|
{{/if}}
|
||||||
<p>{{secondFactorDescription}}</p>
|
<p>{{secondFactorDescription}}</p>
|
||||||
{{yield}}
|
{{yield}}
|
||||||
{{#if backupEnabled}}
|
{{#if backupEnabled}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action=(action "login")}}
|
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword secondFactorToken=secondFactorToken action=(action "login")}}
|
||||||
{{#d-modal-body title="login.title" class="login-modal"}}
|
{{#d-modal-body title="login.title" class="login-modal"}}
|
||||||
{{#if showLoginButtons}}
|
{{#if showLoginButtons}}
|
||||||
{{login-buttons
|
{{login-buttons
|
||||||
@ -36,8 +36,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{#second-factor-form secondFactorMethod=secondFactorMethod loginSecondFactor=loginSecondFactor backupEnabled=backupEnabled class=secondFactorClass}}
|
{{#second-factor-form
|
||||||
{{second-factor-input value=loginSecondFactor inputId='login-second-factor' secondFactorMethod=secondFactorMethod}}
|
secondFactorMethod=secondFactorMethod
|
||||||
|
secondFactorToken=secondFactorToken
|
||||||
|
class=secondFactorClass
|
||||||
|
isLogin=true}}
|
||||||
|
{{second-factor-input value=secondFactorToken inputId='login-second-factor' secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
|
||||||
{{/second-factor-form}}
|
{{/second-factor-form}}
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword loginSecondFactor=loginSecondFactor action=(action "login")}}
|
{{#login-modal screenX=lastX screenY=lastY loginName=loginName loginPassword=loginPassword secondFactorToken=secondFactorToken action=(action "login")}}
|
||||||
{{#d-modal-body title="login.title" class=(concat "login-modal" " " (if hasAtLeastOneLoginButton "has-alt-auth"))}}
|
{{#d-modal-body title="login.title" class=(concat "login-modal" " " (if hasAtLeastOneLoginButton "has-alt-auth"))}}
|
||||||
|
|
||||||
{{#if canLoginLocal}}
|
{{#if canLoginLocal}}
|
||||||
@ -21,8 +21,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{#second-factor-form secondFactorMethod=secondFactorMethod loginSecondFactor=loginSecondFactor backupEnabled=backupEnabled class=secondFactorClass}}
|
{{#second-factor-form
|
||||||
{{second-factor-input value=loginSecondFactor inputId='login-second-factor' secondFactorMethod=secondFactorMethod}}
|
secondFactorMethod=secondFactorMethod
|
||||||
|
secondFactorToken=secondFactorToken
|
||||||
|
class=secondFactorClass
|
||||||
|
backupEnabled=backupEnabled
|
||||||
|
isLogin=true}}
|
||||||
|
{{second-factor-input value=secondFactorToken inputId='login-second-factor' secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
|
||||||
{{/second-factor-form}}
|
{{/second-factor-form}}
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -17,16 +17,17 @@
|
|||||||
{{else}}
|
{{else}}
|
||||||
<form>
|
<form>
|
||||||
{{#if secondFactorRequired}}
|
{{#if secondFactorRequired}}
|
||||||
{{#second-factor-form secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
|
{{#if errorMessage}}
|
||||||
{{text-field
|
<div class='alert alert-error'>{{errorMessage}}</div>
|
||||||
id="second-factor"
|
<br/>
|
||||||
value=secondFactor
|
{{/if}}
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="off"
|
{{#second-factor-form
|
||||||
autofocus="autofocus"
|
secondFactorMethod=secondFactorMethod
|
||||||
maxlength="6"
|
secondFactorToken=secondFactorToken
|
||||||
secondFactorMethod=secondFactorMethod
|
backupEnabled=backupEnabled
|
||||||
}}
|
isLogin=false}}
|
||||||
|
{{second-factor-input value=secondFactorToken inputId='second-factor' secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
|
||||||
{{/second-factor-form}}
|
{{/second-factor-form}}
|
||||||
{{d-button action=(action "submit") class='btn-primary' label='submit'}}
|
{{d-button action=(action "submit") class='btn-primary' label='submit'}}
|
||||||
{{else}}
|
{{else}}
|
||||||
@ -44,12 +45,6 @@
|
|||||||
|
|
||||||
{{d-button action=(action "submit") class='btn-primary' label='user.change_password.set_password'}}
|
{{d-button action=(action "submit") class='btn-primary' label='user.change_password.set_password'}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if errorMessage}}
|
|
||||||
<br/><br/>
|
|
||||||
<div class='alert alert-error'>{{errorMessage}}</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
<section class="user-preferences second-factor-backup-preferences">
|
<section class="user-preferences second-factor-backup-preferences">
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
|
||||||
<h3>{{i18n "user.second_factor_backup.title"}}</h3>
|
|
||||||
{{#if backupEnabled}}
|
|
||||||
<p>{{{i18n "user.second_factor_backup.remaining_codes" count=remainingCodes}}}</p>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
{{#if successMessage}}
|
{{#if successMessage}}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
@ -22,17 +17,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class='control-label'>{{i18n 'user.second_factor.label'}}</label>
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
{{second-factor-input
|
{{#second-factor-form
|
||||||
value=secondFactorToken
|
secondFactorMethod=secondFactorMethod
|
||||||
maxlength=6
|
backupEnabled=backupEnabled
|
||||||
inputId="second-factor-token"}}
|
secondFactorToken=secondFactorToken
|
||||||
|
secondFactorTitle=(i18n 'user.second_factor_backup.title')
|
||||||
<div class='instructions'>
|
optionalText=(if backupEnabled (i18n "user.second_factor_backup.remaining_codes" count=remainingCodes))
|
||||||
{{i18n "user.second_factor.disable_description"}}
|
isLogin=false}}
|
||||||
</div>
|
{{second-factor-input value=secondFactorToken inputId='second-factor-token' secondFactorMethod=secondFactorMethod}}
|
||||||
|
{{/second-factor-form}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
<section class='user-preferences'>
|
<section class='user-preferences'>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<div class="controls">
|
|
||||||
<h3>{{i18n 'user.second_factor.title'}}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if errorMessage}}
|
{{#if errorMessage}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@ -16,15 +10,16 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if model.second_factor_enabled}}
|
{{#if model.second_factor_enabled}}
|
||||||
<label class='control-label'>{{i18n 'user.second_factor.label'}}</label>
|
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
{{second-factor-input maxlength=6 value=secondFactorToken inputId='second-factor-token'}}
|
{{#second-factor-form
|
||||||
</div>
|
secondFactorMethod=secondFactorMethod
|
||||||
|
backupEnabled=backupEnabled
|
||||||
<div class='instructions controls'>
|
secondFactorToken=secondFactorToken
|
||||||
{{i18n 'user.second_factor.disable_description'}}
|
secondFactorTitle=(i18n 'user.second_factor.title')
|
||||||
|
isLogin=false}}
|
||||||
|
{{second-factor-input value=secondFactorToken inputId='second-factor-token' secondFactorMethod=secondFactorMethod}}
|
||||||
|
{{/second-factor-form}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -91,15 +86,16 @@
|
|||||||
<label class='control-label'>{{i18n 'user.password.title'}}</label>
|
<label class='control-label'>{{i18n 'user.password.title'}}</label>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
{{text-field value=password
|
<div>
|
||||||
|
{{text-field value=password
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
classNames="input-xxlarge"
|
classNames="input-xxlarge"
|
||||||
autofocus="autofocus"}}
|
autofocus="autofocus"}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class='instructions'>
|
||||||
<div class='instructions controls'>
|
{{i18n 'user.second_factor.confirm_password_description'}}
|
||||||
{{i18n 'user.second_factor.confirm_password_description'}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ class UsersController < ApplicationController
|
|||||||
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
|
||||||
|
|
||||||
if second_factor_token.present? && second_factor_token[/\d{6}/] && UserSecondFactor.methods[second_factor_method]
|
if second_factor_token.present? && UserSecondFactor.methods[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!
|
||||||
second_factor_authenticated = @user&.authenticate_second_factor(second_factor_token, second_factor_method)
|
second_factor_authenticated = @user&.authenticate_second_factor(second_factor_token, second_factor_method)
|
||||||
end
|
end
|
||||||
@ -1059,7 +1059,7 @@ class UsersController < ApplicationController
|
|||||||
def create_second_factor_backup
|
def create_second_factor_backup
|
||||||
raise Discourse::NotFound if SiteSetting.enable_sso || !SiteSetting.enable_local_logins
|
raise Discourse::NotFound if SiteSetting.enable_sso || !SiteSetting.enable_local_logins
|
||||||
|
|
||||||
unless current_user.authenticate_totp(params[:second_factor_token])
|
unless current_user.authenticate_second_factor(params[:second_factor_token], params[:second_factor_method].to_i)
|
||||||
return render json: failed_json.merge(
|
return render json: failed_json.merge(
|
||||||
error: I18n.t("login.invalid_second_factor_code")
|
error: I18n.t("login.invalid_second_factor_code")
|
||||||
)
|
)
|
||||||
@ -1075,22 +1075,26 @@ class UsersController < ApplicationController
|
|||||||
def update_second_factor
|
def update_second_factor
|
||||||
params.require(:second_factor_token)
|
params.require(:second_factor_token)
|
||||||
params.require(:second_factor_method)
|
params.require(:second_factor_method)
|
||||||
|
params.require(:second_factor_target)
|
||||||
|
|
||||||
second_factor_method = params[:second_factor_method].to_i
|
auth_method = params[:second_factor_method].to_i
|
||||||
|
auth_token = params[:second_factor_token]
|
||||||
|
|
||||||
|
update_second_factor_method = params[:second_factor_target].to_i
|
||||||
|
|
||||||
[request.remote_ip, current_user.id].each do |key|
|
[request.remote_ip, current_user.id].each do |key|
|
||||||
RateLimiter.new(nil, "second-factor-min-#{key}", 3, 1.minute).performed!
|
RateLimiter.new(nil, "second-factor-min-#{key}", 3, 1.minute).performed!
|
||||||
end
|
end
|
||||||
|
|
||||||
if second_factor_method == UserSecondFactor.methods[:totp]
|
if update_second_factor_method == UserSecondFactor.methods[:totp]
|
||||||
user_second_factor = current_user.user_second_factors.totp
|
user_second_factor = current_user.user_second_factors.totp
|
||||||
elsif second_factor_method == UserSecondFactor.methods[:backup_codes]
|
elsif update_second_factor_method == UserSecondFactor.methods[:backup_codes]
|
||||||
user_second_factor = current_user.user_second_factors.backup_codes
|
user_second_factor = current_user.user_second_factors.backup_codes
|
||||||
end
|
end
|
||||||
|
|
||||||
raise Discourse::InvalidParameters unless user_second_factor
|
raise Discourse::InvalidParameters unless user_second_factor
|
||||||
|
|
||||||
unless current_user.authenticate_totp(params[:second_factor_token])
|
unless current_user.authenticate_second_factor(auth_token, auth_method)
|
||||||
return render json: failed_json.merge(
|
return render json: failed_json.merge(
|
||||||
error: I18n.t("login.invalid_second_factor_code")
|
error: I18n.t("login.invalid_second_factor_code")
|
||||||
)
|
)
|
||||||
@ -1100,7 +1104,7 @@ class UsersController < ApplicationController
|
|||||||
user_second_factor.update!(enabled: true)
|
user_second_factor.update!(enabled: true)
|
||||||
else
|
else
|
||||||
# when disabling totp, backup is disabled too
|
# when disabling totp, backup is disabled too
|
||||||
if second_factor_method == UserSecondFactor.methods[:totp]
|
if update_second_factor_method == UserSecondFactor.methods[:totp]
|
||||||
current_user.user_second_factors.destroy_all
|
current_user.user_second_factors.destroy_all
|
||||||
|
|
||||||
Jobs.enqueue(
|
Jobs.enqueue(
|
||||||
@ -1108,7 +1112,7 @@ class UsersController < ApplicationController
|
|||||||
type: :account_second_factor_disabled,
|
type: :account_second_factor_disabled,
|
||||||
user_id: current_user.id
|
user_id: current_user.id
|
||||||
)
|
)
|
||||||
elsif second_factor_method == UserSecondFactor.methods[:backup_codes]
|
elsif update_second_factor_method == UserSecondFactor.methods[:backup_codes]
|
||||||
current_user.user_second_factors.where(method: UserSecondFactor.methods[:backup_codes]).destroy_all
|
current_user.user_second_factors.where(method: UserSecondFactor.methods[:backup_codes]).destroy_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -782,6 +782,7 @@ en:
|
|||||||
copied_to_clipboard: "Copied to Clipboard"
|
copied_to_clipboard: "Copied to Clipboard"
|
||||||
copy_to_clipboard_error: "Error copying data to Clipboard"
|
copy_to_clipboard_error: "Error copying data to Clipboard"
|
||||||
remaining_codes: "You have <strong>{{count}}</strong> backup codes remaining."
|
remaining_codes: "You have <strong>{{count}}</strong> backup codes remaining."
|
||||||
|
use: "<a href>Use a backup code</a>"
|
||||||
codes:
|
codes:
|
||||||
title: "Backup Codes Generated"
|
title: "Backup Codes Generated"
|
||||||
description: "Each of these backup codes can only be used once. Keep them somewhere safe but accessible."
|
description: "Each of these backup codes can only be used once. Keep them somewhere safe but accessible."
|
||||||
@ -800,6 +801,7 @@ en:
|
|||||||
extended_description: |
|
extended_description: |
|
||||||
Two factor authentication adds extra security to your account by requiring a one-time token in addition to your password. Tokens can be generated on <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> and <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a> devices.
|
Two factor authentication adds extra security to your account by requiring a one-time token in addition to your password. Tokens can be generated on <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> and <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a> devices.
|
||||||
oauth_enabled_warning: "Please note that social logins will be disabled once two factor authentication has been enabled on your account."
|
oauth_enabled_warning: "Please note that social logins will be disabled once two factor authentication has been enabled on your account."
|
||||||
|
use: "<a href>Use Authenticator app</a>"
|
||||||
|
|
||||||
change_about:
|
change_about:
|
||||||
title: "Change About Me"
|
title: "Change About Me"
|
||||||
|
@ -3107,6 +3107,7 @@ describe UsersController do
|
|||||||
put "/users/second_factor.json", params: {
|
put "/users/second_factor.json", params: {
|
||||||
second_factor_token: '000000',
|
second_factor_token: '000000',
|
||||||
second_factor_method: UserSecondFactor.methods[:totp],
|
second_factor_method: UserSecondFactor.methods[:totp],
|
||||||
|
second_factor_target: UserSecondFactor.methods[:totp],
|
||||||
enable: 'true',
|
enable: 'true',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3122,8 +3123,9 @@ describe UsersController do
|
|||||||
it 'should allow second factor for the user to be enabled' do
|
it 'should allow second factor for the user to be enabled' do
|
||||||
put "/users/second_factor.json", params: {
|
put "/users/second_factor.json", params: {
|
||||||
second_factor_token: ROTP::TOTP.new(user_second_factor.data).now,
|
second_factor_token: ROTP::TOTP.new(user_second_factor.data).now,
|
||||||
enable: 'true',
|
second_factor_method: UserSecondFactor.methods[:totp],
|
||||||
second_factor_method: UserSecondFactor.methods[:totp]
|
second_factor_target: UserSecondFactor.methods[:totp],
|
||||||
|
enable: 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
@ -3133,7 +3135,8 @@ describe UsersController do
|
|||||||
it 'should allow second factor for the user to be disabled' do
|
it 'should allow second factor for the user to be disabled' do
|
||||||
put "/users/second_factor.json", params: {
|
put "/users/second_factor.json", 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],
|
||||||
|
second_factor_target: UserSecondFactor.methods[:totp]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
@ -3146,7 +3149,7 @@ describe UsersController do
|
|||||||
context 'when token is missing' do
|
context 'when token is missing' do
|
||||||
it 'returns the right response' do
|
it 'returns the right response' do
|
||||||
put "/users/second_factor.json", params: {
|
put "/users/second_factor.json", params: {
|
||||||
second_factor_method: UserSecondFactor.methods[:backup_codes],
|
second_factor_target: UserSecondFactor.methods[:backup_codes]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(400)
|
expect(response.status).to eq(400)
|
||||||
@ -3157,7 +3160,8 @@ describe UsersController do
|
|||||||
it 'returns the right response' do
|
it 'returns the right response' do
|
||||||
put "/users/second_factor.json", params: {
|
put "/users/second_factor.json", params: {
|
||||||
second_factor_token: '000000',
|
second_factor_token: '000000',
|
||||||
second_factor_method: UserSecondFactor.methods[:backup_codes],
|
second_factor_method: UserSecondFactor.methods[:totp],
|
||||||
|
second_factor_target: UserSecondFactor.methods[:backup_codes]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
@ -3172,7 +3176,8 @@ describe UsersController do
|
|||||||
it 'should allow second factor backup for the user to be disabled' do
|
it 'should allow second factor backup for the user to be disabled' do
|
||||||
put "/users/second_factor.json", params: {
|
put "/users/second_factor.json", 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[:backup_codes]
|
second_factor_method: UserSecondFactor.methods[:totp],
|
||||||
|
second_factor_target: UserSecondFactor.methods[:backup_codes]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
@ -3189,7 +3194,8 @@ describe UsersController do
|
|||||||
context 'when not logged in' do
|
context 'when not logged in' do
|
||||||
it 'should return the right response' do
|
it 'should return the right response' do
|
||||||
put "/users/second_factors_backup.json", params: {
|
put "/users/second_factors_backup.json", params: {
|
||||||
second_factor_token: 'wrongtoken'
|
second_factor_token: 'wrongtoken',
|
||||||
|
second_factor_method: UserSecondFactor.methods[:totp]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(403)
|
expect(response.status).to eq(403)
|
||||||
@ -3204,7 +3210,8 @@ describe UsersController do
|
|||||||
describe 'create 2fa request' do
|
describe 'create 2fa request' do
|
||||||
it 'fails on incorrect password' do
|
it 'fails on incorrect password' do
|
||||||
put "/users/second_factors_backup.json", params: {
|
put "/users/second_factors_backup.json", params: {
|
||||||
second_factor_token: 'wrongtoken'
|
second_factor_token: 'wrongtoken',
|
||||||
|
second_factor_method: UserSecondFactor.methods[:totp]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
@ -3219,7 +3226,8 @@ describe UsersController do
|
|||||||
SiteSetting.enable_local_logins = false
|
SiteSetting.enable_local_logins = false
|
||||||
|
|
||||||
put "/users/second_factors_backup.json", params: {
|
put "/users/second_factors_backup.json", 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]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
@ -3232,7 +3240,8 @@ describe UsersController do
|
|||||||
SiteSetting.enable_sso = true
|
SiteSetting.enable_sso = true
|
||||||
|
|
||||||
put "/users/second_factors_backup.json", params: {
|
put "/users/second_factors_backup.json", 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]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
expect(response.status).to eq(404)
|
||||||
@ -3243,7 +3252,8 @@ describe UsersController do
|
|||||||
user_second_factor
|
user_second_factor
|
||||||
|
|
||||||
put "/users/second_factors_backup.json", params: {
|
put "/users/second_factors_backup.json", 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]
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user