Migrate backup codes settings to Vue

Fixes https://github.com/nextcloud/server/issues/11034.

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Christoph Wurst 2018-09-11 10:41:35 +02:00
parent 522d6804f7
commit 7882be160a
No known key found for this signature in database
GPG Key ID: CC42AC2A7F0E56D8
13 changed files with 4955 additions and 223 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
import axio from 'axios';
export function getState () {
const url = OC.generateUrl('/apps/twofactor_backupcodes/settings/state');
return axio.get(url, {
headers: {
requesttoken: OC.requestToken
}
}).then(resp => resp.data);
}
export function generateCodes () {
const url = OC.generateUrl('/apps/twofactor_backupcodes/settings/create');
return axio.post(url, {}, {
headers: {
requesttoken: OC.requestToken
}
}).then(resp => resp.data)
}

View File

@ -0,0 +1,7 @@
export function print (data) {
const newTab = window.open('', t('twofactor_backupcodes', 'Nextcloud backup codes'));
newTab.document.write('<h1>' + t('twofactor_backupcodes', 'Nextcloud backup codes') + '</h1>');
newTab.document.write(data);
newTab.print();
newTab.close();
}

View File

@ -1,16 +1,9 @@
/* global OC */
import Vue from 'vue';
import PersonalSettings from './views/PersonalSettings';
(function (OC) {
'use strict';
OC.Settings = OC.Settings || {};
OC.Settings.TwoFactorBackupCodes = OC.Settings.TwoFactorBackupCodes || {};
$(function () {
var view = new OC.Settings.TwoFactorBackupCodes.View({
el: $('#twofactor-backupcodes-settings')
});
view.render();
});
})(OC);
Vue.prototype.t = t;
export default new Vue({
el: '#twofactor-backupcodes-settings',
render: h => h(PersonalSettings)
});

View File

@ -1,207 +0,0 @@
/* global Backbone, Handlebars, OC, _ */
(function(OC, Handlebars, $, _) {
'use strict';
OC.Settings = OC.Settings || {};
OC.Settings.TwoFactorBackupCodes = OC.Settings.TwoFactorBackupCodes || {};
var TEMPLATE = '<div>'
+ '{{#unless enabled}}'
+ '<button id="generate-backup-codes">' + t('twofactor_backupcodes', 'Generate backup codes') + '</button>'
+ '{{else}}'
+ '<p>'
+ '{{#unless codes}}'
+ t('twofactor_backupcodes', 'Backup codes have been generated. {{used}} of {{total}} codes have been used.')
+ '{{else}}'
+ t('twofactor_backupcodes', 'These are your backup codes. Please save and/or print them as you will not be able to read the codes again later')
+ '<ul>'
+ '{{#each codes}}'
+ '<li class="backup-code">{{this}}</li>'
+ '{{/each}}'
+ '</ul>'
+ '<a href="{{download}}" class="button" download="Nextcloud-backup-codes.txt">' + t('twofactor_backupcodes', 'Save backup codes') + '</a>'
+ '<button id="print-backup-codes" class="button">' + t('twofactor_backupcodes', 'Print backup codes') + '</button>'
+ '{{/unless}}'
+ '</p>'
+ '<p>'
+ '<button id="generate-backup-codes">' + t('twofactor_backupcodes', 'Regenerate backup codes') + '</button>'
+ '</p>'
+ '<p>'
+ t('twofactor_backupcodes', 'If you regenerate backup codes, you automatically invalidate old codes.')
+ '</p>'
+ '{{/unless}}'
+ '</div';
/**
* @class OC.Settings.TwoFactorBackupCodes.View
*/
var View = OC.Backbone.View.extend({
/**
* @type {undefined|Function}
*/
_template: undefined,
/**
* @param {Object} data
* @returns {string}
*/
template: function(data) {
if (!this._template) {
this._template = Handlebars.compile(TEMPLATE);
}
return this._template(data);
},
/**
* @type {boolean}
*/
_loading: undefined,
/**
* @type {boolean}
*/
_enabled: undefined,
/**
* @type {Number}
*/
_total: undefined,
/**
* @type {Number}
*/
_used: undefined,
/**
* @type {Array}
*/
_codes: undefined,
events: {
'click #generate-backup-codes': '_onGenerateBackupCodes',
'click #print-backup-codes': '_onPrintBackupCodes'
},
/**
* @returns {undefined}
*/
initialize: function() {
this._load();
},
/**
* @returns {self}
*/
render: function() {
this.$el.html(this.template({
enabled: this._enabled,
total: this._total,
used: this._used,
codes: this._codes,
download: this._getDownloadData()
}));
return this;
},
/**
* @private
* @returns {String}
*/
_getDownloadData: function() {
if (!this._codes) {
return '';
}
return 'data:text/plain,' + encodeURIComponent(_.reduce(this._codes, function(prev, code) {
return prev + code + '\r\n';
}, ''));
},
/**
* @private
* @returns {String}
*/
_getPrintData: function() {
if (!this._codes) {
return '';
}
return _.reduce(this._codes, function(prev, code) {
return prev + code + "<br>";
}, '');
},
/**
* Load codes from the server
*
* @returns {undefined}
*/
_load: function() {
this._loading = true;
var url = OC.generateUrl('/apps/twofactor_backupcodes/settings/state');
var loading = $.ajax(url, {
method: 'GET'
});
$.when(loading).done(function(data) {
this._enabled = data.enabled;
this._total = data.total;
this._used = data.used;
}.bind(this));
$.when(loading).always(function() {
this._loading = false;
this.render();
}.bind(this));
},
/**
* Event handler to generate the codes
*
* @returns {undefined}
*/
_onGenerateBackupCodes: function() {
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onGenerateBackupCodes, this));
return;
}
// Hide old codes
this._enabled = false;
this.render();
$('#generate-backup-codes').addClass('icon-loading-small');
var url = OC.generateUrl('/apps/twofactor_backupcodes/settings/create');
$.ajax(url, {
method: 'POST'
}).done(function(data) {
this._enabled = data.state.enabled;
this._total = data.state.total;
this._used = data.state.used;
this._codes = data.codes;
this.render();
}.bind(this)).fail(function() {
OC.Notification.showTemporary(t('twofactor_backupcodes', 'An error occurred while generating your backup codes'));
$('#generate-backup-codes').removeClass('icon-loading-small');
});
},
/**
* Event handler to print the codes
*
* @returns {undefined}
*/
_onPrintBackupCodes: function() {
var data = this._getPrintData();
var newTab = window.open('', t('twofactor_backupcodes', 'Nextcloud backup codes'));
newTab.document.write('<h1>' + t('twofactor_backupcodes', 'Nextcloud backup codes') + '</h1>');
newTab.document.write(data);
newTab.print();
newTab.close();
}
});
OC.Settings.TwoFactorBackupCodes.View = View;
})(OC, Handlebars, $, _);

