Jump to content

Require security question selection if empty & notify client on change


Recommended Posts

Another hook, but a pretty good one, really. Require your clients to enter in a security question if they have none in the system.

NOTE

Do not attempt this if you don't have security questions setup, it will just send you in a continual loop!

Place the following as a php file in whmcs/includes/hooks/

<?php

//redirect clients to security questions page if they don't have one setup. Nothing else.
//provided by https://www.whmcs.guru
use Illuminate\Database\Capsule\Manager as Capsule;

function check_client_security($vars)
{

//are we logged in? If not, then return
$uid  = $_SESSION['uid'];
if (empty($uid))
{
	return;
}

$displayTitle = $vars['displayTitle'];

$securityq = Capsule::table('tblclients')->where('id', '=', $uid)->pluck('securityqid');
if (is_array($securityq))
{
	$securityq = $securityq['0'];

}

if (empty($securityq))
{

	if ($displayTitle == "Security Settings")
	{
		//we are already here, assign the variable
		$extraVariables = [];
		$extraVariables['ctest'] = 'Please establish a security question for your account<br />This will be used to validate your account should we ever need to<br />';
		return $extraVariables;
	}
	header('Location: clientarea.php?action=security');
}
else
{
	return;
}
}

add_hook('ClientAreaPage', 1, "check_client_security");

 

Now, for the really paranoid, tell your clients when someone's changed their password, or security question. Save this as a php file in whmcs/includes/hooks/ as well

Make sure to replace YOURADMINHERE with your admin API user

<?php
/*
Client area Password and Security Question modification notification
Developed by http://www.whmcsguru.com
*/

use Illuminate\Database\Capsule\Manager as Capsule;
function hook_client_password_notify($vars)
{

$userid = $vars['userid'];
$filename = APP::getCurrentFileName();
$action = $_GET['action'];
$success = $_GET['success'];

if ($filename=='clientarea' && $action=='changepw') {
	send_password_notify($userid);
}
}
function check_security_change($vars)
{
$userid = $_SESSION['uid'];
$filename = $vars['filename'];
$action = $_GET['action'];
$success = $_GET['successful'];
if ($filename =="clientarea")
{
	if ($action == "changesq")
	{
		if ($success == "true")
		{
			send_security_notify($userid);
		}
	}
}
}
function send_password_notify($userid)
{
$ip = $_SERVER['REMOTE_ADDR'] ;
$hostname = gethostbyaddr($ip);
$userinfo = Capsule::table('tblclients')->select('firstname', 'lastname')->WHERE('id', $userid)->get();
//greet them
foreach ($userinfo as $userrow)
{
	$firstname = $userrow->firstname;
	$lastname = $userrow->lastname;
}
$command = "sendemail";
$adminuser = "YOURADMINHERE";
$values["customtype"] = "general";
$values["customsubject"] = "Password Modification from $hostname";
$values["custommessage"] = "<p>Hello $firstname $lastname,<p>A remote user successfully changed your password. If this was not you, please do contact us immediately<br /> You may contact us by replying directly to this email<p>IP Address: $ip<br/>Hostn
ame: $hostname<br />";
$values["id"] = $userid;

$results = localAPI($command, $values, $adminuser);
}
function send_security_notify($userid)
{
$ip = $_SERVER['REMOTE_ADDR'] ;
$hostname = gethostbyaddr($ip);
$userinfo = Capsule::table('tblclients')->select('firstname', 'lastname')->WHERE('id', $userid)->get();
//greet them
foreach ($userinfo as $userrow)
{
	$firstname = $userrow->firstname;
	$lastname = $userrow->lastname;
}
$command = "sendemail";
$adminuser = "YOURADMINHERE";
$values["customtype"] = "general";
$values["customsubject"] = "Security Question Modification from $hostname";
$values["custommessage"] = "<p>Hello $firstname $lastname,<p>A remote user successfully changed your security question. If this was not you, please do contact us immediately<br /> You may contact us by replying directly to this email<p>IP Address: $ip<
br/>Hostname: $hostname<br />";
$values["id"] = $userid;

$results = localAPI($command, $values, $adminuser);
}

add_hook('ClientChangePassword', 1, 'hook_client_password_notify');
add_hook('ClientAreaPage', 1, "check_security_change");

 

These hooks combined will let your clients know a bit more about what they've got going on.Always a good thing :)

Edited by twhiting9275
Link to comment
Share on other sites

  • 2 months later...

Looks like I forgot that part. For this, you need to edit the template (templates/yourtemplate/clientareasecurity.tpl)

After


