fix(TOTP): Translate success status messages on /settings/recovery_codes
view.translate was not being called on the success status messages before writing to the DOM. This remedies that. Also change writing status/error messages to the DOM from using .html to .text to prevent XSS from creeping in. fixes #6728
This commit is contained in:
Родитель
2e2ba86a75
Коммит
6984fa8b5f
|
@ -26,7 +26,7 @@ const Mixin = {
|
|||
// This copies the text to clipboard by creating a tiny transparent
|
||||
// textArea with the content. Then it executes the browser `copy` command and removes textArea.
|
||||
$('<textArea id=\"temporary-copy-area\" class=\"temporary-copy-text-area\"></textArea>').appendTo(appendToElement);
|
||||
this.$('textArea.temporary-copy-text-area').html(text);
|
||||
this.$('textArea.temporary-copy-text-area').text(text);
|
||||
|
||||
if (this.getUserAgent().isIos()) {
|
||||
// iOS does not allow you to directly use the `document.execCommand('copy')` function.
|
||||
|
@ -85,14 +85,14 @@ const Mixin = {
|
|||
this.$('.error').addClass('hidden');
|
||||
this.$('.modal-success').removeClass('hidden');
|
||||
this.$('.modal-success').addClass('success');
|
||||
this.$('.modal-success').html(msg);
|
||||
this.$('.modal-success').text(this.translate(msg));
|
||||
},
|
||||
|
||||
_displayError(msg) {
|
||||
this.$('.error').removeClass('hidden');
|
||||
this.$('.modal-success').addClass('hidden');
|
||||
this.$('.modal-success').removeClass('success');
|
||||
this.$('.error').html(msg);
|
||||
this.$('.error').text(this.translate(msg));
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Cocktail = require('cocktail');
|
||||
const FormView = require('../form');
|
||||
const ModalSettingsPanelMixin = require('../mixins/modal-settings-panel-mixin');
|
||||
const Template = require('templates/settings/recovery_codes.mustache');
|
||||
const RecoveryCodePrintTemplate = require('templates/settings/recovery_codes_print.mustache');
|
||||
const RecoveryCode = require('../../models/recovery-code');
|
||||
const SaveOptionsMixin = require('../mixins/save-options-mixin');
|
||||
const UserAgentMixin = require('../../lib/user-agent-mixin');
|
||||
import Cocktail from 'cocktail';
|
||||
import FormView from '../form';
|
||||
import ModalSettingsPanelMixin from '../mixins/modal-settings-panel-mixin';
|
||||
import Template from 'templates/settings/recovery_codes.mustache';
|
||||
import RecoveryCodePrintTemplate from 'templates/settings/recovery_codes_print.mustache';
|
||||
import RecoveryCode from '../../models/recovery-code';
|
||||
import SaveOptionsMixin from '../mixins/save-options-mixin';
|
||||
import UserAgentMixin from '../../lib/user-agent-mixin';
|
||||
|
||||
const t = msg => msg;
|
||||
|
||||
|
@ -115,7 +115,9 @@ const View = FormView.extend({
|
|||
|
||||
context.set({
|
||||
isIos: this.getUserAgent().isIos(),
|
||||
modalSuccessMsg,
|
||||
// There can be several modalSuccessMsg's, make sure they are translated
|
||||
// before displaying to the user user. See #6728
|
||||
modalSuccessMsg: modalSuccessMsg && this.translate(modalSuccessMsg),
|
||||
recoveryCodes,
|
||||
showRecoveryCodes: recoveryCodes.length > 0
|
||||
});
|
||||
|
|
|
@ -113,7 +113,7 @@ const View = FormView.extend({
|
|||
const qrImageAltText = t('Use the code %(code)s to set up two-step authentication in supported applications.');
|
||||
this.$('.qr-image').attr('alt', this.translate(qrImageAltText, {code: result.secret}));
|
||||
|
||||
this.$('.code').html(this._getFormattedCode(result.secret));
|
||||
this.$('.code').text(this._getFormattedCode(result.secret));
|
||||
this._showQrCode();
|
||||
this._hideStatus();
|
||||
});
|
||||
|
|
|
@ -4,13 +4,18 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const {assert} = require('chai');
|
||||
const BaseView = require('views/base');
|
||||
const Cocktail = require('cocktail');
|
||||
const SaveOptionsMixin = require('views/mixins/save-options-mixin');
|
||||
const sinon = require('sinon');
|
||||
import { assert } from 'chai';
|
||||
import BaseView from 'views/base';
|
||||
import Cocktail from 'cocktail';
|
||||
import SaveOptionsMixin from 'views/mixins/save-options-mixin';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const View = BaseView.extend({});
|
||||
const View = BaseView.extend({
|
||||
template: () => `
|
||||
<div class="modal-success"></div>
|
||||
<div class="error"></div>
|
||||
`
|
||||
});
|
||||
|
||||
Cocktail.mixin(
|
||||
View,
|
||||
|
@ -25,7 +30,10 @@ describe('views/mixins/save-options-mixin', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
view = new View({});
|
||||
sinon.stub(view, 'translate').callsFake(msg => `translated ${msg}`);
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
return view.render();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
@ -100,4 +108,15 @@ describe('views/mixins/save-options-mixin', () => {
|
|||
assert.equal(view._displaySuccess.called, true, 'display success called');
|
||||
});
|
||||
});
|
||||
|
||||
it('_displaySuccess should translate the text', () => {
|
||||
view._displaySuccess('success message');
|
||||
assert.equal(view.$('.modal-success').text(), 'translated success message');
|
||||
});
|
||||
|
||||
it('_displayError should translate the text', () => {
|
||||
view._displayError('error message');
|
||||
assert.equal(view.$('.error').text(), 'translated error message');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const $ = require('jquery');
|
||||
const assert = require('chai').assert;
|
||||
const Broker = require('models/auth_brokers/base');
|
||||
const Metrics = require('lib/metrics');
|
||||
const { Model } = require('backbone');
|
||||
const Notifier = require('lib/channels/notifier');
|
||||
const sinon = require('sinon');
|
||||
const TestHelpers = require('../../../lib/helpers');
|
||||
const User = require('models/user');
|
||||
const View = require('views/settings/recovery_codes');
|
||||
const WindowMock = require('../../../mocks/window');
|
||||
import $ from 'jquery';
|
||||
import { assert } from 'chai';
|
||||
import Broker from 'models/auth_brokers/base';
|
||||
import Metrics from 'lib/metrics';
|
||||
import { Model } from 'backbone';
|
||||
import Notifier from 'lib/channels/notifier';
|
||||
import sinon from 'sinon';
|
||||
import TestHelpers from '../../../lib/helpers';
|
||||
import User from 'models/user';
|
||||
import View from 'views/settings/recovery_codes';
|
||||
import WindowMock from '../../../mocks/window';
|
||||
|
||||
describe('views/settings/recovery_codes', () => {
|
||||
let account;
|
||||
|
@ -43,6 +43,8 @@ describe('views/settings/recovery_codes', () => {
|
|||
window: windowMock
|
||||
});
|
||||
|
||||
sinon.stub(view, 'translate').callsFake(msg => `translated ${msg}`);
|
||||
|
||||
sinon.stub(view, 'getSignedInAccount').callsFake(() => account);
|
||||
|
||||
sinon.stub(account, 'replaceRecoveryCodes').callsFake(() => Promise.resolve({
|
||||
|
@ -89,12 +91,10 @@ describe('views/settings/recovery_codes', () => {
|
|||
view = null;
|
||||
});
|
||||
|
||||
describe('should show recovery codes', () => {
|
||||
it('show codes', () => {
|
||||
assert.equal(view.$('.recovery-code').length, 8);
|
||||
assert.equal(view.$('.recovery-code:first').text(), '00001111', 'correct recovery code');
|
||||
assert.include(view.$('.modal-success').text(), 'authentication enabled');
|
||||
});
|
||||
it('should show recovery codes, translated success message', () => {
|
||||
assert.equal(view.$('.recovery-code').length, 8);
|
||||
assert.equal(view.$('.recovery-code:first').text(), '00001111', 'correct recovery code');
|
||||
assert.include(view.$('.modal-success').text(), 'translated Two-step authentication enabled');
|
||||
});
|
||||
|
||||
describe('should print codes', () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче