View | Details | Raw Unified | Return to bug 30588
Collapse All | Expand All

(-)a/C4/Auth.pm (-3 / +14 lines)
Lines 1270-1278 sub checkauth { Link Here
1270
        # Auth is completed unless an additional auth is needed
1270
        # Auth is completed unless an additional auth is needed
1271
        if ( $require_2FA ) {
1271
        if ( $require_2FA ) {
1272
            my $patron = Koha::Patrons->find({userid => $userid});
1272
            my $patron = Koha::Patrons->find({userid => $userid});
1273
            if ( C4::Context->preference('TwoFactorAuthentication') eq "enforced"
1273
            if ( C4::Context->preference('TwoFactorAuthentication') eq "enforced" && $patron->auth_method eq 'password' ) {
1274
                || $patron->auth_method eq 'two-factor' )
1274
                $auth_state = 'setup-additional-auth-needed';
1275
            {
1275
                $session->param('waiting-for-2FA-setup', 1);
1276
                %info = ();# We remove the warnings/errors we may have set incorrectly before
1277
            } elsif ( $patron->auth_method eq 'two-factor' ) {
1276
                # Ask for the OTP token
1278
                # Ask for the OTP token
1277
                $auth_state = 'additional-auth-needed';
1279
                $auth_state = 'additional-auth-needed';
1278
                $session->param('waiting-for-2FA', 1);
1280
                $session->param('waiting-for-2FA', 1);
Lines 1385-1390 sub checkauth { Link Here
1385
        );
1387
        );
1386
    }
1388
    }
1387
1389
1390
    if ( $auth_state eq 'setup-additional-auth-needed' ) {
1391
        $template->param(
1392
            TwoFA_setup => 1,
1393
        );
1394
    }
1395
1388
    if ( $type eq 'opac' ) {
1396
    if ( $type eq 'opac' ) {
1389
        require Koha::Virtualshelves;
1397
        require Koha::Virtualshelves;
1390
        my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
1398
        my $some_public_shelves = Koha::Virtualshelves->get_some_shelves(
Lines 1778-1783 sub check_cookie_auth { Link Here
1778
                return ( "additional-auth-needed", $session )
1786
                return ( "additional-auth-needed", $session )
1779
                    if $session->param('waiting-for-2FA');
1787
                    if $session->param('waiting-for-2FA');
1780
1788
1789
                return ( "setup-additional-auth-needed", $session )
1790
                    if $session->param('waiting-for-2FA-setup');
1791
1781
                return ( "ok", $session );
1792
                return ( "ok", $session );
1782
            } else {
1793
            } else {
1783
                $session->delete();
1794
                $session->delete();
(-)a/Koha/Auth/TwoFactorAuth.pm (+2 lines)
Lines 58-63 sub new { Link Here
58
    my $secret32 = $params->{secret32};
58
    my $secret32 = $params->{secret32};
59
    my $secret = $params->{secret};
59
    my $secret = $params->{secret};
60
60
61
    # FIXME Raise an exception if the syspref is disabled
62
61
    Koha::Exceptions::MissingParameter->throw("Mandatory patron parameter missing")
63
    Koha::Exceptions::MissingParameter->throw("Mandatory patron parameter missing")
62
        unless $patron && ref($patron) eq 'Koha::Patron';
64
        unless $patron && ref($patron) eq 'Koha::Patron';
63
65
(-)a/Koha/REST/V1/Auth.pm (+22 lines)
Lines 236-241 sub authenticate_api_request { Link Here
236
                Koha::Exceptions::Authentication::Required->throw(
236
                Koha::Exceptions::Authentication::Required->throw(
237
                    error => 'Authentication failure.' );
237
                    error => 'Authentication failure.' );
238
            }
238
            }
239
        }
240
        elsif (  $c->req->url->to_abs->path eq '/api/v1/auth/two-factor/registration'
241
              || $c->req->url->to_abs->path eq '/api/v1/auth/two-factor/registration/verification' ) {
242
243
            if ( $status eq 'setup-additional-auth-needed' ) {
244
                $user        = Koha::Patrons->find( $session->param('number') );
245
                $cookie_auth = 1;
246
            }
247
            elsif ( $status eq 'ok' ) {
248
                $user = Koha::Patrons->find( $session->param('number') );
249
                if ( $user->auth_method ne 'password' ) {
250
                    # If the user already enabled 2FA they don't need to register again
251
                    Koha::Exceptions::Authentication->throw(
252
                        error => 'Cannot request this route.' );
253
                }
254
                $cookie_auth = 1;
255
            }
256
            else {
257
                Koha::Exceptions::Authentication::Required->throw(
258
                    error => 'Authentication failure.' );
259
            }
260
239
        } else {
261
        } else {
240
            if ($status eq "ok") {
262
            if ($status eq "ok") {
241
                $user = Koha::Patrons->find( $session->param('number') );
263
                $user = Koha::Patrons->find( $session->param('number') );
(-)a/Koha/REST/V1/TwoFactorAuth.pm (+103 lines)
Lines 78-81 sub send_otp_token { Link Here
78
78
79
}
79
}
80
80
81
=head3 registration
82
83
Ask for a registration secret. It will return a QR code image and a secret32.
84
85
The secret must be sent back to the server with the pin code for the verification step.
86
87
=cut
88
89
sub registration {
90
91
    my $c = shift->openapi->valid_input or return;
92
93
    my $patron = Koha::Patrons->find( $c->stash('koha.user')->borrowernumber );
94
95
    return try {
96
        my $secret = Koha::AuthUtils::generate_salt( 'weak', 16 );
97
        my $auth   = Koha::Auth::TwoFactorAuth->new(
98
            { patron => $patron, secret => $secret } );
99
100
        my $response = {
101
            issuer   => $auth->issuer,
102
            key_id   => $auth->key_id,
103
            qr_code  => $auth->qr_code,
104
            secret32 => $auth->secret32,
105
106
            # IMPORTANT: get secret32 after qr_code call !
107
        };
108
        $auth->clear;
109
110
        return $c->render(status => 201, openapi => $response);
111
    }
112
    catch {
113
        $c->unhandled_exception($_);
114
    };
115
116
}
117
118
=head3 verification
119
120
Verify the registration, get the pin code and the secret retrieved from the registration.
121
122
The 2FA_ENABLE notice will be generated if the pin code is correct, and the patron will have their two-factor authentication setup completed.
123
124
=cut
125
126
sub verification {
127
128
    my $c = shift->openapi->valid_input or return;
129
130
    my $patron = Koha::Patrons->find( $c->stash('koha.user')->borrowernumber );
131
132
    return try {
133
134
        my $pin_code = $c->validation->param('pin_code');
135
        my $secret32 = $c->validation->param('secret32');
136
137
        my $auth     = Koha::Auth::TwoFactorAuth->new(
138
            { patron => $patron, secret32 => $secret32 } );
139
140
        my $verified = $auth->verify(
141
            $pin_code,
142
            1,        # range
143
            $secret32,
144
            undef,    # timestamp (defaults to now)
145
            30,       # interval (default 30)
146
        );
147
148
        unless ($verified) {
149
            return $c->render(
150
                status  => 400,
151
                openapi => { error => "Invalid pin" }
152
            );
153
        }
154
155
        # FIXME Generate a (new?) secret
156
        $patron->encode_secret($secret32);
157
        $patron->auth_method('two-factor')->store;
158
        if ( $patron->notice_email_address ) {
159
            $patron->queue_notice(
160
                {
161
                    letter_params => {
162
                        module      => 'members',
163
                        letter_code => '2FA_ENABLE',
164
                        branchcode  => $patron->branchcode,
165
                        lang        => $patron->lang,
166
                        tables      => {
167
                            branches  => $patron->branchcode,
168
                            borrowers => $patron->id
169
                        },
170
                    },
171
                    message_transports => ['email'],
172
                }
173
            );
174
        }
175
176
        return $c->render(status => 204, openapi => {});
177
    }
178
    catch {
179
        $c->unhandled_exception($_);
180
    };
181
182
}
183
81
1;
184
1;
(-)a/api/v1/swagger/paths/auth.yaml (+89 lines)
Lines 39-41 Link Here
39
    x-koha-authorization:
39
    x-koha-authorization:
40
      permissions:
40
      permissions:
41
        catalogue: "1"
41
        catalogue: "1"
42
43
/auth/two-factor/registration:
44
  post:
45
    x-mojo-to: TwoFactorAuth#registration
46
    operationId: Two factor register
47
    tags:
48
      - 2fa
49
    summary: Generate a secret
50
    produces:
51
      - application/json
52
    responses:
53
      "201":
54
        description: OK
55
        schema:
56
          type: object
57
          properties:
58
            secret32:
59
              type: string
60
            qr_code:
61
              type: string
62
            issuer:
63
              type: string
64
            key_id:
65
              type: string
66
          additionalProperties: false
67
      "400":
68
        description: Bad Request
69
        schema:
70
          $ref: "../swagger.yaml#/definitions/error"
71
      "403":
72
        description: Access forbidden
73
        schema:
74
          $ref: "../swagger.yaml#/definitions/error"
75
      "500":
76
        description: |
77
          Internal server error. Possible `error_code` attribute values:
78
79
          * `internal_server_error`
80
        schema:
81
          $ref: "../swagger.yaml#/definitions/error"
82
    x-koha-authorization:
83
      permissions:
84
        catalogue: "1"
85
86
/auth/two-factor/registration/verification:
87
  post:
88
    x-mojo-to: TwoFactorAuth#verification
89
    operationId: Two factor register verification
90
    tags:
91
      - 2fa
92
    summary: Verify two-factor registration
93
    parameters:
94
      - name: secret32
95
        in: formData
96
        description: the secret
97
        required: true
98
        type: string
99
      - name: pin_code
100
        in: formData
101
        description: the pin code
102
        required: true
103
        type: string
104
    produces:
105
      - application/json
106
    responses:
107
      "204":
108
        description: OK
109
      "401":
110
        description: Authentication required
111
        schema:
112
          $ref: "../swagger.yaml#/definitions/error"
113
      "400":
114
        description: Bad Request
115
        schema:
116
          $ref: "../swagger.yaml#/definitions/error"
117
      "403":
118
        description: Access forbidden
119
        schema:
120
          $ref: "../swagger.yaml#/definitions/error"
121
      "500":
122
        description: |
123
          Internal server error. Possible `error_code` attribute values:
124
125
          * `internal_server_error`
126
        schema:
127
          $ref: "../swagger.yaml#/definitions/error"
128
    x-koha-authorization:
129
      permissions:
130
        catalogue: "1"
(-)a/api/v1/swagger/swagger.yaml (+4 lines)
Lines 105-110 paths: Link Here
105
    $ref: "./paths/article_requests.yaml#/~1article_requests~1{article_request_id}"
105
    $ref: "./paths/article_requests.yaml#/~1article_requests~1{article_request_id}"
106
  /auth/otp/token_delivery:
106
  /auth/otp/token_delivery:
107
    $ref: paths/auth.yaml#/~1auth~1otp~1token_delivery
107
    $ref: paths/auth.yaml#/~1auth~1otp~1token_delivery
108
  /auth/two-factor/registration:
109
    $ref: paths/auth.yaml#/~1auth~1two-factor~1registration
110
  /auth/two-factor/registration/verification:
111
    $ref: paths/auth.yaml#/~1auth~1two-factor~1registration~1verification
108
  "/biblios/{biblio_id}":
112
  "/biblios/{biblio_id}":
109
    $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}"
113
    $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}"
110
  "/biblios/{biblio_id}/checkouts":
114
  "/biblios/{biblio_id}/checkouts":
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt (-1 / +50 lines)
Lines 17-22 Link Here
17
    [% IF ( nopermission ) %]Access denied[% END %] › Koha
17
    [% IF ( nopermission ) %]Access denied[% END %] › Koha
18
</title>
18
</title>
19
[% INCLUDE 'doc-head-close.inc' %]
19
[% INCLUDE 'doc-head-close.inc' %]
20
[% PROCESS 'auth-two-factor.inc' %]
20
</head>
21
</head>
21
<body id="main_auth" class="main_main-auth">
22
<body id="main_auth" class="main_main-auth">
22
23
Lines 71-77 Link Here
71
<p><a href="[% shibbolethLoginUrl | $raw %]">Log in using a Shibboleth account</a>.</p>
72
<p><a href="[% shibbolethLoginUrl | $raw %]">Log in using a Shibboleth account</a>.</p>
72
[% END %]
73
[% END %]
73
74
74
[% IF !TwoFA_prompt && !Koha.Preference('staffShibOnly') %]
75
[% IF !TwoFA_prompt && !TwoFA_setup && !Koha.Preference('staffShibOnly') %]
75
    <!-- login prompt time-->
76
    <!-- login prompt time-->
76
    <form action="[% script_name | html %]" method="post" name="loginform" id="loginform">
77
    <form action="[% script_name | html %]" method="post" name="loginform" id="loginform">
77
        <input type="hidden" name="koha_login_context" value="intranet" />
78
        <input type="hidden" name="koha_login_context" value="intranet" />
Lines 168-173 Link Here
168
        </p>
169
        </p>
169
170
170
    </form>
171
    </form>
172
[% ELSIF TwoFA_setup %]
173
    [% PROCESS registration_form %]
171
[% END %]
174
[% END %]
172
175
173
[% IF ( nopermission ) %]
176
[% IF ( nopermission ) %]
Lines 216-221 Link Here
216
                    });
219
                    });
217
                [% END %]
220
                [% END %]
218
            });
