Enable OTP 2FA

- Update SchebTwoFactorBundle to version 3
- Enable Google 2fa on the bundle
- Disallow ability to use both email and google as 2fa
- Update Ocramius Proxy Manager to handle typed function & attributes (from PHP 7)
- use `$this->addFlash` shortcut instead of `$this->get('session')->getFlashBag()->add`
- update admin to be able to create/reset the 2fa
This commit is contained in:
Jeremy Benoist 2018-12-02 12:43:05 +01:00
parent acd4412080
commit a6b242a1fd
No known key found for this signature in database
GPG key ID: BCA73962457ACC3C
36 changed files with 554 additions and 178 deletions

View file

@ -0,0 +1,22 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Add 2fa OTP (named google authenticator).
*/
final class Version20181202073750 extends WallabagMigration
{
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL, CHANGE twoFactorAuthentication emailTwoFactor BOOLEAN NOT NULL, DROP trusted');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP googleAuthenticatorSecret, CHANGE emailtwofactor twoFactorAuthentication BOOLEAN NOT NULL, ADD trusted TEXT DEFAULT NULL');
}
}

View file

@ -89,4 +89,22 @@ $(document).ready(() => {
} }
}; };
}); });
// mimic radio button because emailTwoFactor is a boolean
$('#update_user_googleTwoFactor').on('change', () => {
$('#update_user_emailTwoFactor').prop('checked', false);
});
$('#update_user_emailTwoFactor').on('change', () => {
$('#update_user_googleTwoFactor').prop('checked', false);
});
// same mimic for super admin
$('#user_googleTwoFactor').on('change', () => {
$('#user_emailTwoFactor').prop('checked', false);
});
$('#user_emailTwoFactor').on('change', () => {
$('#user_googleTwoFactor').prop('checked', false);
});
}); });

View file

@ -50,25 +50,30 @@ $(document).ready(() => {
$('#tag_label').focus(); $('#tag_label').focus();
return false; return false;
}); });
$('#nav-btn-add').on('click', () => { $('#nav-btn-add').on('click', () => {
toggleNav('.nav-panel-add', '#entry_url'); toggleNav('.nav-panel-add', '#entry_url');
return false; return false;
}); });
const materialAddForm = $('.nav-panel-add'); const materialAddForm = $('.nav-panel-add');
materialAddForm.on('submit', () => { materialAddForm.on('submit', () => {
materialAddForm.addClass('disabled'); materialAddForm.addClass('disabled');
$('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur'); $('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur');
}); });
$('#nav-btn-search').on('click', () => { $('#nav-btn-search').on('click', () => {
toggleNav('.nav-panel-search', '#search_entry_term'); toggleNav('.nav-panel-search', '#search_entry_term');
return false; return false;
}); });
$('.close').on('click', (e) => { $('.close').on('click', (e) => {
$(e.target).parent('.nav-panel-item').hide(100); $(e.target).parent('.nav-panel-item').hide(100);
$('.nav-panel-actions').show(100); $('.nav-panel-actions').show(100);
$('.nav-panels').css('background', 'transparent'); $('.nav-panels').css('background', 'transparent');
return false; return false;
}); });
$(window).scroll(() => { $(window).scroll(() => {
const s = $(window).scrollTop(); const s = $(window).scrollTop();
const d = $(document).height(); const d = $(document).height();

View file

@ -198,10 +198,14 @@ fos_oauth_server:
refresh_token_lifetime: 1209600 refresh_token_lifetime: 1209600
scheb_two_factor: scheb_two_factor:
trusted_computer: trusted_device:
enabled: true enabled: true
cookie_name: wllbg_trusted_computer cookie_name: wllbg_trusted_computer
cookie_lifetime: 2592000 lifetime: 2592000
google:
enabled: "%twofactor_auth%"
template: WallabagUserBundle:Authentication:form.html.twig
email: email:
enabled: "%twofactor_auth%" enabled: "%twofactor_auth%"

View file

@ -51,3 +51,11 @@ craue_config_settings_modify:
fos_js_routing: fos_js_routing:
resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml" resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"
2fa_login:
path: /2fa
defaults:
_controller: "scheb_two_factor.form_controller:form"
2fa_login_check:
path: /2fa_check

View file

@ -56,9 +56,17 @@ security:
path: /logout path: /logout
target: / target: /
two_factor:
provider: fos_userbundle
auth_form_path: 2fa_login
check_path: 2fa_login_check
access_control: access_control:
- { path: ^/api/(doc|version|info|user), roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api/(doc|version|info|user), roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
# force role for logout otherwise when 2fa enable, you won't be able to logout
# https://github.com/scheb/two-factor-bundle/issues/168#issuecomment-430822478
- { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
@ -67,5 +75,6 @@ security:
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/settings, roles: ROLE_SUPER_ADMIN } - { path: ^/settings, roles: ROLE_SUPER_ADMIN }
- { path: ^/annotations, roles: ROLE_USER } - { path: ^/annotations, roles: ROLE_USER }
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: ^/users, roles: ROLE_SUPER_ADMIN } - { path: ^/users, roles: ROLE_SUPER_ADMIN }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }

View file

@ -31,7 +31,7 @@
"issues": "https://github.com/wallabag/wallabag/issues" "issues": "https://github.com/wallabag/wallabag/issues"
}, },
"require": { "require": {
"php": ">=7.1.0", "php": ">=7.1.3",
"ext-pcre": "*", "ext-pcre": "*",
"ext-dom": "*", "ext-dom": "*",
"ext-curl": "*", "ext-curl": "*",
@ -70,7 +70,7 @@
"friendsofsymfony/user-bundle": "2.0.*", "friendsofsymfony/user-bundle": "2.0.*",
"friendsofsymfony/oauth-server-bundle": "^1.5", "friendsofsymfony/oauth-server-bundle": "^1.5",
"stof/doctrine-extensions-bundle": "^1.2", "stof/doctrine-extensions-bundle": "^1.2",
"scheb/two-factor-bundle": "^2.14", "scheb/two-factor-bundle": "^3.0",
"grandt/phpepub": "dev-master", "grandt/phpepub": "dev-master",
"wallabag/php-mobi": "~1.0", "wallabag/php-mobi": "~1.0",
"kphoen/rulerz-bundle": "~0.13", "kphoen/rulerz-bundle": "~0.13",
@ -147,7 +147,7 @@
"config": { "config": {
"bin-dir": "bin", "bin-dir": "bin",
"platform": { "platform": {
"php": "7.1" "php": "7.1.3"
} }
}, },
"minimum-stability": "dev", "minimum-stability": "dev",

View file

@ -57,7 +57,8 @@ class ShowUserCommand extends ContainerAwareCommand
sprintf('Display name: %s', $user->getName()), sprintf('Display name: %s', $user->getName()),
sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')), sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')),
sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'), sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'),
sprintf('2FA activated: %s', $user->isTwoFactorAuthentication() ? 'yes' : 'no'), sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'),
sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'),
]); ]);
} }