{if $securityquestionsenabled && !$twofaactivation}

   <h2>{$LANG.clientareanavsecurityquestions}</h2>

add

  {if $ctest}
       {include file="$template/includes/alert.tpl" type="error" errorshtml=$ctest}

 

That will display the message

Unfortunately, no way to display the message without editing that file, but it's a very simple edit

Link to comment
Share on other sites

I am not really a fan of editing templates, as I have to remember to redo it every time I update whmcs.

 

would it possible instead to just popup an alert dialog when the user logs in telling them to set their security question, rather than automatically redirect them. Presumably this could be done with just a hook ?

Link to comment
Share on other sites

  • 4 weeks later...

I have found a problem with this.

I often need to "login as client", but this hook stops me being able to do anything as I get redirected to the security page.

 

would be possible to just inject a JavaScript alert instead, telling them to set security question, rather than redirect to the security page.

Link to comment
Share on other sites

I often need to "login as client", but this hook stops me being able to do anything as I get redirected to the security page.

 

This will fix that:

 

<?php

//redirect clients to security questions page if they don't have one setup. Nothing else.
//provided by https://www.whmcs.guru
use Illuminate\Database\Capsule\Manager as Capsule;

function check_client_security($vars)
{

   //are we logged in? If not, then return
   $uid  = $_SESSION['uid'];
   if (empty($uid))
   {
       return;
   }

//check for admin userid
   $isadmin = $_SESSION['adminid'];
   if (!is_empty($isadmin))
   {
   return;
   }

   $displayTitle = $vars['displayTitle'];

   $securityq = Capsule::table('tblclients')->where('id', '=', $uid)->pluck('securityqid');
   if (is_array($securityq))
   {
       $securityq = $securityq['0'];

   }

   if (empty($securityq))
   {

       if ($displayTitle == "Security Settings")
       {
           //we are already here, assign the variable
           $extraVariables = [];
           $extraVariables['ctest'] = 'Please establish a security question for your account<br />This will be used to validate your account should we ever need to<br />';
           return $extraVariables;
       }
       header('Location: clientarea.php?action=security');
   }
   else
   {
       return;
   }
}

add_hook('ClientAreaPage', 1, "check_client_security");

 

Not going to add one for the notification, because users should be notified when passwords are changed anyways , however you bring up a good point about redirecting admin user... That should be bypassed now :)

Link to comment
Share on other sites

Thanks for the effort, but as I have discovered there are other connotations due to the fact that there is only 1 security question on the primary user. So because of this I do not want to FORCE any user to this page and stop them from performing any other action unless they complete this information. I would rather just give them a reminder.

 

e.g.

As is the case for many clients, they have several sub-accounts, with different permissions, so different people can login and submit tickets, or receive emails etc. One of those people logs in, and is forced to complete the security question before he/she can submit a ticket or do anything.

Now someone else calls up a couple of days later, and is asked the security question "what is your mothers maiden name".

He gives his answer, but it is wrong, because it is not his mothers maiden name, it is the mother of the person who set the question/answer, and he doesn't know who did it and of course doesn't know the maiden name of all his staff's mothers.

 

This security question is thus in itself security issue, because it requires sharing personal details with other people in your company so they can contact providers and answer the security question. So since most websites use the same common questions, now anyone in your company who knows this information could use it to contact any service provider, website etc that you use, which is nothing to do with the company, and bypass the security checks if you have used the same security question, which is highly likely.

 

 

I have attempted to get round the overall issue by adding a generic "secret pass phrase" question that is not personal, and which can be shared. But this again presents an issue as this is more likely to be forgotten due to the fact it is not personal.

Link to comment
Share on other sites

Unfortunately, no way to display the message without editing that file, but it's a very simple edit
I am not really a fan of editing templates, as I have to remember to redo it every time I update whmcs.

would it possible instead to just popup an alert dialog when the user logs in telling them to set their security question, rather than automatically redirect them. Presumably this could be done with just a hook ?

If you want to customize display, you've gotta edit templates. No way around that.

that isn't strictly true - I feel a quick Tuesday demonstration approaching! :)

 

I have found a problem with this.

I often need to "login as client", but this hook stops me being able to do anything as I get redirected to the security page.

would be possible to just inject a JavaScript alert instead, telling them to set security question, rather than redirect to the security page.

yes. :idea:

 

Thanks for the effort, but as I have discovered there are other connotations due to the fact that there is only 1 security question on the primary user. So because of this I do not want to FORCE any user to this page and stop them from performing any other action unless they complete this information. I would rather just give them a reminder.

i've written a sample hook (technically two hooks in the same file)...

 

<?php

//Show jquery modal popup to clients encouraging them to setup a security question.
//provided by brian!

