The following warnings occurred:
Warning [2] Undefined variable $unreadreports - Line: 26 - File: global.php(961) : eval()'d code PHP 8.1.2-1ubuntu2.14 (Linux)
File Line Function
/global.php(961) : eval()'d code 26 errorHandler->error
/global.php 961 eval
/printthread.php 16 require_once
Warning [2] Undefined property: MyLanguage::$archive_pages - Line: 2 - File: printthread.php(287) : eval()'d code PHP 8.1.2-1ubuntu2.14 (Linux)
File Line Function
/printthread.php(287) : eval()'d code 2 errorHandler->error
/printthread.php 287 eval
/printthread.php 117 printthread_multipage



UserSpice
2-Factor Authentication (2FA - Done) - Printable Version

+- UserSpice (https://userspice.com/forums)
+-- Forum: Miscellaneous (https://userspice.com/forums/forumdisplay.php?fid=28)
+--- Forum: Modifications and Hackery (https://userspice.com/forums/forumdisplay.php?fid=29)
+--- Thread: 2-Factor Authentication (2FA - Done) (/showthread.php?tid=839)

Pages: 1 2 3 4 5


2-Factor Authentication (2FA - Done) - Trioxin - 11-19-2017

I now have a fullly working 2FA implemented in UserSpice. Will post here soon when I have a break from work.


2-Factor Authentication (2FA - Done) - mudmin - 11-19-2017

Whoa. That would be awesome. Thanks.


2-Factor Authentication (2FA - Done) - Brandin - 11-19-2017

Yay!!!!! I've been trying to do this for awhile and it would break and I just stopped bothering!!! So excited for this!


2-Factor Authentication (2FA - Done) - Jamie - 11-20-2017

That's incredible, looking forward and patiently waiting for this to be released.




2-Factor Authentication (2FA - Done) - Trioxin - 11-20-2017

first install composer packages like so:

<pre>
Code:
composer require pragmarx/google2fa
composer require bacon/bacon-qr-code
</pre>






Then in /users/init.php we put:

Code:
require_once($abs_us_root.$us_url_root.'vendor/autoload.php');

Then make a page called /usersc/enable2FA.php:

<pre>
Code:
<?php require_once '../users/init.php'; ?>
<?php require_once $abs_us_root.$us_url_root.'users/includes/header.php'; ?>
<?php require_once $abs_us_root.$us_url_root.'users/includes/navigation.php'; ?>
<?php if (!securePage($_SERVER['PHP_SELF'])){die();}?>
<?php

//dealing with if the user is logged in
if($user->isLoggedIn() || !$user->isLoggedIn() && !checkMenu(2,$user->data()->id)){
    if (($settings->site_offline==1) && (!in_array($user->data()->id, $master_account)) && ($currentPage != 'login.php') && ($currentPage != 'maintenance.php')){
        $user->logout();
        Redirect::to($us_url_root.'users/maintenance.php');
    }
}

$currentUser = $user->data();

use PragmaRX\Google2FA\Google2FA;
$google2fa = new Google2FA();

$google2fa_url = $google2fa->getQRCodeGoogleUrl(
    'Your Site Name',
    $currentUser->email,
    $currentUser->twoKey
);
?>

<section class="cid-qABkfm0Pyl mbr-fullscreen mbr-parallax-background" id="header2-0" data-rv-view="1854">

    <div class="mbr-overlay" style="opacity: 0.4; background-color: rgb(40, 0, 60);"></div>

    <div class="container">
        <div class="row">
            <div class="mbr-white col-md-10">

                <div class="well">
                    <div class="row">
                        <div class="col-xs-12 col-md-3">
                            <p><a href="/users/account.php" class="btn btn-primary">Account Home</a></p>
                            <p><a class="btn btn-primary " href="/usersc/wallet.php" role="button">Manage Wallet</a></p>

                        </div>
                        <div class="col-xs-12 col-md-9">
                            <h1>Enable 2-Factor</h1>
                            <p>Scan this QR code with your authenticator app or input the key: <b><?php echo $currentUser->twoKey; ?></b></p>
                            <p><img src="<?php echo $google2fa_url; ?>"></p>
                            <p>Then enter one of your one-time passkeys here:</p>
                            <p>
                                <table border="0">
                                    <tr>
                                        <td><input class="form-control" placeholder="2FA Code" type="text" name="twoCode" id="twoCode" size="10"></td>
                                        <td><button id="twoBtn" class="btn btn-primary">Verify</button></td>
                                    </tr>
                                </table>
                            </p>

                        </div>
                    </div>
                </div>

            </div> <!-- /container -->

        </div> <!-- /#page-wrapper -->
    </div>
</section>

<!-- footers -->
<?php require_once $abs_us_root.$us_url_root.'users/includes/page_footer.php'; // the final html footer copyright row + the external js calls ?>

<!-- Place any per-page javascript here -->
<script>
    $(document).ready(function() {
        $("#twoBtn").click(function(e){
            e.preventDefault();
            $.ajax({
                type: "POST",
                url: "/api/",
                data: {
                    action: "verify2FA",
                    twoCode: $("#twoCode").val()
                },
                success: function(result) {
                    var resultO = JSON.parse(result);
                    if(!resultO.error){
                        alert('2FA verified and enabled.');
                    }else{
                        alert('Incorrect 2FA code.');
                    }
                },
                error: function(result) {
                    alert('There was a problem verifying 2FA. Please check Internet or contact support.');
                }
            });
        });
    });
</script>
<?php require_once $abs_us_root.$us_url_root.'users/includes/html_footer.php'; // currently just the closing /body and /html ?>
</pre>






Do you have an API on your site? I always make one of these at the location /api/index.php. Something like this:

<pre>
Code:
<?php

$responseAr = array();

require_once('../users/init.php');
require_once('../vendor/autoload.php');

use PragmaRX\Google2FA\Google2FA;
$google2fa = new Google2FA();

$action = "";
$responseAr['success'] = true;

if (!securePage($_SERVER['PHP_SELF'])){die();}

if(isset($_REQUEST['action'])){
    $action = $_REQUEST['action'];
    $responseAr['error'] = false;
    $db = DB::getInstance();
}else{
    returnError('No API action specified.');
}

if(isset($user->data())) {
    $currentUser = $user->data();
    $loggedIn = true;
    if (userHasPermission($currentUser->id, 4) || userHasPermission($currentUser->id, 5) || userHasPermission($currentUser->id, 6)) {
        $loggedIn = true;
    }else {
        $loggedIn = false;
    }
}}else{
    $loggedIn = false;
}

switch($action){
    case "pingEndpoint":
        $responseAr['testResponse'] = 'testData';
        break;
    case "verify2FA":
        $requestAr = requestCheck(['twoCode']);
        $twoQ = $db->query("select twoKey, twoEnabled from users where id = ?", [$currentUser->id]);
        if($twoQ->count() > 0){
            $twoO = $twoQ->results()[0];
            $twoVal = $google2fa->verifyKey($twoO->twoKey, $requestAr['twoCode']);
            if($twoVal){
                $responseAr['2FAValidated'] = true;
                if($twoO->twoEnabled == 0){
                    $db->query("update users set twoEnabled = 1 where id = ?", [$currentUser->id]);
                }
            }else{
                returnError('Incorrect 2FA code.');
            }
        }else{
            returnError('Invalid user or not logged in.');
        }
        break;
    default:
        returnError('Invalid API action specified.');
        break;
}

echo json_encode($responseAr);
</pre>






Near the top of users/login.php (After init) we need:

<pre>
Code:
use PragmaRX\Google2FA\Google2FA;
$google2fa = new Google2FA();
</pre>





We also need to add to users/login.php:

<pre>
Code:
<div class="form-group">
            <label for="twoCode">2-Factor (If enabled)</label>
            <input type="text" class="form-control"  name="twoCode" id="twoCode"  placeholder="2FA Code" autocomplete="off">
        </div>
</pre>






Also in users/login.php on the line after if ($validation->passed()) { add this:

<pre>
Code:
$twoPassed = true;
            $twoQ = $db->query("select twoKey from users where username = ? and twoEnabled = 1", [Input::get('username')]);
            if($twoQ->count() > 0){
                $twoKey = $twoQ->results()[0]->twoKey;
                $twoCode = trim(Input::get('twoCode'));
                if($google2fa->verifyKey($twoKey, $twoCode) == false){
                    $twoPassed = false;
                }
            }
            //Log user in if all is good
            if($twoPassed){
                $remember = (Input::get('remember') === 'on') ? true : false;
                $user = new User();
                $login = $user->loginEmail(Input::get('username'), trim(Input::get('password')), $remember);
            }else{
                $login = false;
            }
</pre>






In users/join.php near the top (After init) we add:

<pre>
Code:
use PragmaRX\Google2FA\Google2FA;
$google2fa = new Google2FA();
</pre>






Also in users/join.php in the line before $user->create(array( we add:

Code:
$twoKey = $google2fa->generateSecretKey();

And then in the $user->create(array we add along with the other stuff:

Code:
'twoKey' => $twoKey

In MySQL you need to add to the users table 2 fields:

<pre>
Code:
twoKey | varchar(16) | uf8_bin | no default
twoEnabled | tinyint(1) | default: 0
</pre>






Lastly, you will want to add to users/account.php a link to the 2fa page. Here's how I did it. In the PHP code before the html I put:

<pre>
Code:
$twoQ = $db->query("select twoKey from users where id = ? and twoEnabled = 0", [$userdetails->id]);
if($twoQ->count() > 0){
    $twoText = 'We recommend that you enable two-factor authentication to help protect your account. <a href="/usersc/enable2FA.php">Click here to setup 2FA</a>';
}else{
    $twoText = '';
}
</pre>






Then in the HTML I put:

Code:
<?php echo $twoText; ?>

In your usersc/includes/custom_functions.php add:

<pre>
Code:
function returnError($errorMsg){
    $responseAr = [];
    $responseAr['success'] = true;
    $responseAr['error'] = true;
    $responseAr['errorMsg'] = $errorMsg;
    die(json_encode($responseAr));
}

function userHasPermission($userID,$permissionID) {
    $permissionsAr = fetchUserPermissions($userID);
    //if($permissions[0])
    foreach($permissionsAr as $perm)
    {
        if($perm->permission_id == $permissionID)
        {
            return TRUE;
        }
    }
    return FALSE;
}

function requestCheck($expectedAr)
{
    if(isset($_GET) && isset($_POST))
    {
        $requestAr = array_replace_recursive($_GET, $_POST);
    }elseif(isset($_GET)){
        $requestAr = $_GET;
    }elseif(isset($_POST)){
        $requestAr = $_POST;
    }else{
        $requestAr = array();
    }
    $diffAr = array_diff_key(array_flip($expectedAr),$requestAr);
    if(count($diffAr) > 0)
    {
        returnError("Missing variables: ".implode(',',array_flip($diffAr)).".");
    }else {
        return $requestAr;
    }
}
</pre>




I think that's it. Here's a paste of these instructions with better formatting: https://hastebin.com/webayenehi.xml


2-Factor Authentication (2FA - Done) - Trioxin - 11-20-2017

Here it is on hastebin since this forum didn't format it too good: https://hastebin.com/lupufafagi.xml

I didn't make a way to turn it off. Most sites make you contact them to turn it off though so best to do that anyway.


2-Factor Authentication (2FA - Done) - Jamie - 11-20-2017

I'm getting this error when attempting to enable it.
https://image.prntscr.com/image/7VZQg2cSSZmcZFVEo5fVMQ.png

Any ideas?


2-Factor Authentication (2FA - Done) - Trioxin - 11-20-2017

Make sure you can visit your /api/?action=verify2FA with no errors. You might need to set the permissions for that page in the userspice admin panel.

Also, it's supposed to be like this:

yoursite.com/api/


2-Factor Authentication (2FA - Done) - Jamie - 11-20-2017

Checked Developer Tools and it states code 500 on /api/


2-Factor Authentication (2FA - Done) - Jamie - 11-20-2017

The page on /api/?verify2FA is just blank for me.