221
            });
222
223
            if( $("#registration-form").length ) {
224
                $.ajax({
225
                    data: {},
226
                    type: 'POST',
227
                    url: '/api/v1/auth/two-factor/registration',
228
                    success: function (data) {
229
                        $("#qr_code").attr('src', data.qr_code);
230
                        $("#secret32").val(data.secret32);
231
                        $("#issuer").html(data.issuer);
232
                        $("#key_id").html(data.key_id);
233
                        $("#registration-form").show();
234
                    },
235
                    error: function (data) {
236
                        alert(data);
237
                    },
238
                });
239
            };
240
241
            $("#register-2FA").on("click", function(e){
242
                e.preventDefault();
243
                const data = {
244
                    secret32: $("#secret32").val(),
245
                    pin_code: $("#pin_code").val(),
246
                };
247
                if (!data.pin_code) return;
248
249
                $.ajax({
250
                    data: data,
251
                    type: 'POST',
252
                    url: '/api/v1/auth/two-factor/registration/verification',
253
                    success: function (data) {
254
                        alert(_("Two-factor authentication correctly configured. You will be redirected to the login screen."));
255
                        window.location = "/cgi-bin/koha/mainpage.pl";
256
                    },
257
                    error: function (data) {
258
                        const error = data.responseJSON.error;
259
                        if ( error == 'Invalid pin' ) {
260
                            $("#errors").html(_("Invalid PIN code")).show();
261
                        } else {
262
                            alert(error);
263
                        }
264
                    },
265
                });
266
            });