function client_custom_headoutput_hook($vars) {

   $head_return = '';
   $head_return = '<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
 <script src="'.$vars['BASE_PATH_JS'].'/jquery-ui.js"></script>
 <script>
 $( function() {
   $( "#dialog-confirm" ).dialog({
     resizable: false,
     height: "auto",
     width: 400,
     modal: true,
     buttons: {
       "'.Lang::trans('clientareanavsecurityquestions').'": function() {
       window.location.href = "clientarea.php?action=security";
       },
       "'.Lang::trans('cancel').'": function() {
         $( this ).dialog( "close" );
       }
     }
   });
 } );
 </script>';
   return $head_return;
}
add_hook("ClientAreaHeadOutput",1,"client_custom_headoutput_hook");

function client_custom_headeroutput_hook($vars) {

   $client = Menu::context('client');
   $adminloggedin = $vars['adminMasqueradingAsClient'];
   $subaccount = $vars['loggedinuser']['contactid'];
   $templatefile = $vars['templatefile'];

   if (empty($client->id) or $adminloggedin or $subaccount) { return; }

   if ($client->securityQuestionId == "0" and $templatefile == "clientareahome") {

       $header_return = '';
       $header_return = '<div id="dialog-confirm" title="'.Lang::trans('clientareasecurityquestion').'"><p><span class="ui-icon ui-icon-unlocked" style="float:left; margin:0 7px 70px 0;"></span>'.Lang::trans('clientAreaSecurityNoSecurityQuestions').'</p></div>';
       return $header_return;
   }
}
add_hook("ClientAreaHeaderOutput",1,"client_custom_headeroutput_hook");

so what it does is to add a jquery modal popup to clientareahome if the client doesn't have a security question set - i've tweaked the checks so that if it's an admin masquerading as the client or a subaccount (contact), then neither will see the popup... therefore, only the main client should see it, but you could tweak it more if you needed to be very specific about who sees it... also removed the database query as variables are now being pulled from the template and the Class documentation.

 

WPx3Gzw.png

 

i've used language strings in the hook to make it multilingual - the "Change Security Question" button takes the user to the security questions page, "Cancel" or "X" will simply close the popup... if they dismiss it, then they won't see it on any of the other pages, but if they return to clientareahome, they'll see the popup reminder again... I did toy with making it appear only once per session, but assumed the whole point to this was to encourage them to set the question.

 

also, I can see no reason why you couldn't use this with other modal solutions... e.g if you use Bootstrap instead of jQuery dialog, it's slightly simpler, doesn't necessarily need any additional .js/css files and the output is more flexible... :idea:

 

<?php

//Show Bootstrap modal popup to clients encouraging them to setup a security question.
//provided by brian!

function client_custom_headoutput_hook($vars) {

   $head_return = '';
   $head_return = '<script>
$(window).load(function()
{
   $(\'#myModal\').modal(\'show\');
});
 </script>';
   return $head_return;
}
add_hook("ClientAreaHeadOutput",1,"client_custom_headoutput_hook");

function client_custom_headeroutput_hook($vars) {

   $client = Menu::context('client');
   $adminloggedin = $vars['adminMasqueradingAsClient'];
   $subaccount = $vars['loggedinuser']['contactid'];
   $templatefile = $vars['templatefile'];

   if (empty($client->id) or $adminloggedin or $subaccount) { return; }

   if ($client->securityQuestionId == "0" and $templatefile == "clientareahome") {

       $header_return = '';
       $header_return = '<div class="container">
   <div class="modal fade" id="myModal" role="dialog">
       <div class="modal-dialog">
           <div class="modal-content">
           <div class="modal-header">
               <button type="button" class="close" data-dismiss="modal">×</button>
               <h4 class="modal-title"><i class="fa fa-unlock fa-lg"></i>  '.Lang::trans('clientareasecurityquestion').'</h4>
           </div>
           <div class="modal-body">
               <p>'.Lang::trans('clientAreaSecurityNoSecurityQuestions').'</p>
           </div>
           <div class="modal-footer">
               <a class="btn btn-primary" href="clientarea.php?action=security">'.Lang::trans('clientareanavsecurityquestions').'</a>
               <button type="button" class="btn btn-default" data-dismiss="modal">'.Lang::trans('cancel').'</button>
           </div>
           </div>
       </div>
   </div>
</div>';
       return $header_return;
   }
}
add_hook("ClientAreaHeaderOutput",1,"client_custom_headeroutput_hook");

 

 

0xJiiDB.png

 

oh did I forget to mention - not one single template edit in sight... maybe one day i'll be a WHMCS Guru too! :-P

Link to comment
Share on other sites

  • 11 months later...

Hey @brian!

Hope you are doing well.

1) Does this modal solution still work in current version?  It looks good!!

