aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/DoctrineMigrations/Version20181202073750.php22
-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.yml8
-rw-r--r--app/config/routing.yml8
-rw-r--r--app/config/security.yml9
-rw-r--r--composer.json6
-rw-r--r--src/Wallabag/CoreBundle/Command/ShowUserCommand.php3
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php35
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/UserInformationType.php9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.th.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml9
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig88
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig45
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php67
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php94
-rw-r--r--src/Wallabag/UserBundle/Form/UserType.php9
-rw-r--r--src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php2
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig14
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig17
-rw-r--r--tests/Wallabag/CoreBundle/Command/ShowUserCommandTest.php3
-rw-r--r--tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php113
-rw-r--r--tests/Wallabag/CoreBundle/Controller/SecurityControllerTest.php28
-rw-r--r--tests/Wallabag/UserBundle/Mailer/AuthCodeMailerTest.php2
36 files changed, 553 insertions, 177 deletions
diff --git a/app/DoctrineMigrations/Version20181202073750.php b/app/DoctrineMigrations/Version20181202073750.php
new file mode 100644
index 00000000..a2308b99
--- /dev/null
+++ b/app/DoctrineMigrations/Version20181202073750.php
@@ -0,0 +1,22 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Schema\Schema;
6use Wallabag\CoreBundle\Doctrine\WallabagMigration;
7
8/**
9 * Add 2fa OTP (named google authenticator).
10 */
11final class Version20181202073750 extends WallabagMigration
12{
13 public function up(Schema $schema): void
14 {
15 $this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL, CHANGE twoFactorAuthentication emailTwoFactor BOOLEAN NOT NULL, DROP trusted');
16 }
17
18 public function down(Schema $schema): void
19 {
20 $this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP googleAuthenticatorSecret, CHANGE emailtwofactor twoFactorAuthentication BOOLEAN NOT NULL, ADD trusted TEXT DEFAULT NULL');
21 }
22}
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..908f53b7 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -198,10 +198,14 @@ 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 google:
207 enabled: "%twofactor_auth%"
208 template: WallabagUserBundle:Authentication:form.html.twig
205 209
206 email: 210 email:
207 enabled: "%twofactor_auth%" 211 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..771580c6 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",
@@ -147,7 +147,7 @@
147 "config": { 147 "config": {
148 "bin-dir": "bin", 148 "bin-dir": "bin",
149 "platform": { 149 "platform": {
150 "php": "7.1" 150 "php": "7.1.3"
151 } 151 }
152 }, 152 },
153 "minimum-stability": "dev", 153 "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..5bbe1c74 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -46,7 +46,7 @@ class ConfigController extends Controller
46 $activeTheme = $this->get('liip_theme.active_theme'); 46 $activeTheme = $this->get('liip_theme.active_theme');
47 $activeTheme->setName($config->getTheme()); 47 $activeTheme->setName($config->getTheme());
48 48
49 $this->get('session')->getFlashBag()->add( 49 $this->addFlash(
50 'notice', 50 'notice',
51 'flashes.config.notice.config_saved' 51 'flashes.config.notice.config_saved'
52 ); 52 );
@@ -68,7 +68,7 @@ class ConfigController extends Controller
68 $userManager->updateUser($user, true); 68 $userManager->updateUser($user, true);
69 } 69 }
70 70
71 $this->get('session')->getFlashBag()->add('notice', $message); 71 $this->addFlash('notice', $message);
72 72
73 return $this->redirect($this->generateUrl('config') . '#set4'); 73 return $this->redirect($this->generateUrl('config') . '#set4');
74 } 74 }
@@ -80,10 +80,29 @@ class ConfigController extends Controller
80 ]); 80 ]);
81 $userForm->handleRequest($request); 81 $userForm->handleRequest($request);
82 82
83 // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
84 if (true === $user->isGoogleAuthenticatorEnabled() && false === $userForm->isSubmitted()) {
85 $userForm->get('googleTwoFactor')->setData(true);
86 }
87
83 if ($userForm->isSubmitted() && $userForm->isValid()) { 88 if ($userForm->isSubmitted() && $userForm->isValid()) {
89 // handle creation / reset of the OTP secret if checkbox changed from the previous state
90 if (true === $userForm->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
91 $secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret();
92
93 $user->setGoogleAuthenticatorSecret($secret);
94 $user->setEmailTwoFactor(false);
95
96 $qrCode = $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user);
97
98 $this->addFlash('OTPSecret', ['code' => $secret, 'qrCode' => $qrCode]);
99 } elseif (false === $userForm->get('googleTwoFactor')->getData() && true === $user->isGoogleAuthenticatorEnabled()) {
100 $user->setGoogleAuthenticatorSecret(null);
101 }
102
84 $userManager->updateUser($user, true); 103 $userManager->updateUser($user, true);
85 104
86 $this->get('session')->getFlashBag()->add( 105 $this->addFlash(
87 'notice', 106 'notice',
88 'flashes.config.notice.user_updated' 107 'flashes.config.notice.user_updated'
89 ); 108 );
@@ -99,7 +118,7 @@ class ConfigController extends Controller
99 $em->persist($config); 118 $em->persist($config);
100 $em->flush(); 119 $em->flush();
101 120
102 $this->get('session')->getFlashBag()->add( 121 $this->addFlash(
103 'notice', 122 'notice',
104 'flashes.config.notice.rss_updated' 123 'flashes.config.notice.rss_updated'
105 ); 124 );
@@ -131,7 +150,7 @@ class ConfigController extends Controller
131 $em->persist($taggingRule); 150 $em->persist($taggingRule);
132 $em->flush(); 151 $em->flush();
133 152
134 $this->get('session')->getFlashBag()->add( 153 $this->addFlash(
135 'notice', 154 'notice',
136 'flashes.config.notice.tagging_rules_updated' 155 'flashes.config.notice.tagging_rules_updated'
137 ); 156 );
@@ -178,7 +197,7 @@ class ConfigController extends Controller
178 return new JsonResponse(['token' => $config->getRssToken()]); 197 return new JsonResponse(['token' => $config->getRssToken()]);
179 } 198 }
180 199
181 $this->get('session')->getFlashBag()->add( 200 $this->addFlash(
182 'notice', 201 'notice',
183 'flashes.config.notice.rss_token_updated' 202 'flashes.config.notice.rss_token_updated'
184 ); 203 );
@@ -203,7 +222,7 @@ class ConfigController extends Controller
203 $em->remove($rule); 222 $em->remove($rule);
204 $em->flush(); 223 $em->flush();
205 224
206 $this->get('session')->getFlashBag()->add( 225 $this->addFlash(
207 'notice', 226 'notice',
208 'flashes.config.notice.tagging_rules_deleted' 227 'flashes.config.notice.tagging_rules_deleted'
209 ); 228 );
@@ -269,7 +288,7 @@ class ConfigController extends Controller
269 break; 288 break;
270 } 289 }
271 290
272 $this->get('session')->getFlashBag()->add( 291 $this->addFlash(
273 'notice', 292 'notice',
274 'flashes.config.notice.' . $type . '_reset' 293 'flashes.config.notice.' . $type . '_reset'
275 ); 294 );
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 96679a9c..e62ba6d0 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 # rss_limit: 'Number of items in the feed' 100 # rss_limit: 'Number of items in the feed'
101 form_user: 101 form_user:
102 # 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Navn' 103 name_label: 'Navn'
104 email_label: 'Emailadresse' 104 email_label: 'Emailadresse'
105 # twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 # 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, to get a one time code)'
107 delete: 107 delete:
108 # title: Delete my account (a.k.a danger zone) 108 # title: Delete my account (a.k.a danger zone)
109 # 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. 109 # 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'Emailadresse' 533 email_label: 'Emailadresse'
534 # enabled_label: 'Enabled' 534 # enabled_label: 'Enabled'
535 # last_login_label: 'Last login' 535 # last_login_label: 'Last login'
536 # twofactor_label: Two factor authentication 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 # save: Save 538 # save: Save
538 # delete: Delete 539 # delete: Delete
539 # delete_confirm: Are you sure? 540 # 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 c56e87b5..f2d0408f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -99,11 +99,11 @@ config:
99 all: 'Alle' 99 all: 'Alle'
100 rss_limit: 'Anzahl der Einträge pro Feed' 100 rss_limit: 'Anzahl der Einträge pro Feed'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Name' 103 name_label: 'Name'
104 email_label: 'E-Mail-Adresse' 104 email_label: 'E-Mail-Adresse'
105 twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
107 delete: 107 delete:
108 title: 'Lösche mein Konto (a.k.a Gefahrenzone)' 108 title: 'Lösche mein Konto (a.k.a Gefahrenzone)'
109 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.' 109 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.'
@@ -533,7 +533,8 @@ user:
533 email_label: 'E-Mail-Adresse' 533 email_label: 'E-Mail-Adresse'
534 enabled_label: 'Aktiviert' 534 enabled_label: 'Aktiviert'
535 last_login_label: 'Letzter Login' 535 last_login_label: 'Letzter Login'
536 twofactor_label: 'Zwei-Faktor-Authentifizierung' 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 save: 'Speichern' 538 save: 'Speichern'
538 delete: 'Löschen' 539 delete: 'Löschen'
539 delete_confirm: 'Bist du sicher?' 540 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 d57cea0f..859acdc0 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -99,11 +99,11 @@ config:
99 all: 'All' 99 all: 'All'
100 rss_limit: 'Number of items in the feed' 100 rss_limit: 'Number of items in the feed'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Name' 103 name_label: 'Name'
104 email_label: 'Email' 104 email_label: 'Email'
105 twoFactorAuthentication_label: 'Two factor authentication' 105 emailTwoFactor_label: 'Using email (receive a code by email)'
106 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, to get a one time code)'
107 delete: 107 delete:
108 title: Delete my account (a.k.a danger zone) 108 title: Delete my account (a.k.a danger zone)
109 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. 109 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'Email' 533 email_label: 'Email'
534 enabled_label: 'Enabled' 534 enabled_label: 'Enabled'
535 last_login_label: 'Last login' 535 last_login_label: 'Last login'
536 twofactor_label: Two factor authentication 536 twofactor_email_label: Two factor authentication by email
537 twofactor_google_label: Two factor authentication by Google
537 save: Save 538 save: Save
538 delete: Delete 539 delete: Delete
539 delete_confirm: Are you sure? 540 delete_confirm: Are you sure?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index e1c4e221..3c3cbed4 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 rss_limit: 'Límite de artículos en feed RSS' 100 rss_limit: 'Límite de artículos en feed RSS'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Nombre' 103 name_label: 'Nombre'
104 email_label: 'Dirección de e-mail' 104 email_label: 'Dirección de e-mail'
105 twoFactorAuthentication_label: 'Autenticación en dos pasos' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 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 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
107 delete: 107 delete:
108 title: Eliminar mi cuenta (Zona peligrosa) 108 title: Eliminar mi cuenta (Zona peligrosa)
109 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. 109 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'E-mail' 533 email_label: 'E-mail'
534 enabled_label: 'Activado' 534 enabled_label: 'Activado'
535 last_login_label: 'Último inicio de sesión' 535 last_login_label: 'Último inicio de sesión'
536 twofactor_label: Autenticación en dos pasos 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 save: Guardar 538 save: Guardar
538 delete: Eliminar 539 delete: Eliminar
539 delete_confirm: ¿Estás seguro? 540 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 2ede433e..ca25b390 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 rss_limit: 'محدودیت آر-اس-اس' 100 rss_limit: 'محدودیت آر-اس-اس'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'نام' 103 name_label: 'نام'
104 email_label: 'نشانی ایمیل' 104 email_label: 'نشانی ایمیل'
105 twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 # 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, to get a one time code)'
107 delete: 107 delete:
108 # title: Delete my account (a.k.a danger zone) 108 # title: Delete my account (a.k.a danger zone)
109 # 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. 109 # 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'نشانی ایمیل' 533 email_label: 'نشانی ایمیل'
534 # enabled_label: 'Enabled' 534 # enabled_label: 'Enabled'
535 # last_login_label: 'Last login' 535 # last_login_label: 'Last login'
536 # twofactor_label: Two factor authentication 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 # save: Save 538 # save: Save
538 # delete: Delete 539 # delete: Delete
539 # delete_confirm: Are you sure? 540 # 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 d69ae280..b809ca32 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -99,11 +99,11 @@ config:
99 all: "Tous" 99 all: "Tous"
100 rss_limit: "Nombre d’articles dans le flux" 100 rss_limit: "Nombre d’articles dans le flux"
101 form_user: 101 form_user:
102 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) pour obtenir un code temporaire à chaque nouvelle connexion non approuvée. Vous ne pouvez pas choisir les deux options."
103 name_label: "Nom" 103 name_label: "Nom"
104 email_label: "Adresse courriel" 104 email_label: "Adresse courriel"
105 twoFactorAuthentication_label: "Double authentification" 105 emailTwoFactor_label: 'En utlisant l’email (recevez un code par email)'
106 help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." 106 googleTwoFactor_label: 'En utilisant une application de mot de passe à usage unique (ouvrez l’app, comme Google Authenticator, pour obtenir un mot de passe à usage unique)'
107 delete: 107 delete:
108 title: "Supprimer mon compte (attention danger !)" 108 title: "Supprimer mon compte (attention danger !)"
109 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é." 109 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é."
@@ -534,6 +534,8 @@ user:
534 enabled_label: "Activé" 534 enabled_label: "Activé"
535 last_login_label: "Dernière connexion" 535 last_login_label: "Dernière connexion"
536 twofactor_label: "Double authentification" 536 twofactor_label: "Double authentification"
537 twofactor_email_label: Double authentification par email
538 twofactor_google_label: Double authentification par Google
537 save: "Sauvegarder" 539 save: "Sauvegarder"
538 delete: "Supprimer" 540 delete: "Supprimer"
539 delete_confirm: "Êtes-vous sûr ?" 541 delete_confirm: "Êtes-vous sûr ?"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index f16ffb6b..7279dba1 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 rss_limit: 'Numero di elementi nel feed' 100 rss_limit: 'Numero di elementi nel feed'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Nome' 103 name_label: 'Nome'
104 email_label: 'E-mail' 104 email_label: 'E-mail'
105 twoFactorAuthentication_label: 'Autenticazione a due fattori' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 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, to get a one time code)'
107 delete: 107 delete:
108 title: Cancella il mio account (zona pericolosa) 108 title: Cancella il mio account (zona pericolosa)
109 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. 109 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'E-mail' 533 email_label: 'E-mail'
534 enabled_label: 'Abilitato' 534 enabled_label: 'Abilitato'
535 last_login_label: 'Ultima connessione' 535 last_login_label: 'Ultima connessione'
536 twofactor_label: Autenticazione a due fattori 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 save: Salva 538 save: Salva
538 delete: Cancella 539 delete: Cancella
539 delete_confirm: Sei sicuro? 540 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 b568fbc5..f262ba7b 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -99,11 +99,11 @@ config:
99 all: 'Totes' 99 all: 'Totes'
100 rss_limit: "Nombre d'articles dins un flux RSS" 100 rss_limit: "Nombre d'articles dins un flux RSS"
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Nom' 103 name_label: 'Nom'
104 email_label: 'Adreça de corrièl' 104 email_label: 'Adreça de corrièl'
105 twoFactorAuthentication_label: 'Dobla autentificacion' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 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, to get a one time code)'
107 delete: 107 delete:
108 title: Suprimir mon compte (Mèfi zòna perilhosa) 108 title: Suprimir mon compte (Mèfi zòna perilhosa)
109 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. 109 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'Adreça de corrièl' 533 email_label: 'Adreça de corrièl'
534 enabled_label: 'Actiu' 534 enabled_label: 'Actiu'
535 last_login_label: 'Darrièra connexion' 535 last_login_label: 'Darrièra connexion'
536 twofactor_label: 'Autentificacion doble-factor' 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 save: 'Enregistrar' 538 save: 'Enregistrar'
538 delete: 'Suprimir' 539 delete: 'Suprimir'
539 delete_confirm: 'Sètz segur ?' 540 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 b58e14f4..99c2183e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -99,11 +99,11 @@ config:
99 all: 'Wszystkie' 99 all: 'Wszystkie'
100 rss_limit: 'Link do RSS' 100 rss_limit: 'Link do RSS'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Nazwa' 103 name_label: 'Nazwa'
104 email_label: 'Adres email' 104 email_label: 'Adres email'
105 twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 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, to get a one time code)'
107 delete: 107 delete:
108 title: Usuń moje konto (niebezpieczna strefa !) 108 title: Usuń moje konto (niebezpieczna strefa !)
109 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. 109 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'Adres email' 533 email_label: 'Adres email'
534 enabled_label: 'Włączony' 534 enabled_label: 'Włączony'
535 last_login_label: 'Ostatnie logowanie' 535 last_login_label: 'Ostatnie logowanie'
536 twofactor_label: Autoryzacja dwuetapowa 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 save: Zapisz 538 save: Zapisz
538 delete: Usuń 539 delete: Usuń
539 delete_confirm: Jesteś pewien? 540 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 add28bf7..806c2d78 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 rss_limit: 'Número de itens no feed' 100 rss_limit: 'Número de itens no feed'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Nome' 103 name_label: 'Nome'
104 email_label: 'E-mail' 104 email_label: 'E-mail'
105 twoFactorAuthentication_label: 'Autenticação de dois passos' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 # 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, to get a one time code)'
107 delete: 107 delete:
108 # title: Delete my account (a.k.a danger zone) 108 # title: Delete my account (a.k.a danger zone)
109 # 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. 109 # 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'E-mail' 533 email_label: 'E-mail'
534 enabled_label: 'Habilitado' 534 enabled_label: 'Habilitado'
535 last_login_label: 'Último login' 535 last_login_label: 'Último login'
536 twofactor_label: 'Autenticação de dois passos' 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 save: 'Salvar' 538 save: 'Salvar'
538 delete: 'Apagar' 539 delete: 'Apagar'
539 delete_confirm: 'Tem certeza?' 540 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 6a38c4a1..ed75ed6e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 rss_limit: 'Limită RSS' 100 rss_limit: 'Limită RSS'
101 form_user: 101 form_user:
102 # 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'Nume' 103 name_label: 'Nume'
104 email_label: 'E-mail' 104 email_label: 'E-mail'
105 # twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 # 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, to get a one time code)'
107 delete: 107 delete:
108 # title: Delete my account (a.k.a danger zone) 108 # title: Delete my account (a.k.a danger zone)
109 # 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. 109 # 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.
@@ -533,7 +533,8 @@ user:
533 email_label: 'E-mail' 533 email_label: 'E-mail'
534 # enabled_label: 'Enabled' 534 # enabled_label: 'Enabled'
535 # last_login_label: 'Last login' 535 # last_login_label: 'Last login'
536 # twofactor_label: Two factor authentication 536 # twofactor_email_label: Two factor authentication by email
537 # twofactor_google_label: Two factor authentication by Google
537 # save: Save 538 # save: Save
538 # delete: Delete 539 # delete: Delete
539 # delete_confirm: Are you sure? 540 # 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 1b7ac38a..1c6e6771 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
@@ -96,11 +96,11 @@ config:
96 archive: 'архивные' 96 archive: 'архивные'
97 rss_limit: 'Количество записей в фиде' 97 rss_limit: 'Количество записей в фиде'
98 form_user: 98 form_user:
99 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) to get a one time code on every new untrusted connection. You can't choose both option."
100 name_label: 'Имя' 100 name_label: 'Имя'
101 email_label: 'Email' 101 email_label: 'Email'
102 twoFactorAuthentication_label: 'Двухфакторная аутентификация' 102 # emailTwoFactor_label: 'Using email (receive a code by email)'
103 help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." 103 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
104 delete: 104 delete:
105 title: "Удалить мой аккаунт (или опасная зона)" 105 title: "Удалить мой аккаунт (или опасная зона)"
106 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." 106 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@@ -521,7 +521,8 @@ user:
521 email_label: 'Email' 521 email_label: 'Email'
522 enabled_label: 'Включить' 522 enabled_label: 'Включить'
523 last_login_label: 'Последний вход' 523 last_login_label: 'Последний вход'
524 twofactor_label: "Двухфакторная аутентификация" 524 # twofactor_email_label: Two factor authentication by email
525 # twofactor_google_label: Two factor authentication by Google
525 save: "Сохранить" 526 save: "Сохранить"
526 delete: "Удалить" 527 delete: "Удалить"
527 delete_confirm: "Вы уверены?" 528 delete_confirm: "Вы уверены?"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
index fe1b35be..af798943 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
@@ -99,11 +99,11 @@ config:
99 all: 'ทั้งหมด' 99 all: 'ทั้งหมด'
100 rss_limit: 'จำนวนไอเทมที่เก็บ' 100 rss_limit: 'จำนวนไอเทมที่เก็บ'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'ชื่อ' 103 name_label: 'ชื่อ'
104 email_label: 'อีเมล' 104 email_label: 'อีเมล'
105 twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
107 delete: 107 delete:
108 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!) 108 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
109 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก 109 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@@ -531,7 +531,8 @@ user:
531 email_label: 'อีเมล' 531 email_label: 'อีเมล'
532 enabled_label: 'เปิดใช้งาน' 532 enabled_label: 'เปิดใช้งาน'
533 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' 533 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย'
534 twofactor_label: Two factor authentication 534 # twofactor_email_label: Two factor authentication by email
535 # twofactor_google_label: Two factor authentication by Google
535 save: บันทึก 536 save: บันทึก
536 delete: ลบ 537 delete: ลบ
537 delete_confirm: ตุณแน่ใจหรือไม่? 538 delete_confirm: ตุณแน่ใจหรือไม่?
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index 638714e4..352a2cc4 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -99,11 +99,11 @@ config:
99 # all: 'All' 99 # all: 'All'
100 rss_limit: 'RSS içeriğinden talep edilecek makale limiti' 100 rss_limit: 'RSS içeriğinden talep edilecek makale limiti'
101 form_user: 101 form_user:
102 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) to get a one time code on every new untrusted connection. You can't choose both option."
103 name_label: 'İsim' 103 name_label: 'İsim'
104 email_label: 'E-posta' 104 email_label: 'E-posta'
105 twoFactorAuthentication_label: 'İki adımlı doğrulama' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
106 # 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, to get a one time code)'
107 delete: 107 delete:
108 # title: Delete my account (a.k.a danger zone) 108 # title: Delete my account (a.k.a danger zone)
109 # 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. 109 # 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.
@@ -531,7 +531,8 @@ user:
531 email_label: 'E-posta' 531 email_label: 'E-posta'
532 # enabled_label: 'Enabled' 532 # enabled_label: 'Enabled'
533 # last_login_label: 'Last login' 533 # last_login_label: 'Last login'
534 # twofactor_label: Two factor authentication 534 # twofactor_email_label: Two factor authentication by email
535 # twofactor_google_label: Two factor authentication by Google
535 # save: Save 536 # save: Save
536 # delete: Delete 537 # delete: Delete
537 # delete_confirm: Are you sure? 538 # 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..5c4e44dd 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
@@ -176,43 +176,36 @@
176 176
177 <fieldset class="w500p inline"> 177 <fieldset class="w500p inline">
178 <div class="row"> 178 <div class="row">
179 {{ form_label(form.user.twoFactorAuthentication) }} 179 {{ form_label(form.user.emailTwoFactor) }}
180 {{ form_errors(form.user.twoFactorAuthentication) }} 180 {{ form_errors(form.user.emailTwoFactor) }}
181 {{ form_widget(form.user.twoFactorAuthentication) }} 181 {{ form_widget(form.user.emailTwoFactor) }}
182 </div> 182 </div>
183 <a href="#" title="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 183 <br/>
184 <i class="material-icons">live_help</i> 184 <div class="row">
185 </a> 185 {{ form_label(form.user.googleTwoFactor) }}
186 {{ form_widget(form.user.googleTwoFactor) }}
187 {{ form_errors(form.user.googleTwoFactor) }}
188 </div>
189 {% for OTPSecret in app.session.flashbag.get('OTPSecret') %}
190 <div class="row">
191 You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password.
192 <br/>
193 That code will disapear after a page reload.
194 <br/><br/>
195 <strong>{{ OTPSecret.code }}</strong>
196 <br/><br/>
197 Or you can scan that QR Code with your app:
198 <br/>
199 <img id="2faQrcode" class="hide-on-med-and-down" />
200
201 <script>
202 document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ OTPSecret.qrCode }}');;
203 </script>
204 </div>
205 {% endfor %}
186 </fieldset> 206 </fieldset>
187 {% endif %} 207 {% endif %}
188 208
189 <h2>{{ 'config.reset.title'|trans }}</h2>
190 <fieldset class="w500p inline">
191 <p>{{ 'config.reset.description'|trans }}</p>
192 <ul>
193 <li>
194 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
195 {{ 'config.reset.annotations'|trans }}
196 </a>
197 </li>
198 <li>
199 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
200 {{ 'config.reset.tags'|trans }}
201 </a>
202 </li>
203 <li>
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
216 {{ form_widget(form.user._token) }} 209 {{ form_widget(form.user._token) }}
217 {{ form_widget(form.user.save) }} 210 {{ form_widget(form.user.save) }}
218 </form> 211 </form>
@@ -277,7 +270,7 @@
277 {% endfor %} 270 {% endfor %}
278 </ul> 271 </ul>
279 272
280 {{ form_start(form.new_tagging_rule) }} 273 {{ form_start(form.new_tagging_rule) }}
281 {{ form_errors(form.new_tagging_rule) }} 274 {{ form_errors(form.new_tagging_rule) }}
282 275
283 <fieldset class="w500p inline"> 276 <fieldset class="w500p inline">
@@ -382,4 +375,31 @@
382 </table> 375 </table>
383 </div> 376 </div>
384 </div> 377 </div>
378
379 <h2>{{ 'config.reset.title'|trans }}</h2>
380 <fieldset class="w500p inline">
381 <p>{{ 'config.reset.description'|trans }}</p>
382 <ul>
383 <li>
384 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
385 {{ 'config.reset.annotations'|trans }}
386 </a>
387 </li>
388 <li>
389 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
390 {{ 'config.reset.tags'|trans }}
391 </a>
392 </li>
393 <li>
394 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
395 {{ 'config.reset.archived'|trans }}
396 </a>
397 </li>
398 <li>
399 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
400 {{ 'config.reset.entries'|trans }}
401 </a>
402 </li>
403 </ul>
404 </fieldset>
385{% endblock %} 405{% 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 35800989..887d154f 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
@@ -112,8 +112,7 @@
112 <img id="androidQrcode" class="hide-on-med-and-down" /> 112 <img id="androidQrcode" class="hide-on-med-and-down" />
113 </div> 113 </div>
114 <script> 114 <script>
115 const imgBase64 = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}'); 115 document.getElementById('androidQrcode').src = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}');;
116 document.getElementById('androidQrcode').src = imgBase64;
117 </script> 116 </script>
118 </div> 117 </div>
119 118
@@ -198,22 +197,38 @@
198 </div> 197 </div>
199 198
200 {% if twofactor_auth %} 199 {% if twofactor_auth %}
201 <div class="row"> 200 <div class="row">
202 <div class="input-field col s11">
203 {{ 'config.form_user.two_factor_description'|trans }} 201 {{ 'config.form_user.two_factor_description'|trans }}
204 202
205 <br /> 203 <div class="input-field col s11">
206 204 {{ form_widget(form.user.emailTwoFactor) }}
207 {{ form_widget(form.user.twoFactorAuthentication) }} 205 {{ form_label(form.user.emailTwoFactor) }}
208 {{ form_label(form.user.twoFactorAuthentication) }} 206 {{ form_errors(form.user.emailTwoFactor) }}
209 {{ form_errors(form.user.twoFactorAuthentication) }} 207 </div>
210 </div> 208 <div class="input-field col s11">
211 <div class="input-field col s1"> 209 {{ form_widget(form.user.googleTwoFactor) }}
212 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 210 {{ form_label(form.user.googleTwoFactor) }}
213 <i class="material-icons">live_help</i> 211 {{ form_errors(form.user.googleTwoFactor) }}
214 </a> 212 </div>
215 </div> 213 </div>
216 </div> 214
215 {% for OTPSecret in app.session.flashbag.get('OTPSecret') %}
216 <div class="card-panel yellow darken-1 black-text">
217 You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password.
218 <br/>
219 That code will disapear after a page reload.
220 <br/><br/>
221 <strong>{{ OTPSecret.code }}</strong>
222 <br/><br/>
223 Or you can scan that QR Code with your app:
224 <br/>
225 <img id="2faQrcode" class="hide-on-med-and-down" />
226
227 <script>
228 document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ OTPSecret.qrCode }}');;
229 </script>
230 </div>
231 {% endfor %}
217 {% endif %} 232 {% endif %}
218 233
219 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 234 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php
index a9746fb4..08ed25dd 100644
--- a/src/Wallabag/UserBundle/Controller/ManageController.php
+++ b/src/Wallabag/UserBundle/Controller/ManageController.php
@@ -8,6 +8,7 @@ use Pagerfanta\Adapter\DoctrineORMAdapter;
8use Pagerfanta\Exception\OutOfRangeCurrentPageException; 8use Pagerfanta\Exception\OutOfRangeCurrentPageException;
9use Pagerfanta\Pagerfanta; 9use Pagerfanta\Pagerfanta;
10use Symfony\Bundle\FrameworkBundle\Controller\Controller; 10use Symfony\Bundle\FrameworkBundle\Controller\Controller;
11use Symfony\Component\Form\FormInterface;
11use Symfony\Component\HttpFoundation\Request; 12use Symfony\Component\HttpFoundation\Request;
12use Symfony\Component\Routing\Annotation\Route; 13use Symfony\Component\Routing\Annotation\Route;
13use Wallabag\UserBundle\Entity\User; 14use Wallabag\UserBundle\Entity\User;
@@ -31,10 +32,10 @@ class ManageController extends Controller
31 // enable created user by default 32 // enable created user by default
32 $user->setEnabled(true); 33 $user->setEnabled(true);
33 34
34 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user); 35 $form = $this->createEditForm('NewUserType', $user, $request);
35 $form->handleRequest($request);
36 36
37 if ($form->isSubmitted() && $form->isValid()) { 37 if ($form->isSubmitted() && $form->isValid()) {
38 $user = $this->handleOtp($form, $user);
38 $userManager->updateUser($user); 39 $userManager->updateUser($user);
39 40
40 // dispatch a created event so the associated config will be created 41 // dispatch a created event so the associated config will be created
@@ -62,14 +63,14 @@ class ManageController extends Controller
62 */ 63 */
63 public function editAction(Request $request, User $user) 64 public function editAction(Request $request, User $user)
64 { 65 {
66 $userManager = $this->container->get('fos_user.user_manager');
67
65 $deleteForm = $this->createDeleteForm($user); 68 $deleteForm = $this->createDeleteForm($user);
66 $editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user); 69 $form = $this->createEditForm('UserType', $user, $request);
67 $editForm->handleRequest($request);
68 70
69 if ($editForm->isSubmitted() && $editForm->isValid()) { 71 if ($form->isSubmitted() && $form->isValid()) {
70 $em = $this->getDoctrine()->getManager(); 72 $user = $this->handleOtp($form, $user);
71 $em->persist($user); 73 $userManager->updateUser($user);
72 $em->flush();
73 74
74 $this->get('session')->getFlashBag()->add( 75 $this->get('session')->getFlashBag()->add(
75 'notice', 76 'notice',
@@ -81,7 +82,7 @@ class ManageController extends Controller
81 82
82 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ 83 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [
83 'user' => $user, 84 'user' => $user,
84 'edit_form' => $editForm->createView(), 85 'edit_form' => $form->createView(),
85 'delete_form' => $deleteForm->createView(), 86 'delete_form' => $deleteForm->createView(),
86 'twofactor_auth' => $this->getParameter('twofactor_auth'), 87 'twofactor_auth' => $this->getParameter('twofactor_auth'),
87 ]); 88 ]);
@@ -157,7 +158,7 @@ class ManageController extends Controller
157 } 158 }
158 159
159 /** 160 /**
160 * Creates a form to delete a User entity. 161 * Create a form to delete a User entity.
161 * 162 *
162 * @param User $user The User entity 163 * @param User $user The User entity
163 * 164 *
@@ -171,4 +172,50 @@ class ManageController extends Controller
171 ->getForm() 172 ->getForm()
172 ; 173 ;
173 } 174 }
175
176 /**
177 * Create a form to create or edit a User entity.
178 *
179 * @param string $type Might be NewUserType or UserType
180 * @param User $user The new / edit user
181 * @param Request $request The request
182 *
183 * @return FormInterface
184 */
185 private function createEditForm($type, User $user, Request $request)
186 {
187 $form = $this->createForm('Wallabag\UserBundle\Form\\' . $type, $user);
188 $form->handleRequest($request);
189
190 // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
191 if (true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) {
192 $form->get('googleTwoFactor')->setData(true);
193 }
194
195 return $form;
196 }
197
198 /**
199 * Handle OTP update, taking care to only have one 2fa enable at a time.
200 *
201 * @see ConfigController
202 *
203 * @param FormInterface $form
204 * @param User $user
205 *
206 * @return User
207 */
208 private function handleOtp(FormInterface $form, User $user)
209 {
210 if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
211 $user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret());
212 $user->setEmailTwoFactor(false);
213
214 return $user;
215 }
216
217 $user->setGoogleAuthenticatorSecret(null);
218
219 return $user;
220 }
174} 221}
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 48446e3c..6e305719 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -8,8 +8,8 @@ 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\Email\TwoFactorInterface as EmailTwoFactorInterface;
12use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 12use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface;
13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
14use Symfony\Component\Security\Core\User\UserInterface; 14use Symfony\Component\Security\Core\User\UserInterface;
15use Wallabag\ApiBundle\Entity\Client; 15use Wallabag\ApiBundle\Entity\Client;
@@ -28,7 +28,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
28 * @UniqueEntity("email") 28 * @UniqueEntity("email")
29 * @UniqueEntity("username") 29 * @UniqueEntity("username")
30 */ 30 */
31class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 31class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface
32{ 32{
33 use EntityTimestampsTrait; 33 use EntityTimestampsTrait;
34 34
@@ -123,16 +123,16 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
123 private $authCode; 123 private $authCode;
124 124
125 /** 125 /**
126 * @var bool 126 * @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true)
127 *
128 * @ORM\Column(type="boolean")
129 */ 127 */
130 private $twoFactorAuthentication = false; 128 private $googleAuthenticatorSecret;
131 129
132 /** 130 /**
133 * @ORM\Column(type="json_array", nullable=true) 131 * @var bool
132 *
133 * @ORM\Column(type="boolean")
134 */ 134 */
135 private $trusted; 135 private $emailTwoFactor = false;
136 136
137 public function __construct() 137 public function __construct()
138 { 138 {
@@ -233,49 +233,89 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
233 /** 233 /**
234 * @return bool 234 * @return bool
235 */ 235 */
236 public function isTwoFactorAuthentication() 236 public function isEmailTwoFactor()
237 {
238 return $this->emailTwoFactor;
239 }
240
241 /**
242 * @param bool $emailTwoFactor
243 */
244 public function setEmailTwoFactor($emailTwoFactor)
237 { 245 {
238 return $this->twoFactorAuthentication; 246 $this->emailTwoFactor = $emailTwoFactor;
239 } 247 }
240 248
241 /** 249 /**
242 * @param bool $twoFactorAuthentication 250 * Used in the user config form to be "like" the email option.
243 */ 251 */
244 public function setTwoFactorAuthentication($twoFactorAuthentication) 252 public function isGoogleTwoFactor()
245 { 253 {
246 $this->twoFactorAuthentication = $twoFactorAuthentication; 254 return $this->isGoogleAuthenticatorEnabled();
247 } 255 }
248 256
249 public function isEmailAuthEnabled() 257 /**
258 * {@inheritdoc}
259 */
260 public function isEmailAuthEnabled(): bool
250 { 261 {
251 return $this->twoFactorAuthentication; 262 return $this->emailTwoFactor;
252 } 263 }
253 264
254 public function getEmailAuthCode() 265 /**
266 * {@inheritdoc}
267 */
268 public function getEmailAuthCode(): string
255 { 269 {
256 return $this->authCode; 270 return $this->authCode;
257 } 271 }
258 272
259 public function setEmailAuthCode($authCode) 273 /**
274 * {@inheritdoc}
275 */
276 public function setEmailAuthCode(string $authCode): void
260 { 277 {
261 $this->authCode = $authCode; 278 $this->authCode = $authCode;
262 } 279 }
263 280
264 public function addTrustedComputer($token, \DateTime $validUntil) 281 /**
282 * {@inheritdoc}
283 */
284 public function getEmailAuthRecipient(): string
265 { 285 {
266 $this->trusted[$token] = $validUntil->format('r'); 286 return $this->email;
267 } 287 }
268 288
269 public function isTrustedComputer($token) 289 /**
290 * {@inheritdoc}
291 */
292 public function isGoogleAuthenticatorEnabled(): bool
270 { 293 {
271 if (isset($this->trusted[$token])) { 294 return $this->googleAuthenticatorSecret ? true : false;
272 $now = new \DateTime(); 295 }
273 $validUntil = new \DateTime($this->trusted[$token]);
274 296
275 return $now < $validUntil; 297 /**
276 } 298 * {@inheritdoc}
299 */
300 public function getGoogleAuthenticatorUsername(): string
301 {
302 return $this->username;
303 }
277 304
278 return false; 305 /**
306 * {@inheritdoc}
307 */
308 public function getGoogleAuthenticatorSecret(): string
309 {
310 return $this->googleAuthenticatorSecret;
311 }
312
313 /**
314 * {@inheritdoc}
315 */
316 public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void
317 {
318 $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
279 } 319 }
280 320
281 /** 321 /**
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..e8e29aa9 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
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..8be37e79 100644
--- a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
@@ -50,10 +50,21 @@
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> 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) }}
61 </div>
62
63 {% if user.isGoogleAuthenticatorEnabled %}
64 <div class="input-field col s12">
65 <p><strong>OTP Secret</strong>: {{ user.googleAuthenticatorSecret }}</p>
66 </div>
67 {% endif %}
57 </div> 68 </div>
58 {% endif %} 69 {% endif %}
59 70
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..9ca52c64 100644
--- a/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
+++ b/tests/Wallabag/CoreBundle/Controller/ConfigControllerTest.php
@@ -297,6 +297,119 @@ class ConfigControllerTest extends WallabagCoreTestCase
297 $this->assertContains('flashes.config.notice.user_updated', $alert[0]); 297 $this->assertContains('flashes.config.notice.user_updated', $alert[0]);
298 } 298 }
299 299
300 public function testUserEnable2faEmail()
301 {
302 $this->logInAs('admin');
303 $client = $this->getClient();
304
305 $crawler = $client->request('GET', '/config');
306
307 $this->assertSame(200, $client->getResponse()->getStatusCode());
308
309 $form = $crawler->filter('button[id=update_user_save]')->form();
310
311 $data = [
312 'update_user[emailTwoFactor]' => '1',
313 ];
314
315 $client->submit($form, $data);
316
317 $this->assertSame(302, $client->getResponse()->getStatusCode());
318
319 $crawler = $client->followRedirect();
320
321 $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
322 $this->assertContains('flashes.config.notice.user_updated', $alert[0]);
323
324 // restore user
325 $em = $this->getEntityManager();
326 $user = $em
327 ->getRepository('WallabagUserBundle:User')
328 ->findOneByUsername('admin');
329
330 $this->assertTrue($user->isEmailTwoFactor());
331
332 $user->setEmailTwoFactor(false);
333 $em->persist($user);
334 $em->flush();
335 }
336
337 public function testUserEnable2faGoogle()
338 {
339 $this->logInAs('admin');
340 $client = $this->getClient();
341
342 $crawler = $client->request('GET', '/config');
343
344 $this->assertSame(200, $client->getResponse()->getStatusCode());
345
346 $form = $crawler->filter('button[id=update_user_save]')->form();
347
348 $data = [
349 'update_user[googleTwoFactor]' => '1',
350 ];
351
352 $client->submit($form, $data);
353
354 $this->assertSame(302, $client->getResponse()->getStatusCode());
355
356 $crawler = $client->followRedirect();
357
358 $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
359 $this->assertContains('flashes.config.notice.user_updated', $alert[0]);
360
361 // restore user
362 $em = $this->getEntityManager();
363 $user = $em
364 ->getRepository('WallabagUserBundle:User')
365 ->findOneByUsername('admin');
366
367 $this->assertTrue($user->isGoogleAuthenticatorEnabled());
368
369 $user->setGoogleAuthenticatorSecret(null);
370 $em->persist($user);
371 $em->flush();
372 }
373
374 public function testUserEnable2faBoth()
375 {
376 $this->logInAs('admin');
377 $client = $this->getClient();
378
379 $crawler = $client->request('GET', '/config');
380
381 $this->assertSame(200, $client->getResponse()->getStatusCode());
382
383 $form = $crawler->filter('button[id=update_user_save]')->form();
384
385 $data = [
386 'update_user[googleTwoFactor]' => '1',
387 'update_user[emailTwoFactor]' => '1',
388 ];
389
390 $client->submit($form, $data);
391
392 $this->assertSame(302, $client->getResponse()->getStatusCode());
393
394 $crawler = $client->followRedirect();
395
396 $this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
397 $this->assertContains('flashes.config.notice.user_updated', $alert[0]);
398
399 // restore user
400 $em = $this->getEntityManager();
401 $user = $em
402 ->getRepository('WallabagUserBundle:User')
403 ->findOneByUsername('admin');
404
405 $this->assertTrue($user->isGoogleAuthenticatorEnabled());
406 $this->assertFalse($user->isEmailTwoFactor());
407
408 $user->setGoogleAuthenticatorSecret(null);
409 $em->persist($user);
410 $em->flush();
411 }
412
300 public function testRssUpdateResetToken() 413 public function testRssUpdateResetToken()
301 { 414 {
302 $this->logInAs('admin'); 415 $this->logInAs('admin');
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/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');