Skip to content

Commit f8eb7be

Browse files
authored
Merge pull request #1171 from nextcloud/2fa-backup-codes
add 2fa backup codes app
2 parents e3a5767 + 581a83c commit f8eb7be

File tree

33 files changed

+1510
-22
lines changed

33 files changed

+1510
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
!/apps/admin_audit
2828
!/apps/updatenotification
2929
!/apps/theming
30+
!/apps/twofactor_backupcodes
3031
!/apps/workflowengine
3132
/apps/files_external/3rdparty/irodsphp/PHPUnitTest
3233
/apps/files_external/3rdparty/irodsphp/web
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/**
4+
* @author Christoph Wurst <[email protected]>
5+
*
6+
* @license GNU AGPL version 3 or any later version
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
OC_App::registerPersonal('twofactor_backupcodes', 'settings/personal');
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="ISO-8859-1" ?>
2+
<database>
3+
<name>*dbname*</name>
4+
<create>true</create>
5+
<overwrite>false</overwrite>
6+
<charset>utf8</charset>
7+
<table>
8+
<name>*dbprefix*twofactor_backup_codes</name>
9+
<declaration>
10+
<field>
11+
<name>id</name>
12+
<type>integer</type>
13+
<autoincrement>1</autoincrement>
14+
<default>0</default>
15+
<notnull>true</notnull>
16+
<length>4</length>
17+
</field>
18+
<field>
19+
<name>user_id</name>
20+
<type>text</type>
21+
<default></default>
22+
<notnull>true</notnull>
23+
<length>64</length>
24+
</field>
25+
<field>
26+
<name>code</name>
27+
<type>text</type>
28+
<notnull>true</notnull>
29+
<length>64</length>
30+
</field>
31+
<field>
32+
<name>used</name>
33+
<type>integer</type>
34+
<notnull>true</notnull>
35+
<default>0</default>
36+
<length>1</length>
37+
</field>
38+
39+
<index>
40+
<name>two_factor_backupcodes_user_id</name>
41+
<field>
42+
<name>user_id</name>
43+
<sorting>ascending</sorting>
44+
</field>
45+
</index>
46+
</declaration>
47+
</table>
48+
</database>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0"?>
2+
<info>
3+
<id>twofactor_backupcodes</id>
4+
<name>Two factor backup codes</name>
5+
<description>A two-factor auth backup codes provider</description>
6+
<licence>agpl</licence>
7+
<author>Christoph Wurst</author>
8+
<version>1.0.0</version>
9+
<namespace>TwoFactor_BackupCodes</namespace>
10+
<category>other</category>
11+
12+
<two-factor-providers>
13+
<provider>OCA\TwoFactor_BackupCodes\Provider\BackupCodesProvider</provider>
14+
</two-factor-providers>
15+
16+
<dependencies>
17+
<owncloud min-version="9.2" max-version="9.2" />
18+
</dependencies>
19+
</info>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/**
4+
* @author Christoph Wurst <[email protected]>
5+
*
6+
* @license GNU AGPL version 3 or any later version
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
return [
23+
'routes' => [
24+
[
25+
'name' => 'settings#state',
26+
'url' => '/settings/state',
27+
'verb' => 'GET'
28+
],
29+
[
30+
'name' => 'settings#createCodes',
31+
'url' => '/settings/create',
32+
'verb' => 'POST'
33+
],
34+
]
35+
];
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.challenge-form {
2+
margin: 16px auto 1px !important;
3+
}
4+
5+
.challenge {
6+
margin-top: 0 !important;
7+
margin-left: 0 !important;
8+
}
9+
10+
.confirm-inline {
11+
position: absolute;
12+
right: 10px;
13+
top: 0;
14+
margin: 0 !important;
15+
padding-right: 25px !important;
16+
background-color: transparent !important;
17+
border: none !important;
18+
opacity: .5;
19+
}
20+
21+
.backup-code {
22+
font-family: monospace;
23+
letter-spacing: 0.02em;
24+
font-size: 1.2em;
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* global OC */
2+
3+
(function (OC) {
4+
'use strict';
5+
6+
OC.Settings = OC.Settings || {};
7+
OC.Settings.TwoFactorBackupCodes = OC.Settings.TwoFactorBackupCodes || {};
8+
9+
$(function () {
10+
var view = new OC.Settings.TwoFactorBackupCodes.View({
11+
el: $('#twofactor-backupcodes-settings')
12+
});
13+
view.render();
14+
});
15+
})(OC);
16+
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* global Backbone, Handlebars, OC, _ */
2+
3+
(function (OC, Handlebars, $, _) {
4+
'use strict';
5+
6+
OC.Settings = OC.Settings || {};
7+
OC.Settings.TwoFactorBackupCodes = OC.Settings.TwoFactorBackupCodes || {};
8+
9+
var TEMPLATE = '<div>'
10+
+ '{{#unless enabled}}'
11+
+ '<button id="generate-backup-codes">' + t('twofactor_backupcodes', 'Generate backup codes') + '</button>'
12+
+ '{{else}}'
13+
+ '<p>'
14+
+ '{{#unless codes}}'
15+
+ t('twofactor_backupcodes', 'Backup codes have been generated. {{used}} of {{total}} codes have been used.')
16+
+ '{{else}}'
17+
+ 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')
18+
+ '<ul>'
19+
+ '{{#each codes}}'
20+
+ '<li class="backup-code">{{this}}</li>'
21+
+ '{{/each}}'
22+
+ '</ul>'
23+
+ '<a href="{{download}}" class="button" download="Nextcloud-backup-codes.txt">' + t('twofactor_backupcodes', 'Save backup codes') + '</a>'
24+
+ '<button id="print-backup-codes" class="button">' + t('twofactor_backupcodes', 'Print backup codes') + '</button>'
25+
+ '{{/unless}}'
26+
+ '</p>'
27+
+ '<p>'
28+
+ '<button id="generate-backup-codes">' + t('twofactor_backupcodes', 'Regenerate backup codes') + '</button>'
29+
+ '</p>'
30+
+ '<p>'
31+
+ t('twofactor_backupcodes', 'If you regenerate backup codes, you automatically invalidate old codes.')
32+
+ '</p>'
33+
+ '{{/unless}}'
34+
+ '</div';
35+
36+
var View = OC.Backbone.View.extend({
37+
_template: undefined,
38+
template: function (data) {
39+
if (!this._template) {
40+
this._template = Handlebars.compile(TEMPLATE);
41+
}
42+
return this._template(data);
43+
},
44+
_loading: undefined,
45+
_enabled: undefined,
46+
_total: undefined,
47+
_used: undefined,
48+
_codes: undefined,
49+
events: {
50+
'click #generate-backup-codes': '_onGenerateBackupCodes',
51+
'click #print-backup-codes': '_onPrintBackupCodes',
52+
},
53+
initialize: function () {
54+
this._load();
55+
},
56+
render: function () {
57+
this.$el.html(this.template({
58+
enabled: this._enabled,
59+
total: this._total,
60+
used: this._used,
61+
codes: this._codes,
62+
download: this._getDownloadDataHref()
63+
}));
64+
},
65+
_getDownloadDataHref: function () {
66+
if (!this._codes) {
67+
return '';
68+
}
69+
return 'data:text/plain,' + encodeURIComponent(_.reduce(this._codes, function (prev, code) {
70+
return prev + code + "\r\n";
71+
}, ''));
72+
},
73+
_load: function () {
74+
this._loading = true;
75+
76+
var url = OC.generateUrl('/apps/twofactor_backupcodes/settings/state');
77+
var loading = $.ajax(url, {
78+
method: 'GET',
79+
});
80+
81+
$.when(loading).done(function (data) {
82+
this._enabled = data.enabled;
83+
this._total = data.total;
84+
this._used = data.used;
85+
}.bind(this));
86+
$.when(loading).always(function () {
87+
this._loading = false;
88+
this.render();
89+
}.bind(this));
90+
},
91+
_onGenerateBackupCodes: function () {
92+
// Hide old codes
93+
this._enabled = false;
94+
this.render();
95+
$('#generate-backup-codes').addClass('icon-loading-small');
96+
var url = OC.generateUrl('/apps/twofactor_backupcodes/settings/create');
97+
$.ajax(url, {
98+
method: 'POST'
99+
}).done(function (data) {
100+
this._enabled = data.state.enabled;
101+
this._total = data.state.total;
102+
this._used = data.state.used;
103+
this._codes = data.codes;
104+
this.render();
105+
}.bind(this)).fail(function () {
106+
OC.Notification.showTemporary('An error occurred while generating your backup codes');
107+
$('#generate-backup-codes').removeClass('icon-loading-small');
108+
});
109+
},
110+
_onPrintBackupCodes: function () {
111+
var url = this._getDownloadDataHref();
112+
window.open(url, 'Nextcloud backpu codes');
113+
window.print();
114+
window.close();
115+
}
116+
});
117+
118+
OC.Settings.TwoFactorBackupCodes.View = View;
119+
120+
})(OC, Handlebars, $, _);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
/**
4+
* @author Christoph Wurst <[email protected]>
5+
*
6+
* @license GNU AGPL version 3 or any later version
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
namespace OCA\TwoFactor_BackupCodes\Controller;
24+
25+
use OCA\TwoFactor_BackupCodes\Service\BackupCodeStorage;
26+
use OCP\AppFramework\Controller;
27+
use OCP\AppFramework\Http\JSONResponse;
28+
use OCP\IRequest;
29+
use OCP\IUserSession;
30+
31+
class SettingsController extends Controller {
32+
33+
/** @var BackupCodeStorage */
34+
private $storage;
35+
36+
/** @var IUserSession */
37+
private $userSession;
38+
39+
/**
40+
* @param string $appName
41+
* @param IRequest $request
42+
* @param BackupCodeStorage $storage
43+
* @param IUserSession $userSession
44+
*/
45+
public function __construct($appName, IRequest $request, BackupCodeStorage $storage, IUserSession $userSession) {
46+
parent::__construct($appName, $request);
47+
$this->userSession = $userSession;
48+
$this->storage = $storage;
49+
}
50+
51+
/**
52+
* @NoAdminRequired
53+
* @return JSONResponse
54+
*/
55+
public function state() {
56+
$user = $this->userSession->getUser();
57+
return $this->storage->getBackupCodesState($user);
58+
}
59+
60+
/**
61+
* @NoAdminRequired
62+
* @return JSONResponse
63+
*/
64+
public function createCodes() {
65+
$user = $this->userSession->getUser();
66+
$codes = $this->storage->createCodes($user);
67+
return [
68+
'codes' => $codes,
69+
'state' => $this->storage->getBackupCodesState($user),
70+
];
71+
}
72+
73+
}

0 commit comments

Comments
 (0)