2) Anyway to actually show the user their current security question answer?  Even if it is partly obfuscated?  At present (at least in my installation) the client side page that shows the Change Security Question - just shows an empty field for the security answer.  I had a client ask why they couldn't see the answer they'd written when they were logged in.

Is that normal behaviour of this screen / page?  If so, how to show part of the answer (or if that's too hard, the entire answer?)

 Portal Home > Client Area > My Details > Security Settings

EDIT: I found this, which looks like it would do the trick, but would be better if the answer could perhaps show in a modal - like the one @brian! has produced for the pop up message.

 

 

Edited by sol2010
Link to comment
Share on other sites

Hi,

13 hours ago, sol2010 said:

Hope you are doing well.

I am thanks... end of tax year... crazy times!

plus i've got a number of ongoing projects that quite happily worked on v7.4.2, that bizarrely break on v7.5... of course, there is little v7.5 documentation yet to work with (that never changes), so fumbling in the dark and hoping the forthcoming v7.5.1 maintenance update fixes everything!

13 hours ago, sol2010 said:

1) Does this modal solution still work in current version? 

yes it still works in v7.5...

rbxTg7w.png

13 hours ago, sol2010 said:

It looks good!!

yeah someone took the code, made it into a commercial addon and didn't credit me with creating the code for the core idea... welcome to the community!

13 hours ago, sol2010 said:

2) Anyway to actually show the user their current security question answer?  Even if it is partly obfuscated?  At present (at least in my installation) the client side page that shows the Change Security Question - just shows an empty field for the security answer.  I had a client ask why they couldn't see the answer they'd written when they were logged in.

Is that normal behaviour of this screen / page?  If so, how to show part of the answer (or if that's too hard, the entire answer?)

it's normal behaviour in the sense that it's what the template is designed to do - if you wanted to change it as you describe and show the full answer, it would be just require a simple tweak to the clientareasecurity.tpl template by changing...

<input type="password" name="currentsecurityqans" id="inputCurrentAns" class="form-control" autocomplete="off" />

to...

<input type="text" name="currentsecurityqans" id="inputCurrentAns" class="form-control" autocomplete="off" value="{$clientsdetails.securityqans}" readonly />

4xmQJKb.png

you could display it in other ways, not necessarily a readonly text field - it's just a Smarty variable that you can output anywhere you like!

13 hours ago, sol2010 said:

EDIT: I found this, which looks like it would do the trick, but would be better if the answer could perhaps show in a modal - like the one @brian! has produced for the pop up message.

you could use a hook to query the db for both the question and the answer, but the answer value is available as a Smarty variable once the client is logged in... so is the question ID value, though you'd still need to query the database to convert that id into the relevant question - on any page other than the security page (which already has the question as a variable).

so if you were going to show it as a popup, that's how I would do it... on the security page, a popup hook could easily use the Q&A Smarty variables... anywhere else and you could get the answer from Smarty and the question from the database. :idea:

Link to comment
Share on other sites

  • 2 weeks later...

Thank you @brian!

Sad to hear that someone could use your great work commercially and not credit you.  I hope you can somehow put them in the "virtual stock" to throw a few rotten tomatoes at them.

I will let you know how it goes when I find the time to have a look :-)

Greetings to you!

Link to comment
Share on other sites

7 hours ago, sol2010 said:

Sad to hear that someone could use your great work commercially and not credit you.  I hope you can somehow put them in the "virtual stock" to throw a few rotten tomatoes at them.

oh when looking at threads, if a module from this guy is the only commercial solution, I simply don't reply to the thread - safe in the knowledge that he can't reply to it either as it would be self-promotion/advertising and against the community rules. :D

7 hours ago, sol2010 said:

I will let you know how it goes when I find the time to have a look :-)

i'll look forward to it. :idea:

Link to comment
Share on other sites

  • 3 months later...
  • 2 years later...
19 hours ago, Faizal1 said:

Thanks Brian. I have a couple of hooks that stopped working since the upgrade. Will try to look into it and see if I can fix with my limited skills 😉

i'll have one or two too lol - fundamentally the core of these hooks will continuing to work, the required changes will likely centre around identifying the person logging in and reacting to that accordingly.

Link to comment
Share on other sites

  • 2 weeks later...

I haven't disappeared, just been working on some stuff in the background.

This does work with V8, with a few tweaks.... Here we go

Template modifications:
If you chose to go the template route, edit

