Create login system, add i18n, add 2fa auth
This commit is contained in:
parent
b16da81513
commit
2caec9b758
BIN
database.mwb
BIN
database.mwb
Binary file not shown.
6
home.php
Normal file
6
home.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/required.php";
|
||||||
|
|
||||||
|
dieifnotloggedin();
|
||||||
|
?>
|
||||||
|
Home
|
116
index.php
116
index.php
@ -1,17 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/required.php";
|
||||||
|
|
||||||
|
require_once __DIR__ . "/lib/login.php";
|
||||||
|
|
||||||
|
/* Authenticate user */
|
||||||
|
$userpass_ok = false;
|
||||||
|
$multiauth = false;
|
||||||
|
if ($VARS['progress'] == "1") {
|
||||||
|
if (authenticate_user($VARS['username'], $VARS['password'])) {
|
||||||
|
switch (get_account_status($VARS['username'])) {
|
||||||
|
case "LOCKED_OR_DISABLED":
|
||||||
|
$alert = lang("account locked", false);
|
||||||
|
break;
|
||||||
|
case "TERMINATED":
|
||||||
|
$alert = lang("account terminated", false);
|
||||||
|
break;
|
||||||
|
case "CHANGE_PASSWORD":
|
||||||
|
$alert = lang("password expired", false);
|
||||||
|
case "NORMAL":
|
||||||
|
$userpass_ok = true;
|
||||||
|
break;
|
||||||
|
case "ALERT_ON_ACCESS":
|
||||||
|
sendAlertEmail($VARS['username']);
|
||||||
|
$userpass_ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($userpass_ok) {
|
||||||
|
if (userHasTOTP($VARS['username'])) {
|
||||||
|
$multiauth = true;
|
||||||
|
} else {
|
||||||
|
doLoginUser($VARS['username']);
|
||||||
|
header('Location: home.php');
|
||||||
|
die("Logged in, go to home.php");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$alert = lang("login incorrect", false);
|
||||||
|
}
|
||||||
|
} else if ($VARS['progress'] == "2") {
|
||||||
|
if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
|
||||||
|
doLoginUser($VARS['username']);
|
||||||
|
header('Location: home.php');
|
||||||
|
die("Logged in, go to home.php");
|
||||||
|
} else {
|
||||||
|
$alert = lang("2fa incorrect", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<!--
|
|
||||||
To change this license header, choose License Headers in Project Properties.
|
|
||||||
To change this template file, choose Tools | Templates
|
|
||||||
and open the template in the editor.
|
|
||||||
-->
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title></title>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" contgreent="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title><?php echo SITE_TITLE; ?></title>
|
||||||
|
|
||||||
|
<link href="static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="static/css/app.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<?php
|
<div class="container">
|
||||||
// put your code here
|
<div class="row">
|
||||||
?>
|
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
|
||||||
|
<div>
|
||||||
|
<img class="img-responsive banner-image" src="static/img/banner.png" />
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-primary">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title"><?php lang("sign in"); ?></h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<form action="" method="POST">
|
||||||
|
<?php
|
||||||
|
if (!is_empty($alert)) {
|
||||||
|
?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<?php echo $alert; ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($multiauth != true) {
|
||||||
|
?>
|
||||||
|
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" /><br />
|
||||||
|
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" /><br />
|
||||||
|
<input type="hidden" name="progress" value="1" />
|
||||||
|
<?php
|
||||||
|
} else if ($multiauth) {
|
||||||
|
?>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<?php lang("2fa prompt"); ?>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" /><br />
|
||||||
|
<input type="hidden" name="progress" value="2" />
|
||||||
|
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<?php lang("continue"); ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="static/js/jquery-3.2.1.min.js"></script>
|
||||||
|
<script src="static/js/bootstrap.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
15
lang/en_us.php
Normal file
15
lang/en_us.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
define("STRINGS", [
|
||||||
|
"sign in" => "Sign In",
|
||||||
|
"username" => "Username",
|
||||||
|
"password" => "Password",
|
||||||
|
"continue" => "Continue",
|
||||||
|
"authcode" => "Authentication code",
|
||||||
|
"2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
|
||||||
|
"2fa incorrect" => "Authentication code incorrect.",
|
||||||
|
"login incorrect" => "Login incorrect.",
|
||||||
|
"account locked" => "This account has been disabled. Contact technical support.",
|
||||||
|
"password expired" => "You must change your password before continuing.",
|
||||||
|
"account terminated" => "Account terminated. Access denied."
|
||||||
|
]);
|
77
lib/login.php
Normal file
77
lib/login.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Base32\Base32;
|
||||||
|
use OTPHP\TOTP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an alert email to the system admin
|
||||||
|
*
|
||||||
|
* Used when an account with the status ALERT_ON_ACCESS logs in
|
||||||
|
* @param String $username the account username
|
||||||
|
*/
|
||||||
|
function sendAlertEmail($username) {
|
||||||
|
// TODO: add email code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup $_SESSION values to log in a user
|
||||||
|
* @param string $username
|
||||||
|
*/
|
||||||
|
function doLoginUser($username) {
|
||||||
|
global $database;
|
||||||
|
$userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
|
||||||
|
$_SESSION['username'] = $username;
|
||||||
|
$_SESSION['uid'] = $userinfo['uid'];
|
||||||
|
$_SESSION['email'] = $userinfo['email'];
|
||||||
|
$_SESSION['realname'] = $userinfo['realname'];
|
||||||
|
$_SESSION['loggedin'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user has TOTP setup
|
||||||
|
* @global $database $database
|
||||||
|
* @param string $username
|
||||||
|
* @return boolean true if TOTP secret exists, else false
|
||||||
|
*/
|
||||||
|
function userHasTOTP($username) {
|
||||||
|
global $database;
|
||||||
|
$secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
|
||||||
|
if (is_empty($secret)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and store a TOTP secret for the given user.
|
||||||
|
* @param string $username
|
||||||
|
* @return string OTP provisioning URI (for generating a QR code)
|
||||||
|
*/
|
||||||
|
function newTOTP($username) {
|
||||||
|
global $database;
|
||||||
|
$secret = random_bytes(20);
|
||||||
|
$encoded_secret = Base32::encode($secret);
|
||||||
|
$userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
|
||||||
|
$totp = new TOTP($userdata['email'], $encoded_secret);
|
||||||
|
$totp->setIssuer(SYSTEM_NAME);
|
||||||
|
$database->update('accounts', ['authsecret' => $encoded_secret], ['username' => $username]);
|
||||||
|
return $totp->getProvisioningUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a TOTP multiauth code
|
||||||
|
* @global $database
|
||||||
|
* @param string $username
|
||||||
|
* @param int $code
|
||||||
|
* @return boolean true if it's legit, else false
|
||||||
|
*/
|
||||||
|
function verifyTOTP($username, $code) {
|
||||||
|
global $database;
|
||||||
|
$userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
|
||||||
|
if (is_empty($userdata['authsecret'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$totp = new TOTP(null, $userdata['authsecret']);
|
||||||
|
echo $userdata['authsecret'] . ", " . $totp->now() . ", " . $code;
|
||||||
|
return $totp->verify($code);
|
||||||
|
}
|
51
required.php
51
required.php
@ -13,6 +13,8 @@ require __DIR__ . '/vendor/autoload.php';
|
|||||||
// Settings file
|
// Settings file
|
||||||
require __DIR__ . '/settings.php';
|
require __DIR__ . '/settings.php';
|
||||||
|
|
||||||
|
require __DIR__ . '/lang/' . LANGUAGE . ".php";
|
||||||
|
|
||||||
function sendError($error) {
|
function sendError($error) {
|
||||||
die("<!DOCTYPE html><html><head><title>Error</title></head><body><h1 style='color: red; font-family: sans-serif; font-size:100%;'>" . htmlspecialchars($error) . "</h1></body></html>");
|
die("<!DOCTYPE html><html><head><title>Error</title></head><body><h1 style='color: red; font-family: sans-serif; font-size:100%;'>" . htmlspecialchars($error) . "</h1></body></html>");
|
||||||
}
|
}
|
||||||
@ -65,6 +67,20 @@ function is_empty($str) {
|
|||||||
return (is_null($str) || !isset($str) || $str == '');
|
return (is_null($str) || !isset($str) || $str == '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lang($key, $echo = true) {
|
||||||
|
if (array_key_exists($key, STRINGS)) {
|
||||||
|
$str = STRINGS[$key];
|
||||||
|
} else {
|
||||||
|
$str = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($echo) {
|
||||||
|
echo $str;
|
||||||
|
} else {
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a user to the system. /!\ Assumes input is OK /!\
|
* Add a user to the system. /!\ Assumes input is OK /!\
|
||||||
* @param string $username Username, saved in lowercase.
|
* @param string $username Username, saved in lowercase.
|
||||||
@ -113,6 +129,41 @@ function user_exists($username) {
|
|||||||
return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
|
return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the given credentials against the database.
|
||||||
|
* @param string $username
|
||||||
|
* @param string $password
|
||||||
|
* @return boolean True if OK, else false
|
||||||
|
*/
|
||||||
|
function authenticate_user($username, $password) {
|
||||||
|
global $database;
|
||||||
|
if (is_empty($username) || is_empty($password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user_exists($username)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
|
||||||
|
return (comparePassword($password, $hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_account_status($username) {
|
||||||
|
global $database;
|
||||||
|
$statuscode = $database->select('accounts', [
|
||||||
|
'[>]acctstatus' => [
|
||||||
|
'acctstatus' => 'statusid'
|
||||||
|
]
|
||||||
|
], [
|
||||||
|
'accounts.acctstatus',
|
||||||
|
'acctstatus.statuscode'
|
||||||
|
], [
|
||||||
|
'username' => $username,
|
||||||
|
"LIMIT" => 1
|
||||||
|
]
|
||||||
|
)[0]['statuscode'];
|
||||||
|
return $statuscode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the given credentials to see if they're legit.
|
* Checks the given credentials to see if they're legit.
|
||||||
* @param string $username
|
* @param string $username
|
||||||
|
@ -18,6 +18,9 @@ define("LDAP_BASEDN", "ou=users,dc=example,dc=com");
|
|||||||
|
|
||||||
define("SITE_TITLE", "Netsyms Business Apps :: Single Sign On");
|
define("SITE_TITLE", "Netsyms Business Apps :: Single Sign On");
|
||||||
|
|
||||||
|
// Used to identify the system in OTP and other places
|
||||||
|
define("SYSTEM_NAME", "Netsyms SSO Demo");
|
||||||
|
|
||||||
define("COPYRIGHT_NAME", "Netsyms Technologies");
|
define("COPYRIGHT_NAME", "Netsyms Technologies");
|
||||||
|
|
||||||
// For supported values, see http://php.net/manual/en/timezones.php
|
// For supported values, see http://php.net/manual/en/timezones.php
|
||||||
@ -26,5 +29,8 @@ define("TIMEZONE", "America/Denver");
|
|||||||
// Base URL for site links.
|
// Base URL for site links.
|
||||||
define('URL', 'http://localhost:8000/');
|
define('URL', 'http://localhost:8000/');
|
||||||
|
|
||||||
|
// See lang folder for language options
|
||||||
|
define('LANGUAGE', "en_us");
|
||||||
|
|
||||||
// Maximum number of rows to get in a query.
|
// Maximum number of rows to get in a query.
|
||||||
define("QUERY_LIMIT", 1000);
|
define("QUERY_LIMIT", 1000);
|
3
static/css/app.css
Normal file
3
static/css/app.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.banner-image {
|
||||||
|
margin: 2em 0em;
|
||||||
|
}
|
11
static/css/bootstrap.min.css
vendored
Normal file
11
static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2337
static/css/font-awesome.css
vendored
Normal file
2337
static/css/font-awesome.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
static/css/font-awesome.min.css
vendored
Normal file
4
static/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
static/fonts/FontAwesome.otf
Normal file
BIN
static/fonts/FontAwesome.otf
Normal file
Binary file not shown.
BIN
static/fonts/fontawesome-webfont.eot
Normal file
BIN
static/fonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
static/fonts/fontawesome-webfont.svg
Normal file
2671
static/fonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
BIN
static/fonts/fontawesome-webfont.ttf
Normal file
BIN
static/fonts/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
static/fonts/fontawesome-webfont.woff
Normal file
BIN
static/fonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
static/fonts/fontawesome-webfont.woff2
Normal file
BIN
static/fonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
BIN
static/img/banner.png
Normal file
BIN
static/img/banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
110
static/img/banner.svg
Normal file
110
static/img/banner.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
7
static/js/bootstrap.min.js
vendored
Normal file
7
static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
static/js/jquery-3.2.1.min.js
vendored
Normal file
4
static/js/jquery-3.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
32
totp_test.php
Normal file
32
totp_test.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require __DIR__ . "/required.php";
|
||||||
|
require __DIR__ . "/lib/login.php";
|
||||||
|
|
||||||
|
use Endroid\QrCode\QrCode;
|
||||||
|
use OTPHP\TOTP;
|
||||||
|
|
||||||
|
if ($_GET['show'] == '1') {
|
||||||
|
|
||||||
|
$totp = new TOTP(
|
||||||
|
"admin@netsyms.com", // The label (string)
|
||||||
|
"ZBUJDTW5D5E6KBMDICAJSKRCX6VGQZCZ" // The secret encoded in base 32 (string)
|
||||||
|
);
|
||||||
|
|
||||||
|
echo "Current OTP: " . $totp->now();
|
||||||
|
|
||||||
|
die();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$user = "skylarmt";
|
||||||
|
|
||||||
|
$totp = newTOTP($user);
|
||||||
|
|
||||||
|
// Create a QR code
|
||||||
|
$qrCode = new QrCode($totp);
|
||||||
|
$qrCode->setSize(300);
|
||||||
|
|
||||||
|
// now we can output the QR code
|
||||||
|
header('Content-Type: ' . $qrCode->getContentType(QrCode::IMAGE_TYPE_PNG));
|
||||||
|
$qrCode->render(null, 'png');
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user