· 9 years ago · Oct 05, 2016, 06:46 AM
1<?php
2namespace Fr;
3
4class LS {
5
6 /**
7 * ------------
8 * BEGIN CONFIG
9 * ------------
10 * Edit the configuraion
11 */
12
13 public static $default_config = array(
14 /**
15 * Basic Config of logSys
16 */
17 "basic" => array(
18 "company" => "My Site",
19 "email" => "email@mysite.com",
20 "email_callback" => 0
21 ),
22
23 /**
24 * Database Configuration
25 */
26 "db" => array(
27 "host" => "",
28 "port" => 3306,
29 "username" => "",
30 "password" => "",
31 "name" => "",
32 "table" => "users",
33 "token_table" => "resetTokens"
34 ),
35
36 /**
37 * Keys used for encryption
38 * DONT MAKE THIS PUBLIC
39 */
40 "keys" => array(
41 /**
42 * Changing cookie key will expire all current active login sessions
43 */
44 "cookie" => "ckxc436jd*^30f840v*9!@#$",
45 /**
46 * `salt` should not be changed after users are created
47 */
48 "salt" => "^#$4%9f+1^p9)M@4M)V$"
49 ),
50
51 /**
52 * Enable/Disable certain features
53 */
54 "features" => array(
55 /**
56 * Should I Call session_start();
57 */
58 "start_session" => true,
59 /**
60 * Enable/Disable Login using Username & E-Mail
61 */
62 "email_login" => true,
63 /**
64 * Enable/Disable `Remember Me` feature
65 */
66 "remember_me" => true,
67 /**
68 * Should FrLS::init() be called automatically
69 */
70 "auto_init" => false,
71
72 /**
73 * Prevent Brute Forcing
74 * ---------------------
75 * By enabling this, logSys will deny login for the time mentioned
76 * in the "brute_force"->"time_limit" seconds after "brute_force"->"tries"
77 * number of incorrect login tries.
78 */
79 "block_brute_force" => true,
80
81 /**
82 * Two Step Login
83 * --------------
84 * By enabling this, a checking is done when user visits
85 * whether the device he/she uses is approved by the user.
86 * Allows the original user to revoke logins in other devices/places
87 * Useful if the user forgot to logout in some place.
88 */
89 "two_step_login" => false
90 ),
91
92 /**
93 * `Blocking Brute Force Attacks` options
94 */
95 "brute_force" => array(
96 /**
97 * No of tries alloted to each user
98 */
99 "tries" => 5,
100 /**
101 * The time IN SECONDS for which block from login action should be done after
102 * incorrect login attempts. Use http://www.easysurf.cc/utime.htm#m60s
103 * for converting minutes to seconds. Default : 5 minutes
104 */
105 "time_limit" => 300
106 ),
107
108 /**
109 * Information about pages
110 */
111 "pages" => array(
112 /**
113 * Pages that doesn't require logging in.
114 * Exclude login page, but include REGISTER page.
115 * Use RELATIVE links. To find the relative link of
116 * a page, do var_dump(FrLS::curPage());
117 */
118 "no_login" => array(),
119
120 /**
121 * Pages that both logged in and not logged in users can access
122 */
123 "everyone" => array(),
124
125 /**
126 * The login page. ex : /login.php or /accounts/login.php
127 */
128 "login_page" => "",
129
130 /**
131 * The home page. The main page for logged in users.
132 * logSys redirects to here after user logs in
133 */
134 "home_page" => ""
135 ),
136
137 /**
138 * Settings about cookie creation
139 */
140 "cookies" => array(
141 /**
142 * Default : cookies expire in 30 days. The value is
143 * for setting in strtotime() function
144 * http://php.net/manual/en/function.strtotime.php
145 */
146 "expire" => "+30 days",
147 "path" => "/",
148 "domain" => "",
149 ),
150
151 /**
152 * 2 Step Login
153 */
154 'two_step_login' => array(
155 /**
156 * Message to show before displaying "Enter Token" form.
157 */
158 'instruction' => '',
159
160 /**
161 * Callback when token is generated.
162 * Used to send message to user (Phone/E-Mail)
163 */
164 'send_callback' => '',
165
166 /**
167 * The table to stoe user's sessions
168 */
169 'devices_table' => 'user_devices',
170
171 /**
172 * The length of token generated.
173 * A low value is better for tokens sent via Mobile SMS
174 */
175 'token_length' => 4,
176
177 /**
178 * Whether the token should be numeric only ?
179 * Default Token : Alphabetic + Numeric mixed strings
180 */
181 'numeric' => false,
182
183 /**
184 * The expire time of cookie that authorizes the device
185 * to login using the user's account with 2 Step Verification
186 * The value is for setting in strtotime() function
187 * http://php.net/manual/en/function.strtotime.php
188 */
189 'expire' => '+45 days',
190
191 /**
192 * Should logSys checks if device is valid, everytime
193 * logSys is initiated ie everytime a page loads
194 * If you want to check only the first time a user loads
195 * a page, then set the value to TRUE, else FALSE
196 */
197 'first_check_only' => true
198 )
199 );
200
201 /* ------------
202 * END Config.
203 * ------------
204 * No more editing after this line.
205 */
206
207 public static $config = array();
208 private static $constructed = false;
209
210 /**
211 * Merge user config and default config
212 * $direct is for knowing whether the function is called by self::construct()
213 */
214 public static function config($config = null, $direct = true){
215 if($config != null){
216 self::$config = $config;
217 }
218 self::$config = array_replace_recursive(self::$default_config, self::$config);
219 if($direct == true){
220 self::construct();
221 }
222 }
223
224 /**
225 * Log something in the Francium.log file.
226 * To enable logging, make a file called "Francium.log" in the directory
227 * where "class.logsys.php" file is situated
228 */
229 public static function log($msg = ""){
230 $log_file = __DIR__ . "/Francium.log";
231 if(file_exists($log_file)){
232 if($msg != ""){
233 $message = "[" . date("Y-m-d H:i:s") . "] $msg";
234 $fh = fopen($log_file, 'a');
235 fwrite($fh, $message . "n");
236 fclose($fh);
237 }
238 }
239 }
240
241 public static $loggedIn = false;
242 public static $db = true;
243 public static $user = false;
244 private static $init_called = false;
245 private static $cookie, $session, $remember_cookie, $dbh;
246
247 public static function construct($called_from = ""){
248 if(self::$constructed === false){
249 self::config(null, false);
250 self::$constructed = true;
251
252 if(self::$config['features']['start_session'] === true){
253 session_start();
254 }
255 /**
256 * Try connecting to Database Server
257 */
258 try{
259 /**
260 * Add the login page to the array of pages that doesn't need logging in
261 */
262 array_push(self::$config['pages']['no_login'], self::$config['pages']['login_page']);
263
264 self::$dbh = new PDO("mysql:dbname=". self::$config['db']['name'] .";host=". self::$config['db']['host'] .";port=". self::$config['db']['port']. ";charset=utf8", self::$config['db']['username'], self::$config['db']['password']);
265 self::$db = true;
266
267 self::$cookie = isset($_COOKIE['logSyslogin']) ? $_COOKIE['logSyslogin'] : false;
268 self::$session = isset($_SESSION['logSyscuruser']) ? $_SESSION['logSyscuruser'] : false;
269 self::$remember_cookie = isset($_COOKIE['logSysrememberMe']) ? $_COOKIE['logSysrememberMe'] : false;
270
271 $encUserID = hash("sha256", self::$config['keys']['cookie'] . self::$session . self::$config['keys']['cookie']);
272
273 if(self::$cookie == $encUserID){
274 self::$loggedIn = true;
275 }else{
276 self::$loggedIn = false;
277 }
278
279 /**
280 * If there is a Remember Me Cookie and the user is not logged in,
281 * then log in the user with the ID in the remember cookie, if it
282 * matches with the decrypted value in `logSyslogin` cookie
283 */
284 if(self::$config['features']['remember_me'] === true && self::$remember_cookie !== false && self::$loggedIn === false){
285 $encUserID = hash("sha256", self::$config['keys']['cookie']. self::$remember_cookie . self::$config['keys']['cookie']);
286 if(self::$cookie == $encUserID){
287 self::$loggedIn = true;
288 }else{
289 self::$loggedIn = false;
290 }
291
292 if(self::$loggedIn === true){
293 $_SESSION['logSyscuruser'] = self::$remember_cookie;
294 self::$session = self::$remember_cookie;
295 }
296 }
297
298 self::$user = self::$session;
299
300 /**
301 * Check if devices is authorized to use the account
302 */
303 if(self::$config['features']['two_step_login'] === true && self::$loggedIn){
304 $login_page = self::curPage() === self::$config['pages']['login_page'];
305
306 if(!isset($_SESSION['device_check']) && !isset($_COOKIE['logSysdevice']) && $login_page === false){
307 /**
308 * The device cookie is not even set. So, logout
309 */
310 self::logout();
311 $called_from = "login";
312 }else if(self::$config['two_step_login']['first_check_only'] === false || (self::$config['two_step_login']['first_check_only'] === true && !isset($_SESSION['device_check']))){
313 $sql = self::$dbh->prepare("SELECT '1' FROM `". self::$config['two_step_login']['devices_table'] ."` WHERE `uid` = ? AND `token` = ?");
314 $sql->execute(array(self::$user, $_COOKIE['logSysdevice']));
315
316 /**
317 * Device not authorized, so remove device cookie & logout
318 */
319 if($sql->fetchColumn() !== '1' && $login_page === false){
320 setcookie("logSysdevice", "", time() - 10);
321 self::logout();
322 $called_from = "login";
323 }else{
324 /**
325 * This session has been checked and verified
326 */
327 $_SESSION['device_check'] = 1;
328 }
329 }
330 }
331
332 if(self::$config['features']['auto_init'] === true && $called_from != "logout" && $called_from != "login"){
333 self::init();
334 }
335 return true;
336 }catch(PDOException $e) {
337 /**
338 * Couldn't connect to Database
339 */
340 self::log('Couldn't connect to database. Check FrLS::$config["db"] credentials');
341 return false;
342 }
343 }
344 }
345
346 /**
347 * A function that will automatically redirect user according to his/her login status
348 */
349 public static function init() {
350 self::construct();
351 if(in_array(self::curPage(), self::$config['pages']['everyone'])){
352 /**
353 * No redirects as this page can be accessed
354 * by anyone whether he/she is logged in or not
355 */
356 }else if(self::$loggedIn === true && in_array(self::curPage(), self::$config['pages']['no_login'])){
357 self::redirect(self::$config['pages']['home_page']);
358 }else if(self::$loggedIn === false && array_search(self::curPage(), self::$config['pages']['no_login']) === false){
359 self::redirect(self::$config['pages']['login_page']);
360 }
361 self::$init_called = true;
362 }
363
364 /**
365 * A function to login the user with the username and password.
366 * As of version 0.4, it is required to include the remember_me parameter
367 * when calling this function to avail the "Remember Me" feature.
368 */
369 public static function login($username, $password, $remember_me = false, $cookies = true){
370 self::construct("login");
371 if(self::$db === true){
372 /**
373 * We Add LIMIT to 1 in SQL query because to
374 * get an array with key as the column name.
375 */
376 if(self::$config['features']['email_login'] === true){
377 $query = "SELECT `id`, `password`, `attempt` FROM `". self::$config['db']['table'] ."` WHERE `username`=:login OR `email`=:login ORDER BY `id` LIMIT 1";
378 }else{
379 $query = "SELECT `id`, `password`, `attempt` FROM `". self::$config['db']['table'] ."` WHERE `username`=:login ORDER BY `id` LIMIT 1";
380 }
381
382 $sql = self::$dbh->prepare($query);
383 $sql->bindValue(":login", $username);
384
385 $sql->execute();
386 $cols = $sql->fetch(PDO::FETCH_ASSOC);
387
388 if(empty($cols)){
389 // No such user like that
390 return false;
391 }else{
392 /**
393 * Get the user details
394 */
395 $us_id = $cols['id'];
396 $us_pass = $cols['password'];
397 $status = $cols['attempt'];
398
399 if(substr($status, 0, 2) == "b-"){
400 $blockedTime = substr($status, 2);
401 if(time() < $blockedTime){
402 $blocked = true;
403 return array(
404 "status" => "blocked",
405 "minutes" => round(abs($blockedTime - time()) / 60, 0),
406 "seconds" => round(abs($blockedTime - time()) / 60*60, 2)
407 );
408 }else{
409 // remove the block, because the time limit is over
410 self::updateUser(array(
411 "attempt" => "" // No tries at all
412 ), $us_id);
413 }
414 }
415 /**
416 * Why login if password is empty ?
417 * --------------------------------
418 * If using OAuth, you have to login someone without knowing their password,
419 * this usage is helpful. But, it makes a serious security problem too.
420 * Hence, before calling FrLS::login() in the login page, it is
421 * required to check whether the password fieldis left blank
422 */
423 if(!isset($blocked) && ($password === "" || password_verify($password . self::$config['keys']['salt'], $us_pass) )){
424 if($cookies === true){
425
426 $_SESSION['logSyscuruser'] = $us_id;
427
428 setcookie("logSyslogin", hash("sha256", self::$config['keys']['cookie'] . $us_id . self::$config['keys']['cookie']), strtotime(self::$config['cookies']['expire']), self::$config['cookies']['path'], self::$config['cookies']['domain']);
429
430 if( $remember_me === true && self::$config['features']['remember_me'] === true ){
431 setcookie("logSysrememberMe", $us_id, strtotime(self::$config['cookies']['expire']), self::$config['cookies']['path'], self::$config['cookies']['domain']);
432 }
433 self::$loggedIn = true;
434
435 if(self::$config['features']['block_brute_force'] === true){
436 /**
437 * If Brute Force Protection is Enabled,
438 * Reset the attempt status
439 */
440 self::updateUser(array(
441 "attempt" => "0"
442 ), $us_id);
443 }
444
445 // Redirect
446 if(self::$init_called){
447 self::redirect(self::$config['pages']['home_page']);
448 }
449 return true;
450 }else{
451 /**
452 * If cookies shouldn't be set,
453 * it means login() was called
454 * to get the user's ID. So, return it
455 */
456 return $us_id;
457 }
458 }else{
459 /**
460 * Incorrect password
461 * ------------------
462 * Check if brute force protection is enabled
463 */
464 if(self::$config['features']['block_brute_force'] === true){
465 $max_tries = self::$config['brute_force']['tries'];
466
467 if($status == ""){
468 // User was not logged in before
469 self::updateUser(array(
470 "attempt" => "1" // Tried 1 time
471 ), $us_id);
472 }else if($status == $max_tries){
473 /**
474 * Account Blocked. User will be only able to
475 * re-login at the time in UNIX timestamp
476 */
477 $eligible_for_next_login_time = strtotime("+". self::$config['brute_force']['time_limit'] ." seconds", time());
478 self::updateUser(array(
479 "attempt" => "b-" . $eligible_for_next_login_time
480 ), $us_id);
481 return array(
482 "status" => "blocked",
483 "minutes" => round(abs($eligible_for_next_login_time - time()) / 60, 0),
484 "seconds" => round(abs($eligible_for_next_login_time - time()) / 60*60, 2)
485 );
486 }else if($status < $max_tries){
487 // If the attempts are less than Max and not Max
488 self::updateUser(array(
489 "attempt" => $status + 1 // Increase the no of tries by +1.
490 ), $us_id);
491 }
492 }
493 return false;
494 }
495 }
496 }
497 }
498
499 /**
500 * A function to register a user with passing the username, password
501 * and optionally any other additional fields.
502 */
503 public static function register( $id, $password, $other = array() ){
504 self::construct();
505 if( self::userExists($id) || (isset($other['email']) && self::userExists($other['email'])) ){
506 return "exists";
507 }else{
508 $hashedPass = password_hash($password. self::$config['keys']['salt'], PASSWORD_DEFAULT);
509
510 if( count($other) == 0 ){
511 /* If there is no other fields mentioned, make the default query */
512 $sql = self::$dbh->prepare("INSERT INTO `". self::$config['db']['table'] ."` (`username`, `password`) VALUES(:username, :password)");
513 }else{
514 /* if there are other fields to add value to, make the query and bind values according to it */
515 $keys = array_keys($other);
516 $columns = implode(",", $keys);
517 $colVals = implode(",:", $keys);
518 $sql = self::$dbh->prepare("INSERT INTO `". self::$config['db']['table'] ."` (`username`, `password`, $columns) VALUES(:username, :password, :$colVals)");
519 foreach($other as $key => $value){
520 $value = htmlspecialchars($value);
521 $sql->bindValue(":$key", $value);
522 }
523 }
524 /* Bind the default values */
525 $sql->bindValue(":username", $id);
526 $sql->bindValue(":password", $hashedPass);
527 $sql->execute();
528 return true;
529 }
530 }
531
532 /**
533 * Logout the current logged in user by deleting the cookies and destroying session
534 */
535 public static function logout(){
536 self::construct("logout");
537 session_destroy();
538 setcookie("logSyslogin", "", time() - 10, self::$config['cookies']['path'], self::$config['cookies']['domain']);
539 setcookie("logSysrememberMe", "", time() - 10, self::$config['cookies']['path'], self::$config['cookies']['domain']);
540
541 /**
542 * Wait for the cookies to be removed, then redirect
543 */
544 usleep(2000);
545 self::redirect(self::$config['pages']['login_page']);
546 return true;
547 }
548
549 /**
550 * A function to handle the Forgot Password process
551 */
552 public static function forgotPassword(){
553 self::construct();
554 $curStatus = "initial"; // The Current Status of Forgot Password process
555 $identName = self::$config['features']['email_login'] === false ? "Username" : "Username / E-Mail";
556
557 if( !isset($_POST['logSysForgotPass']) && !isset($_GET['resetPassToken']) && !isset($_POST['logSysForgotPassChange']) ){
558 $html = '<form action="'. self::curPageURL() .'" method="POST">';
559 $html .= "<label>";
560 $html .= "<p>{$identName}</p>";
561 $html .= "<input type='text' id='logSysIdentification' placeholder='Enter your {$identName}' size='25' name='identification' />";
562 $html .= "</label>";
563 $html .= "<p><button name='logSysForgotPass' type='submit'>Reset Password</button></p>";
564 $html .= "</form>";
565 echo $html;
566 /**
567 * The user had moved to the reset password form ie she/he is currently seeing the forgot password form
568 */
569 $curStatus = "resetPasswordForm";
570 }elseif( isset($_GET['resetPassToken']) && !isset($_POST['logSysForgotPassChange']) ){
571 /**
572 * The user gave the password reset token. Check if the token is valid.
573 */
574 $reset_pass_token = urldecode($_GET['resetPassToken']);
575 $sql = self::$dbh->prepare("SELECT COUNT(1) FROM `". self::$config['db']['token_table'] ."` WHERE `token` = ?");
576 $sql->execute(array($reset_pass_token));
577
578 if($sql->fetchColumn() == 0 || $reset_pass_token == ""){
579 echo "<h3>Error : Wrong/Invalid Token</h3>";
580 $curStatus = "invalidToken"; // The token user gave was not valid
581 }else{
582 /**
583 * The token is valid, display the new password form
584 */
585 $html = "<p>The Token key was Authorized. Now, you can change the password</p>";
586 $html .= "<form action='".self::curPageURL()."' method='POST'>";
587 $html .= "<input type='hidden' name='token' value='{$reset_pass_token}' />";
588 $html .= "<label>";
589 $html .= "<p>New Password</p>";
590 $html .= "<input type='password' name='logSysForgotPassNewPassword' />";
591 $html .= "</label><br/>";
592 $html .= "<label>";
593 $html .= "<p>Retype Password</p>";
594 $html .= "<input type='password' name='logSysForgotPassRetypedPassword'/>";
595 $html .= "</label><br/>";
596 $html .= "<p><button name='logSysForgotPassChange'>Reset Password</button></p>";
597 $html .= "</form>";
598 echo $html;
599 /**
600 * The token was correct, displayed the change/new password form
601 */
602 $curStatus = "changePasswordForm";
603 }
604 }elseif(isset($_POST['logSysForgotPassChange']) && isset($_POST['logSysForgotPassNewPassword']) && isset($_POST['logSysForgotPassRetypedPassword'])){
605 $reset_pass_token = urldecode($_POST['token']);
606 $sql = self::$dbh->prepare("SELECT `uid` FROM `". self::$config['db']['token_table'] ."` WHERE `token` = ?");
607 $sql->execute(array($reset_pass_token));
608
609 $user = $sql->fetchColumn();
610
611 if( $user == null || $reset_pass_token == null ){
612 echo "<h3>Error : Wrong/Invalid Token</h3>";
613 $curStatus = "invalidToken"; // The token user gave was not valid
614 }else{
615 if($_POST['logSysForgotPassNewPassword'] == "" || $_POST['logSysForgotPassRetypedPassword'] == ""){
616 echo "<h3>Error : Passwords Fields Left Blank</h3>";
617 $curStatus = "fieldsLeftBlank";
618 }elseif( $_POST['logSysForgotPassNewPassword'] != $_POST['logSysForgotPassRetypedPassword'] ){
619 echo "<h3>Error : Passwords Don't Match</h3>";
620 $curStatus = "passwordDontMatch"; // The new password and retype password submitted didn't match
621 }else{
622 /**
623 * We must create a fake assumption that the user is logged in to
624 * change the password as FrLS::changePassword()
625 * requires the user to be logged in.
626 */
627 self::$user = $user;
628 self::$loggedIn = true;
629
630 if(self::changePassword($_POST['logSysForgotPassNewPassword'])){
631 self::$user = false;
632 self::$loggedIn = false;
633
634 /**
635 * The token shall not be used again, so remove it.
636 */
637 $sql = self::$dbh->prepare("DELETE FROM `". self::$config['db']['token_table'] ."` WHERE `token` = ?");
638 $sql->execute(array($reset_pass_token));
639
640 echo "<h3>Success : Password Reset Successful</h3><p>You may now login with your new password.</p>";
641 $curStatus = "passwordChanged"; // The password was successfully changed
642 }
643 }
644 }
645 }elseif(isset($_POST['identification'])){
646 /**
647 * Check if username/email is provided and if it's valid and exists
648 */
649 $identification = $_POST['identification'];
650 if($identification == ""){
651 echo "<h3>Error : {$identName} not provided</h3>";
652 $curStatus = "identityNotProvided"; // The identity was not given
653 }else{
654 $sql = self::$dbh->prepare("SELECT `email`, `id` FROM `". self::$config['db']['table'] ."` WHERE `username`=:login OR `email`=:login");
655 $sql->bindValue(":login", $identification);
656
657 $sql->execute();
658 $cols = $sql->fetch(PDO::FETCH_ASSOC);
659
660 if(empty($cols)){
661 echo "<h3>Error : User Not Found</h3>";
662 $curStatus = "userNotFound"; // The user with the identity given was not found in the users database
663 }else{
664 $email = $cols['email'];
665 $uid = $cols['id'];
666
667 /**
668 * Make token and insert into the table
669 */
670 $token = self::rand_string(40);
671 $sql = self::$dbh->prepare("INSERT INTO `". self::$config['db']['token_table'] ."` (`token`, `uid`, `requested`) VALUES (?, ?, NOW())");
672 $sql->execute(array($token, $uid));
673 $encodedToken = urlencode($token);
674
675 /**
676 * Prepare the email to be sent
677 */
678 $subject = "Reset Password";
679 $body = "You requested for resetting your password on ". self::$config['basic']['company'] .". For this, please click the following link :
680 <blockquote>
681 <a href='". self::curPageURL() ."?resetPassToken={$encodedToken}'>Reset Password : {$token}</a>
682 </blockquote>";
683 self::sendMail($email, $subject, $body);
684
685 echo "<p>An email has been sent to your email inbox with instructions. Check Your Mail Inbox and SPAM Folders.</p><p>You can close this window.</p>";
686 $curStatus = "emailSent"; // E-Mail has been sent
687 }
688 }
689 }
690 return $curStatus;
691 }
692
693 /**
694 * A function that handles the logged in user to change her/his password
695 */
696 public static function changePassword($newpass){
697 self::construct();
698 if(self::$loggedIn){
699 $hashedPass = password_hash($newpass . self::$config['keys']['salt'], PASSWORD_DEFAULT);
700 $sql = self::$dbh->prepare("UPDATE `". self::$config['db']['table'] ."` SET `password` = ? WHERE `id` = ?");
701 $sql->execute(array($hashedPass, self::$user));
702 return true;
703 }else{
704 echo "<h3>Error : Not Logged In</h3>";
705 return "notLoggedIn";
706 }
707 }
708
709 /**
710 * Check if user exists with ther username/email given
711 * $identification - Either email/username
712 */
713 public static function userExists($identification){
714 self::construct();
715 if(self::$config['features']['email_login'] === true){
716 $query = "SELECT COUNT(1) FROM `". self::$config['db']['table'] ."` WHERE `username`=:login OR `email`=:login";
717 }else{
718 $query = "SELECT COUNT(1) FROM `". self::$config['db']['table'] ."` WHERE `username`=:login";
719 }
720 $sql = self::$dbh->prepare($query);
721 $sql->execute(array(
722 ":login" => $identification
723 ));
724 return $sql->fetchColumn() == "0" ? false : true;
725 }
726
727 /**
728 * Fetches data of user in database. Returns a single value or an
729 * array of value according to parameteres given to the function
730 */
731 public static function getUser($what = "*", $user = null){
732 self::construct();
733 if($user == null){
734 $user = self::$user;
735 }
736 if( is_array($what) ){
737 $columns = implode("`,`", $what);
738 $columns = "`{$columns}`";
739 }else{
740 $columns = $what != "*" ? "`$what`" : "*";
741 }
742
743 $sql = self::$dbh->prepare("SELECT {$columns} FROM `". self::$config['db']['table'] ."` WHERE `id` = ? ORDER BY `id` LIMIT 1");
744 $sql->execute(array($user));
745
746 $data = $sql->fetch(PDO::FETCH_ASSOC);
747 if( !is_array($what) ){
748 $data = $what == "*" ? $data : $data[$what];
749 }
750 return $data;
751 }
752
753 /**
754 * Updates the info of user in DB
755 */
756 public static function updateUser($toUpdate = array(), $user = null){
757 self::construct();
758 if( is_array($toUpdate) && !isset($toUpdate['id']) ){
759 if($user == null){
760 $user = self::$user;
761 }
762 $columns = "";
763 foreach($toUpdate as $k => $v){
764 $columns .= "`$k` = :$k, ";
765 }
766 $columns = substr($columns, 0, -2); // Remove last ","
767
768 $sql = self::$dbh->prepare("UPDATE `". self::$config['db']['table'] ."` SET {$columns} WHERE `id`=:id");
769 $sql->bindValue(":id", $user);
770 foreach($toUpdate as $key => $value){
771 $value = htmlspecialchars($value);
772 $sql->bindValue(":$key", $value);
773 }
774 $sql->execute();
775
776 }else{
777 return false;
778 }
779 }
780
781 /**
782 * Returns a string which shows the time since the user has joined
783 */
784 public static function joinedSince($user = null){
785 self::construct();
786 if($user == null){
787 $user = self::$user;
788 }
789 $created = self::getUser("created");
790 $timeFirst = strtotime($created);
791 $timeSecond = strtotime("now");
792 $memsince = $timeSecond - strtotime($created);
793 $regged = date("n/j/Y", strtotime($created));
794
795 if($memsince < 60) {
796 $memfor = $memsince . " Seconds";
797 }else if($memsince < 120){
798 $memfor = floor ($memsince / 60) . " Minute";
799 }else if($memsince < 3600 && $memsince > 120){
800 $memfor = floor($memsince / 60) . " Minutes";
801 }else if($memsince < 7200 && $memsince > 3600){
802 $memfor = floor($memsince / 3600) . " Hour";
803 }else if($memsince < 86400 && $memsince > 3600){
804 $memfor = floor($memsince / 3600) . " Hours";
805 }else if($memsince < 172800){
806 $memfor = floor($memsince / 86400) . " Day";
807 }else if($memsince < 604800 && $memsince > 172800){
808 $memfor = floor($memsince / 86400) . " Days";
809 }else if($memsince < 1209600 && $memsince > 604800){
810 $memfor = floor($memsince / 604800) . " Week";
811 }else if($memsince < 2419200 && $memsince > 1209600){
812 $memfor = floor($memsince / 604800) . " Weeks";
813 }else if($memsince < 4838400){
814 $memfor = floor($memsince / 2419200) . " Month";
815 }else if($memsince < 31536000 && $memsince > 4838400){
816 $memfor = floor($memsince / 2419200) . " Months";
817 }else if($memsince < 63072000){
818 $memfor = floor($memsince / 31536000) . " Year";
819 }else if($memsince > 63072000){
820 $memfor = floor($memsince / 31536000) . " Years";
821 }
822 return (string) $memfor;
823 }
824
825 /**
826 * 2 Step Verification Login Process
827 * ---------------------------------
828 * When user logs in, it checks whether there is a cookie named "logSysdevice" and if there is :
829 * 1. Checks `config` -> `two_step_login` -> `devices_table` table in DB whethere there is a token with value as that of $_COOKIES['logSysdevice']
830 * 2. If there is a row in table, then the "Enter Received Token" form is not shown and is directly logged in if username & pass is correct
831 * If there is not a cookie, then :
832 * 1. The "Enter Received token" form is shown
833 * 2. If the token entered is correct, then a unique string is set as $_COOKIE['logSysdevice'] value and inserted to `config` -> `two_step_login` -> `devices_table` table in DB
834 * 3. The $_COOKIE['logSysdevice'] is set to be stored for 4 months
835 * ---------------------
836 * ^ In the above instructions, the token sending to E-Mail/SMS is not mentioned. Assume that it is done
837 */
838 public static function twoStepLogin($identification = "", $password = "", $remember_me = false){
839 if(isset($_POST['logSys_two_step_login-token']) && isset($_POST['logSys_two_step_login-uid']) && $_SESSION['logSys_two_step_login-first_step'] === '1'){
840 /**
841 * The user's ID and token is got through the form
842 * User = One who is about to log in and is stuck at 2 step verification
843 */
844 $uid = $_POST['logSys_two_step_login-uid'];
845 $token = $_POST['logSys_two_step_login-token'];
846
847 $sql = self::$dbh->prepare("SELECT COUNT(1) FROM `". self::$config['db']['token_table'] ."` WHERE `token` = ? AND `uid` = ?");
848 $sql->execute(array($token, $uid));
849
850 if($sql->fetchColumn() == 0){
851 /**
852 * To prevent user from Brute Forcing the token, we set the
853 * status of the first login step to false,
854 * so that the user would have to login again
855 */
856 $_SESSION['logSys_two_step_login-first_step'] = '0';
857 echo "<h3>Error : Wrong/Invalid Token</h3>";
858 return "invalidToken";
859 }else{
860 /**
861 * Register User's new device if and only if
862 * the user wants to remember the device from
863 * which the user is logging in
864 */
865 if(isset($_POST['logSys_two_step_login-dontask'])){
866 $device_token = self::rand_string(10);
867 $sql = self::$dbh->prepare("INSERT INTO `". self::$config['two_step_login']['devices_table'] ."` (`uid`, `token`, `last_access`) VALUES (?, ?, NOW())");
868 $sql->execute(array($uid, $device_token));
869 setcookie("logSysdevice", $device_token, strtotime(self::$config['two_step_login']['expire']), self::$config['cookies']['path'], self::$config['cookies']['domain']);
870 }else{
871 /**
872 * Verify login for this session
873 */
874 $_SESSION["device_check"] = "1";
875 }
876
877 /**
878 * Revoke token from reusing
879 */
880 $sql = self::$dbh->prepare("DELETE FROM `". self::$config['db']['token_table'] ."` WHERE `token` = ? AND `uid` = ?");
881 $sql->execute(array($token, $uid));
882 self::login(self::getUser("username", $uid), "", isset($_POST['logSys_two_step_login-remember_me']));
883 }
884 return true;
885 }else if($identification != "" && $password != ""){
886 $login = self::login($identification, $password, $remember_me, false);
887 if($login === false){
888 /**
889 * Username/Password wrong
890 */
891 return false;
892 }else if(is_array($login) && $login['status'] == "blocked"){
893 return $login;
894 }else{
895 /**
896 * Get the user ID from FrLS::login()
897 */
898 $uid = $login;
899
900 /**
901 * Check if device is verfied so that 2 Step Verification can be skipped
902 */
903 if(isset($_COOKIE['logSysdevice'])){
904 $sql = self::$dbh->prepare("SELECT 1 FROM `". self::$config['two_step_login']['devices_table'] ."` WHERE `uid` = ? AND `token` = ?");
905 $sql->execute(array($uid, $_COOKIE['logSysdevice']));
906 if($sql->fetchColumn() == "1"){
907 $verfied = true;
908 /**
909 * Update last accessed time
910 */
911 $sql = self::$dbh->prepare("UPDATE `". self::$config['two_step_login']['devices_table'] ."` SET `last_access` = NOW() WHERE `uid` = ? AND `token` = ?");
912 $sql->execute(array($uid, $_COOKIE['logSysdevice']));
913
914 self::login(self::getUser("username", $uid), "", $remember_me);
915 return true;
916 }
917 }
918 /**
919 * Start the 2 Step Verification Process
920 * Do only if callback is present and if
921 * the device is not verified
922 */
923 if(is_callable(self::$config['two_step_login']['send_callback']) && !isset($verified)){
924 /**
925 * The first part of 2 Step Login is completed
926 */
927 $_SESSION['logSys_two_step_login-first_step'] = '1';
928
929 /**
930 * The 2nd parameter depends on `config` -> `two_step_login` -> `numeric`
931 */
932 $token = self::rand_string(self::$config['two_step_login']['token_length'], self::$config['two_step_login']['numeric']);
933
934 /**
935 * Save the token in DB
936 */
937 $sql = self::$dbh->prepare("INSERT INTO `". self::$config['db']['token_table'] ."` (`token`, `uid`, `requested`) VALUES (?, ?, NOW())");
938 $sql->execute(array($token, $uid));
939
940 call_user_func_array(self::$config['two_step_login']['send_callback'], array($uid, $token));
941
942 /**
943 * Display the form
944 */
945 $html = "<form action='". self::curPageURL() ."' method='POST'>
946 <p>". self::$config['two_step_login']['instruction'] ."</p>
947 <label>
948 <p>Token Received</p>
949 <input type='text' name='logSys_two_step_login-token' placeholder='Paste the token here... (case sensitive)' />
950 </label>
951 <label style='display: block;'>
952 <span>Remember this device ?</span>
953 <input type='checkbox' name='logSys_two_step_login-dontask' />
954 </label>
955 <input type='hidden' name='logSys_two_step_login-uid' value='". $uid ."' />
956 ". ($remember_me === true ? "<input type='hidden' name='logSys_two_step_login-remember_me' />" : "") ."
957 <label>
958 <button>Verify</button>
959 </label>
960 </form>";
961 echo $html;
962 return "formDisplay";
963 }else{
964 self::log("two_step_login: Token Callback not present");
965 }
966 }
967 }
968 /**
969 * 2 Step Login is not doing any actions or
970 * hasn't returned anything before. If so,
971 * then return false to indicate that the
972 * function is not doing anything
973 */
974 return false;
975 }
976
977 /**
978 * Returns array of devices that are authorized
979 * to login by user's account credentials
980 */
981 public static function getDevices(){
982 if(self::$loggedIn){
983 $sql = self::$dbh->prepare("SELECT * FROM `". self::$config['two_step_login']['devices_table'] ."` WHERE `uid` = ?");
984 $sql->execute(array(self::$user));
985 return $sql->fetchAll(PDO::FETCH_ASSOC);
986 }else{
987 return false;
988 }
989 }
990
991 /**
992 * Revoke a device
993 */
994 public static function revokeDevice($device_token){
995 if(self::$loggedIn){
996 $sql = self::$dbh->prepare("DELETE FROM `". self::$config['two_step_login']['devices_table'] ."` WHERE `uid` = ? AND `token` = ?");
997 $sql->execute(array(self::$user, $device_token));
998 if(isset($_SESSION['device_check'])){
999 unset($_SESSION['device_check']);
1000 }
1001 return $sql->rowCount() == 1;
1002 }
1003 }
1004
1005 /**
1006 * ---------------------
1007 * Extra Tools/Functions
1008 * ---------------------
1009 */
1010
1011 /**
1012 * Check if E-Mail is valid
1013 */
1014 public static function validEmail($email = ""){
1015 return filter_var($email, FILTER_VALIDATE_EMAIL);
1016 }
1017
1018 /**
1019 * Get the current page URL
1020 */
1021 public static function curPageURL() {
1022 $pageURL = 'http';
1023 if(isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on"){$pageURL .= "s";}
1024 $pageURL .= "://";
1025 if($_SERVER["SERVER_PORT"] != "80") {
1026 $pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
1027 }else{
1028 $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
1029 }
1030 return $pageURL;
1031 }
1032
1033 /**
1034 * Generate a Random String
1035 * $int - Whether numeric string should be output
1036 */
1037 public static function rand_string($length, $int = false) {
1038 $random_str = "";
1039 $chars = $int ? "0516243741506927589" : "subinsblogabcdefghijklmanopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1040 $size = strlen($chars) - 1;
1041 for($i = 0;$i < $length;$i++) {
1042 $random_str .= $chars[rand(0, $size)];
1043 }
1044 return $random_str;
1045 }
1046
1047 /**
1048 * Get the current page path.
1049 * Eg: /mypage, /folder/mypage.php
1050 */
1051 public static function curPage(){
1052 $parts = parse_url(self::curPageURL());
1053 return $parts["path"];
1054 }
1055
1056 /**
1057 * Do a redirect
1058 */
1059 public static function redirect($url, $status = 302){
1060 header("Location: $url", true, $status);
1061 exit;
1062 }
1063
1064 /**
1065 * Any mails need to be sent by logSys goes to here
1066 */
1067 public static function sendMail($email, $subject, $body){
1068 /**
1069 * If there is a callback for email sending, use it else PHP's mail()
1070 */
1071 if(is_callable(self::$config['basic']['email_callback'])){
1072 call_user_func_array(self::$config['basic']['email_callback'], array($email, $subject, $body));
1073 }else{
1074 $headers = array();
1075 $headers[] = "MIME-Version: 1.0";
1076 $headers[] = "Content-type: text/html; charset=iso-8859-1";
1077 $headers[] = "From: ". self::$config['basic']['email'];
1078 $headers[] = "Reply-To: ". self::$config['basic']['email'];
1079 mail($email, $subject, $body, implode("rn", $headers));
1080 }
1081 }
1082
1083 /**
1084 * CSRF Protection
1085 */
1086 public static function csrf($type = ""){
1087 if(!isset($_COOKIE['csrf_token'])){
1088 $csrf_token = self::rand_string(5);
1089 setcookie("csrf_token", $csrf_token, 0, self::$config['cookies']['path'], self::$config['cookies']['domain']);
1090 }else{
1091 $csrf_token = $_COOKIE['csrf_token'];
1092 }
1093 if($type == "s"){
1094 /**
1095 * Output as string
1096 */
1097 return urlencode($csrf_token);
1098 }elseif($type == "g"){
1099 /**
1100 * Output as a GET parameter
1101 */
1102 return "&csrf_token=" . urlencode($csrf_token);
1103 }elseif($type == "i"){
1104 /**
1105 * Output as an input field
1106 */
1107 echo "<input type='hidden' name='csrf_token' value='{$csrf_token}' />";
1108 }else{
1109 /**
1110 * Check CSRF validity
1111 */
1112 if((isset($_POST['csrf_token']) && $_COOKIE['csrf_token'] == $_POST['csrf_token']) || (isset($_GET['csrf_token']) && $_COOKIE['csrf_token'] == $_GET['csrf_token'])){
1113 return true;
1114 }else{
1115 /**
1116 * CSRF Token doesn't match.
1117 */
1118 return false;
1119 }
1120 }
1121 }
1122
1123 /**
1124 * -------------------------
1125 * End Extra Tools/Functions
1126 * -------------------------
1127 */
1128}
1129
1130<?php
1131require "config.php";
1132?>
1133<!DOCTYPE html>
1134<html>
1135 <head>
1136 <title>Change Password</title>
1137 </head>
1138 <body>
1139 <?php
1140 if(isset($_POST['change_password'])){
1141 if(isset($_POST['current_password']) && $_POST['current_password'] != "" && isset($_POST['new_password']) && $_POST['new_password'] != "" && isset($_POST['retype_password']) && $_POST['retype_password'] != "" && isset($_POST['current_password']) && $_POST['current_password'] != ""){
1142
1143 $curpass = $_POST['current_password'];
1144 $new_password = $_POST['new_password'];
1145 $retype_password = $_POST['retype_password'];
1146
1147 if($new_password != $retype_password){
1148 echo "<p><h2>Passwords Doesn't match</h2><p>The passwords you entered didn't match. Try again.</p></p>";
1149 }else if(FrLS::login(FrLS::getUser("username"), "", false, false) == false){
1150 echo "<h2>Current Password Wrong!</h2><p>The password you entered for your account is wrong.</p>";
1151 }else{
1152 $change_password = FrLS::changePassword($new_password);
1153 if($change_password === true){
1154 echo "<h2>Password Changed Successfully</h2>";
1155 }
1156 }
1157 }else{
1158 echo "<p><h2>Password Fields was blank</h2><p>Form fields were left blank</p></p>";
1159 }
1160 }
1161 ?>
1162 <form action="<?php echo FrLS::curPageURL();?>" method='POST'>
1163 <label>
1164 <p>Current Password</p>
1165 <input type='password' name='current_password' />
1166 </label>
1167 <label>
1168 <p>New Password</p>
1169 <input type='password' name='new_password' />
1170 </label>
1171 <label>
1172 <p>Retype New Password</p>
1173 <input type='password' name='retype_password' />
1174 </label>
1175 <button style="display: block;margin-top: 10px;" name='change_password' type='submit'>Change Password</button>
1176 </form>
1177 </body>
1178</html>
1179
1180<?php
1181/**
1182 * For Development Purposes
1183 */
1184ini_set("display_errors", "on");
1185
1186require __DIR__ . "/../../src/class.logsys.php";
1187FrLS::config(array(
1188 "db" => array(
1189 "host" => "localhost",
1190 "port" => 3306,
1191 "username" => "root",
1192 "password" => "backstreetboys",
1193 "name" => "test",
1194 "table" => "users"
1195 ),
1196 "features" => array(
1197 "auto_init" => true
1198 ),
1199 "pages" => array(
1200 "no_login" => array(
1201 "/Francium/logSys/",
1202 "/Francium/logSys/examples/basic/reset.php",
1203 "/Francium/logSys/examples/basic/register.php"
1204 ),
1205 "everyone" => array(
1206 "/Francium/logSys/examples/two-step-login/status.php"
1207 ),
1208 "login_page" => "/Francium/logSys/examples/basic/login.php",
1209 "home_page" => "/Francium/logSys/examples/basic/home.php"
1210 )
1211));
1212
1213<?php
1214require "config.php";
1215if(isset($_POST['action_login'])){
1216 $identification = $_POST['login'];
1217 $password = $_POST['password'];
1218 if($identification == "" || $password == ""){
1219 $msg = array("Error", "Username / Password Wrong !");
1220 }else{
1221 $login = FrLS::login($identification, $password, isset($_POST['remember_me']));
1222 if($login === false){
1223 $msg = array("Error", "Username / Password Wrong !");
1224 }else if(is_array($login) && $login['status'] == "blocked"){
1225 $msg = array("Error", "Too many login attempts. You can attempt login after ". $login['minutes'] ." minutes (". $login['seconds'] ." seconds)");
1226 }
1227 }
1228}
1229?>
1230<html>
1231 <head>
1232 <title>Log In</title>
1233 </head>
1234 <body>
1235 <div class="content">
1236 <h2>Log In</h2>
1237 <?php
1238 if(isset($msg)){
1239 echo "<h2>{$msg[0]}</h2><p>{$msg[1]}</p>";
1240 }
1241 ?>
1242 <form action="login.php" method="POST" style="margin:0px auto;display:table;">
1243 <label>
1244 <p>Username / E-Mail</p>
1245 <input name="login" type="text"/>
1246 </label><br/>
1247 <label>
1248 <p>Password</p>
1249 <input name="password" type="password"/>
1250 </label><br/>
1251 <label>
1252 <p>
1253 <input type="checkbox" name="remember_me"/> Remember Me
1254 </p>
1255 </label>
1256 <div clear></div>
1257 <button style="width:150px;" name="action_login">Log In</button>
1258 </form>
1259 <style>
1260 input[type=text], input[type=password]{
1261 width: 230px;
1262 }
1263 </style>
1264 <p>
1265 <p>Don't have an account ?</p>
1266 <a class="button" href="register.php">Register</a>
1267 </p>
1268 <p>
1269 <p>Forgot Your Password ?</p>
1270 <a class="button" href="reset.php">Reset Password</a>
1271 </p>
1272 </div>
1273 </body>
1274</html>
1275
1276<?php
1277require "config.php";
1278FrLS::logout();
1279?>
1280
1281<?php
1282include "config.php";
1283?>
1284<!DOCTYPE html>
1285<html>
1286 <head></head>
1287 <body>
1288 <div class="content">
1289 <h1>Register</h1>
1290 <form action="register.php" method="POST">
1291 <label>
1292 <input name="username" placeholder="Username" />
1293 </label>
1294 <label>
1295 <input name="email" placeholder="E-Mail" />
1296 </label>
1297 <label>
1298 <input name="pass" type="password" placeholder="Password" />
1299 </label>
1300 <label>
1301 <input name="retyped_password" type="password" placeholder="Retype Password" />
1302 </label>
1303 <label>
1304 <input name="name" placeholder="Name" />
1305 </label>
1306 <label>
1307 <button name="submit">Register</button>
1308 </label>
1309 </form>
1310 <?php
1311 if( isset($_POST['submit']) ){
1312 $username = $_POST['username'];
1313 $email = $_POST['email'];
1314 $password = $_POST['pass'];
1315 $retyped_password = $_POST['retyped_password'];
1316 $name = $_POST['name'];
1317 if( $username == "" || $email == "" || $password == '' || $retyped_password == '' || $name == '' ){
1318 echo "<h2>Fields Left Blank</h2>", "<p>Some Fields were left blank. Please fill up all fields.</p>";
1319 }elseif( !FrLS::validEmail($email) ){
1320 echo "<h2>E-Mail Is Not Valid</h2>", "<p>The E-Mail you gave is not valid</p>";
1321 }elseif( !ctype_alnum($username) ){
1322 echo "<h2>Invalid Username</h2>", "<p>The Username is not valid. Only ALPHANUMERIC characters are allowed and shouldn't exceed 10 characters.</p>";
1323 }elseif($password != $retyped_password){
1324 echo "<h2>Passwords Don't Match</h2>", "<p>The Passwords you entered didn't match</p>";
1325 }else{
1326 $createAccount = FrLS::register($username, $password,
1327 array(
1328 "email" => $email,
1329 "name" => $name,
1330 "created" => date("Y-m-d H:i:s") // Just for testing
1331 )
1332 );
1333 if($createAccount === "exists"){
1334 echo "<label>User Exists.</label>";
1335 }elseif($createAccount === true){
1336 echo "<label>Success. Created account. <a href='login.php'>Log In</a></label>";
1337 }
1338 }
1339 }
1340 ?>
1341 <style>
1342 label{
1343 display: block;
1344 margin-bottom: 5px;
1345 }
1346 </style>
1347 </div>
1348 </body>
1349</html>