267
219
        });
268
        });
220
    </script>
269
    </script>
221
[% END %]
270
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/two_factor_auth.tt (-45 / +64 lines)
Lines 10-15 Link Here
10
[% WRAPPER 'header.inc' %]
10
[% WRAPPER 'header.inc' %]
11
    [% INCLUDE 'patron-search-header.inc' %]
11
    [% INCLUDE 'patron-search-header.inc' %]
12
[% END %]
12
[% END %]
13
[% PROCESS 'auth-two-factor.inc' %]
13
14
14
[% WRAPPER 'sub-header.inc' %]
15
[% WRAPPER 'sub-header.inc' %]
15
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
16
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
Lines 45-87 Link Here
45
                        </p>
46
                        </p>
46
                    </div>
47
                    </div>
47
                [% ELSE %]
48
                [% ELSE %]
49
                    [% PROCESS registration_form %]
48
50
49
                    [% IF op == 'register' %]
51
                    <div id="registration-status">
50
                        <div class="dialog message">
51
                            <p>We recommend cloud-based mobile authenticator apps such as Authy, Duo Mobile, and LastPass. They can restore access if you lose your hardware device.</p>
52
                            <p>Can't scan the code?</p>
53
                            <p>To add the entry manually, provide the following details to the application on your phone.</p>