View File

@ -0,0 +1,102 @@
<template>
<div>
<button v-if="!enabled"
id="generate-backup-codes">{{ t('twofactor_backupcodes', 'Generate backup codes') }}</button>
<p v-else>
<template v-if="!codes">
{{ t('twofactor_backupcodes', 'Backup codes have been generated. {used} of {total} codes have been used.', {used, total}) }}
</template>
<template v-else>
{{ t('twofactor_backupcodes', 'These are your backup codes. Please save and/or print them as you will not be able to read the codes again later') }}
<ul>
<li v-for="code in codes" class="backup-code">{{code}}</li>
</ul>
<a :href="downloadUrl"
class="button"
download="Nextcloud-backup-codes.txt">{{ t('twofactor_backupcodes', 'Save backup codes') }}</a>
<button class="button"
v-on:click="printCodes">{{ t('twofactor_backupcodes', 'Print backup codes') }}</button>
</template>
</p>
<p>
<button id="generate-backup-codes"
:class="{'icon-loading-small': generatingCodes}"
v-on:click="generateBackupCodes">{{ t('twofactor_backupcodes', 'Regenerate backup codes') }}</button>
</p>
<p>
{{ t('twofactor_backupcodes', 'If you regenerate backup codes, you automatically invalidate old codes.') }}
</p>
</div>
</template>
<script>
import confirmPassword from 'nextcloud-password-confirmation';
import {getState, generateCodes} from '../service/BackupCodesService';
import {print} from '../service/PrintService';
export default {
name: "PersonalSettings",
data() {
return {
enabled: false,
generatingCodes: false,
codes: undefined
};
},
computed: {
downloadUrl: function() {
if (!this.codes) {
return '';
}
return 'data:text/plain,' + encodeURIComponent(this.codes.reduce((prev, code) => {
return prev + code + '\r\n';
}, ''));
}
},
created: function() {
getState()
.then(state => {
this.enabled = state.enabled;
this.total = state.total;
this.used = state.used;
})
.catch(console.error.bind(this));
},
methods: {
generateBackupCodes: function() {
confirmPassword().then(() => {
// Hide old codes
this.enabled = false;
this.generatingCodes = true;
generateCodes().then(data => {
this.enabled = data.state.enabled;
this.total = data.state.total;
this.used = data.state.used;
this.codes = data.codes;
this.generatingCodes = false;
}).catch(err => {
OC.Notification.showTemporary(t('twofactor_backupcodes', 'An error occurred while generating your backup codes'));
this.generatingCodes = false;
throw err;
});
}).catch(console.error.bind(this));
},
getPrintData: function(codes) {
if (!codes) {
return '';
}
return codes.reduce((prev, code) => {
return prev + code + "<br>";
}, '');
},
printCodes: function() {
print(this.getPrintData(this.codes));
}
}
}
</script>

View File

@ -0,0 +1,46 @@
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
entry: path.join(__dirname, 'settings.js'),
output: {
path: path.resolve(__dirname, 'build'),
publicPath: '/build/',
filename: 'settings.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['vue-style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
plugins: [new VueLoaderPlugin()],
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js',
},
extensions: ['*', '.js', '.vue', '.json']
}
};

View File

@ -0,0 +1,12 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
devtool: '#eval-source-map',
})

View File

@ -0,0 +1,7 @@
const merge = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'production',
devtool: '#source-map'
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
{
"name": "twofactor_backupcodes",
"version": "1.0.0",
"description": "Nextcloud Two-Factor Backup Codes",
"main": "index.js",
"directories": {
"lib": "lib",
"test": "tests"
},
"scripts": {
"build": "webpack --progress --hide-modules --config js/webpack.prod.js",
"dev": "webpack --progress --watch --config js/webpack.dev.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.18.0",
"nextcloud-password-confirmation": "^0.1.0",
"vue": "^2.5.17"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"babel": "^6.23.0",
"babel-loader": "^8.0.2",
"vue-loader": "^15.4.1",
"vue-template-compiler": "^2.5.17",
"webpack": "^4.18.0",
"webpack-cli": "^3.1.0",
"webpack-merge": "^4.1.4"
}
}

View File

@ -1,7 +1,6 @@
<?php
script('twofactor_backupcodes', 'settingsview');
script('twofactor_backupcodes', 'settings');
script('twofactor_backupcodes', 'build/settings');
style('twofactor_backupcodes', 'style');
?>