View file

@ -46,7 +46,7 @@ class ConfigController extends Controller
$activeTheme = $this->get('liip_theme.active_theme'); $activeTheme = $this->get('liip_theme.active_theme');
$activeTheme->setName($config->getTheme()); $activeTheme->setName($config->getTheme());
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.config_saved' 'flashes.config.notice.config_saved'
); );
@ -68,7 +68,7 @@ class ConfigController extends Controller
$userManager->updateUser($user, true); $userManager->updateUser($user, true);
} }
$this->get('session')->getFlashBag()->add('notice', $message); $this->addFlash('notice', $message);
return $this->redirect($this->generateUrl('config') . '#set4'); return $this->redirect($this->generateUrl('config') . '#set4');
} }
@ -80,10 +80,29 @@ class ConfigController extends Controller
]); ]);
$userForm->handleRequest($request); $userForm->handleRequest($request);
// `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
if (true === $user->isGoogleAuthenticatorEnabled() && false === $userForm->isSubmitted()) {
$userForm->get('googleTwoFactor')->setData(true);
}
if ($userForm->isSubmitted() && $userForm->isValid()) { if ($userForm->isSubmitted() && $userForm->isValid()) {
// handle creation / reset of the OTP secret if checkbox changed from the previous state
if (true === $userForm->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
$secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret();
$user->setGoogleAuthenticatorSecret($secret);
$user->setEmailTwoFactor(false);
$qrCode = $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user);
$this->addFlash('OTPSecret', ['code' => $secret, 'qrCode' => $qrCode]);
} elseif (false === $userForm->get('googleTwoFactor')->getData() && true === $user->isGoogleAuthenticatorEnabled()) {
$user->setGoogleAuthenticatorSecret(null);
}
$userManager->updateUser($user, true); $userManager->updateUser($user, true);
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.user_updated' 'flashes.config.notice.user_updated'
); );
@ -99,7 +118,7 @@ class ConfigController extends Controller
$em->persist($config); $em->persist($config);
$em->flush(); $em->flush();
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.rss_updated' 'flashes.config.notice.rss_updated'
); );
@ -131,7 +150,7 @@ class ConfigController extends Controller
$em->persist($taggingRule); $em->persist($taggingRule);
$em->flush(); $em->flush();
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.tagging_rules_updated' 'flashes.config.notice.tagging_rules_updated'
); );
@ -178,7 +197,7 @@ class ConfigController extends Controller
return new JsonResponse(['token' => $config->getRssToken()]); return new JsonResponse(['token' => $config->getRssToken()]);
} }
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.rss_token_updated' 'flashes.config.notice.rss_token_updated'
); );
@ -203,7 +222,7 @@ class ConfigController extends Controller
$em->remove($rule); $em->remove($rule);
$em->flush(); $em->flush();
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.tagging_rules_deleted' 'flashes.config.notice.tagging_rules_deleted'
); );
@ -269,7 +288,7 @@ class ConfigController extends Controller
break; break;
} }
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.' . $type . '_reset' 'flashes.config.notice.' . $type . '_reset'
); );

View file