54
                            <p>Account: [% issuer | html %]</p>
55
                            <p>Key: [% key_id | html %]</p>
56
                            <p>Time based: Yes</p>
57
                        </div>
58
59
                        [% IF invalid_pin %]
60
                            <div class="dialog error">Invalid PIN code</div>
61
                        [% END %]
62
                        <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
63
                            <fieldset class="rows">
64
                                <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" />
65
                                <input type="hidden" name="op" value="register-2FA" />
66
                                <input type="hidden" name="secret32" value="[% secret32 | html %]" />
67
                                <ol>
68
                                    <li>
69
                                        <label for="qr_code">QR code: </label>
70
                                        <img id="qr_code" src="[% qr_code | $raw %]" />
71
                                    </li>
72
                                    <li>
73
                                        <label for="pin_code">PIN code: </label>
74
                                        <input type="text" id="pin_code" name="pin_code" value="" />
75
                                    </li>
76
                                </ol>
77
                            </fieldset>
78
                            <fieldset class="action">
79
                                <input type="submit" value="Register with two-factor app" />
80
                                <a class="cancel" href="/cgi-bin/koha/members/two_factor_auth.pl">Cancel</a>
81
                            </fieldset>
82
                        </form>
83
                    [% ELSE %]
84
                        [% IF patron.auth_method == "two-factor" %]
