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
|
110
index.php
110
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>
|
||||
<!--
|
||||
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>
|
||||
<head>
|
||||
<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>
|
||||
<body>
|
||||
<div class="container">
|
||||
<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
|
||||
// put your code here
|
||||
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>
|
||||
</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
|
||||
require __DIR__ . '/settings.php';
|
||||
|
||||
require __DIR__ . '/lang/' . LANGUAGE . ".php";
|
||||
|
||||
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>");
|
||||
}
|
||||
@ -65,6 +67,20 @@ function is_empty($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 /!\
|
||||
* @param string $username Username, saved in lowercase.
|
||||
@ -113,6 +129,41 @@ function user_exists($username) {
|
||||
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.
|
||||
* @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");
|
||||
|
||||
// Used to identify the system in OTP and other places
|
||||
define("SYSTEM_NAME", "Netsyms SSO Demo");
|
||||
|
||||
define("COPYRIGHT_NAME", "Netsyms Technologies");
|
||||
|
||||
// For supported values, see http://php.net/manual/en/timezones.php
|
||||
@ -26,5 +29,8 @@ define("TIMEZONE", "America/Denver");
|
||||
// Base URL for site links.
|
||||
define('URL', 'http://localhost:8000/');
|
||||
|
||||
// See lang folder for language options
|
||||
define('LANGUAGE', "en_us");
|
||||
|
||||
// Maximum number of rows to get in a query.
|
||||
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