@ -21,9 +21,14 @@ class UserInformationType extends AbstractType
->add('email', EmailType::class, [ ->add('email', EmailType::class, [
'label' => 'config.form_user.email_label', 'label' => 'config.form_user.email_label',
]) ])
->add('twoFactorAuthentication', CheckboxType::class, [ ->add('emailTwoFactor', CheckboxType::class, [
'required' => false, 'required' => false,
'label' => 'config.form_user.twoFactorAuthentication_label', 'label' => 'config.form_user.emailTwoFactor_label',
])
->add('googleTwoFactor', CheckboxType::class, [
'required' => false,
'label' => 'config.form_user.googleTwoFactor_label',
'mapped' => false,
]) ])
->add('save', SubmitType::class, [ ->add('save', SubmitType::class, [
'label' => 'config.form.save', 'label' => 'config.form.save',

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
# rss_limit: 'Number of items in the feed' # rss_limit: 'Number of items in the feed'
form_user: form_user:
# two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" # 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."
name_label: 'Navn' name_label: 'Navn'
email_label: 'Emailadresse' email_label: 'Emailadresse'
# twoFactorAuthentication_label: 'Two factor authentication' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# 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. # 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:
email_label: 'Emailadresse' email_label: 'Emailadresse'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?

View file

@ -99,11 +99,11 @@ config:
all: 'Alle' all: 'Alle'
rss_limit: 'Anzahl der Einträge pro Feed' rss_limit: 'Anzahl der Einträge pro Feed'
form_user: form_user:
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" # 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."
name_label: 'Name' name_label: 'Name'
email_label: 'E-Mail-Adresse' email_label: 'E-Mail-Adresse'
twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: 'Lösche mein Konto (a.k.a Gefahrenzone)' title: 'Lösche mein Konto (a.k.a Gefahrenzone)'
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.' 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:
email_label: 'E-Mail-Adresse' email_label: 'E-Mail-Adresse'
enabled_label: 'Aktiviert' enabled_label: 'Aktiviert'
last_login_label: 'Letzter Login' last_login_label: 'Letzter Login'
twofactor_label: 'Zwei-Faktor-Authentifizierung' # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: 'Speichern' save: 'Speichern'
delete: 'Löschen' delete: 'Löschen'
delete_confirm: 'Bist du sicher?' delete_confirm: 'Bist du sicher?'

View file

@ -99,11 +99,11 @@ config:
all: 'All' all: 'All'
rss_limit: 'Number of items in the feed' rss_limit: 'Number of items in the feed'
form_user: form_user:
two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." 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."
name_label: 'Name' name_label: 'Name'
email_label: 'Email' email_label: 'Email'
twoFactorAuthentication_label: 'Two factor authentication' emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: Delete my account (a.k.a danger zone) title: Delete my account (a.k.a danger zone)
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. 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:
email_label: 'Email' email_label: 'Email'
enabled_label: 'Enabled' enabled_label: 'Enabled'
last_login_label: 'Last login' last_login_label: 'Last login'
twofactor_label: Two factor authentication twofactor_email_label: Two factor authentication by email
twofactor_google_label: Two factor authentication by Google
save: Save save: Save
delete: Delete delete: Delete
delete_confirm: Are you sure? delete_confirm: Are you sure?

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
rss_limit: 'Límite de artículos en feed RSS' rss_limit: 'Límite de artículos en feed RSS'
form_user: form_user:
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." # 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."
name_label: 'Nombre' name_label: 'Nombre'
email_label: 'Dirección de e-mail' email_label: 'Dirección de e-mail'
twoFactorAuthentication_label: 'Autenticación en dos pasos' # emailTwoFactor_label: 'Using email (receive a code by email)'
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." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: Eliminar mi cuenta (Zona peligrosa) title: Eliminar mi cuenta (Zona peligrosa)
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. 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:
email_label: 'E-mail' email_label: 'E-mail'
enabled_label: 'Activado' enabled_label: 'Activado'
last_login_label: 'Último inicio de sesión' last_login_label: 'Último inicio de sesión'
twofactor_label: Autenticación en dos pasos # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: Guardar save: Guardar
delete: Eliminar delete: Eliminar
delete_confirm: ¿Estás seguro? delete_confirm: ¿Estás seguro?

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
rss_limit: 'محدودیت آر-اس-اس' rss_limit: 'محدودیت آر-اس-اس'
form_user: form_user:
two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" # 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."
name_label: 'نام' name_label: 'نام'
email_label: 'نشانی ایمیل' email_label: 'نشانی ایمیل'
twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# 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. # 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:
email_label: 'نشانی ایمیل' email_label: 'نشانی ایمیل'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?

View file

@ -99,11 +99,11 @@ config:
all: "Tous" all: "Tous"
rss_limit: "Nombre darticles dans le flux" rss_limit: "Nombre darticles dans le flux"
form_user: form_user:
two_factor_description: "Activer lauthentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée." two_factor_description: "Activer lauthentification 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."
name_label: "Nom" name_label: "Nom"
email_label: "Adresse courriel" email_label: "Adresse courriel"
twoFactorAuthentication_label: "Double authentification" emailTwoFactor_label: 'En utlisant lemail (recevez un code par email)'
help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." googleTwoFactor_label: 'En utilisant une application de mot de passe à usage unique (ouvrez lapp, comme Google Authenticator, pour obtenir un mot de passe à usage unique)'
delete: delete:
title: "Supprimer mon compte (attention danger !)" title: "Supprimer mon compte (attention danger !)"
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é (cest IRRÉVERSIBLE). Vous serez ensuite déconnecté." 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é (cest IRRÉVERSIBLE). Vous serez ensuite déconnecté."
@ -534,6 +534,8 @@ user:
enabled_label: "Activé" enabled_label: "Activé"
last_login_label: "Dernière connexion" last_login_label: "Dernière connexion"
twofactor_label: "Double authentification" twofactor_label: "Double authentification"
twofactor_email_label: Double authentification par email
twofactor_google_label: Double authentification par Google
save: "Sauvegarder" save: "Sauvegarder"
delete: "Supprimer" delete: "Supprimer"
delete_confirm: "Êtes-vous sûr ?" delete_confirm: "Êtes-vous sûr ?"

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
rss_limit: 'Numero di elementi nel feed' rss_limit: 'Numero di elementi nel feed'
form_user: form_user:
two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata" # 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."
name_label: 'Nome' name_label: 'Nome'
email_label: 'E-mail' email_label: 'E-mail'
twoFactorAuthentication_label: 'Autenticazione a due fattori' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via E-mail." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: Cancella il mio account (zona pericolosa) title: Cancella il mio account (zona pericolosa)
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. 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:
email_label: 'E-mail' email_label: 'E-mail'
enabled_label: 'Abilitato' enabled_label: 'Abilitato'
last_login_label: 'Ultima connessione' last_login_label: 'Ultima connessione'
twofactor_label: Autenticazione a due fattori # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: Salva save: Salva
delete: Cancella delete: Cancella
delete_confirm: Sei sicuro? delete_confirm: Sei sicuro?

View file

@ -99,11 +99,11 @@ config:
all: 'Totes' all: 'Totes'
rss_limit: "Nombre d'articles dins un flux RSS" rss_limit: "Nombre d'articles dins un flux RSS"
form_user: form_user:
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." # 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."
name_label: 'Nom' name_label: 'Nom'
email_label: 'Adreça de corrièl' email_label: 'Adreça de corrièl'
twoFactorAuthentication_label: 'Dobla autentificacion' # emailTwoFactor_label: 'Using email (receive a code by email)'
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." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: Suprimir mon compte (Mèfi zòna perilhosa) title: Suprimir mon compte (Mèfi zòna perilhosa)
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. 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:
email_label: 'Adreça de corrièl' email_label: 'Adreça de corrièl'
enabled_label: 'Actiu' enabled_label: 'Actiu'
last_login_label: 'Darrièra connexion' last_login_label: 'Darrièra connexion'
twofactor_label: 'Autentificacion doble-factor' # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: 'Enregistrar' save: 'Enregistrar'
delete: 'Suprimir' delete: 'Suprimir'
delete_confirm: 'Sètz segur?' delete_confirm: 'Sètz segur?'

View file

@ -99,11 +99,11 @@ config:
all: 'Wszystkie' all: 'Wszystkie'
rss_limit: 'Link do RSS' rss_limit: 'Link do RSS'
form_user: form_user:
two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu" 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."
name_label: 'Nazwa' name_label: 'Nazwa'
email_label: 'Adres email' email_label: 'Adres email'
twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' # emailTwoFactor_label: 'Using email (receive a code by email)'
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." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: Usuń moje konto (niebezpieczna strefa !) title: Usuń moje konto (niebezpieczna strefa !)
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. 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:
email_label: 'Adres email' email_label: 'Adres email'
enabled_label: 'Włączony' enabled_label: 'Włączony'
last_login_label: 'Ostatnie logowanie' last_login_label: 'Ostatnie logowanie'
twofactor_label: Autoryzacja dwuetapowa # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: Zapisz save: Zapisz
delete: Usuń delete: Usuń
delete_confirm: Jesteś pewien? delete_confirm: Jesteś pewien?

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
rss_limit: 'Número de itens no feed' rss_limit: 'Número de itens no feed'
form_user: form_user:
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.' # 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."
name_label: 'Nome' name_label: 'Nome'
email_label: 'E-mail' email_label: 'E-mail'
twoFactorAuthentication_label: 'Autenticação de dois passos' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# 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. # 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:
email_label: 'E-mail' email_label: 'E-mail'
enabled_label: 'Habilitado' enabled_label: 'Habilitado'
last_login_label: 'Último login' last_login_label: 'Último login'
twofactor_label: 'Autenticação de dois passos' # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: 'Salvar' save: 'Salvar'
delete: 'Apagar' delete: 'Apagar'
delete_confirm: 'Tem certeza?' delete_confirm: 'Tem certeza?'

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
rss_limit: 'Limită RSS' rss_limit: 'Limită RSS'
form_user: form_user:
# two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" # 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."
name_label: 'Nume' name_label: 'Nume'
email_label: 'E-mail' email_label: 'E-mail'
# twoFactorAuthentication_label: 'Two factor authentication' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# 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. # 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:
email_label: 'E-mail' email_label: 'E-mail'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?

View file

@ -96,11 +96,11 @@ config:
archive: 'архивные' archive: 'архивные'
rss_limit: 'Количество записей в фиде' rss_limit: 'Количество записей в фиде'
form_user: form_user:
two_factor_description: "Включить двухфакторную аутентификацию, Вы получите сообщение на указанный email с кодом, при каждом новом непроверенном подключении." # 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."
name_label: 'Имя' name_label: 'Имя'
email_label: 'Email' email_label: 'Email'
twoFactorAuthentication_label: 'Двухфакторная аутентификация' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: "Удалить мой аккаунт (или опасная зона)" title: "Удалить мой аккаунт (или опасная зона)"
description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@ -521,7 +521,8 @@ user:
email_label: 'Email' email_label: 'Email'
enabled_label: 'Включить' enabled_label: 'Включить'
last_login_label: 'Последний вход' last_login_label: 'Последний вход'
twofactor_label: "Двухфакторная аутентификация" # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: "Сохранить" save: "Сохранить"
delete: "Удалить" delete: "Удалить"
delete_confirm: "Вы уверены?" delete_confirm: "Вы уверены?"

View file

@ -99,11 +99,11 @@ config:
all: 'ทั้งหมด' all: 'ทั้งหมด'
rss_limit: 'จำนวนไอเทมที่เก็บ' rss_limit: 'จำนวนไอเทมที่เก็บ'
form_user: form_user:
two_factor_description: "การเปิดใช้งาน two factor authentication คือคุณจะต้องได้รับอีเมลกับ code ที่ยังไม่ตรวจสอบในการเชื่อมต่อ" # 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."
name_label: 'ชื่อ' name_label: 'ชื่อ'
email_label: 'อีเมล' email_label: 'อีเมล'
twoFactorAuthentication_label: 'Two factor authentication' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
title: ลบบัญชีของฉัน (โซนที่เป็นภัย!) title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@ -531,7 +531,8 @@ user:
email_label: 'อีเมล' email_label: 'อีเมล'
enabled_label: 'เปิดใช้งาน' enabled_label: 'เปิดใช้งาน'
last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย'
twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
save: บันทึก save: บันทึก
delete: ลบ delete: ลบ
delete_confirm: ตุณแน่ใจหรือไม่? delete_confirm: ตุณแน่ใจหรือไม่?

View file

@ -99,11 +99,11 @@ config:
# all: 'All' # all: 'All'
rss_limit: 'RSS içeriğinden talep edilecek makale limiti' rss_limit: 'RSS içeriğinden talep edilecek makale limiti'
form_user: form_user:
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." # 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."
name_label: 'İsim' name_label: 'İsim'
email_label: 'E-posta' email_label: 'E-posta'
twoFactorAuthentication_label: 'İki adımlı doğrulama' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, to get a one time code)'
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# 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. # 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:
email_label: 'E-posta' email_label: 'E-posta'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by Google
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?

View file

@ -176,43 +176,36 @@
<fieldset class="w500p inline"> <fieldset class="w500p inline">
<div class="row"> <div class="row">
{{ form_label(form.user.twoFactorAuthentication) }} {{ form_label(form.user.emailTwoFactor) }}
{{ form_errors(form.user.twoFactorAuthentication) }} {{ form_errors(form.user.emailTwoFactor) }}
{{ form_widget(form.user.twoFactorAuthentication) }} {{ form_widget(form.user.emailTwoFactor) }}
</div> </div>
<a href="#" title="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> <br/>
<i class="material-icons">live_help</i> <div class="row">
</a> {{ form_label(form.user.googleTwoFactor) }}
{{ form_widget(form.user.googleTwoFactor) }}
{{ form_errors(form.user.googleTwoFactor) }}
</div>
{% for OTPSecret in app.session.flashbag.get('OTPSecret') %}
<div class="row">
You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password.
<br/>
That code will disapear after a page reload.
<br/><br/>
<strong>{{ OTPSecret.code }}</strong>
<br/><br/>
Or you can scan that QR Code with your app:
<br/>
<img id="2faQrcode" class="hide-on-med-and-down" />
<script>
document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ OTPSecret.qrCode }}');;
</script>
</div>
{% endfor %}
</fieldset> </fieldset>
{% endif %} {% endif %}
<h2>{{ 'config.reset.title'|trans }}</h2>
<fieldset class="w500p inline">
<p>{{ 'config.reset.description'|trans }}</p>
<ul>
<li>
<a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.annotations'|trans }}
</a>
</li>
<li>
<a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.tags'|trans }}
</a>
</li>
<li>
<a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.archived'|trans }}
</a>
</li>
<li>
<a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.entries'|trans }}
</a>
</li>
</ul>
</fieldset>
{{ form_widget(form.user._token) }} {{ form_widget(form.user._token) }}
{{ form_widget(form.user.save) }} {{ form_widget(form.user.save) }}
</form> </form>
@ -382,4 +375,31 @@
</table> </table>
</div> </div>
</div> </div>
<h2>{{ 'config.reset.title'|trans }}</h2>
<fieldset class="w500p inline">
<p>{{ 'config.reset.description'|trans }}</p>
<ul>
<li>
<a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.annotations'|trans }}
</a>
</li>
<li>
<a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.tags'|trans }}
</a>
</li>
<li>
<a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.archived'|trans }}
</a>
</li>
<li>
<a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
{{ 'config.reset.entries'|trans }}
</a>
</li>
</ul>
</fieldset>
{% endblock %} {% endblock %}

View file

@ -112,8 +112,7 @@
<img id="androidQrcode" class="hide-on-med-and-down" /> <img id="androidQrcode" class="hide-on-med-and-down" />
</div> </div>
<script> <script>
const imgBase64 = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}'); document.getElementById('androidQrcode').src = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}');;
document.getElementById('androidQrcode').src = imgBase64;
</script> </script>
</div> </div>
@ -199,21 +198,37 @@
{% if twofactor_auth %} {% if twofactor_auth %}
<div class="row"> <div class="row">
<div class="input-field col s11">
{{ 'config.form_user.two_factor_description'|trans }} {{ 'config.form_user.two_factor_description'|trans }}
<br /> <div class="input-field col s11">
{{ form_widget(form.user.emailTwoFactor) }}
{{ form_label(form.user.emailTwoFactor) }}
{{ form_errors(form.user.emailTwoFactor) }}
</div>
<div class="input-field col s11">
{{ form_widget(form.user.googleTwoFactor) }}
{{ form_label(form.user.googleTwoFactor) }}
{{ form_errors(form.user.googleTwoFactor) }}
</div>
</div>
{{ form_widget(form.user.twoFactorAuthentication) }} {% for OTPSecret in app.session.flashbag.get('OTPSecret') %}
{{ form_label(form.user.twoFactorAuthentication) }} <div class="card-panel yellow darken-1 black-text">
{{ form_errors(form.user.twoFactorAuthentication) }} You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password.
</div> <br/>
<div class="input-field col s1"> That code will disapear after a page reload.
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> <br/><br/>
<i class="material-icons">live_help</i> <strong>{{ OTPSecret.code }}</strong>
</a> <br/><br/>
</div> Or you can scan that QR Code with your app:
<br/>
<img id="2faQrcode" class="hide-on-med-and-down" />
<script>
document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ OTPSecret.qrCode }}');;
</script>
</div> </div>
{% endfor %}
{% endif %} {% endif %}
{{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}

View file

@ -8,6 +8,7 @@ use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException; use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Pagerfanta\Pagerfanta; use Pagerfanta\Pagerfanta;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
@ -31,10 +32,10 @@ class ManageController extends Controller
// enable created user by default // enable created user by default
$user->setEnabled(true); $user->setEnabled(true);
$form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user); $form = $this->createEditForm('NewUserType', $user, $request);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$user = $this->handleOtp($form, $user);
$userManager->updateUser($user); $userManager->updateUser($user);
// dispatch a created event so the associated config will be created // dispatch a created event so the associated config will be created
@ -62,14 +63,14 @@ class ManageController extends Controller
*/ */
public function editAction(Request $request, User $user) public function editAction(Request $request, User $user)
{ {
$deleteForm = $this->createDeleteForm($user); $userManager = $this->container->get('fos_user.user_manager');
$editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) { $deleteForm = $this->createDeleteForm($user);
$em = $this->getDoctrine()->getManager(); $form = $this->createEditForm('UserType', $user, $request);
$em->persist($user);
$em->flush(); if ($form->isSubmitted() && $form->isValid()) {
$user = $this->handleOtp($form, $user);
$userManager->updateUser($user);
$this->get('session')->getFlashBag()->add( $this->get('session')->getFlashBag()->add(
'notice', 'notice',
@ -81,7 +82,7 @@ class ManageController extends Controller
return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ return $this->render('WallabagUserBundle:Manage:edit.html.twig', [
'user' => $user, 'user' => $user,
'edit_form' => $editForm->createView(), 'edit_form' => $form->createView(),
'delete_form' => $deleteForm->createView(), 'delete_form' => $deleteForm->createView(),
'twofactor_auth' => $this->getParameter('twofactor_auth'), 'twofactor_auth' => $this->getParameter('twofactor_auth'),
]); ]);
@ -157,7 +158,7 @@ class ManageController extends Controller
} }
/** /**
* Creates a form to delete a User entity. * Create a form to delete a User entity.
* *
* @param User $user The User entity * @param User $user The User entity
* *
@ -171,4 +172,50 @@ class ManageController extends Controller
->getForm() ->getForm()
; ;
} }
/**
* Create a form to create or edit a User entity.
*
* @param string $type Might be NewUserType or UserType
* @param User $user The new / edit user
* @param Request $request The request
*
* @return FormInterface
*/
private function createEditForm($type, User $user, Request $request)
{
$form = $this->createForm('Wallabag\UserBundle\Form\\' . $type, $user);
$form->handleRequest($request);
// `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
if (true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) {
$form->get('googleTwoFactor')->setData(true);
}
return $form;
}
/**
* Handle OTP update, taking care to only have one 2fa enable at a time.
*
* @see ConfigController
*
* @param FormInterface $form
* @param User $user
*
* @return User
*/
private function handleOtp(FormInterface $form, User $user)
{
if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
$user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret());
$user->setEmailTwoFactor(false);
return $user;
}
$user->setGoogleAuthenticatorSecret(null);
return $user;
}
} }