52
                        [% IF patron.auth_method == "two-factor" %]
53
                        <div id="registration-status-enabled">
54
                        [% ELSE %]
55
                        <div id="registration-status-enabled" style="display: none;">
56
                        [% END %]
85
                            <div class="two-factor-status">Status: Enabled</div>
57
                            <div class="two-factor-status">Status: Enabled</div>
86
58
87
                            <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
59
                            <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
Lines 89-105 Link Here
89
                                <input type="hidden" name="op" value="disable-2FA" />
61
                                <input type="hidden" name="op" value="disable-2FA" />
90
                                <input type="submit" value="Disable two-factor authentication" />
62
                                <input type="submit" value="Disable two-factor authentication" />
91
                            </form>
63
                            </form>
64
                        </div>
65
66
                        [% IF patron.auth_method == "password" %]
67
                        <div id="registration-status-disabled">
92
                        [% ELSE %]
68
                        [% ELSE %]
69
                        <div id="registration-status-disabled" style="display: none;">
70
                        [% END %]
93
                            <div class="two-factor-status">Status: Disabled</div>
71
                            <div class="two-factor-status">Status: Disabled</div>
72
                            [% IF Koha.Preference('TwoFactorAuthentication') == 'enforced' %]
73
                                <div>Two-factor authentication is mandatory to login. If you do not enable now it will be asked at your next login.</div>
74
                            [% END %]
94
75
95
                            <form id="two-factor-auth" action="/cgi-bin/koha/members/two_factor_auth.pl" method="post">
76
                            <input id="enable-2FA" type="submit" value="Enable two-factor authentication" />
96
                                <input type="hidden" name="csrf_token" value="[% csrf_token | html %]" />
77
                        </div>
97
                                <input type="hidden" name="op" value="enable-2FA" />
78
                    </div>
98
                                <input type="submit" value="Enable two-factor authentication" />
99
                            </form>
100
101
                        [% END %]
102
                    [% END %]
103
                [% END %]
79
                [% END %]
104
            </main>
80
            </main>
105
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
81
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
Lines 117-125 Link Here
117
    [% Asset.js("js/members-menu.js") | $raw %]
93
    [% Asset.js("js/members-menu.js") | $raw %]
118
    <script>
94
    <script>
