alexandregz / twofactor_gauthenticator

This RoundCube plugin adds the 2-step verification(OTP) to the login proccess
MIT License
214 stars 74 forks source link

The plugin breaks in elastic skin #164

Open InputOutputZ opened 1 year ago

InputOutputZ commented 1 year ago

Hi there,

I'm just reporting two bugs and a fix.

The first bug, its the check box doesn't reflect if the two factor authentication was already enabled, and fixed it in twofactor_gauthenticator_form method around $activateData initialisation.

Also, the plugin layout breaks in recent Roundcube elastic skin, and I have patched it to the best of my ability, here is the changes:-

In twofactor_gauthenticator_form.js

    // remember option
    if(rcmail.env.allow_save_device_30days){
        text += `<tr class="form-group row">
            <td class="title" style="display: none;">
              <label for="rcmloginuser">Username</label>
            </td>
            <td class="input input-group input-group-lg">
              <div class="custom-control custom-switch">
                <input type="checkbox" class="custom-control-input" id="remember_2FA" name="_remember_2FA" value="yes">
                <label class="custom-control-label" for="remember_2FA">`+rcmail.gettext('dont_ask_me_30days', 'twofactor_gauthenticator')+`</label>
              </div>
            </td>
          </tr>`;
    }

Also, in twofactor_gauthenticator.js

setup2FAfields, line 52 to 54.

            var url_qr_code_values = 'otpauth://totp/' +$('#prefs-title').html().split(/ - /)[1]+ '?secret=' +$('#2FA_secret').get(0).value +'&issuer=RoundCube2FA%20'+window.location.hostname;
            $('table tr:last').before('<tr class="form-group row"><td class="title col-sm-6">' +rcmail.gettext('qr_code', 'twofactor_gauthenticator')+ '</td><td class="col-sm-6"><input type="button" class="button mainaction btn btn-primary" id="2FA_change_qr_code" value="' 
                    +rcmail.gettext('hide_qr_code', 'twofactor_gauthenticator')+ '"><div id="2FA_qr_code" style="display: visible; margin-top: 10px;"></div></td></tr>');

