Skip to content

Commit

Permalink
Add an option to skip two factor authentication on trusted devices.
Browse files Browse the repository at this point in the history
A checkbox is added to the two factor verification page.  If that is
checked, then a signed cookie (separate from the session cookie) is set.
If that cookie is set, then two factor verification is skipped for sign
in attempts.
  • Loading branch information
drgrice1 committed Feb 29, 2024
1 parent 4e9e668 commit c0cbfd9
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 2 deletions.
42 changes: 41 additions & 1 deletion lib/WeBWorK/Authen.pm
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,38 @@ sub verify {

$self->{was_verified} = $self->do_verify;

my $remember_2fa = $c->signed_cookie('WeBWorK.2FA.' . $c->ce->{courseName});

if ($self->{was_verified}
&& $self->{login_type} eq 'normal'
&& !$self->{external_auth}
&& (!$c->{rpc} || ($c->{rpc} && !$c->stash->{disable_cookies}))
&& $remember_2fa
&& !$c->db->getPassword($self->{user_id})->otp_secret)
{
# If there is not a otp secret saved in the database, and there is a cookie saved to skip two factor
# authentication, then delete it. The user needs to set up two factor authentication again.
$c->signed_cookie(
'WeBWorK.2FA.' . $c->ce->{courseName} => 1,
{
max_age => 0,
expires => 1,
path => $c->ce->{webworkURLRoot},
samesite => $c->ce->{CookieSameSite},
secure => $c->ce->{CookieSecure},
httponly => 1
}
);
$remember_2fa = 0;
}

if ($self->{was_verified}
&& $self->{login_type} eq 'normal'
&& !$self->{external_auth}
&& (!$c->{rpc} || ($c->{rpc} && !$c->stash->{disable_cookies}))
&& $c->ce->two_factor_authentication_enabled
&& ($self->{initial_login} || $self->session->{two_factor_verification_needed}))
&& ($self->{initial_login} || $self->session->{two_factor_verification_needed})
&& !$remember_2fa)
{
$self->{was_verified} = 0;
$self->session(two_factor_verification_needed => 1);
Expand Down Expand Up @@ -446,6 +472,20 @@ sub verify_normal_user {
{
delete $self->session->{two_factor_verification_needed};

# Store a cookie that signifies this devices skips two factor
# authentication if the skip_2fa checkbox was checked.
$c->signed_cookie(
'WeBWorK.2FA.' . $c->ce->{courseName} => 1,
{
max_age => 3600 * 24 * 365, # This cookie is valid for one year.
expires => time + 3600 * 24 * 365,
path => $c->ce->{webworkURLRoot},
samesite => $c->ce->{CookieSameSite},
secure => $c->ce->{CookieSecure},
httponly => 1
}
) if $c->param('skip_2fa');

# This is the case of initial setup. Save the secret from the session to the database.
if ($self->session->{otp_secret}) {
$password->otp_secret($self->session->{otp_secret});
Expand Down
11 changes: 10 additions & 1 deletion templates/ContentGenerator/TwoFactorAuthentication.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,21 @@
<%= form_for current_route, method => 'POST', begin =%>
<%= $hidden_fields =%>
%
<div class="col-xl-5 col-lg-6 col-md-7 col-sm-8 my-3">
<div class="col-xl-9 col-lg-10 col-md-11 my-3">
<div class="form-floating mb-2">
<%= text_field otp_code => '', id => 'otp_code', class => 'form-control', placeholder => '',
autocomplete => 'off', autocapitalize => 'none', spellcheck => 'false' =%>
<%= label_for otp_code => maketext('One-Time Code') =%>
</div>
<div class="form-check mb-2">
<%= check_box(skip_2fa => 1, id => 'skip_2fa', class => 'form-check-input') =%>
<%= label_for skip_2fa => maketext('Skip two factor verification on this device.') =%>
</div>
<div class="alert alert-warning mb-2">
<%= maketext('If you check the box above, then two factor verification will be skipped from now on when '
. 'signing in with this browser. This feature is not safe for public workstations, untrusted machines, '
. 'and machines over which you do not have direct control.') =%>
</div>
% if ($authen_error) {
<div class="alert alert-danger" tabindex="0"><%= $authen_error =%></div>
% }
Expand Down

0 comments on commit c0cbfd9

Please sign in to comment.