119
        $(document).ready(function(){
95
        $(document).ready(function(){
120
            $(".delete").on("click", function(e){
96
            $("#enable-2FA").on("click", function(e){
121
                return confirmDelete(_("Are you sure you want to delete this key?"));
97
                e.preventDefault();
98
                $.ajax({
99
                    data: {},
100
                    type: 'POST',
101
                    url: '/api/v1/auth/two-factor/registration',
102
                    success: function (data) {
103
                        $("#qr_code").attr('src', data.qr_code);
104
                        $("#secret32").val(data.secret32);
105
                        $("#issuer").html(data.issuer);
106
                        $("#key_id").html(data.key_id);
107
                        $("#registration-form").show();
108
                        $("#registration-status").hide();
109
                    },
110
                    error: function (data) {
111
                        alert(data);
112
                    },
113
                });
122
            });
114
            });
115
116
            $("#register-2FA").on("click", function(e){
117
                e.preventDefault();
118
                const data = {
119
                    secret32: $("#secret32").val(),
120
                    pin_code: $("#pin_code").val(),
121
                };
122
                if (!data.pin_code) return;
123
124
                $.ajax({
125
                    data: data,
126
                    type: 'POST',
127
                    url: '/api/v1/auth/two-factor/registration/verification',
128
                    success: function (data) {
129
                        window.location = "/cgi-bin/koha/members/two_factor_auth.pl";
130
                    },
131
                    error: function (data) {
132
                        const error = data.responseJSON.error;
133
                        if ( error == 'Invalid pin' ) {
134
                            $("#errors").html(_("Invalid PIN code")).show();
135
                        } else {
136
                            alert(error);
137
                        }
138
                    },
139
                });
140
            });
141
123
        });
142
        });
124
    </script>
143
    </script>
125
[% END %]
144
[% END %]
(-)a/members/two_factor_auth.pl (-65 / +1 lines)
Lines 56-125 else { Link Here
56
        token      => scalar $cgi->param('csrf_token'),
56
        token      => scalar $cgi->param('csrf_token'),
57
    };
57
    };
58
58
59
    if ( $op eq 'register-2FA' ) {
59
    if ( $op eq 'disable-2FA' ) {
60
        output_and_exit( $cgi, $cookie, $template, 'wrong_csrf_token' )
61
          unless Koha::Token->new->check_csrf($csrf_pars);
62
63
        my $pin_code = $cgi->param('pin_code');
64
        my $secret32 = $cgi->param('secret32');
65
        my $auth     = Koha::Auth::TwoFactorAuth->new(
66
            { patron => $logged_in_user, secret32 => $secret32 } );
67
68
        my $verified = $auth->verify(
69
            $pin_code,
70
            1,        # range
71
            $secret32,
72
            undef,    # timestamp (defaults to now)
73
            30,       # interval (default 30)
74
        );
75
76
        if ($verified) {
77
78
            # FIXME Generate a (new?) secret
79
            $logged_in_user->encode_secret($secret32);
80
            $logged_in_user->auth_method('two-factor')->store;
81
            $op = 'registered';
82
            if ( $logged_in_user->notice_email_address ) {
83
                $logged_in_user->queue_notice(
84
                    {
85
                        letter_params => {
86
                            module      => 'members',
87
                            letter_code => '2FA_ENABLE',
88
                            branchcode  => $logged_in_user->branchcode,
89
                            lang        => $logged_in_user->lang,
90
                            tables      => {
91
                                branches  => $logged_in_user->branchcode,
92
                                borrowers => $logged_in_user->id
93
                            },
94
                        },
95
                        message_transports => ['email'],
96
                    }
97
                );
98
            }
99
        }
100
        else {
101
            $template->param( invalid_pin => 1, );
102
            $op = 'enable-2FA';
103
        }
104
    }
105
106
    if ( $op eq 'enable-2FA' ) {
107
        my $secret = Koha::AuthUtils::generate_salt( 'weak', 16 );
108
        my $auth   = Koha::Auth::TwoFactorAuth->new(
109
            { patron => $logged_in_user, secret => $secret } );
110
111
        $template->param(
112
            issuer   => $auth->issuer,
113
            key_id   => $auth->key_id,
114
            qr_code  => $auth->qr_code,
115
            secret32 => $auth->secret32,
116
117
            # IMPORTANT: get secret32 after qr_code call !
118
        );
119
        $auth->clear;
120
        $op = 'register';
121
    }
122
    elsif ( $op eq 'disable-2FA' ) {
123
        output_and_exit( $cgi, $cookie, $template, 'wrong_csrf_token' )
60
        output_and_exit( $cgi, $cookie, $template, 'wrong_csrf_token' )
124
          unless Koha::Token->new->check_csrf($csrf_pars);
61
          unless Koha::Token->new->check_csrf($csrf_pars);
125
        my $auth =
62
        my $auth =
126
- 

Return to bug 30588