View file

@ -8,8 +8,8 @@ use FOS\UserBundle\Model\User as BaseUser;
use JMS\Serializer\Annotation\Accessor; use JMS\Serializer\Annotation\Accessor;
use JMS\Serializer\Annotation\Groups; use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\XmlRoot; use JMS\Serializer\Annotation\XmlRoot;
use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface as EmailTwoFactorInterface;
use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Wallabag\ApiBundle\Entity\Client; use Wallabag\ApiBundle\Entity\Client;
@ -28,7 +28,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
* @UniqueEntity("email") * @UniqueEntity("email")
* @UniqueEntity("username") * @UniqueEntity("username")
*/ */
class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface
{ {
use EntityTimestampsTrait; use EntityTimestampsTrait;
@ -122,17 +122,17 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
*/ */
private $authCode; private $authCode;
/**
* @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true)
*/
private $googleAuthenticatorSecret;
/** /**
* @var bool * @var bool
* *
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $twoFactorAuthentication = false; private $emailTwoFactor = false;
/**
* @ORM\Column(type="json_array", nullable=true)
*/
private $trusted;
public function __construct() public function __construct()
{ {
@ -233,49 +233,89 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
/** /**
* @return bool * @return bool
*/ */
public function isTwoFactorAuthentication() public function isEmailTwoFactor()
{ {
return $this->twoFactorAuthentication; return $this->emailTwoFactor;
} }
/** /**
* @param bool $twoFactorAuthentication * @param bool $emailTwoFactor
*/ */
public function setTwoFactorAuthentication($twoFactorAuthentication) public function setEmailTwoFactor($emailTwoFactor)
{ {
$this->twoFactorAuthentication = $twoFactorAuthentication; $this->emailTwoFactor = $emailTwoFactor;
} }
public function isEmailAuthEnabled() /**
* Used in the user config form to be "like" the email option.
*/
public function isGoogleTwoFactor()
{ {
return $this->twoFactorAuthentication; return $this->isGoogleAuthenticatorEnabled();
} }
public function getEmailAuthCode() /**
* {@inheritdoc}
*/
public function isEmailAuthEnabled(): bool
{
return $this->emailTwoFactor;
}
/**
* {@inheritdoc}
*/
public function getEmailAuthCode(): string
{ {
return $this->authCode; return $this->authCode;
} }
public function setEmailAuthCode($authCode) /**
* {@inheritdoc}
*/
public function setEmailAuthCode(string $authCode): void
{ {
$this->authCode = $authCode; $this->authCode = $authCode;
} }
public function addTrustedComputer($token, \DateTime $validUntil) /**
* {@inheritdoc}
*/
public function getEmailAuthRecipient(): string
{ {
$this->trusted[$token] = $validUntil->format('r'); return $this->email;
} }
public function isTrustedComputer($token) /**
* {@inheritdoc}
*/
public function isGoogleAuthenticatorEnabled(): bool
{ {
if (isset($this->trusted[$token])) { return $this->googleAuthenticatorSecret ? true : false;
$now = new \DateTime();
$validUntil = new \DateTime($this->trusted[$token]);
return $now < $validUntil;
} }
return false; /**
* {@inheritdoc}
*/
public function getGoogleAuthenticatorUsername(): string
{
return $this->username;
}
/**
* {@inheritdoc}
*/
public function getGoogleAuthenticatorSecret(): string
{
return $this->googleAuthenticatorSecret;
}
/**
* {@inheritdoc}
*/
public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void
{
$this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
} }
/** /**

View file

@ -35,9 +35,14 @@ class UserType extends AbstractType
'required' => false, 'required' => false,
'label' => 'user.form.enabled_label', 'label' => 'user.form.enabled_label',
]) ])
->add('twoFactorAuthentication', CheckboxType::class, [ ->add('emailTwoFactor', CheckboxType::class, [
'required' => false, 'required' => false,
'label' => 'user.form.twofactor_label', 'label' => 'user.form.twofactor_email_label',
])
->add('googleTwoFactor', CheckboxType::class, [
'required' => false,
'label' => 'user.form.twofactor_google_label',
'mapped' => false,
]) ])
->add('save', SubmitType::class, [ ->add('save', SubmitType::class, [
'label' => 'user.form.save', 'label' => 'user.form.save',

View file

@ -78,7 +78,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
* *
* @param TwoFactorInterface $user * @param TwoFactorInterface $user
*/ */
public function sendAuthCode(TwoFactorInterface $user) public function sendAuthCode(TwoFactorInterface $user): void
{ {
$template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig');

View file

@ -1,7 +1,8 @@
{# Override `vendor/scheb/two-factor-bundle/Resources/views/Authentication/form.html.twig` #}
{% extends "WallabagUserBundle::layout.html.twig" %} {% extends "WallabagUserBundle::layout.html.twig" %}
{% block fos_user_content %} {% block fos_user_content %}
<form class="form" action="" method="post"> <form class="form" action="{{ path("2fa_login_check") }}" method="post">
<div class="card-content"> <div class="card-content">
<div class="row"> <div class="row">
@ -9,14 +10,19 @@
<p class="error">{{ flashMessage|trans }}</p> <p class="error">{{ flashMessage|trans }}</p>
{% endfor %} {% endfor %}
{# Authentication errors #}
{% if authenticationError %}
<p class="error">{{ authenticationError|trans(authenticationErrorData) }}</p>
{% endif %}
<div class="input-field col s12"> <div class="input-field col s12">
<label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label> <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
<input id="_auth_code" type="text" autocomplete="off" name="_auth_code" /> <input id="_auth_code" type="text" autocomplete="off" name="{{ authCodeParameterName }}" />
</div> </div>
{% if useTrustedOption %} {% if displayTrustedOption %}
<div class="input-field col s12"> <div class="input-field col s12">
<input id="_trusted" type="checkbox" name="_trusted" /> <input id="_trusted" type="checkbox" name="{{ trustedParameterName }}" />
<label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label> <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label>
</div> </div>
{% endif %} {% endif %}

View file

@ -50,10 +50,21 @@
{% if twofactor_auth %} {% if twofactor_auth %}
<div class="row"> <div class="row">
<div class="input-field col s12"> <div class="input-field col s12">
{{ form_widget(edit_form.twoFactorAuthentication) }} {{ form_widget(edit_form.emailTwoFactor) }}
{{ form_label(edit_form.twoFactorAuthentication) }} {{ form_label(edit_form.emailTwoFactor) }}
{{ form_errors(edit_form.twoFactorAuthentication) }} {{ form_errors(edit_form.emailTwoFactor) }}
</div> </div>
<div class="input-field col s12">
{{ form_widget(edit_form.googleTwoFactor) }}
{{ form_label(edit_form.googleTwoFactor) }}
{{ form_errors(edit_form.googleTwoFactor) }}
</div>
{% if user.isGoogleAuthenticatorEnabled %}
<div class="input-field col s12">
<p><strong>OTP Secret</strong>: {{ user.googleAuthenticatorSecret }}</p>
</div>
{% endif %}
</div> </div>
{% endif %} {% endif %}

View file

@ -59,7 +59,8 @@ class ShowUserCommandTest extends WallabagCoreTestCase
$this->assertContains('Username: admin', $tester->getDisplay()); $this->assertContains('Username: admin', $tester->getDisplay());
$this->assertContains('Email: bigboss@wallabag.org', $tester->getDisplay()); $this->assertContains('Email: bigboss@wallabag.org', $tester->getDisplay());
$this->assertContains('Display name: Big boss', $tester->getDisplay()); $this->assertContains('Display name: Big boss', $tester->getDisplay());
$this->assertContains('2FA activated: no', $tester->getDisplay()); $this->assertContains('2FA (email) activated', $tester->getDisplay());
$this->assertContains('2FA (OTP) activated', $tester->getDisplay());
} }
public function testShowUser() public function testShowUser()

View file

@ -297,6 +297,119 @@ class ConfigControllerTest extends WallabagCoreTestCase
$this->assertContains('flashes.config.notice.user_updated', $alert[0]); $this->assertContains('flashes.config.notice.user_updated', $alert[0]);
} }
public function testUserEnable2faEmail()
{
$this->logInAs('admin');
$client = $this->getClient();
$crawler = $client->request('GET', '/config');
$this->assertSame(200, $client->getResponse()->getStatusCode());
$form = $crawler->filter('button[id=update_user_save]')->form();
$data = [
'update_user[emailTwoFactor]' => '1',
];
$client->submit($form, $data);
$this->assertSame(302, $client->getResponse()->getStatusCode());
$crawler = $client->followRedirect();
$this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
$this->assertContains('flashes.config.notice.user_updated', $alert[0]);
// restore user
$em = $this->getEntityManager();
$user = $em
->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin');
$this->assertTrue($user->isEmailTwoFactor());
$user->setEmailTwoFactor(false);
$em->persist($user);
$em->flush();
}
public function testUserEnable2faGoogle()
{
$this->logInAs('admin');
$client = $this->getClient();
$crawler = $client->request('GET', '/config');
$this->assertSame(200, $client->getResponse()->getStatusCode());
$form = $crawler->filter('button[id=update_user_save]')->form();
$data = [
'update_user[googleTwoFactor]' => '1',
];
$client->submit($form, $data);
$this->assertSame(302, $client->getResponse()->getStatusCode());
$crawler = $client->followRedirect();
$this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
$this->assertContains('flashes.config.notice.user_updated', $alert[0]);
// restore user
$em = $this->getEntityManager();
$user = $em
->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin');
$this->assertTrue($user->isGoogleAuthenticatorEnabled());
$user->setGoogleAuthenticatorSecret(null);
$em->persist($user);
$em->flush();
}
public function testUserEnable2faBoth()
{
$this->logInAs('admin');
$client = $this->getClient();
$crawler = $client->request('GET', '/config');
$this->assertSame(200, $client->getResponse()->getStatusCode());
$form = $crawler->filter('button[id=update_user_save]')->form();
$data = [
'update_user[googleTwoFactor]' => '1',
'update_user[emailTwoFactor]' => '1',
];
$client->submit($form, $data);
$this->assertSame(302, $client->getResponse()->getStatusCode());
$crawler = $client->followRedirect();
$this->assertGreaterThan(1, $alert = $crawler->filter('body')->extract(['_text']));
$this->assertContains('flashes.config.notice.user_updated', $alert[0]);
// restore user
$em = $this->getEntityManager();
$user = $em
->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin');
$this->assertTrue($user->isGoogleAuthenticatorEnabled());
$this->assertFalse($user->isEmailTwoFactor());
$user->setGoogleAuthenticatorSecret(null);
$em->persist($user);
$em->flush();
}
public function testRssUpdateResetToken() public function testRssUpdateResetToken()
{ {
$this->logInAs('admin'); $this->logInAs('admin');

View file

@ -26,7 +26,7 @@ class SecurityControllerTest extends WallabagCoreTestCase
$this->assertContains('config.form_rss.description', $crawler->filter('body')->extract(['_text'])[0]); $this->assertContains('config.form_rss.description', $crawler->filter('body')->extract(['_text'])[0]);
} }
public function testLoginWith2Factor() public function testLoginWith2FactorEmail()
{ {
$client = $this->getClient(); $client = $this->getClient();
@ -42,7 +42,7 @@ class SecurityControllerTest extends WallabagCoreTestCase
$user = $em $user = $em
->getRepository('WallabagUserBundle:User') ->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin'); ->findOneByUsername('admin');
$user->setTwoFactorAuthentication(true); $user->setEmailTwoFactor(true);
$em->persist($user); $em->persist($user);
$em->flush(); $em->flush();
@ -54,12 +54,12 @@ class SecurityControllerTest extends WallabagCoreTestCase
$user = $em $user = $em
->getRepository('WallabagUserBundle:User') ->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin'); ->findOneByUsername('admin');
$user->setTwoFactorAuthentication(false); $user->setEmailTwoFactor(false);
$em->persist($user); $em->persist($user);
$em->flush(); $em->flush();
} }
public function testTrustedComputer() public function testLoginWith2FactorGoogle()
{ {
$client = $this->getClient(); $client = $this->getClient();
@ -69,15 +69,27 @@ class SecurityControllerTest extends WallabagCoreTestCase
return; return;
} }
$client->followRedirects();
$em = $client->getContainer()->get('doctrine.orm.entity_manager'); $em = $client->getContainer()->get('doctrine.orm.entity_manager');
$user = $em $user = $em
->getRepository('WallabagUserBundle:User') ->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin'); ->findOneByUsername('admin');
$user->setGoogleAuthenticatorSecret('26LDIHYGHNELOQEM');
$em->persist($user);
$em->flush();
$date = new \DateTime(); $this->logInAsUsingHttp('admin');
$user->addTrustedComputer('ABCDEF', $date->add(new \DateInterval('P1M'))); $crawler = $client->request('GET', '/config');
$this->assertTrue($user->isTrustedComputer('ABCDEF')); $this->assertContains('scheb_two_factor.trusted', $crawler->filter('body')->extract(['_text'])[0]);
$this->assertFalse($user->isTrustedComputer('FEDCBA'));
// restore user
$user = $em
->getRepository('WallabagUserBundle:User')
->findOneByUsername('admin');
$user->setGoogleAuthenticatorSecret(null);
$em->persist($user);
$em->flush();
} }
public function testEnabledRegistration() public function testEnabledRegistration()

View file

@ -33,7 +33,7 @@ TWIG;
public function testSendEmail() public function testSendEmail()
{ {
$user = new User(); $user = new User();
$user->setTwoFactorAuthentication(true); $user->setEmailTwoFactor(true);
$user->setEmailAuthCode(666666); $user->setEmailAuthCode(666666);
$user->setEmail('test@wallabag.io'); $user->setEmail('test@wallabag.io');
$user->setName('Bob'); $user->setName('Bob');