Also in twofactor_gauthenticator.php


    public function twofactor_gauthenticator_form() 
    {
        $rcmail = rcmail::get_instance();

        $this->add_texts('localization/', true);
        $rcmail->output->set_env('product_name', $rcmail->config->get('product_name'));

        $data = self::__get2FAconfig();

        // Fields will be positioned inside of a table
        $table = new html_table(array('cols' => 2, 'class' => 'propform cols-sm-6-6'));

        // Activate/deactivate
        $field_id = '2FA_activate';

        $activateData = array('name' => $field_id, 'id' => $field_id, 'type' => 'checkbox');

        if(array_key_exists('secret', $data)){
            $activateData['checked'] = "checked";
        }

        $checkbox_activate = new html_checkbox($activateData);
        $table->add('title', html::label($field_id, rcube::Q($this->gettext('activate'))));
        $checked = $data['activate'] ? null: 1; // :-?
        $table->add(null, $checkbox_activate->show( $checked )); 

        // secret
        $field_id = '2FA_secret';

        $input_descsecret = new html_inputfield(array('name' => $field_id, 'id' => $field_id, 'size' => 60, 'type' => 'password', 'value' => $data['secret'], 'autocomplete' => 'new-password'));
        $table->add('title', html::label($field_id, rcube::Q($this->gettext('secret'))));
        $html_secret = $input_descsecret->show();
        $html_secret = "<div style='display:block;'>".$html_secret;
        if($data['secret'])
        {
            $html_secret .= '<hr /><input type="button" class="button mainaction" id="2FA_change_secret" value="'.$this->gettext('show_secret').'"><br /><br />';
        }
        else
        {
            $html_secret .= '<hr /><input type="button" class="button mainaction" id="2FA_create_secret" disabled="disabled" value="'.$this->gettext('create_secret').'"><br /><br />';
        }
        $html_secret .= "</div>";

        $table->add(null, $html_secret);

        // recovery codes
        $table->add('title', $this->gettext('recovery_codes'));

        $html_recovery_codes = '';
        $i=0;
        for($i = 0; $i < $this->_number_recovery_codes; $i++)
        {
            $value = isset($data['recovery_codes'][$i]) ? $data['recovery_codes'][$i] : '';
            if(($i+1) == $this->_number_recovery_codes){
                $html_recovery_codes .= ' <input type="password" name="2FA_recovery_codes[]" value="'.$value.'" maxlength="10"> <hr /> ';
            }else{
                $html_recovery_codes .= ' <input type="password" name="2FA_recovery_codes[]" value="'.$value.'" maxlength="10"> <br /> ';
            }

        }
        if($data['secret']) {
            $html_recovery_codes .= '<input type="button" class="button mainaction" id="2FA_show_recovery_codes" value="'.$this->gettext('show_recovery_codes').'"><br /><br />';
    }
    else {
            $html_recovery_codes .= '<input type="button" class="button mainaction" id="2FA_show_recovery_codes" disabled="disabled" value="'.$this->gettext('show_recovery_codes').'"><br /><br />';
    }
        $table->add(null, $html_recovery_codes);

        // qr-code
        if($data['secret']) {
            $table->add('title', $this->gettext('qr_code'));
            $table->add(null, '<input type="button" class="button mainaction" id="2FA_change_qr_code" value="'.$this->gettext('show_qr_code').'"> 
                                <div id="2FA_qr_code" style="display: none; margin-top: 10px; text-align: center;"></div>');

            // new JS qr-code, without call to Google
            $this->include_script('2FA_qr_code.js');
        }

        // infor
        $table->add(null, '<td><br>'.$this->gettext('msg_infor').'</td>');

        // button to setup all fields if doesn't exists secret
        $html_setup_all_fields = '';
        if(!$data['secret']) {
            $html_setup_all_fields = '<button type="button" class="button mainaction btn btn-primary submit" id="2FA_setup_fields" value="fill">Fill all fields</button><br />'.$this->gettext('setup_all_fields').'<br /><hr />';
        }

        $html_check_code = '<div style="text-align:center;"><button style="margin-right: 10px; margin-bottom: 5px;" type="button" class="button mainaction btn btn-primary" id="2FA_check_code" value="'.$this->gettext('check_code').'">'.$this->gettext('check_code').'</button> <input data-icon="key" size="40" autocapitalize="off" autocomplete="off" placeholder="Enter TOTP code..." type="text" class="form-control" style="display: inline-block; width: 250px; margin-bottom: 5px;" id="2FA_code_to_check" maxlength="10"></div>';

        // Build the table with the divs around it
        $out = html::tag('fieldset', array('class' => 'main'), html::tag('legend', array('id' => 'prefs-title'), $this->gettext('twofactor_gauthenticator') . ' - ' . $rcmail->user->data['username']). html::div(array('class' => 'settingsbox', 'style' => 'margin: 0;'),
        html::div(array('class' => 'boxcontent'), $table->show() . 
            html::p(null, 
                    $rcmail->output->button(array(
                        'command' => 'plugin.twofactor_gauthenticator-save',
                        'type' => 'button',
                        'class' => 'button mainaction btn btn-primary submit',
                        'label' => 'save'
                    ))."<hr>"

                    // button show/hide secret
                    //.'<input type="button" class="button mainaction" id="2FA_change_secret" value="'.$this->gettext('show_secret').'">'

                    // button to setup all fields
                    .$html_setup_all_fields
                    .$html_check_code
                )
            )
        ));

        // Construct the form
        $rcmail->output->add_gui_object('twofactor_gauthenticatorform', 'twofactor_gauthenticator-form');

        $out = $rcmail->output->form_tag(array(
            'id' => 'twofactor_gauthenticator-form',
            'name' => 'twofactor_gauthenticator-form',
            'method' => 'post',
            'action' => './?_task=settings&_action=plugin.twofactor_gauthenticator-save',
        ), $out);

        $out = "<div class='box formcontainer scroller formcontent'>".$out."</div>";

        return $out;
    }

With thanks.

Zakaria.

alexandregz commented 1 year ago

thx for the feedback. Can you send a PR, plz?

InputOutputZ commented 1 year ago

Sure, #165 here its.

Nebucatnetzer commented 9 months ago

Would this maybe help with my problem that the recovery codes aren't showing? image

InputOutputZ commented 9 months ago

Would this maybe help with my problem that the recovery codes aren't showing? image

Possibly. You have to test it first, check console log, it could be javascript issue in your RC skin.

Nebucatnetzer commented 9 months ago

I didn't see any errors in the console.

InputOutputZ commented 9 months ago

I didn't see any errors in the console.

In this case, I would recommend to give try to my last patch commit.

Nebucatnetzer commented 8 months ago

I stopped using Roundcube recently and hadn't had time to test the patch before, JFYI.