[% END %]
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/two_factor_auth.tt
+++ a/koha-tmpl/intranet-tmpl/prog/en/modules/members/two_factor_auth.tt
@@ -0,0 +1,115 @@
+[% USE raw %]
+[% USE Koha %]
+[% USE Asset %]
+[% SET footerjs = 1 %]
+[% INCLUDE 'doc-head-open.inc' %]
+Two-Factor Authentication › Patrons › Koha
+[% INCLUDE 'doc-head-close.inc' %]
+
+
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'patron-search.inc' %]
+
+
+
+
+
+
+
+
+ [% INCLUDE 'members-toolbar.inc' %]
+
+ [% IF op == 'register' %]
+
Register Two-Factor Authenticator
+
+
We recommend cloud-based mobile authenticator apps such as Authy, Duo Mobile, and LastPass. They can restore access if you lose your hardware device.
+
Can't scan the code?
+
To add the entry manually, provide the following details to the application on your phone.
+
Account: [% issuer %]
+
Key: [% key_id %]
+
Time based: Yes
+
+
+ [% IF invalid_pin %]
+
Invalid pin code
+ [% END %]
+
+ [% ELSE %]
+
Two-Factor Authentication
+ [% IF patron.auth_method == "two-factor" %]
+
Status: Enabled
+
+
+ [% ELSE %]
+
Status: Disabled
+
+
+
+ [% END %]
+ [% END %]
+
+
+
+
+
+
+
+
+
+[% MACRO jsinclude BLOCK %]
+ [% INCLUDE 'str/members-menu.inc' %]
+ [% Asset.js("js/members-menu.js") | $raw %]
+
+[% END %]
+
+[% INCLUDE 'intranet-bottom.inc' %]
--- a/members/two_factor_auth.pl
+++ a/members/two_factor_auth.pl
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see .
+
+use Modern::Perl;
+
+use CGI;
+
+use C4::Auth qw( get_template_and_user );
+use C4::Output qw( output_and_exit output_html_with_http_headers );
+
+use Koha::Patrons;
+use Koha::Auth::TwoFactorAuth;
+
+my $cgi = CGI->new;
+
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+ {
+ template_name => 'members/two_factor_auth.tt',
+ query => $cgi,
+ type => 'intranet',
+ flagsrequired => { editcatalogue => '*' },
+ }
+);
+
+unless ( C4::Context->preference('TwoFactorAuthentication') ) {
+ print $cgi->redirect("/cgi-bin/koha/errors/404.pl");
+ exit;
+}
+
+my $logged_in_user = Koha::Patrons->find($loggedinuser);
+
+my $op = $cgi->param('op') // '';
+
+if ( $op eq 'register-2FA' ) {
+ my $pin_code = $cgi->param('pin_code');
+ my $secret32 = $cgi->param('secret32');
+ my $auth = Koha::Auth::TwoFactorAuth::get_auth(
+ { patron => $logged_in_user, secret32 => $secret32 } );
+
+ my $verified = $auth->verify(
+ $pin_code,
+ 1, # range
+ $secret32,
+ undef, # timestamp (defaults to now)
+ 30, # interval (default 30)
+ );
+
+ if ($verified) {
+ $logged_in_user->secret($secret32);
+ $op = 'registered';
+
+ # FIXME Generate a (new?) secret
+ $logged_in_user->auth_method('two-factor')->store;
+ }
+ else {
+ $template->param( invalid_pin => 1, );
+ $op = 'enable-2FA';
+ }
+}
+
+if ( $op eq 'enable-2FA' ) {
+
+ my $secret = Koha::AuthUtils::generate_salt( 'weak', 16 );
+ my $auth = Koha::Auth::TwoFactorAuth::get_auth(
+ { patron => $logged_in_user, secret => $secret } );
+
+ my $secret32 = $auth->generate_secret32;
+ my $qr_code_url =
+ $auth->qr_code( $secret32, $auth->key_id, $auth->issuer, );
+
+ $template->param(
+ issuer => $auth->issuer,
+ key_id => $auth->key_id,
+ secret32 => $secret32,
+ qr_code_url => $qr_code_url,
+ );
+ $auth->clear;
+ $op = 'register';
+}
+elsif ( $op eq 'disable-2FA' ) {
+ $logged_in_user->auth_method('password')->store;
+}
+
+$template->param(
+ csrf_token => Koha::Token->new->generate_csrf(
+ { session_id => scalar $cgi->cookie('CGISESSID') }
+ ),
+ patron => $logged_in_user,
+ op => $op,
+);
+
+output_html_with_http_headers $cgi, $cookie, $template->output;
--