diff --git a/lib/WeBWorK/Authen.pm b/lib/WeBWorK/Authen.pm index 3421217607..ca8263fe66 100644 --- a/lib/WeBWorK/Authen.pm +++ b/lib/WeBWorK/Authen.pm @@ -156,11 +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' - && $c->ce->two_factor_authentication_enabled && !$self->{external_auth} - && ($self->{initial_login} || $self->session->{two_factor_verification_needed})) + && (!$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}) + && !$remember_2fa) { $self->{was_verified} = 0; $self->session(two_factor_verification_needed => 1); @@ -445,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}); diff --git a/templates/ContentGenerator/TwoFactorAuthentication.html.ep b/templates/ContentGenerator/TwoFactorAuthentication.html.ep index 6a1fbf8b77..65e582d0e4 100644 --- a/templates/ContentGenerator/TwoFactorAuthentication.html.ep +++ b/templates/ContentGenerator/TwoFactorAuthentication.html.ep @@ -36,12 +36,21 @@ <%= form_for current_route, method => 'POST', begin =%> <%= $hidden_fields =%> % -