aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKevin Decherf <kevin@kdecherf.com>2019-01-30 01:02:27 +0100
committerGitHub <noreply@github.com>2019-01-30 01:02:27 +0100
commit2e5b3fa361098498a9e42a65396a27e1eb487fba (patch)
treef20677c3d68c1ea756f0835ff179a0d7d3431a67
parentc6024246b744e411175318065f7c396bbb5a213e (diff)
parent4654a83b6438b88e3b7062a21d18999d9df2fb8e (diff)
downloadwallabag-2e5b3fa361098498a9e42a65396a27e1eb487fba.tar.gz
wallabag-2e5b3fa361098498a9e42a65396a27e1eb487fba.tar.zst
wallabag-2e5b3fa361098498a9e42a65396a27e1eb487fba.zip
Merge pull request #3798 from wallabag/update-two-factor-bundle
Enable OTP 2FA
-rw-r--r--.editorconfig2
-rw-r--r--.travis.yml6
-rw-r--r--app/DoctrineMigrations/Version20181202073750.php76
-rw-r--r--app/Resources/static/themes/_global/index.js18
-rwxr-xr-xapp/Resources/static/themes/material/index.js5
-rw-r--r--app/config/config.yml11
-rw-r--r--app/config/routing.yml8
-rw-r--r--app/config/security.yml9
-rw-r--r--composer.json9
-rw-r--r--src/Wallabag/CoreBundle/Command/ShowUserCommand.php3
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php133
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/UserInformationType.php9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml27
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml18
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml28
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml27
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml27
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml27
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.th.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml26
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig97
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig55
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig114
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig63
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php33
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php148
-rw-r--r--src/Wallabag/UserBundle/Form/UserType.php9
-rw-r--r--src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php4
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig14
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig11
-rw-r--r--tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php3
-rw-r--r--tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php81
-rw-r--r--tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php28
-rw-r--r--tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php14
-rw-r--r--tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php2
41 files changed, 1094 insertions, 233 deletions
diff --git a/.editorconfig b/.editorconfig
index 6553d30f..14044044 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -13,5 +13,5 @@ insert_final_newline = true
13indent_style = space 13indent_style = space
14indent_size = 2 14indent_size = 2
15 15
16[Makefile] 16[*akefile]
17indent_style = tab 17indent_style = tab
diff --git a/.travis.yml b/.travis.yml
index 0ca1e192..393d0033 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -51,13 +51,13 @@ install:
51 51
52before_script: 52before_script:
53 - PHP=$TRAVIS_PHP_VERSION 53 - PHP=$TRAVIS_PHP_VERSION
54 - if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; 54 - echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
55 # xdebug isn't enable for PHP 7.1 55 - phpenv config-rm xdebug.ini || echo "xdebug not available"
56 - if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
57 - composer self-update --no-progress 56 - composer self-update --no-progress
58 57
59script: 58script:
60 - travis_wait bash composer install -o --no-interaction --no-progress --prefer-dist 59 - travis_wait bash composer install -o --no-interaction --no-progress --prefer-dist
60
61 - echo "travis_fold:start:prepare" 61 - echo "travis_fold:start:prepare"
62 - make prepare DB=$DB 62 - make prepare DB=$DB
63 - echo "travis_fold:end:prepare" 63 - echo "travis_fold:end:prepare"
diff --git a/app/DoctrineMigrations/Version20181202073750.php b/app/DoctrineMigrations/Version20181202073750.php
new file mode 100644
index 00000000..5978291e
--- /dev/null
+++ b/app/DoctrineMigrations/Version20181202073750.php
@@ -0,0 +1,76 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Schema\Schema;
6use Wallabag\CoreBundle\Doctrine\WallabagMigration;
7
8/**
9 * Add 2fa OTP stuff.
10 */
11final class Version20181202073750 extends WallabagMigration
12{
13 public function up(Schema $schema): void
14 {
15 switch ($this->connection->getDatabasePlatform()->getName()) {
16 case 'sqlite':
17 $this->addSql('DROP INDEX UNIQ_1D63E7E5C05FB297');
18 $this->addSql('DROP INDEX UNIQ_1D63E7E5A0D96FBF');
19 $this->addSql('DROP INDEX UNIQ_1D63E7E592FC23A8');
20 $this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('user', true) . ' AS SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, twoFactorAuthentication FROM ' . $this->getTable('user', true) . '');
21 $this->addSql('DROP TABLE ' . $this->getTable('user', true) . '');
22 $this->addSql('CREATE TABLE ' . $this->getTable('user', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL COLLATE BINARY, username_canonical VARCHAR(180) NOT NULL COLLATE BINARY, email VARCHAR(180) NOT NULL COLLATE BINARY, email_canonical VARCHAR(180) NOT NULL COLLATE BINARY, enabled BOOLEAN NOT NULL, password VARCHAR(255) NOT NULL COLLATE BINARY, last_login DATETIME DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, name CLOB DEFAULT NULL COLLATE BINARY, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, authCode INTEGER DEFAULT NULL, emailTwoFactor BOOLEAN NOT NULL, salt VARCHAR(255) DEFAULT NULL, confirmation_token VARCHAR(180) DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:array)
23 , googleAuthenticatorSecret VARCHAR(255) DEFAULT NULL, backupCodes CLOB DEFAULT NULL --(DC2Type:json_array)
24 )');
25 $this->addSql('INSERT INTO ' . $this->getTable('user', true) . ' (id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, emailTwoFactor) SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, twoFactorAuthentication FROM __temp__' . $this->getTable('user', true) . '');
26 $this->addSql('DROP TABLE __temp__' . $this->getTable('user', true) . '');
27 $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5C05FB297 ON ' . $this->getTable('user', true) . ' (confirmation_token)');
28 $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5A0D96FBF ON ' . $this->getTable('user', true) . ' (email_canonical)');
29 $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E592FC23A8 ON ' . $this->getTable('user', true) . ' (username_canonical)');
30 break;
31 case 'mysql':
32 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL');
33 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE twoFactorAuthentication emailTwoFactor BOOLEAN NOT NULL');
34 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP trusted');
35 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD backupCodes LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:json_array)\'');
36 break;
37 case 'postgresql':
38 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL');
39 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' RENAME COLUMN twofactorauthentication TO emailTwoFactor');
40 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP trusted');
41 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD backupCodes TEXT DEFAULT NULL');
42 break;
43 }
44 }
45
46 public function down(Schema $schema): void
47 {
48 switch ($this->connection->getDatabasePlatform()->getName()) {
49 case 'sqlite':
50 $this->addSql('DROP INDEX UNIQ_1D63E7E592FC23A8');
51 $this->addSql('DROP INDEX UNIQ_1D63E7E5A0D96FBF');
52 $this->addSql('DROP INDEX UNIQ_1D63E7E5C05FB297');
53 $this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('user', true) . ' AS SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, emailTwoFactor FROM "' . $this->getTable('user', true) . '"');
54 $this->addSql('DROP TABLE "' . $this->getTable('user', true) . '"');
55 $this->addSql('CREATE TABLE "' . $this->getTable('user', true) . '" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, username_canonical VARCHAR(180) NOT NULL, email VARCHAR(180) NOT NULL, email_canonical VARCHAR(180) NOT NULL, enabled BOOLEAN NOT NULL, password VARCHAR(255) NOT NULL, last_login DATETIME DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, name CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, authCode INTEGER DEFAULT NULL, twoFactorAuthentication BOOLEAN NOT NULL, salt VARCHAR(255) NOT NULL COLLATE BINARY, confirmation_token VARCHAR(255) DEFAULT NULL COLLATE BINARY, roles CLOB NOT NULL COLLATE BINARY, trusted CLOB DEFAULT NULL COLLATE BINARY)');
56 $this->addSql('INSERT INTO "' . $this->getTable('user', true) . '" (id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, twoFactorAuthentication) SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, emailTwoFactor FROM __temp__' . $this->getTable('user', true) . '');
57 $this->addSql('DROP TABLE __temp__' . $this->getTable('user', true) . '');
58 $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E592FC23A8 ON "' . $this->getTable('user', true) . '" (username_canonical)');
59 $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5A0D96FBF ON "' . $this->getTable('user', true) . '" (email_canonical)');
60 $this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5C05FB297 ON "' . $this->getTable('user', true) . '" (confirmation_token)');
61 break;
62 case 'mysql':
63 $this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP googleAuthenticatorSecret');
64 $this->addSql('ALTER TABLE `' . $this->getTable('user') . '` CHANGE emailtwofactor twoFactorAuthentication BOOLEAN NOT NULL');
65 $this->addSql('ALTER TABLE `' . $this->getTable('user') . '` ADD trusted TEXT DEFAULT NULL');
66 $this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP backupCodes');
67 break;
68 case 'postgresql':
69 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP googleAuthenticatorSecret');
70 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' RENAME COLUMN emailTwoFactor TO twofactorauthentication');
71 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD trusted TEXT DEFAULT NULL');
72 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP backupCodes');
73 break;
74 }
75 }
76}
diff --git a/app/Resources/static/themes/_global/index.js b/app/Resources/static/themes/_global/index.js
index bb3e95b6..9ad96fc0 100644
--- a/app/Resources/static/themes/_global/index.js
+++ b/app/Resources/static/themes/_global/index.js
@@ -89,4 +89,22 @@ $(document).ready(() => {
89 } 89 }
90 }; 90 };
91 }); 91 });
92
93 // mimic radio button because emailTwoFactor is a boolean
94 $('#update_user_googleTwoFactor').on('change', () => {
95 $('#update_user_emailTwoFactor').prop('checked', false);
96 });
97
98 $('#update_user_emailTwoFactor').on('change', () => {
99 $('#update_user_googleTwoFactor').prop('checked', false);
100 });
101
102 // same mimic for super admin
103 $('#user_googleTwoFactor').on('change', () => {
104 $('#user_emailTwoFactor').prop('checked', false);
105 });
106
107 $('#user_emailTwoFactor').on('change', () => {
108 $('#user_googleTwoFactor').prop('checked', false);
109 });
92}); 110});
diff --git a/app/Resources/static/themes/material/index.js b/app/Resources/static/themes/material/index.js
index 05794597..2926cad1 100755
--- a/app/Resources/static/themes/material/index.js
+++ b/app/Resources/static/themes/material/index.js
@@ -50,25 +50,30 @@ $(document).ready(() => {
50 $('#tag_label').focus(); 50 $('#tag_label').focus();
51 return false; 51 return false;
52 }); 52 });
53
53 $('#nav-btn-add').on('click', () => { 54 $('#nav-btn-add').on('click', () => {
54 toggleNav('.nav-panel-add', '#entry_url'); 55 toggleNav('.nav-panel-add', '#entry_url');
55 return false; 56 return false;
56 }); 57 });
58
57 const materialAddForm = $('.nav-panel-add'); 59 const materialAddForm = $('.nav-panel-add');
58 materialAddForm.on('submit', () => { 60 materialAddForm.on('submit', () => {
59 materialAddForm.addClass('disabled'); 61 materialAddForm.addClass('disabled');
60 $('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur'); 62 $('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur');
61 }); 63 });
64
62 $('#nav-btn-search').on('click', () => { 65 $('#nav-btn-search').on('click', () => {
63 toggleNav('.nav-panel-search', '#search_entry_term'); 66 toggleNav('.nav-panel-search', '#search_entry_term');
64 return false; 67 return false;
65 }); 68 });
69
66 $('.close').on('click', (e) => { 70 $('.close').on('click', (e) => {
67 $(e.target).parent('.nav-panel-item').hide(100); 71 $(e.target).parent('.nav-panel-item').hide(100);
68 $('.nav-panel-actions').show(100); 72 $('.nav-panel-actions').show(100);
69 $('.nav-panels').css('background', 'transparent'); 73 $('.nav-panels').css('background', 'transparent');
70 return false; 74 return false;
71 }); 75 });
76
72 $(window).scroll(() => { 77 $(window).scroll(() => {
73 const s = $(window).scrollTop(); 78 const s = $(window).scrollTop();
74 const d = $(document).height(); 79 const d = $(document).height();
diff --git a/app/config/config.yml b/app/config/config.yml
index 4b34af30..2d8f9bf0 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -198,10 +198,17 @@ fos_oauth_server:
198 refresh_token_lifetime: 1209600 198 refresh_token_lifetime: 1209600
199 199
200scheb_two_factor: 200scheb_two_factor:
201 trusted_computer: 201 trusted_device:
202 enabled: true 202 enabled: true
203 cookie_name: wllbg_trusted_computer 203 cookie_name: wllbg_trusted_computer
204 cookie_lifetime: 2592000 204 lifetime: 2592000
205
206 backup_codes:
207 enabled: "%twofactor_auth%"
208
209 google:
210 enabled: "%twofactor_auth%"
211 template: WallabagUserBundle:Authentication:form.html.twig
205 212
206 email: 213 email:
207 enabled: "%twofactor_auth%" 214 enabled: "%twofactor_auth%"
diff --git a/app/config/routing.yml b/app/config/routing.yml
index 0bd2d130..a7c0f7e9 100644
--- a/app/config/routing.yml
+++ b/app/config/routing.yml
@@ -51,3 +51,11 @@ craue_config_settings_modify:
51 51
52fos_js_routing: 52fos_js_routing:
53 resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml" 53 resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"
54
552fa_login:
56 path: /2fa
57 defaults:
58 _controller: "scheb_two_factor.form_controller:form"
59
602fa_login_check:
61 path: /2fa_check
diff --git a/app/config/security.yml b/app/config/security.yml
index 96489e26..6a21b4e5 100644
--- a/app/config/security.yml
+++ b/app/config/security.yml
@@ -56,9 +56,17 @@ security:
56 path: /logout 56 path: /logout
57 target: / 57 target: /
58 58
59 two_factor:
60 provider: fos_userbundle
61 auth_form_path: 2fa_login
62 check_path: 2fa_login_check
63
59 access_control: 64 access_control:
60 - { path: ^/api/(doc|version|info|user), roles: IS_AUTHENTICATED_ANONYMOUSLY } 65 - { path: ^/api/(doc|version|info|user), roles: IS_AUTHENTICATED_ANONYMOUSLY }
61 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 66 - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
67 # force role for logout otherwise when 2fa enable, you won't be able to logout
68 # https://github.com/scheb/two-factor-bundle/issues/168#issuecomment-430822478
69 - { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] }
62 - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } 70 - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
63 - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } 71 - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
64 - { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } 72 - { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
@@ -67,5 +75,6 @@ security:
67 - { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY } 75 - { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
68 - { path: ^/settings, roles: ROLE_SUPER_ADMIN } 76 - { path: ^/settings, roles: ROLE_SUPER_ADMIN }
69 - { path: ^/annotations, roles: ROLE_USER } 77 - { path: ^/annotations, roles: ROLE_USER }
78 - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
70 - { path: ^/users, roles: ROLE_SUPER_ADMIN } 79 - { path: ^/users, roles: ROLE_SUPER_ADMIN }
71 - { path: ^/, roles: ROLE_USER } 80 - { path: ^/, roles: ROLE_USER }
diff --git a/composer.json b/composer.json
index 21d71b74..7678d7b8 100644
--- a/composer.json
+++ b/composer.json
@@ -31,7 +31,7 @@
31 "issues": "https://github.com/wallabag/wallabag/issues" 31 "issues": "https://github.com/wallabag/wallabag/issues"
32 }, 32 },
33 "require": { 33 "require": {
34 "php": ">=7.1.0", 34 "php": ">=7.1.3",
35 "ext-pcre": "*", 35 "ext-pcre": "*",
36 "ext-dom": "*", 36 "ext-dom": "*",
37 "ext-curl": "*", 37 "ext-curl": "*",
@@ -70,7 +70,7 @@
70 "friendsofsymfony/user-bundle": "2.0.*", 70 "friendsofsymfony/user-bundle": "2.0.*",
71 "friendsofsymfony/oauth-server-bundle": "^1.5", 71 "friendsofsymfony/oauth-server-bundle": "^1.5",
72 "stof/doctrine-extensions-bundle": "^1.2", 72 "stof/doctrine-extensions-bundle": "^1.2",
73 "scheb/two-factor-bundle": "^2.14", 73 "scheb/two-factor-bundle": "^3.0",
74 "grandt/phpepub": "dev-master", 74 "grandt/phpepub": "dev-master",
75 "wallabag/php-mobi": "~1.0", 75 "wallabag/php-mobi": "~1.0",
76 "kphoen/rulerz-bundle": "~0.13", 76 "kphoen/rulerz-bundle": "~0.13",
@@ -87,7 +87,8 @@
87 "friendsofsymfony/jsrouting-bundle": "^2.2", 87 "friendsofsymfony/jsrouting-bundle": "^2.2",
88 "bdunogier/guzzle-site-authenticator": "^1.0.0", 88 "bdunogier/guzzle-site-authenticator": "^1.0.0",
89 "defuse/php-encryption": "^2.1", 89 "defuse/php-encryption": "^2.1",
90 "html2text/html2text": "^4.1" 90 "html2text/html2text": "^4.1",
91 "pragmarx/recovery": "^0.1.0"
91 }, 92 },
92 "require-dev": { 93 "require-dev": {
93 "doctrine/doctrine-fixtures-bundle": "~3.0", 94 "doctrine/doctrine-fixtures-bundle": "~3.0",
@@ -147,7 +148,7 @@
147 "config": { 148 "config": {
148 "bin-dir": "bin", 149 "bin-dir": "bin",
149 "platform": { 150 "platform": {
150 "php": "7.1" 151 "php": "7.1.3"
151 } 152 }
152 }, 153 },
153 "minimum-stability": "dev", 154 "minimum-stability": "dev",
diff --git a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
index a0184267..c95efbf3 100644
--- a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
@@ -57,7 +57,8 @@ class ShowUserCommand extends ContainerAwareCommand
57 sprintf('Display name: %s', $user->getName()), 57 sprintf('Display name: %s', $user->getName()),
58 sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')), 58 sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')),
59 sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'), 59 sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'),
60 sprintf('2FA activated: %s', $user->isTwoFactorAuthentication() ? 'yes' : 'no'), 60 sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'),
61 sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'),
61 ]); 62 ]);
62 } 63 }
63 64
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index be6feb7c..9257ab18 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -2,6 +2,7 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use PragmaRX\Recovery\Recovery as BackupCodes;
5use Symfony\Bundle\FrameworkBundle\Controller\Controller; 6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
6use Symfony\Component\HttpFoundation\JsonResponse; 7use Symfony\Component\HttpFoundation\JsonResponse;
7use Symfony\Component\HttpFoundation\RedirectResponse; 8use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -46,7 +47,7 @@ class ConfigController extends Controller
46 $activeTheme = $this->get('liip_theme.active_theme'); 47 $activeTheme = $this->get('liip_theme.active_theme');
47 $activeTheme->setName($config->getTheme()); 48 $activeTheme->setName($config->getTheme());
48 49
49 $this->get('session')->getFlashBag()->add( 50 $this->addFlash(
50 'notice', 51 'notice',
51 'flashes.config.notice.config_saved' 52 'flashes.config.notice.config_saved'
52 ); 53 );
@@ -68,7 +69,7 @@ class ConfigController extends Controller
68 $userManager->updateUser($user, true); 69 $userManager->updateUser($user, true);
69 } 70 }
70 71
71 $this->get('session')->getFlashBag()->add('notice', $message); 72 $this->addFlash('notice', $message);
72 73
73 return $this->redirect($this->generateUrl('config') . '#set4'); 74 return $this->redirect($this->generateUrl('config') . '#set4');
74 } 75 }
@@ -83,7 +84,7 @@ class ConfigController extends Controller
83 if ($userForm->isSubmitted() && $userForm->isValid()) { 84 if ($userForm->isSubmitted() && $userForm->isValid()) {
84 $userManager->updateUser($user, true); 85 $userManager->updateUser($user, true);
85 86
86 $this->get('session')->getFlashBag()->add( 87 $this->addFlash(
87 'notice', 88 'notice',
88 'flashes.config.notice.user_updated' 89 'flashes.config.notice.user_updated'
89 ); 90 );
@@ -99,7 +100,7 @@ class ConfigController extends Controller
99 $em->persist($config); 100 $em->persist($config);
100 $em->flush(); 101 $em->flush();
101 102
102 $this->get('session')->getFlashBag()->add( 103 $this->addFlash(
103 'notice', 104 'notice',
104 'flashes.config.notice.rss_updated' 105 'flashes.config.notice.rss_updated'
105 ); 106 );
@@ -131,7 +132,7 @@ class ConfigController extends Controller
131 $em->persist($taggingRule); 132 $em->persist($taggingRule);
132 $em->flush(); 133 $em->flush();
133 134
134 $this->get('session')->getFlashBag()->add( 135 $this->addFlash(
135 'notice', 136 'notice',
136 'flashes.config.notice.tagging_rules_updated' 137 'flashes.config.notice.tagging_rules_updated'
137 ); 138 );
@@ -153,12 +154,124 @@ class ConfigController extends Controller
153 ], 154 ],
154 'twofactor_auth' => $this->getParameter('twofactor_auth'), 155 'twofactor_auth' => $this->getParameter('twofactor_auth'),
155 'wallabag_url' => $this->getParameter('domain_name'), 156 'wallabag_url' => $this->getParameter('domain_name'),
156 'enabled_users' => $this->get('wallabag_user.user_repository') 157 'enabled_users' => $this->get('wallabag_user.user_repository')->getSumEnabledUsers(),
157 ->getSumEnabledUsers(),
158 ]); 158 ]);
159 } 159 }
160 160
161 /** 161 /**
162 * Enable 2FA using email.
163 *
164 * @Route("/config/otp/email", name="config_otp_email")
165 */
166 public function otpEmailAction()
167 {
168 if (!$this->getParameter('twofactor_auth')) {
169 return $this->createNotFoundException('two_factor not enabled');
170 }
171
172 $user = $this->getUser();
173
174 $user->setGoogleAuthenticatorSecret(null);
175 $user->setBackupCodes(null);
176 $user->setEmailTwoFactor(true);
177
178 $this->container->get('fos_user.user_manager')->updateUser($user, true);
179
180 $this->addFlash(
181 'notice',
182 'flashes.config.notice.otp_enabled'
183 );
184
185 return $this->redirect($this->generateUrl('config') . '#set3');
186 }
187
188 /**
189 * Enable 2FA using OTP app, user will need to confirm the generated code from the app.
190 *
191 * @Route("/config/otp/app", name="config_otp_app")
192 */
193 public function otpAppAction()
194 {
195 if (!$this->getParameter('twofactor_auth')) {
196 return $this->createNotFoundException('two_factor not enabled');
197 }
198
199 $user = $this->getUser();
200 $secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret();
201
202 $user->setGoogleAuthenticatorSecret($secret);
203 $user->setEmailTwoFactor(false);
204
205 $backupCodes = (new BackupCodes())->toArray();
206 $backupCodesHashed = array_map(
207 function ($backupCode) {
208 return password_hash($backupCode, PASSWORD_DEFAULT);
209 },
210 $backupCodes
211 );
212
213 $user->setBackupCodes($backupCodesHashed);
214
215 $this->container->get('fos_user.user_manager')->updateUser($user, true);
216
217 return $this->render('WallabagCoreBundle:Config:otp_app.html.twig', [
218 'backupCodes' => $backupCodes,
219 'qr_code' => $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user),
220 ]);
221 }
222
223 /**
224 * Cancelling 2FA using OTP app.
225 *
226 * @Route("/config/otp/app/cancel", name="config_otp_app_cancel")
227 */
228 public function otpAppCancelAction()
229 {
230 if (!$this->getParameter('twofactor_auth')) {
231 return $this->createNotFoundException('two_factor not enabled');
232 }
233
234 $user = $this->getUser();
235 $user->setGoogleAuthenticatorSecret(null);
236 $user->setBackupCodes(null);
237
238 $this->container->get('fos_user.user_manager')->updateUser($user, true);
239
240 return $this->redirect($this->generateUrl('config') . '#set3');
241 }
242
243 /**
244 * Validate OTP code.
245 *
246 * @param Request $request
247 *
248 * @Route("/config/otp/app/check", name="config_otp_app_check")
249 */
250 public function otpAppCheckAction(Request $request)
251 {
252 $isValid = $this->get('scheb_two_factor.security.google_authenticator')->checkCode(
253 $this->getUser(),
254 $request->get('_auth_code')
255 );
256
257 if (true === $isValid) {
258 $this->addFlash(
259 'notice',
260 'flashes.config.notice.otp_enabled'
261 );
262
263 return $this->redirect($this->generateUrl('config') . '#set3');
264 }
265
266 $this->addFlash(
267 'two_factor',
268 'scheb_two_factor.code_invalid'
269 );
270
271 return $this->redirect($this->generateUrl('config_otp_app'));
272 }
273
274 /**
162 * @param Request $request 275 * @param Request $request
163 * 276 *
164 * @Route("/generate-token", name="generate_token") 277 * @Route("/generate-token", name="generate_token")
@@ -178,7 +291,7 @@ class ConfigController extends Controller
178 return new JsonResponse(['token' => $config->getRssToken()]); 291 return new JsonResponse(['token' => $config->getRssToken()]);
179 } 292 }
180 293
181 $this->get('session')->getFlashBag()->add( 294 $this->addFlash(
182 'notice', 295 'notice',
183 'flashes.config.notice.rss_token_updated' 296 'flashes.config.notice.rss_token_updated'
184 ); 297 );
@@ -203,7 +316,7 @@ class ConfigController extends Controller
203 $em->remove($rule); 316 $em->remove($rule);
204 $em->flush(); 317 $em->flush();
205 318
206 $this->get('session')->getFlashBag()->add( 319 $this->addFlash(
207 'notice', 320 'notice',
208 'flashes.config.notice.tagging_rules_deleted' 321 'flashes.config.notice.tagging_rules_deleted'
209 ); 322 );
@@ -269,7 +382,7 @@ class ConfigController extends Controller
269 break; 382 break;
270 } 383 }
271 384
272 $this->get('session')->getFlashBag()->add( 385 $this->addFlash(
273 'notice', 386 'notice',
274 'flashes.config.notice.' . $type . '_reset' 387 'flashes.config.notice.' . $type . '_reset'
275 ); 388 );
diff --git a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
index 07c99949..6e4c9154 100644
--- a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
@@ -21,9 +21,14 @@ class UserInformationType extends AbstractType
21 ->add('email', EmailType::class, [ 21 ->add('email', EmailType::class, [
22 'label' => 'config.form_user.email_label', 22 'label' => 'config.form_user.email_label',
23 ]) 23 ])
24 ->add('twoFactorAuthentication', CheckboxType::class, [ 24 ->add('emailTwoFactor', CheckboxType::class, [
25 'required' => false, 25 'required' => false,
26 'label' => 'config.form_user.twoFactorAuthentication_label', 26 'label' => 'config.form_user.emailTwoFactor_label',
27 ])
28 ->add('googleTwoFactor', CheckboxType::class, [
29 'required' => false,
30 'label' => 'config.form_user.googleTwoFactor_label',
31 'mapped' => false,
27 ]) 32 ])
28 ->add('save', SubmitType::class, [ 33 ->add('save', SubmitType::class, [
29 'label' => 'config.form.save', 34 'label' => 'config.form.save',
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index 5a770dff..454f547d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Adgangskode' 59 password: 'Adgangskode'
60 # rules: 'Tagging rules' 60 # rules: 'Tagging rules'
61 new_user: 'Tilføj bruger' 61 new_user: 'Tilføj bruger'
62 # reset: 'Reset area'
62 form: 63 form:
63 save: 'Gem' 64 save: 'Gem'
64 form_settings: 65 form_settings:
@@ -98,11 +99,19 @@ config:
98 # all: 'All' 99 # all: 'All'
99 # rss_limit: 'Number of items in the feed' 100 # rss_limit: 'Number of items in the feed'
100 form_user: 101 form_user:
101 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Navn' 103 name_label: 'Navn'
103 email_label: 'Emailadresse' 104 email_label: 'Emailadresse'
104 # twoFactorAuthentication_label: 'Two factor authentication' 105 two_factor:
105 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
106 delete: 115 delete:
107 # title: Delete my account (a.k.a danger zone) 116 # title: Delete my account (a.k.a danger zone)
108 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 117 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -160,6 +169,15 @@ config:
160 # and: 'One rule AND another' 169 # and: 'One rule AND another'
161 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 170 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 # page_title: Two-factor authentication
174 # app:
175 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 # cancel: Cancel
180 # enable: Enable
163 181
164entry: 182entry:
165 # default_title: 'Title of the entry' 183 # default_title: 'Title of the entry'
@@ -532,7 +550,8 @@ user:
532 email_label: 'Emailadresse' 550 email_label: 'Emailadresse'
533 # enabled_label: 'Enabled' 551 # enabled_label: 'Enabled'
534 # last_login_label: 'Last login' 552 # last_login_label: 'Last login'
535 # twofactor_label: Two factor authentication 553 # twofactor_email_label: Two factor authentication by email
554 # twofactor_google_label: Two factor authentication by OTP app
536 # save: Save 555 # save: Save
537 # delete: Delete 556 # delete: Delete
538 # delete_confirm: Are you sure? 557 # delete_confirm: Are you sure?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index 2ae8f08e..dc1d4723 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Kennwort' 59 password: 'Kennwort'
60 rules: 'Tagging-Regeln' 60 rules: 'Tagging-Regeln'
61 new_user: 'Benutzer hinzufügen' 61 new_user: 'Benutzer hinzufügen'
62 reset: 'Zurücksetzen'
62 form: 63 form:
63 save: 'Speichern' 64 save: 'Speichern'
64 form_settings: 65 form_settings:
@@ -98,11 +99,19 @@ config:
98 all: 'Alle' 99 all: 'Alle'
99 rss_limit: 'Anzahl der Einträge pro Feed' 100 rss_limit: 'Anzahl der Einträge pro Feed'
100 form_user: 101 form_user:
101 two_factor_description: "Wenn du die Zwei-Faktor-Authentifizierung aktivierst, erhältst du eine E-Mail mit einem Code bei jeder nicht vertrauenswürdigen Verbindung" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Name' 103 name_label: 'Name'
103 email_label: 'E-Mail-Adresse' 104 email_label: 'E-Mail-Adresse'
104 twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' 105 two_factor:
105 help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
106 delete: 115 delete:
107 title: 'Lösche mein Konto (a.k.a Gefahrenzone)' 116 title: 'Lösche mein Konto (a.k.a Gefahrenzone)'
108 description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.' 117 description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.'
@@ -532,7 +541,8 @@ user:
532 email_label: 'E-Mail-Adresse' 541 email_label: 'E-Mail-Adresse'
533 enabled_label: 'Aktiviert' 542 enabled_label: 'Aktiviert'
534 last_login_label: 'Letzter Login' 543 last_login_label: 'Letzter Login'
535 twofactor_label: 'Zwei-Faktor-Authentifizierung' 544 # twofactor_email_label: Two factor authentication by email
545 # twofactor_google_label: Two factor authentication by OTP app
536 save: 'Speichern' 546 save: 'Speichern'
537 delete: 'Löschen' 547 delete: 'Löschen'
538 delete_confirm: 'Bist du sicher?' 548 delete_confirm: 'Bist du sicher?'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index d1d74159..45145c80 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Password' 59 password: 'Password'
60 rules: 'Tagging rules' 60 rules: 'Tagging rules'
61 new_user: 'Add a user' 61 new_user: 'Add a user'
62 reset: 'Reset area'
62 form: 63 form:
63 save: 'Save' 64 save: 'Save'
64 form_settings: 65 form_settings:
@@ -98,11 +99,19 @@ config:
98 all: 'All' 99 all: 'All'
99 rss_limit: 'Number of items in the feed' 100 rss_limit: 'Number of items in the feed'
100 form_user: 101 form_user:
101 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." 102 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Name' 103 name_label: 'Name'
103 email_label: 'Email' 104 email_label: 'Email'
104 twoFactorAuthentication_label: 'Two factor authentication' 105 two_factor:
105 help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 emailTwoFactor_label: 'Using email (receive a code by email)'
107 googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 table_method: Method
109 table_state: State
110 table_action: Action
111 state_enabled: Enabled
112 state_disabled: Disabled
113 action_email: Use email
114 action_app: Use OTP App
106 delete: 115 delete:
107 title: Delete my account (a.k.a danger zone) 116 title: Delete my account (a.k.a danger zone)
108 description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 117 description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -160,6 +169,15 @@ config:
160 and: 'One rule AND another' 169 and: 'One rule AND another'
161 matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 170 matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
162 notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 page_title: Two-factor authentication
174 app:
175 two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 cancel: Cancel
180 enable: Enable
163 181
164entry: 182entry:
165 default_title: 'Title of the entry' 183 default_title: 'Title of the entry'
@@ -532,7 +550,8 @@ user:
532 email_label: 'Email' 550 email_label: 'Email'
533 enabled_label: 'Enabled' 551 enabled_label: 'Enabled'
534 last_login_label: 'Last login' 552 last_login_label: 'Last login'
535 twofactor_label: Two factor authentication 553 twofactor_email_label: Two factor authentication by email
554 twofactor_google_label: Two factor authentication by OTP app
536 save: Save 555 save: Save
537 delete: Delete 556 delete: Delete
538 delete_confirm: Are you sure? 557 delete_confirm: Are you sure?
@@ -578,6 +597,7 @@ flashes:
578 tags_reset: Tags reset 597 tags_reset: Tags reset
579 entries_reset: Entries reset 598 entries_reset: Entries reset
580 archived_reset: Archived entries deleted 599 archived_reset: Archived entries deleted
600 otp_enabled: Two-factor authentication enabled
581 entry: 601 entry:
582 notice: 602 notice:
583 entry_already_saved: 'Entry already saved on %date%' 603 entry_already_saved: 'Entry already saved on %date%'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index 741d3e9f..c1047e55 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Contraseña' 59 password: 'Contraseña'
60 rules: 'Reglas de etiquetado automáticas' 60 rules: 'Reglas de etiquetado automáticas'
61 new_user: 'Añadir un usuario' 61 new_user: 'Añadir un usuario'
62 reset: 'Reiniciar mi cuenta'
62 form: 63 form:
63 save: 'Guardar' 64 save: 'Guardar'
64 form_settings: 65 form_settings:
@@ -98,11 +99,19 @@ config:
98 # all: 'All' 99 # all: 'All'
99 rss_limit: 'Límite de artículos en feed RSS' 100 rss_limit: 'Límite de artículos en feed RSS'
100 form_user: 101 form_user:
101 two_factor_description: "Con la autenticación en dos pasos recibirá código por e-mail en cada nueva conexión que no sea de confianza." 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Nombre' 103 name_label: 'Nombre'
103 email_label: 'Dirección de e-mail' 104 email_label: 'Dirección de e-mail'
104 twoFactorAuthentication_label: 'Autenticación en dos pasos' 105 two_factor:
105 help_twoFactorAuthentication: "Si activas la autenticación en dos pasos, cada vez que quieras iniciar sesión en wallabag recibirás un código por e-mail." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
106 delete: 115 delete:
107 title: Eliminar mi cuenta (Zona peligrosa) 116 title: Eliminar mi cuenta (Zona peligrosa)
108 description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado. 117 description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado.
@@ -160,6 +169,15 @@ config:
160 and: 'Una regla Y la otra' 169 and: 'Una regla Y la otra'
161 matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>' 170 matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 # page_title: Two-factor authentication
174 # app:
175 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 # cancel: Cancel
180 # enable: Enable
163 181
164entry: 182entry:
165 default_title: 'Título del artículo' 183 default_title: 'Título del artículo'
@@ -532,7 +550,8 @@ user:
532 email_label: 'E-mail' 550 email_label: 'E-mail'
533 enabled_label: 'Activado' 551 enabled_label: 'Activado'
534 last_login_label: 'Último inicio de sesión' 552 last_login_label: 'Último inicio de sesión'
535 twofactor_label: Autenticación en dos pasos 553 # twofactor_email_label: Two factor authentication by email
554 # twofactor_google_label: Two factor authentication by OTP app
536 save: Guardar 555 save: Guardar
537 delete: Eliminar 556 delete: Eliminar
538 delete_confirm: ¿Estás seguro? 557 delete_confirm: ¿Estás seguro?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index 2ef5dd52..3042de2e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -59,6 +59,7 @@ config:
59 password: 'رمز' 59 password: 'رمز'
60 rules: 'برچسب‌گذاری خودکار' 60 rules: 'برچسب‌گذاری خودکار'
61 new_user: 'افزودن کاربر' 61 new_user: 'افزودن کاربر'
62 # reset: 'Reset area'
62 form: 63 form:
63 save: 'ذخیره' 64 save: 'ذخیره'
64 form_settings: 65 form_settings:
@@ -98,11 +99,19 @@ config:
98 # all: 'All' 99 # all: 'All'
99 rss_limit: 'محدودیت آر-اس-اس' 100 rss_limit: 'محدودیت آر-اس-اس'
100 form_user: 101 form_user:
101 two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'نام' 103 name_label: 'نام'
103 email_label: 'نشانی ایمیل' 104 email_label: 'نشانی ایمیل'
104 twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' 105 two_factor:
105 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
106 delete: 115 delete:
107 # title: Delete my account (a.k.a danger zone) 116 # title: Delete my account (a.k.a danger zone)
108 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 117 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -160,6 +169,15 @@ config:
160 # and: 'One rule AND another' 169 # and: 'One rule AND another'
161 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 170 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 # page_title: Two-factor authentication
174 # app:
175 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 # cancel: Cancel
180 # enable: Enable
163 181
164entry: 182entry:
165 # default_title: 'Title of the entry' 183 # default_title: 'Title of the entry'
@@ -532,7 +550,8 @@ user:
532 email_label: 'نشانی ایمیل' 550 email_label: 'نشانی ایمیل'
533 # enabled_label: 'Enabled' 551 # enabled_label: 'Enabled'
534 # last_login_label: 'Last login' 552 # last_login_label: 'Last login'
535 # twofactor_label: Two factor authentication 553 # twofactor_email_label: Two factor authentication by email
554 # twofactor_google_label: Two factor authentication by OTP app
536 # save: Save 555 # save: Save
537 # delete: Delete 556 # delete: Delete
538 # delete_confirm: Are you sure? 557 # delete_confirm: Are you sure?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 7a2029b4..57740ba2 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -59,6 +59,7 @@ config:
59 password: "Mot de passe" 59 password: "Mot de passe"
60 rules: "Règles de tag automatiques" 60 rules: "Règles de tag automatiques"
61 new_user: "Créer un compte" 61 new_user: "Créer un compte"
62 reset: "Réinitialisation"
62 form: 63 form:
63 save: "Enregistrer" 64 save: "Enregistrer"
64 form_settings: 65 form_settings:
@@ -98,11 +99,19 @@ config:
98 all: "Tous" 99 all: "Tous"
99 rss_limit: "Nombre d’articles dans le flux" 100 rss_limit: "Nombre d’articles dans le flux"
100 form_user: 101 form_user:
101 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée." 102 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel OU que vous devriez utiliser une application de mot de passe à usage unique (comme Google Authenticator, Authy or FreeOTP) pour obtenir un code temporaire à chaque nouvelle connexion non approuvée. Vous ne pouvez pas choisir les deux options."
102 name_label: "Nom" 103 name_label: "Nom"
103 email_label: "Adresse courriel" 104 email_label: "Adresse courriel"
104 twoFactorAuthentication_label: "Double authentification" 105 two_factor:
105 help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." 106 emailTwoFactor_label: 'En utlisant l’email (recevez un code par email)'
107 googleTwoFactor_label: 'En utilisant une application de mot de passe à usage unique (ouvrez l’app, comme Google Authenticator, Authy or FreeOTP, pour obtenir un mot de passe à usage unique)'
108 table_method: Méthode
109 table_state: État
110 table_action: Action
111 state_enabled: Activé
112 state_disabled: Désactivé
113 action_email: Utiliser l'email
114 action_app: Utiliser une app OTP
106 delete: 115 delete:
107 title: "Supprimer mon compte (attention danger !)" 116 title: "Supprimer mon compte (attention danger !)"
108 description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté." 117 description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté."
@@ -160,6 +169,15 @@ config:
160 and: "Une règle ET l’autre" 169 and: "Une règle ET l’autre"
161 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>" 170 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
162 notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>" 171 notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>"
172 otp:
173 page_title: Authentification double-facteur
174 app:
175 two_factor_code_description_1: Vous venez d’activer l’authentification double-facteur, ouvrez votre application OTP pour configurer la génération du mot de passe à usage unique. Ces informations disparaîtront après un rechargement de la page.
176 two_factor_code_description_2: 'Vous pouvez scanner le QR code avec votre application :'
177 two_factor_code_description_3: 'N’oubliez pas de sauvegarder ces codes de secours dans un endroit sûr, vous pourrez les utiliser si vous ne pouvez plus accéder à votre application OTP :'
178 two_factor_code_description_4: 'Testez un code généré par votre application OTP :'
179 cancel: Annuler
180 enable: Activer
163 181
164entry: 182entry:
165 default_title: "Titre de l’article" 183 default_title: "Titre de l’article"
@@ -533,6 +551,8 @@ user:
533 enabled_label: "Activé" 551 enabled_label: "Activé"
534 last_login_label: "Dernière connexion" 552 last_login_label: "Dernière connexion"
535 twofactor_label: "Double authentification" 553 twofactor_label: "Double authentification"
554 twofactor_email_label: Double authentification par email
555 twofactor_google_label: Double authentification par OTP app
536 save: "Sauvegarder" 556 save: "Sauvegarder"
537 delete: "Supprimer" 557 delete: "Supprimer"
538 delete_confirm: "Êtes-vous sûr ?" 558 delete_confirm: "Êtes-vous sûr ?"
@@ -578,6 +598,7 @@ flashes:
578 tags_reset: "Tags supprimés" 598 tags_reset: "Tags supprimés"
579 entries_reset: "Articles supprimés" 599 entries_reset: "Articles supprimés"
580 archived_reset: "Articles archivés supprimés" 600 archived_reset: "Articles archivés supprimés"
601 otp_enabled: "Authentification à double-facteur activée"
581 entry: 602 entry:
582 notice: 603 notice:
583 entry_already_saved: "Article déjà sauvegardé le %date%" 604 entry_already_saved: "Article déjà sauvegardé le %date%"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index 3a459445..274e5338 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Password' 59 password: 'Password'
60 rules: 'Regole di etichettatura' 60 rules: 'Regole di etichettatura'
61 new_user: 'Aggiungi utente' 61 new_user: 'Aggiungi utente'
62 reset: 'Area di reset'
62 form: 63 form:
63 save: 'Salva' 64 save: 'Salva'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 # all: 'All' 99 # all: 'All'
99 rss_limit: 'Numero di elementi nel feed' 100 rss_limit: 'Numero di elementi nel feed'
100 form_user: 101 form_user:
101 two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Nome' 103 name_label: 'Nome'
103 email_label: 'E-mail' 104 email_label: 'E-mail'
104 twoFactorAuthentication_label: 'Autenticazione a due fattori' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via E-mail." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 title: Cancella il mio account (zona pericolosa) 115 title: Cancella il mio account (zona pericolosa)
108 description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso. 116 description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso.
@@ -160,6 +168,15 @@ config:
160 and: "Una regola E un'altra" 168 and: "Una regola E un'altra"
161 matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>' 169 matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 default_title: "Titolo del contenuto" 182 default_title: "Titolo del contenuto"
@@ -532,7 +549,8 @@ user:
532 email_label: 'E-mail' 549 email_label: 'E-mail'
533 enabled_label: 'Abilitato' 550 enabled_label: 'Abilitato'
534 last_login_label: 'Ultima connessione' 551 last_login_label: 'Ultima connessione'
535 twofactor_label: Autenticazione a due fattori 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
536 save: Salva 554 save: Salva
537 delete: Cancella 555 delete: Cancella
538 delete_confirm: Sei sicuro? 556 delete_confirm: Sei sicuro?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index 9df9e645..4e5370f9 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Senhal' 59 password: 'Senhal'
60 rules: "Règlas d'etiquetas automaticas" 60 rules: "Règlas d'etiquetas automaticas"
61 new_user: 'Crear un compte' 61 new_user: 'Crear un compte'
62 reset: 'Zòna de reïnicializacion'
62 form: 63 form:
63 save: 'Enregistrar' 64 save: 'Enregistrar'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 all: 'Totes' 99 all: 'Totes'
99 rss_limit: "Nombre d'articles dins un flux RSS" 100 rss_limit: "Nombre d'articles dins un flux RSS"
100 form_user: 101 form_user:
101 two_factor_description: "Activar l'autentificacion en dos temps vòl dire que recebretz un còdi per corrièl per cada novèla connexion pas aprovada." 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Nom' 103 name_label: 'Nom'
103 email_label: 'Adreça de corrièl' 104 email_label: 'Adreça de corrièl'
104 twoFactorAuthentication_label: 'Dobla autentificacion' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 help_twoFactorAuthentication: "S'avètz activat l'autentificacion en dos temps, cada còp que volètz vos connectar a wallabag, recebretz un còdi per corrièl." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 title: Suprimir mon compte (Mèfi zòna perilhosa) 115 title: Suprimir mon compte (Mèfi zòna perilhosa)
108 description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat. 116 description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat.
@@ -160,6 +168,15 @@ config:
160 and: "Una règla E l'autra" 168 and: "Una règla E l'autra"
161 matches: 'Teste se un <i>subjècte</i> correspond a una <i>recèrca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>' 169 matches: 'Teste se un <i>subjècte</i> correspond a una <i>recèrca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>'
162 notmatches: 'Teste se <i>subjècte</i> correspond pas a una <i>recèrca</i> (sensibla a la cassa).<br />Example : <code>title notmatches "football"</code>' 170 notmatches: 'Teste se <i>subjècte</i> correspond pas a una <i>recèrca</i> (sensibla a la cassa).<br />Example : <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 default_title: "Títol de l'article" 182 default_title: "Títol de l'article"
@@ -532,7 +549,8 @@ user:
532 email_label: 'Adreça de corrièl' 549 email_label: 'Adreça de corrièl'
533 enabled_label: 'Actiu' 550 enabled_label: 'Actiu'
534 last_login_label: 'Darrièra connexion' 551 last_login_label: 'Darrièra connexion'
535 twofactor_label: 'Autentificacion doble-factor' 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
536 save: 'Enregistrar' 554 save: 'Enregistrar'
537 delete: 'Suprimir' 555 delete: 'Suprimir'
538 delete_confirm: 'Sètz segur ?' 556 delete_confirm: 'Sètz segur ?'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index 684c40e2..a7a4d6c3 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Hasło' 59 password: 'Hasło'
60 rules: 'Zasady tagowania' 60 rules: 'Zasady tagowania'
61 new_user: 'Dodaj użytkownika' 61 new_user: 'Dodaj użytkownika'
62 reset: 'Reset'
62 form: 63 form:
63 save: 'Zapisz' 64 save: 'Zapisz'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 all: 'Wszystkie' 99 all: 'Wszystkie'
99 rss_limit: 'Link do RSS' 100 rss_limit: 'Link do RSS'
100 form_user: 101 form_user:
101 two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Nazwa' 103 name_label: 'Nazwa'
103 email_label: 'Adres email' 104 email_label: 'Adres email'
104 twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 help_twoFactorAuthentication: "Jeżeli włączysz autoryzację dwuetapową. Za każdym razem, kiedy będziesz chciał się zalogować, dostaniesz kod na swój e-mail." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 title: Usuń moje konto (niebezpieczna strefa !) 115 title: Usuń moje konto (niebezpieczna strefa !)
108 description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany. 116 description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany.
@@ -160,6 +168,15 @@ config:
160 and: 'Jedna reguła I inna' 168 and: 'Jedna reguła I inna'
161 matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>' 169 matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>'
162 notmatches: 'Sprawdź czy <i>temat</i> nie zawiera <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł nie zawiera "piłka nożna"</code>' 170 notmatches: 'Sprawdź czy <i>temat</i> nie zawiera <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł nie zawiera "piłka nożna"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 default_title: 'Tytuł wpisu' 182 default_title: 'Tytuł wpisu'
@@ -532,7 +549,8 @@ user:
532 email_label: 'Adres email' 549 email_label: 'Adres email'
533 enabled_label: 'Włączony' 550 enabled_label: 'Włączony'
534 last_login_label: 'Ostatnie logowanie' 551 last_login_label: 'Ostatnie logowanie'
535 twofactor_label: Autoryzacja dwuetapowa 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
536 save: Zapisz 554 save: Zapisz
537 delete: Usuń 555 delete: Usuń
538 delete_confirm: Jesteś pewien? 556 delete_confirm: Jesteś pewien?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
index 7932d7ab..a5483a6d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Senha' 59 password: 'Senha'
60 rules: 'Regras de tags' 60 rules: 'Regras de tags'
61 new_user: 'Adicionar um usuário' 61 new_user: 'Adicionar um usuário'
62 # reset: 'Reset area'
62 form: 63 form:
63 save: 'Salvar' 64 save: 'Salvar'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 # all: 'All' 99 # all: 'All'
99 rss_limit: 'Número de itens no feed' 100 rss_limit: 'Número de itens no feed'
100 form_user: 101 form_user:
101 two_factor_description: 'Habilitar autenticação de dois passos significa que você receberá um e-mail com um código a cada nova conexão desconhecida.' 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Nome' 103 name_label: 'Nome'
103 email_label: 'E-mail' 104 email_label: 'E-mail'
104 twoFactorAuthentication_label: 'Autenticação de dois passos' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 # title: Delete my account (a.k.a danger zone) 115 # title: Delete my account (a.k.a danger zone)
108 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 116 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -160,6 +168,15 @@ config:
160 and: 'Uma regra E outra' 168 and: 'Uma regra E outra'
161 matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>' 169 matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 default_title: 'Título da entrada' 182 default_title: 'Título da entrada'
@@ -532,7 +549,8 @@ user:
532 email_label: 'E-mail' 549 email_label: 'E-mail'
533 enabled_label: 'Habilitado' 550 enabled_label: 'Habilitado'
534 last_login_label: 'Último login' 551 last_login_label: 'Último login'
535 twofactor_label: 'Autenticação de dois passos' 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
536 save: 'Salvar' 554 save: 'Salvar'
537 delete: 'Apagar' 555 delete: 'Apagar'
538 delete_confirm: 'Tem certeza?' 556 delete_confirm: 'Tem certeza?'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index 4d091f03..3b7fbd69 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Parolă' 59 password: 'Parolă'
60 # rules: 'Tagging rules' 60 # rules: 'Tagging rules'
61 new_user: 'Crează un utilizator' 61 new_user: 'Crează un utilizator'
62 # reset: 'Reset area'
62 form: 63 form:
63 save: 'Salvează' 64 save: 'Salvează'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 # all: 'All' 99 # all: 'All'
99 rss_limit: 'Limită RSS' 100 rss_limit: 'Limită RSS'
100 form_user: 101 form_user:
101 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'Nume' 103 name_label: 'Nume'
103 email_label: 'E-mail' 104 email_label: 'E-mail'
104 # twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 # title: Delete my account (a.k.a danger zone) 115 # title: Delete my account (a.k.a danger zone)
108 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 116 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -160,6 +168,15 @@ config:
160 # and: 'One rule AND another' 168 # and: 'One rule AND another'
161 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 169 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 # default_title: 'Title of the entry' 182 # default_title: 'Title of the entry'
@@ -532,7 +549,8 @@ user:
532 email_label: 'E-mail' 549 email_label: 'E-mail'
533 # enabled_label: 'Enabled' 550 # enabled_label: 'Enabled'
534 # last_login_label: 'Last login' 551 # last_login_label: 'Last login'
535 # twofactor_label: Two factor authentication 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
536 # save: Save 554 # save: Save
537 # delete: Delete 555 # delete: Delete
538 # delete_confirm: Are you sure? 556 # delete_confirm: Are you sure?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
index cc327ae4..92746631 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
@@ -58,6 +58,7 @@ config:
58 password: 'Пароль' 58 password: 'Пароль'
59 rules: 'Правила настройки простановки тегов' 59 rules: 'Правила настройки простановки тегов'
60 new_user: 'Добавить пользователя' 60 new_user: 'Добавить пользователя'
61 reset: 'Сброс данных'
61 form: 62 form:
62 save: 'Сохранить' 63 save: 'Сохранить'
63 form_settings: 64 form_settings:
@@ -95,11 +96,18 @@ config:
95 archive: 'архивные' 96 archive: 'архивные'
96 rss_limit: 'Количество записей в фиде' 97 rss_limit: 'Количество записей в фиде'
97 form_user: 98 form_user:
98 two_factor_description: "Включить двухфакторную аутентификацию, Вы получите сообщение на указанный email с кодом, при каждом новом непроверенном подключении." 99 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
99 name_label: 'Имя' 100 name_label: 'Имя'
100 email_label: 'Email' 101 email_label: 'Email'
101 twoFactorAuthentication_label: 'Двухфакторная аутентификация' 102 # emailTwoFactor_label: 'Using email (receive a code by email)'
102 help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." 103 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
104 # table_method: Method
105 # table_state: State
106 # table_action: Action
107 # state_enabled: Enabled
108 # state_disabled: Disabled
109 # action_email: Use email
110 # action_app: Use OTP App
103 delete: 111 delete:
104 title: "Удалить мой аккаунт (или опасная зона)" 112 title: "Удалить мой аккаунт (или опасная зона)"
105 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." 113 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@@ -155,6 +163,15 @@ config:
155 or: 'Одно правило ИЛИ другое' 163 or: 'Одно правило ИЛИ другое'
156 and: 'Одно правило И другое' 164 and: 'Одно правило И другое'
157 matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>' 165 matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>'
166 otp:
167 # page_title: Two-factor authentication
168 # app:
169 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
170 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
171 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
172 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
173 # cancel: Cancel
174 # enable: Enable
158 175
159entry: 176entry:
160 default_title: 'Название записи' 177 default_title: 'Название записи'
@@ -520,7 +537,8 @@ user:
520 email_label: 'Email' 537 email_label: 'Email'
521 enabled_label: 'Включить' 538 enabled_label: 'Включить'
522 last_login_label: 'Последний вход' 539 last_login_label: 'Последний вход'
523 twofactor_label: "Двухфакторная аутентификация" 540 # twofactor_email_label: Two factor authentication by email
541 # twofactor_google_label: Two factor authentication by OTP app
524 save: "Сохранить" 542 save: "Сохранить"
525 delete: "Удалить" 543 delete: "Удалить"
526 delete_confirm: "Вы уверены?" 544 delete_confirm: "Вы уверены?"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
index 148aa541..1fe4fa0e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
@@ -59,6 +59,7 @@ config:
59 password: 'รหัสผ่าน' 59 password: 'รหัสผ่าน'
60 rules: 'การแท็กข้อบังคับ' 60 rules: 'การแท็กข้อบังคับ'
61 new_user: 'เพิ่มผู้ใช้' 61 new_user: 'เพิ่มผู้ใช้'
62 reset: 'รีเซ็ตพื้นที่ '
62 form: 63 form:
63 save: 'บันทึก' 64 save: 'บันทึก'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 all: 'ทั้งหมด' 99 all: 'ทั้งหมด'
99 rss_limit: 'จำนวนไอเทมที่เก็บ' 100 rss_limit: 'จำนวนไอเทมที่เก็บ'
100 form_user: 101 form_user:
101 two_factor_description: "การเปิดใช้งาน two factor authentication คือคุณจะต้องได้รับอีเมลกับ code ที่ยังไม่ตรวจสอบในการเชื่อมต่อ" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'ชื่อ' 103 name_label: 'ชื่อ'
103 email_label: 'อีเมล' 104 email_label: 'อีเมล'
104 twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!) 115 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
108 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก 116 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@@ -160,6 +168,15 @@ config:
160 and: 'หนึ่งข้อบังคับและอื่นๆ' 168 and: 'หนึ่งข้อบังคับและอื่นๆ'
161 matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>' 169 matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>'
162 notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>' 170 notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 default_title: 'หัวข้อรายการ' 182 default_title: 'หัวข้อรายการ'
@@ -530,7 +547,8 @@ user:
530 email_label: 'อีเมล' 547 email_label: 'อีเมล'
531 enabled_label: 'เปิดใช้งาน' 548 enabled_label: 'เปิดใช้งาน'
532 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' 549 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย'
533 twofactor_label: Two factor authentication 550 # twofactor_email_label: Two factor authentication by email
551 # twofactor_google_label: Two factor authentication by OTP app
534 save: บันทึก 552 save: บันทึก
535 delete: ลบ 553 delete: ลบ
536 delete_confirm: ตุณแน่ใจหรือไม่? 554 delete_confirm: ตุณแน่ใจหรือไม่?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index 6fb9852a..3b8a0d59 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -59,6 +59,7 @@ config:
59 password: 'Şifre' 59 password: 'Şifre'
60 rules: 'Etiketleme kuralları' 60 rules: 'Etiketleme kuralları'
61 new_user: 'Bir kullanıcı ekle' 61 new_user: 'Bir kullanıcı ekle'
62 # reset: 'Reset area'
62 form: 63 form:
63 save: 'Kaydet' 64 save: 'Kaydet'
64 form_settings: 65 form_settings:
@@ -98,11 +99,18 @@ config:
98 # all: 'All' 99 # all: 'All'
99 rss_limit: 'RSS içeriğinden talep edilecek makale limiti' 100 rss_limit: 'RSS içeriğinden talep edilecek makale limiti'
100 form_user: 101 form_user:
101 two_factor_description: "İki adımlı doğrulamayı aktifleştirdiğinizde, her yeni güvenilmeyen bağlantılarda size e-posta ile bir kod alacaksınız." 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
102 name_label: 'İsim' 103 name_label: 'İsim'
103 email_label: 'E-posta' 104 email_label: 'E-posta'
104 twoFactorAuthentication_label: 'İki adımlı doğrulama' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
105 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
106 delete: 114 delete:
107 # title: Delete my account (a.k.a danger zone) 115 # title: Delete my account (a.k.a danger zone)
108 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 116 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -160,6 +168,15 @@ config:
160 and: 'Bir kural ve diğeri' 168 and: 'Bir kural ve diğeri'
161 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 169 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
162 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
163 180
164entry: 181entry:
165 default_title: 'Makalenin başlığı' 182 default_title: 'Makalenin başlığı'
@@ -530,7 +547,8 @@ user:
530 email_label: 'E-posta' 547 email_label: 'E-posta'
531 # enabled_label: 'Enabled' 548 # enabled_label: 'Enabled'
532 # last_login_label: 'Last login' 549 # last_login_label: 'Last login'
533 # twofactor_label: Two factor authentication 550 # twofactor_email_label: Two factor authentication by email
551 # twofactor_google_label: Two factor authentication by OTP app
534 # save: Save 552 # save: Save
535 # delete: Delete 553 # delete: Delete
536 # delete_confirm: Are you sure? 554 # delete_confirm: Are you sure?
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
index bcc57dac..93f8ddf8 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
@@ -86,8 +86,7 @@
86 <br/> 86 <br/>
87 <img id="androidQrcode" /> 87 <img id="androidQrcode" />
88 <script> 88 <script>
89 const imgBase64 = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}'); 89 document.getElementById('androidQrcode').src = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}');
90 document.getElementById('androidQrcode').src = imgBase64;
91 </script> 90 </script>
92 </div> 91 </div>
93 </fieldset> 92 </fieldset>
@@ -169,52 +168,41 @@
169 </div> 168 </div>
170 </fieldset> 169 </fieldset>
171 170
171 {{ form_widget(form.user.save) }}
172
172 {% if twofactor_auth %} 173 {% if twofactor_auth %}
174 <h5>{{ 'config.otp.page_title'|trans }}</h5>
175
173 <div class="row"> 176 <div class="row">
174 {{ 'config.form_user.two_factor_description'|trans }} 177 {{ 'config.form_user.two_factor_description'|trans }}
175 </div> 178 </div>
176 179
177 <fieldset class="w500p inline"> 180 <table>
178 <div class="row"> 181 <thead>
179 {{ form_label(form.user.twoFactorAuthentication) }} 182 <tr>
180 {{ form_errors(form.user.twoFactorAuthentication) }} 183 <th>{{ 'config.form_user.two_factor.table_method'|trans }}</th>
181 {{ form_widget(form.user.twoFactorAuthentication) }} 184 <th>{{ 'config.form_user.two_factor.table_state'|trans }}</th>
182 </div> 185 <th>{{ 'config.form_user.two_factor.table_action'|trans }}</th>
183 <a href="#" title="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 186 </tr>
184 <i class="material-icons">live_help</i> 187 </thead>
185 </a>
186 </fieldset>
187 {% endif %}
188 188
189 <h2>{{ 'config.reset.title'|trans }}</h2> 189 <tbody>
190 <fieldset class="w500p inline"> 190 <tr>
191 <p>{{ 'config.reset.description'|trans }}</p> 191 <td>{{ 'config.form_user.two_factor.emailTwoFactor_label'|trans }}</td>
192 <ul> 192 <td>{% if app.user.isEmailTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
193 <li> 193 <td><a href="{{ path('config_otp_email') }}" class="waves-effect waves-light btn{% if app.user.isEmailTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_email'|trans }}</a></td>
194 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 194 </tr>
195 {{ 'config.reset.annotations'|trans }} 195 <tr>
196 </a> 196 <td>{{ 'config.form_user.two_factor.googleTwoFactor_label'|trans }}</td>
197 </li> 197 <td>{% if app.user.isGoogleTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
198 <li> 198 <td><a href="{{ path('config_otp_app') }}" class="waves-effect waves-light btn{% if app.user.isGoogleTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_app'|trans }}</a></td>
199 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 199 </tr>
200 {{ 'config.reset.tags'|trans }} 200 </tbody>
201 </a> 201 </table>
202 </li> 202
203 <li> 203 {% endif %}
204 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
205 {{ 'config.reset.archived'|trans }}
206 </a>
207 </li>
208 <li>
209 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
210 {{ 'config.reset.entries'|trans }}
211 </a>
212 </li>
213 </ul>
214 </fieldset>
215 204
216 {{ form_widget(form.user._token) }} 205 {{ form_widget(form.user._token) }}
217 {{ form_widget(form.user.save) }}
218 </form> 206 </form>
219 207
220 {% if enabled_users > 1 %} 208 {% if enabled_users > 1 %}
@@ -277,7 +265,7 @@
277 {% endfor %} 265 {% endfor %}
278 </ul> 266 </ul>
279 267
280 {{ form_start(form.new_tagging_rule) }} 268 {{ form_start(form.new_tagging_rule) }}
281 {{ form_errors(form.new_tagging_rule) }} 269 {{ form_errors(form.new_tagging_rule) }}
282 270
283 <fieldset class="w500p inline"> 271 <fieldset class="w500p inline">
@@ -382,4 +370,31 @@
382 </table> 370 </table>
383 </div> 371 </div>
384 </div> 372 </div>
373
374 <h2>{{ 'config.reset.title'|trans }}</h2>
375 <fieldset class="w500p inline">
376 <p>{{ 'config.reset.description'|trans }}</p>
377 <ul>
378 <li>
379 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
380 {{ 'config.reset.annotations'|trans }}
381 </a>
382 </li>
383 <li>
384 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
385 {{ 'config.reset.tags'|trans }}
386 </a>
387 </li>
388 <li>
389 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
390 {{ 'config.reset.archived'|trans }}
391 </a>
392 </li>
393 <li>
394 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
395 {{ 'config.reset.entries'|trans }}
396 </a>
397 </li>
398 </ul>
399 </fieldset>
385{% endblock %} 400{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig
new file mode 100644
index 00000000..0919646e
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig
@@ -0,0 +1,55 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'config.page_title'|trans }} > {{ 'config.otp.page_title'|trans }}{% endblock %}
4
5{% block content %}
6 <h5>{{ 'config.otp.page_title'|trans }}</h5>
7
8 <ol>
9 <li>
10 <p>{{ 'config.otp.app.two_factor_code_description_1'|trans }}</p>
11 <p>{{ 'config.otp.app.two_factor_code_description_2'|trans }}</p>
12
13 <p>
14 <img id="2faQrcode" class="hide-on-med-and-down" />
15 <script>
16 document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ qr_code }}');
17 </script>
18 </p>
19 </li>
20 <li>
21 <p>{{ 'config.otp.app.two_factor_code_description_3'|trans }}</p>
22
23 <p><strong>{{ backupCodes|join("\n")|nl2br }}</strong></p>
24 </li>
25 <li>
26 <p>{{ 'config.otp.app.two_factor_code_description_4'|trans }}</p>
27
28 {% for flashMessage in app.session.flashbag.get("two_factor") %}
29 <div class="card-panel red darken-1 black-text">
30 {{ flashMessage|trans }}
31 </div>
32 {% endfor %}
33
34 <form class="form" action="{{ path("config_otp_app_check") }}" method="post">
35 <div class="card-content">
36 <div class="row">
37 <div class="input-field col s12">
38 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
39 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" />
40 </div>
41 </div>
42 </div>
43 <div class="card-action">
44 <a href="{{ path('config_otp_app_cancel') }}" class="waves-effect waves-light grey btn">
45 {{ 'config.otp.app.cancel'|trans }}
46 </a>
47 <button class="btn waves-effect waves-light" type="submit" name="send">
48 {{ 'config.otp.app.enable'|trans }}
49 <i class="material-icons right">send</i>
50 </button>
51 </div>
52 </form>
53 </li>
54 </ol>
55{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
index f896fe2d..412c18f4 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
@@ -16,6 +16,7 @@
16 <li class="tab col s12 m6 l3"><a href="#set3">{{ 'config.tab_menu.user_info'|trans }}</a></li> 16 <li class="tab col s12 m6 l3"><a href="#set3">{{ 'config.tab_menu.user_info'|trans }}</a></li>
17 <li class="tab col s12 m6 l3"><a href="#set4">{{ 'config.tab_menu.password'|trans }}</a></li> 17 <li class="tab col s12 m6 l3"><a href="#set4">{{ 'config.tab_menu.password'|trans }}</a></li>
18 <li class="tab col s12 m6 l3"><a href="#set5">{{ 'config.tab_menu.rules'|trans }}</a></li> 18 <li class="tab col s12 m6 l3"><a href="#set5">{{ 'config.tab_menu.rules'|trans }}</a></li>
19 <li class="tab col s12 m6 l3"><a href="#set6">{{ 'config.tab_menu.reset'|trans }}</a></li>
19 </ul> 20 </ul>
20 </div> 21 </div>
21 22
@@ -111,8 +112,7 @@
111 <img id="androidQrcode" class="hide-on-med-and-down" /> 112 <img id="androidQrcode" class="hide-on-med-and-down" />
112 </div> 113 </div>
113 <script> 114 <script>
114 const imgBase64 = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}'); 115 document.getElementById('androidQrcode').src = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}');
115 document.getElementById('androidQrcode').src = imgBase64;
116 </script> 116 </script>
117 </div> 117 </div>
118 118
@@ -196,59 +196,42 @@
196 </div> 196 </div>
197 </div> 197 </div>
198 198
199 {% if twofactor_auth %} 199 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
200 <div class="row">
201 <div class="input-field col s11">
202 {{ 'config.form_user.two_factor_description'|trans }}
203
204 <br />
205 200
206 {{ form_widget(form.user.twoFactorAuthentication) }} 201 {% if twofactor_auth %}
207 {{ form_label(form.user.twoFactorAuthentication) }} 202 <br/>
208 {{ form_errors(form.user.twoFactorAuthentication) }} 203 <br/>
209 </div> 204 <div class="row">
210 <div class="input-field col s1"> 205 <h5>{{ 'config.otp.page_title'|trans }}</h5>
211 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 206
212 <i class="material-icons">live_help</i> 207 <p>{{ 'config.form_user.two_factor_description'|trans }}</p>
213 </a> 208
209 <table>
210 <thead>
211 <tr>
212 <th>{{ 'config.form_user.two_factor.table_method'|trans }}</th>
213 <th>{{ 'config.form_user.two_factor.table_state'|trans }}</th>
214 <th>{{ 'config.form_user.two_factor.table_action'|trans }}</th>
215 </tr>
216 </thead>
217
218 <tbody>
219 <tr>
220 <td>{{ 'config.form_user.two_factor.emailTwoFactor_label'|trans }}</td>
221 <td>{% if app.user.isEmailTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
222 <td><a href="{{ path('config_otp_email') }}" class="waves-effect waves-light btn{% if app.user.isEmailTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_email'|trans }}</a></td>
223 </tr>
224 <tr>
225 <td>{{ 'config.form_user.two_factor.googleTwoFactor_label'|trans }}</td>
226 <td>{% if app.user.isGoogleTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
227 <td><a href="{{ path('config_otp_app') }}" class="waves-effect waves-light btn{% if app.user.isGoogleTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_app'|trans }}</a></td>
228 </tr>
229 </tbody>
230 </table>
214 </div> 231 </div>
215 </div>
216 {% endif %} 232 {% endif %}
217
218 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
219 {{ form_widget(form.user._token) }} 233 {{ form_widget(form.user._token) }}
220 </form> 234 </form>
221
222 <br /><hr /><br />
223
224 <div class="row">
225 <h5>{{ 'config.reset.title'|trans }}</h5>
226 <p>{{ 'config.reset.description'|trans }}</p>
227 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
228 {{ 'config.reset.annotations'|trans }}
229 </a>
230 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
231 {{ 'config.reset.tags'|trans }}
232 </a>
233 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
234 {{ 'config.reset.archived'|trans }}
235 </a>
236 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
237 {{ 'config.reset.entries'|trans }}
238 </a>
239 </div>
240
241 {% if enabled_users > 1 %}
242 <br /><hr /><br />
243
244 <div class="row">
245 <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
246 <p>{{ 'config.form_user.delete.description'|trans }}</p>
247 <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
248 {{ 'config.form_user.delete.button'|trans }}
249 </a>
250 </div>
251 {% endif %}
252 </div> 235 </div>
253 236
254 <div id="set4" class="col s12"> 237 <div id="set4" class="col s12">
@@ -422,6 +405,37 @@
422 </div> 405 </div>
423 </div> 406 </div>
424 </div> 407 </div>
408
409 <div id="set6" class="col s12">
410 <div class="row">
411 <h5>{{ 'config.reset.title'|trans }}</h5>
412 <p>{{ 'config.reset.description'|trans }}</p>
413 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
414 {{ 'config.reset.annotations'|trans }}
415 </a>
416 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
417 {{ 'config.reset.tags'|trans }}
418 </a>
419 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
420 {{ 'config.reset.archived'|trans }}
421 </a>
422 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
423 {{ 'config.reset.entries'|trans }}
424 </a>
425 </div>
426
427 {% if enabled_users > 1 %}
428 <br /><hr /><br />
429
430 <div class="row">
431 <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
432 <p>{{ 'config.form_user.delete.description'|trans }}</p>
433 <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
434 {{ 'config.form_user.delete.button'|trans }}
435 </a>
436 </div>
437 {% endif %}
438 </div>
425 </div> 439 </div>
426 440
427 </div> 441 </div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig
new file mode 100644
index 00000000..7875d787
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig
@@ -0,0 +1,63 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'config.page_title'|trans }} > {{ 'config.otp.page_title'|trans }}{% endblock %}
4
5{% block content %}
6 <div class="row">
7 <div class="col s12">
8 <div class="card-panel settings">
9 <div class="row">
10 <h5>{{ 'config.otp.page_title'|trans }}</h5>
11
12 <ol>
13 <li>
14 <p>{{ 'config.otp.app.two_factor_code_description_1'|trans }}</p>
15 <p>{{ 'config.otp.app.two_factor_code_description_2'|trans }}</p>
16
17 <p>
18 <img id="2faQrcode" class="hide-on-med-and-down" />
19 <script>
20 document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ qr_code }}');
21 </script>
22 </p>
23 </li>
24 <li>
25 <p>{{ 'config.otp.app.two_factor_code_description_3'|trans }}</p>
26
27 <p><strong>{{ backupCodes|join("\n")|nl2br }}</strong></p>
28 </li>
29 <li>
30 <p>{{ 'config.otp.app.two_factor_code_description_4'|trans }}</p>
31
32 {% for flashMessage in app.session.flashbag.get("two_factor") %}
33 <div class="card-panel red darken-1 black-text">
34 {{ flashMessage|trans }}
35 </div>
36 {% endfor %}
37
38 <form class="form" action="{{ path("config_otp_app_check") }}" method="post">
39 <div class="card-content">
40 <div class="row">
41 <div class="input-field col s12">
42 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
43 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" />
44 </div>
45 </div>
46 </div>
47 <div class="card-action">
48 <a href="{{ path('config_otp_app_cancel') }}" class="waves-effect waves-light grey btn">
49 {{ 'config.otp.app.cancel'|trans }}
50 </a>
51 <button class="btn waves-effect waves-light" type="submit" name="send">
52 {{ 'config.otp.app.enable'|trans }}
53 <i class="material-icons right">send</i>
54 </button>
55 </div>
56 </form>
57 </li>
58 </ol>
59 </div>
60 </div>
61 </div>
62 </div>
63{% endblock %}
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php
index a9746fb4..63a06206 100644
--- a/src/Wallabag/UserBundle/Controller/ManageController.php
+++ b/src/Wallabag/UserBundle/Controller/ManageController.php
@@ -62,14 +62,29 @@ class ManageController extends Controller
62 */ 62 */
63 public function editAction(Request $request, User $user) 63 public function editAction(Request $request, User $user)
64 { 64 {
65 $userManager = $this->container->get('fos_user.user_manager');
66
65 $deleteForm = $this->createDeleteForm($user); 67 $deleteForm = $this->createDeleteForm($user);
66 $editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user); 68 $form = $this->createForm('Wallabag\UserBundle\Form\UserType', $user);
67 $editForm->handleRequest($request); 69 $form->handleRequest($request);
68 70
69 if ($editForm->isSubmitted() && $editForm->isValid()) { 71 // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
70 $em = $this->getDoctrine()->getManager(); 72 if ($this->getParameter('twofactor_auth') && true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) {
71 $em->persist($user); 73 $form->get('googleTwoFactor')->setData(true);
72 $em->flush(); 74 }
75
76 if ($form->isSubmitted() && $form->isValid()) {
77 // handle creation / reset of the OTP secret if checkbox changed from the previous state
78 if ($this->getParameter('twofactor_auth')) {
79 if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
80 $user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret());
81 $user->setEmailTwoFactor(false);
82 } elseif (false === $form->get('googleTwoFactor')->getData() && true === $user->isGoogleAuthenticatorEnabled()) {
83 $user->setGoogleAuthenticatorSecret(null);
84 }
85 }
86
87 $userManager->updateUser($user);
73 88
74 $this->get('session')->getFlashBag()->add( 89 $this->get('session')->getFlashBag()->add(
75 'notice', 90 'notice',
@@ -81,7 +96,7 @@ class ManageController extends Controller
81 96
82 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ 97 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [
83 'user' => $user, 98 'user' => $user,
84 'edit_form' => $editForm->createView(), 99 'edit_form' => $form->createView(),
85 'delete_form' => $deleteForm->createView(), 100 'delete_form' => $deleteForm->createView(),
86 'twofactor_auth' => $this->getParameter('twofactor_auth'), 101 'twofactor_auth' => $this->getParameter('twofactor_auth'),
87 ]); 102 ]);
@@ -131,8 +146,6 @@ class ManageController extends Controller
131 $form->handleRequest($request); 146 $form->handleRequest($request);
132 147
133 if ($form->isSubmitted() && $form->isValid()) { 148 if ($form->isSubmitted() && $form->isValid()) {
134 $this->get('logger')->info('searching users');
135
136 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : ''); 149 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
137 150
138 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm); 151 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
@@ -157,7 +170,7 @@ class ManageController extends Controller
157 } 170 }
158 171
159 /** 172 /**
160 * Creates a form to delete a User entity. 173 * Create a form to delete a User entity.
161 * 174 *
162 * @param User $user The User entity 175 * @param User $user The User entity
163 * 176 *
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 48446e3c..43fa6a80 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -8,8 +8,9 @@ use FOS\UserBundle\Model\User as BaseUser;
8use JMS\Serializer\Annotation\Accessor; 8use JMS\Serializer\Annotation\Accessor;
9use JMS\Serializer\Annotation\Groups; 9use JMS\Serializer\Annotation\Groups;
10use JMS\Serializer\Annotation\XmlRoot; 10use JMS\Serializer\Annotation\XmlRoot;
11use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 11use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
12use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 12use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface as EmailTwoFactorInterface;
13use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface;
13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 14use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
14use Symfony\Component\Security\Core\User\UserInterface; 15use Symfony\Component\Security\Core\User\UserInterface;
15use Wallabag\ApiBundle\Entity\Client; 16use Wallabag\ApiBundle\Entity\Client;
@@ -28,7 +29,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
28 * @UniqueEntity("email") 29 * @UniqueEntity("email")
29 * @UniqueEntity("username") 30 * @UniqueEntity("username")
30 */ 31 */
31class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 32class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface, BackupCodeInterface
32{ 33{
33 use EntityTimestampsTrait; 34 use EntityTimestampsTrait;
34 35
@@ -123,16 +124,21 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
123 private $authCode; 124 private $authCode;
124 125
125 /** 126 /**
126 * @var bool 127 * @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true)
127 *
128 * @ORM\Column(type="boolean")
129 */ 128 */
130 private $twoFactorAuthentication = false; 129 private $googleAuthenticatorSecret;
131 130
132 /** 131 /**
133 * @ORM\Column(type="json_array", nullable=true) 132 * @ORM\Column(type="json_array", nullable=true)
134 */ 133 */
135 private $trusted; 134 private $backupCodes;
135
136 /**
137 * @var bool
138 *
139 * @ORM\Column(type="boolean")
140 */
141 private $emailTwoFactor = false;
136 142
137 public function __construct() 143 public function __construct()
138 { 144 {
@@ -233,49 +239,119 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
233 /** 239 /**
234 * @return bool 240 * @return bool
235 */ 241 */
236 public function isTwoFactorAuthentication() 242 public function isEmailTwoFactor()
237 { 243 {
238 return $this->twoFactorAuthentication; 244 return $this->emailTwoFactor;
239 } 245 }
240 246
241 /** 247 /**
242 * @param bool $twoFactorAuthentication 248 * @param bool $emailTwoFactor
243 */ 249 */
244 public function setTwoFactorAuthentication($twoFactorAuthentication) 250 public function setEmailTwoFactor($emailTwoFactor)
245 { 251 {
246 $this->twoFactorAuthentication = $twoFactorAuthentication; 252 $this->emailTwoFactor = $emailTwoFactor;
247 } 253 }
248 254
249 public function isEmailAuthEnabled() 255 /**
256 * Used in the user config form to be "like" the email option.
257 */
258 public function isGoogleTwoFactor()
259 {
260 return $this->isGoogleAuthenticatorEnabled();
261 }
262
263 /**
264 * {@inheritdoc}
265 */
266 public function isEmailAuthEnabled(): bool
250 { 267 {
251 return $this->twoFactorAuthentication; 268 return $this->emailTwoFactor;
252 } 269 }
253 270
254 public function getEmailAuthCode() 271 /**
272 * {@inheritdoc}
273 */
274 public function getEmailAuthCode(): string
255 { 275 {
256 return $this->authCode; 276 return $this->authCode;
257 } 277 }
258 278
259 public function setEmailAuthCode($authCode) 279 /**
280 * {@inheritdoc}
281 */
282 public function setEmailAuthCode(string $authCode): void
260 { 283 {
261 $this->authCode = $authCode; 284 $this->authCode = $authCode;
262 } 285 }
263 286
264 public function addTrustedComputer($token, \DateTime $validUntil) 287 /**
288 * {@inheritdoc}
289 */
290 public function getEmailAuthRecipient(): string
265 { 291 {
266 $this->trusted[$token] = $validUntil->format('r'); 292 return $this->email;
267 } 293 }
268 294
269 public function isTrustedComputer($token) 295 /**
296 * {@inheritdoc}
297 */
298 public function isGoogleAuthenticatorEnabled(): bool
270 { 299 {
271 if (isset($this->trusted[$token])) { 300 return $this->googleAuthenticatorSecret ? true : false;
272 $now = new \DateTime(); 301 }
273 $validUntil = new \DateTime($this->trusted[$token]);
274 302
275 return $now < $validUntil; 303 /**
276 } 304 * {@inheritdoc}
305 */
306 public function getGoogleAuthenticatorUsername(): string
307 {
308 return $this->username;
309 }
277 310
278 return false; 311 /**
312 * {@inheritdoc}
313 */
314 public function getGoogleAuthenticatorSecret(): string
315 {
316 return $this->googleAuthenticatorSecret;
317 }
318
319 /**
320 * {@inheritdoc}
321 */
322 public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void
323 {
324 $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
325 }
326
327 public function setBackupCodes(array $codes = null)
328 {
329 $this->backupCodes = $codes;
330 }
331
332 public function getBackupCodes()
333 {
334 return $this->backupCodes;
335 }
336
337 /**
338 * {@inheritdoc}
339 */
340 public function isBackupCode(string $code): bool
341 {
342 return false === $this->findBackupCode($code) ? false : true;
343 }
344
345 /**
346 * {@inheritdoc}
347 */
348 public function invalidateBackupCode(string $code): void
349 {
350 $key = $this->findBackupCode($code);
351
352 if (false !== $key) {
353 unset($this->backupCodes[$key]);
354 }
279 } 355 }
280 356
281 /** 357 /**
@@ -309,4 +385,24 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
309 return $this->clients->first(); 385 return $this->clients->first();
310 } 386 }
311 } 387 }
388
389 /**
390 * Try to find a backup code from the list of backup codes of the current user.
391 *
392 * @param string $code Given code from the user
393 *
394 * @return string|false
395 */
396 private function findBackupCode(string $code)
397 {
398 foreach ($this->backupCodes as $key => $backupCode) {
399 // backup code are hashed using `password_hash`
400 // see ConfigController->otpAppAction
401 if (password_verify($code, $backupCode)) {
402 return $key;
403 }
404 }
405
406 return false;
407 }
312} 408}
diff --git a/src/Wallabag/UserBundle/Form/UserType.php b/src/Wallabag/UserBundle/Form/UserType.php
index 56fea640..026db9a2 100644
--- a/src/Wallabag/UserBundle/Form/UserType.php
+++ b/src/Wallabag/UserBundle/Form/UserType.php
@@ -35,9 +35,14 @@ class UserType extends AbstractType
35 'required' => false, 35 'required' => false,
36 'label' => 'user.form.enabled_label', 36 'label' => 'user.form.enabled_label',
37 ]) 37 ])
38 ->add('twoFactorAuthentication', CheckboxType::class, [ 38 ->add('emailTwoFactor', CheckboxType::class, [
39 'required' => false, 39 'required' => false,
40 'label' => 'user.form.twofactor_label', 40 'label' => 'user.form.twofactor_email_label',
41 ])
42 ->add('googleTwoFactor', CheckboxType::class, [
43 'required' => false,
44 'label' => 'user.form.twofactor_google_label',
45 'mapped' => false,
41 ]) 46 ])
42 ->add('save', SubmitType::class, [ 47 ->add('save', SubmitType::class, [
43 'label' => 'user.form.save', 48 'label' => 'user.form.save',
diff --git a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
index aed805c9..2797efde 100644
--- a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
+++ b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
@@ -78,7 +78,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
78 * 78 *
79 * @param TwoFactorInterface $user 79 * @param TwoFactorInterface $user
80 */ 80 */
81 public function sendAuthCode(TwoFactorInterface $user) 81 public function sendAuthCode(TwoFactorInterface $user): void
82 { 82 {
83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); 83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig');
84 84
@@ -97,7 +97,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
97 97
98 $message = new \Swift_Message(); 98 $message = new \Swift_Message();
99 $message 99 $message
100 ->setTo($user->getEmail()) 100 ->setTo($user->getEmailAuthRecipient())
101 ->setFrom($this->senderEmail, $this->senderName) 101 ->setFrom($this->senderEmail, $this->senderName)
102 ->setSubject($subject) 102 ->setSubject($subject)
103 ->setBody($bodyText, 'text/plain') 103 ->setBody($bodyText, 'text/plain')
diff --git a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig
index c8471bdd..47a5cb78 100644
--- a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig
@@ -1,7 +1,8 @@
1{# Override `vendor/scheb/two-factor-bundle/Resources/views/Authentication/form.html.twig` #}
1{% extends "WallabagUserBundle::layout.html.twig" %} 2{% extends "WallabagUserBundle::layout.html.twig" %}
2 3
3{% block fos_user_content %} 4{% block fos_user_content %}
4<form class="form" action="" method="post"> 5<form class="form" action="{{ path("2fa_login_check") }}" method="post">
5 <div class="card-content"> 6 <div class="card-content">
6 <div class="row"> 7 <div class="row">
7 8
@@ -9,14 +10,19 @@
9 <p class="error">{{ flashMessage|trans }}</p> 10 <p class="error">{{ flashMessage|trans }}</p>
10 {% endfor %} 11 {% endfor %}
11 12
13 {# Authentication errors #}
14 {% if authenticationError %}
15 <p class="error">{{ authenticationError|trans(authenticationErrorData) }}</p>
16 {% endif %}
17
12 <div class="input-field col s12"> 18 <div class="input-field col s12">
13 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label> 19 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
14 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" /> 20 <input id="_auth_code" type="text" autocomplete="off" name="{{ authCodeParameterName }}" />
15 </div> 21 </div>
16 22
17 {% if useTrustedOption %} 23 {% if displayTrustedOption %}
18 <div class="input-field col s12"> 24 <div class="input-field col s12">
19 <input id="_trusted" type="checkbox" name="_trusted" /> 25 <input id="_trusted" type="checkbox" name="{{ trustedParameterName }}" />
20 <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label> 26 <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label>
21 </div> 27 </div>
22 {% endif %} 28 {% endif %}
diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
index 3ffd15f5..2de8f3a5 100644
--- a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
@@ -50,9 +50,14 @@
50 {% if twofactor_auth %} 50 {% if twofactor_auth %}
51 <div class="row"> 51 <div class="row">
52 <div class="input-field col s12"> 52 <div class="input-field col s12">
53 {{ form_widget(edit_form.twoFactorAuthentication) }} 53 {{ form_widget(edit_form.emailTwoFactor) }}
54 {{ form_label(edit_form.twoFactorAuthentication) }} 54 {{ form_label(edit_form.emailTwoFactor) }}
55 {{ form_errors(edit_form.twoFactorAuthentication) }} 55 {{ form_errors(edit_form.emailTwoFactor) }}
56 </div>
57 <div class="input-field col s12">
58 {{ form_widget(edit_form.googleTwoFactor) }}
59 {{ form_label(edit_form.googleTwoFactor) }}
60 {{ form_errors(edit_form.googleTwoFactor) }}
56 </div> 61 </div>
57 </div> 62 </div>
58 {% endif %} 63 {% endif %}
diff --git a/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php b/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php
index 9b34f2a0..ed383a2c 100644
--- a/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php
+++ b/tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php
@@ -59,7 +59,8 @@ class ShowUserCommandTest extends WallabagCoreTestCase
59 $this->assertContains('Username: admin', $tester->getDisplay()); 59 $this->assertContains('Username: admin', $tester->getDisplay());
60 $this->assertContains('Email: bigboss@wallabag.org', $tester->getDisplay()); 60 $this->assertContains('Email: bigboss@wallabag.org', $tester->getDisplay());
61 $this->assertContains('Display name: Big boss', $tester->getDisplay()); 61 $this->assertContains('Display name: Big boss', $tester->getDisplay());
62 $this->assertContains('2FA activated: no', $tester->getDisplay()); 62 $this->assertContains('2FA (email) activated', $tester->getDisplay());
63 $this->assertContains('2FA (OTP) activated', $tester->getDisplay());
63 } 64 }
64 65
65 public function testShowUser() 66 public function testShowUser()
diff --git a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
index c9dbbaa3..1090a686 100644
--- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
@@ -1000,4 +1000,85 @@ class ConfigControllerTest extends WallabagCoreTestCase
1000 $this->assertNotSame('yuyuyuyu', $client->getRequest()->getLocale()); 1000 $this->assertNotSame('yuyuyuyu', $client->getRequest()->getLocale());
1001 $this->assertNotSame('yuyuyuyu', $client->getContainer()->get('session')->get('_locale')); 1001 $this->assertNotSame('yuyuyuyu', $client->getContainer()->get('session')->get('_locale'));
1002 } 1002 }
1003
1004 public function testUserEnable2faEmail()
1005 {
1006 $this->logInAs('admin');
1007 $client = $this->getClient();
1008
1009 $crawler = $client->request('GET', '/config/otp/email');
1010
1011 $this->assertSame(302, $client->getResponse()->getStatusCode());
1012
1013 $crawler = $client->followRedirect();
1014
1015 $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
1016 $this->assertContains('flashes.config.notice.otp_enabled', $alert[0]);
1017
1018 // restore user
1019 $em = $this->getEntityManager();
1020 $user = $em
1021 ->getRepository('WallabagUserBundle:User')
1022 ->findOneByUsername('admin');
1023
1024 $this->assertTrue($user->isEmailTwoFactor());
1025
1026 $user->setEmailTwoFactor(false);
1027 $em->persist($user);
1028 $em->flush();
1029 }
1030
1031 public function testUserEnable2faGoogle()
1032 {
1033 $this->logInAs('admin');
1034 $client = $this->getClient();
1035
1036 $crawler = $client->request('GET', '/config/otp/app');
1037
1038 $this->assertSame(200, $client->getResponse()->getStatusCode());
1039
1040 // restore user
1041 $em = $this->getEntityManager();
1042 $user = $em
1043 ->getRepository('WallabagUserBundle:User')
1044 ->findOneByUsername('admin');
1045
1046 $this->assertTrue($user->isGoogleTwoFactor());
1047 $this->assertGreaterThan(0, $user->getBackupCodes());
1048
1049 $user->setGoogleAuthenticatorSecret(false);
1050 $user->setBackupCodes(null);
1051 $em->persist($user);
1052 $em->flush();
1053 }
1054
1055 public function testUserEnable2faGoogleCancel()
1056 {
1057 $this->logInAs('admin');
1058 $client = $this->getClient();
1059
1060 $crawler = $client->request('GET', '/config/otp/app');
1061
1062 $this->assertSame(200, $client->getResponse()->getStatusCode());
1063
1064 // restore user
1065 $em = $this->getEntityManager();
1066 $user = $em
1067 ->getRepository('WallabagUserBundle:User')
1068 ->findOneByUsername('admin');
1069
1070 $this->assertTrue($user->isGoogleTwoFactor());
1071 $this->assertGreaterThan(0, $user->getBackupCodes());
1072
1073 $crawler = $client->request('GET', '/config/otp/app/cancel');
1074
1075 $this->assertSame(302, $client->getResponse()->getStatusCode());
1076
1077 $user = $em
1078 ->getRepository('WallabagUserBundle:User')
1079 ->findOneByUsername('admin');
1080
1081 $this->assertFalse($user->isGoogleTwoFactor());
1082 $this->assertEmpty($user->getBackupCodes());
1083 }
1003} 1084}
diff --git a/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php b/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php
index 395208a2..b03c7550 100644
--- a/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php
@@ -26,7 +26,7 @@ class SecurityControllerTest extends WallabagCoreTestCase
26 $this->assertContains('config.form_rss.description', $crawler->filter('body')->extract(['_text'])[0]); 26 $this->assertContains('config.form_rss.description', $crawler->filter('body')->extract(['_text'])[0]);
27 } 27 }
28 28
29 public function testLoginWith2Factor() 29 public function testLoginWith2FactorEmail()
30 { 30 {
31 $client = $this->getClient(); 31 $client = $this->getClient();
32 32
@@ -42,7 +42,7 @@ class SecurityControllerTest extends WallabagCoreTestCase
42 $user = $em 42 $user = $em
43 ->getRepository('WallabagUserBundle:User') 43 ->getRepository('WallabagUserBundle:User')
44 ->findOneByUsername('admin'); 44 ->findOneByUsername('admin');
45 $user->setTwoFactorAuthentication(true); 45 $user->setEmailTwoFactor(true);
46 $em->persist($user); 46 $em->persist($user);
47 $em->flush(); 47 $em->flush();
48 48
@@ -54,12 +54,12 @@ class SecurityControllerTest extends WallabagCoreTestCase
54 $user = $em 54 $user = $em
55 ->getRepository('WallabagUserBundle:User') 55 ->getRepository('WallabagUserBundle:User')
56 ->findOneByUsername('admin'); 56 ->findOneByUsername('admin');
57 $user->setTwoFactorAuthentication(false); 57 $user->setEmailTwoFactor(false);
58 $em->persist($user); 58 $em->persist($user);
59 $em->flush(); 59 $em->flush();
60 } 60 }
61 61
62 public function testTrustedComputer() 62 public function testLoginWith2FactorGoogle()
63 { 63 {
64 $client = $this->getClient(); 64 $client = $this->getClient();
65 65
@@ -69,15 +69,27 @@ class SecurityControllerTest extends WallabagCoreTestCase
69 return; 69 return;
70 } 70 }
71 71
72 $client->followRedirects();
73
72 $em = $client->getContainer()->get('doctrine.orm.entity_manager'); 74 $em = $client->getContainer()->get('doctrine.orm.entity_manager');
73 $user = $em 75 $user = $em
74 ->getRepository('WallabagUserBundle:User') 76 ->getRepository('WallabagUserBundle:User')
75 ->findOneByUsername('admin'); 77 ->findOneByUsername('admin');
78 $user->setGoogleAuthenticatorSecret('26LDIHYGHNELOQEM');
79 $em->persist($user);
80 $em->flush();
81
82 $this->logInAsUsingHttp('admin');
83 $crawler = $client->request('GET', '/config');
84 $this->assertContains('scheb_two_factor.trusted', $crawler->filter('body')->extract(['_text'])[0]);
76 85
77 $date = new \DateTime(); 86 // restore user
78 $user->addTrustedComputer('ABCDEF', $date->add(new \DateInterval('P1M'))); 87 $user = $em
79 $this->assertTrue($user->isTrustedComputer('ABCDEF')); 88 ->getRepository('WallabagUserBundle:User')
80 $this->assertFalse($user->isTrustedComputer('FEDCBA')); 89 ->findOneByUsername('admin');
90 $user->setGoogleAuthenticatorSecret(null);
91 $em->persist($user);
92 $em->flush();
81 } 93 }
82 94
83 public function testEnabledRegistration() 95 public function testEnabledRegistration()
diff --git a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
index 3dd9273c..508adb1b 100644
--- a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
+++ b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
@@ -163,7 +163,7 @@ class ContentProxyTest extends TestCase
163 163
164 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 164 $this->assertSame('http://1.1.1.1', $entry->getUrl());
165 $this->assertSame('this is my title', $entry->getTitle()); 165 $this->assertSame('this is my title', $entry->getTitle());
166 $this->assertContains('this is my content', $entry->getContent()); 166 $this->assertContains('content', $entry->getContent());
167 $this->assertSame('http://3.3.3.3/cover.jpg', $entry->getPreviewPicture()); 167 $this->assertSame('http://3.3.3.3/cover.jpg', $entry->getPreviewPicture());
168 $this->assertSame('text/html', $entry->getMimetype()); 168 $this->assertSame('text/html', $entry->getMimetype());
169 $this->assertSame('fr', $entry->getLanguage()); 169 $this->assertSame('fr', $entry->getLanguage());
@@ -205,7 +205,7 @@ class ContentProxyTest extends TestCase
205 205
206 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 206 $this->assertSame('http://1.1.1.1', $entry->getUrl());
207 $this->assertSame('this is my title', $entry->getTitle()); 207 $this->assertSame('this is my title', $entry->getTitle());
208 $this->assertContains('this is my content', $entry->getContent()); 208 $this->assertContains('content', $entry->getContent());
209 $this->assertNull($entry->getPreviewPicture()); 209 $this->assertNull($entry->getPreviewPicture());
210 $this->assertSame('text/html', $entry->getMimetype()); 210 $this->assertSame('text/html', $entry->getMimetype());
211 $this->assertSame('fr', $entry->getLanguage()); 211 $this->assertSame('fr', $entry->getLanguage());
@@ -247,7 +247,7 @@ class ContentProxyTest extends TestCase
247 247
248 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 248 $this->assertSame('http://1.1.1.1', $entry->getUrl());
249 $this->assertSame('this is my title', $entry->getTitle()); 249 $this->assertSame('this is my title', $entry->getTitle());
250 $this->assertContains('this is my content', $entry->getContent()); 250 $this->assertContains('content', $entry->getContent());
251 $this->assertSame('text/html', $entry->getMimetype()); 251 $this->assertSame('text/html', $entry->getMimetype());
252 $this->assertNull($entry->getLanguage()); 252 $this->assertNull($entry->getLanguage());
253 $this->assertSame('200', $entry->getHttpStatus()); 253 $this->assertSame('200', $entry->getHttpStatus());
@@ -296,7 +296,7 @@ class ContentProxyTest extends TestCase
296 296
297 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 297 $this->assertSame('http://1.1.1.1', $entry->getUrl());
298 $this->assertSame('this is my title', $entry->getTitle()); 298 $this->assertSame('this is my title', $entry->getTitle());
299 $this->assertContains('this is my content', $entry->getContent()); 299 $this->assertContains('content', $entry->getContent());
300 $this->assertNull($entry->getPreviewPicture()); 300 $this->assertNull($entry->getPreviewPicture());
301 $this->assertSame('text/html', $entry->getMimetype()); 301 $this->assertSame('text/html', $entry->getMimetype());
302 $this->assertSame('fr', $entry->getLanguage()); 302 $this->assertSame('fr', $entry->getLanguage());
@@ -332,7 +332,7 @@ class ContentProxyTest extends TestCase
332 332
333 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 333 $this->assertSame('http://1.1.1.1', $entry->getUrl());
334 $this->assertSame('this is my title', $entry->getTitle()); 334 $this->assertSame('this is my title', $entry->getTitle());
335 $this->assertContains('this is my content', $entry->getContent()); 335 $this->assertContains('content', $entry->getContent());
336 $this->assertSame('text/html', $entry->getMimetype()); 336 $this->assertSame('text/html', $entry->getMimetype());
337 $this->assertSame('fr', $entry->getLanguage()); 337 $this->assertSame('fr', $entry->getLanguage());
338 $this->assertSame(4.0, $entry->getReadingTime()); 338 $this->assertSame(4.0, $entry->getReadingTime());
@@ -371,7 +371,7 @@ class ContentProxyTest extends TestCase
371 371
372 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 372 $this->assertSame('http://1.1.1.1', $entry->getUrl());
373 $this->assertSame('this is my title', $entry->getTitle()); 373 $this->assertSame('this is my title', $entry->getTitle());
374 $this->assertContains('this is my content', $entry->getContent()); 374 $this->assertContains('content', $entry->getContent());
375 $this->assertSame('text/html', $entry->getMimetype()); 375 $this->assertSame('text/html', $entry->getMimetype());
376 $this->assertSame('fr', $entry->getLanguage()); 376 $this->assertSame('fr', $entry->getLanguage());
377 $this->assertSame(4.0, $entry->getReadingTime()); 377 $this->assertSame(4.0, $entry->getReadingTime());
@@ -406,7 +406,7 @@ class ContentProxyTest extends TestCase
406 406
407 $this->assertSame('http://1.1.1.1', $entry->getUrl()); 407 $this->assertSame('http://1.1.1.1', $entry->getUrl());
408 $this->assertSame('this is my title', $entry->getTitle()); 408 $this->assertSame('this is my title', $entry->getTitle());
409 $this->assertContains('this is my content', $entry->getContent()); 409 $this->assertContains('content', $entry->getContent());
410 $this->assertSame('text/html', $entry->getMimetype()); 410 $this->assertSame('text/html', $entry->getMimetype());
411 $this->assertSame('fr', $entry->getLanguage()); 411 $this->assertSame('fr', $entry->getLanguage());
412 $this->assertSame(4.0, $entry->getReadingTime()); 412 $this->assertSame(4.0, $entry->getReadingTime());
diff --git a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
index e34e13a8..1713c10c 100644
--- a/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
+++ b/tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php
@@ -33,7 +33,7 @@ TWIG;
33 public function testSendEmail() 33 public function testSendEmail()
34 { 34 {
35 $user = new User(); 35 $user = new User();
36 $user->setTwoFactorAuthentication(true); 36 $user->setEmailTwoFactor(true);
37 $user->setEmailAuthCode(666666); 37 $user->setEmailAuthCode(666666);
38 $user->setEmail('test@wallabag.io'); 38 $user->setEmail('test@wallabag.io');
39 $user->setName('Bob'); 39 $user->setName('Bob');