Merge pull request #11163 from nextcloud/refactor/vue-backup-codes

Migrate backup codes settings to Vue
This commit is contained in:
Christoph Wurst 2018-09-27 09:41:48 +02:00 committed by GitHub
commit 0ca78e15bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 4890 additions and 224 deletions

View File

@ -42,6 +42,13 @@ pipeline:
when:
matrix:
TESTS: vue-build-accessibility
vue-build-backupcodes:
image: node
commands:
- ./build/vue-builds.sh ./apps/twofactor_backupcodes/js/settings.js
when:
matrix:
TESTS: vue-build-backupscodes
checkers:
image: nextcloudci/php7.0:php7.0-19
commands:
@ -756,6 +763,7 @@ matrix:
- TESTS: vue-build-updatenotification
- TESTS: vue-build-oauth2
- TESTS: vue-build-accessibility
- TESTS: vue-build-backupcodes
- TESTS: nodb-codecov
ENABLE_REDIS: true
- TESTS: db-codecov

View File

@ -0,0 +1,25 @@
all: dev-setup build-js-production
dev-setup: clean clean-dev npm-init
npm-init:
npm install
npm-update:
npm update
build-js:
npm run dev
build-js-production:
npm run build
watch-js:
npm run watch
clean:
rm -f js/settings.js
rm -f js/settings.js.map
clean-dev:
rm -rf node_modules

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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, $, _);

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 webpack.prod.js",
"dev": "webpack --progress --watch --config 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.1.0",
"babel": "^6.23.0",
"babel-loader": "^8.0.2",
"vue-loader": "^15.4.2",
"vue-template-compiler": "^2.5.17",
"webpack": "^4.20.0",
"webpack-cli": "^3.1.1",
"webpack-merge": "^4.1.4"
}
}

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

@ -0,0 +1,9 @@
import Vue from 'vue';
import PersonalSettings from './views/PersonalSettings';
Vue.prototype.t = t;
export default new Vue({
el: '#twofactor-backupcodes-settings',
render: h => h(PersonalSettings)
});

View File

@ -0,0 +1,105 @@
<template>
<div>
<button v-if="!enabled"
id="generate-backup-codes"
v-on:click="generateBackupCodes">{{ t('twofactor_backupcodes', 'Generate backup codes') }}</button>
<template v-else>
<p>
<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>
</template>
</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

@ -1,6 +1,5 @@
<?php
script('twofactor_backupcodes', 'settingsview');
script('twofactor_backupcodes', 'settings');
style('twofactor_backupcodes', 'style');

View File

@ -0,0 +1,46 @@
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
entry: path.join(__dirname, 'src', 'settings.js'),
output: {
path: path.resolve(__dirname, 'js'),
publicPath: '/js',
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'
})