+# Returns a tfa challenge or undef.
+sub authenticate_2nd_new : prototype($$$$) {
+ my ($username, $realm, $otp, $tfa_challenge) = @_;
+
+ return lock_tfa_config(sub {
+ my ($tfa_cfg, $realm_tfa) = user_get_tfa($username, $realm, 1);
+
+ if (!defined($tfa_cfg)) {
+ return undef;
+ }
+
+ my $realm_type = $realm_tfa && $realm_tfa->{type};
+ if (defined($realm_type) && $realm_type eq 'yubico') {
+ $tfa_cfg->set_yubico_config({
+ id => $realm_tfa->{id},
+ key => $realm_tfa->{key},
+ url => $realm_tfa->{url},
+ });
+ }
+
+ configure_u2f_and_wa($tfa_cfg);
+
+ my $must_save = 0;
+ if (defined($tfa_challenge)) {
+ $tfa_challenge = verify_ticket($tfa_challenge, 0, $username);
+ $must_save = $tfa_cfg->authentication_verify($username, $tfa_challenge, $otp);
+ $tfa_challenge = undef;
+ } else {
+ $tfa_challenge = $tfa_cfg->authentication_challenge($username);
+ if (defined($otp)) {
+ if (defined($tfa_challenge)) {
+ $must_save = $tfa_cfg->authentication_verify($username, $tfa_challenge, $otp);
+ } else {
+ die "no such challenge\n";
+ }
+ }
+ }
+
+ if ($must_save) {
+ cfs_write_file('priv/tfa.cfg', $tfa_cfg);
+ }
+
+ return $tfa_challenge;
+ });
+}
+
+sub configure_u2f_and_wa : prototype($) {
+ my ($tfa_cfg) = @_;
+
+ my $dc = cfs_read_file('datacenter.cfg');
+ if (my $u2f = $dc->{u2f}) {
+ my $origin = $u2f->{origin};
+ if (!defined($origin)) {
+ my $rpcenv = PVE::RPCEnvironment::get();
+ $origin = $rpcenv->get_request_host(1);
+ if ($origin) {
+ $origin = "https://$origin";
+ } else {
+ die "failed to figure out u2f origin\n";
+ }
+ }
+ $tfa_cfg->set_u2f_config({
+ origin => $origin,
+ appid => $u2f->{appid},
+ });
+ }
+ if (my $wa = $dc->{webauthn}) {
+ $tfa_cfg->set_webauthn_config($wa);
+ }
+}
+