Merge pull request #11163 from nextcloud/refactor/vue-backup-codes
Migrate backup codes settings to Vue
This commit is contained in:
commit
0ca78e15bc
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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)
|
||||
});
|
|
@ -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>
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
script('twofactor_backupcodes', 'settingsview');
|
||||
script('twofactor_backupcodes', 'settings');
|
||||
style('twofactor_backupcodes', 'style');
|
||||
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
};
|
|
@ -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',
|
||||
})
|
|
@ -0,0 +1,7 @@
|
|||
const merge = require('webpack-merge')
|
||||
const common = require('./webpack.common.js')
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
devtool: '#source-map'
|
||||
})
|
Loading…
Reference in New Issue