templates/yourtemplate/usersecurity.tpl (for version 😎

after (or around, you play with it as you like)

        {if $user->hasSecurityQuestion()}
            <div class="form-group">
                <label for="inputCurrentAns" class="control-label">{$user->getSecurityQuestion()}</label>
                <input type="password" name="currentsecurityqans" id="inputCurrentAns" class="form-control" autocomplete="off" />
            </div>
        {/if}

add

        {if $ctest}
       {include file="$template/includes/alert.tpl" type="error" errorshtml=$ctest}
        {/if}

Note that it's not necessary to change the template. The user will be redirected, with, or without this

 

The code itself (will only work with v7 and v8 now, sorry guys):
create a hook file (in includes/hooks), with the following

<?php

//redirect clients to security questions page if they don't have one setup. Nothing else.
use Illuminate\Database\Capsule\Manager as Capsule;
$isadmin = $_SESSION['adminid'];
function check_client_security($vars)
{

	//are we logged in? If not, then return
	$uid  = $_SESSION['uid'];
	if (empty($uid))
	{
		return;
	}


	$displayTitle = $vars['displayTitle'];
	$securityq = Capsule::table('tblclients')->where('id', '=', $uid)->value('securityqid');
	if (empty($securityq))
	{

		if ($displayTitle == "Security Settings")
		{
			//we are already here, assign the variable
			$extraVariables = [];
			$extraVariables['ctest'] = 'Please establish a security question for your account<br />This will be used to validate your account should we ever need to<br />';
			return $extraVariables;
		}
		$myver = get_whmcs_version();

		if ($displayTitle != "Security Settings")
		{
			if ($myver >= 8)
			{
				header('Location: /index.php?rp=/user/security');
			return;
			}
			if ($myver < 8 )
			{
				header('Location: /clientarea.php?action=security');
			return;
			}
		}

	}
}
	function get_whmcs_version()
	{
		$theversion = Capsule::table('tblconfiguration')->where('setting', '=', 'Version')->value('value');
		$retver = substr($theversion, 0,1);
		
		return ($retver);

	}
	
	$qrows = Capsule::table('tbladminsecurityquestions')->select('id') ->count();
	if ($qrows > 0)
	{
		if (empty($isadmin))
		{
			add_hook('ClientAreaPage', 1, "check_client_security");
		}
	}

 

I put a check in there to verify there were security questions ( no more loops there), and moved the admin check to the end of the file. Additionally, just a rudimentary check for v8, since the paths have changed.

If you see any problems, let me know

Edited by twhiting9275
Link to comment
Share on other sites

  • 1 month later...
2 hours ago, evolve hosting said:

I tried this using v7.10.2 and I don't see any notification.

I just verified that it does indeed work on 7.10

The caveat being that you cannot be logged in as admin. Why? Because, admins  should be able to login and do their stuff without this nonsense stopping them 😉 .

There's check for the admin session bit, in the hook. If it's detected, it doesn't do anything else. If you're not logged in as admin, you will see this working.

 

Link to comment
Share on other sites

2 hours ago, twhiting9275 said:

I just verified that it does indeed work on 7.10

The caveat being that you cannot be logged in as admin. Why? Because, admins  should be able to login and do their stuff without this nonsense stopping them 😉 .

There's check for the admin session bit, in the hook. If it's detected, it doesn't do anything else. If you're not logged in as admin, you will see this working.

 

There are a few conflicts between our site and your version. I'm not sure what's causing them yet. I dropped in @brian! version for now and it's working. 

Link to comment
Share on other sites

  • 6 months later...
On 4/17/2018 at 12:01 AM, brian! said:

it's normal behaviour in the sense that it's what the template is designed to do - if you wanted to change it as you describe and show the full answer, it would be just require a simple tweak to the clientareasecurity.tpl template by changing...


<input type="password" name="currentsecurityqans" id="inputCurrentAns" class="form-control" autocomplete="off" />

to...


<input type="text" name="currentsecurityqans" id="inputCurrentAns" class="form-control" autocomplete="off" value="{$clientsdetails.securityqans}" readonly />

4xmQJKb.png

you could display it in other ways, not necessarily a readonly text field - it's just a Smarty variable that you can output anywhere you like!

 

 

This is no longer working since 8.0 and I only just realised after a client pointed it out! 😝

I can see the data has moed to tblusers > security_question_answer in the database.

So just to advise it should be updated to:

                     <input type="text" name="currentsecurityqans" id="inputCurrentAns" class="form-control" autocomplete="off" value=" {$user.security_question_answer}" readonly />

 

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use & Guidelines and understand your posts will initially be pre-moderated