Add app passwords (close #15)
This commit is contained in:
parent
99f2e07f63
commit
22fb97d0c4
@ -7,7 +7,16 @@
|
||||
*/
|
||||
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
|
||||
$ok = false;
|
||||
if (empty($VARS['apppass']) && ($user->checkPassword($VARS['password']) || $user->checkAppPassword($VARS['password']))) {
|
||||
$ok = true;
|
||||
} else {
|
||||
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
|
||||
$ok = true;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
Log::insert(LogType::API_AUTH_OK, null, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
sendJsonResp($Strings->get("login successful", false), "OK");
|
||||
} else {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
engageRateLimit();
|
||||
$user = User::byUsername($VARS['username']);
|
||||
if ($user->checkPassword($VARS['password'])) {
|
||||
if ((!$user->has2fa() && $user->checkPassword($VARS['password'])) || $user->checkAppPassword($VARS['password'])) {
|
||||
switch ($user->getStatus()->getString()) {
|
||||
case "LOCKED_OR_DISABLED":
|
||||
Log::insert(LogType::API_LOGIN_FAILED, $uid, "Username: " . strtolower($VARS['username']) . ", Key: " . getCensoredKey());
|
||||
|
@ -19,7 +19,8 @@ $APIS = [
|
||||
"load" => "auth.php",
|
||||
"vars" => [
|
||||
"username" => "string",
|
||||
"password" => "string"
|
||||
"password" => "string",
|
||||
"apppass (optional)" => "/[0-1]/"
|
||||
],
|
||||
"keytype" => "AUTH"
|
||||
],
|
||||
|
@ -90,11 +90,13 @@ function checkVars($vars, $or = false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$checkmethod = "is_$val";
|
||||
if ($checkmethod($VARS[$key]) !== true) {
|
||||
$ok[$key] = false;
|
||||
|
||||
if (strpos($val, "/") === 0) {
|
||||
// regex
|
||||
$ok[$key] = preg_match($val, $VARS[$key]) === 1;
|
||||
} else {
|
||||
$ok[$key] = true;
|
||||
$checkmethod = "is_$val";
|
||||
$ok[$key] = !($checkmethod($VARS[$key]) !== true);
|
||||
}
|
||||
}
|
||||
if ($or) {
|
||||
|
BIN
database.mwb
BIN
database.mwb
Binary file not shown.
21
database.sql
21
database.sql
@ -1,5 +1,5 @@
|
||||
-- MySQL Script generated by MySQL Workbench
|
||||
-- Mon 11 Feb 2019 02:58:22 PM MST
|
||||
-- Mon 11 Feb 2019 04:07:57 PM MST
|
||||
-- Model: New Model Version: 1.0
|
||||
-- MySQL Workbench Forward Engineering
|
||||
|
||||
@ -335,6 +335,25 @@ CREATE TABLE IF NOT EXISTS `userloginkeys` (
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Table `apppasswords`
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `apppasswords` (
|
||||
`passid` INT NOT NULL AUTO_INCREMENT,
|
||||
`hash` VARCHAR(255) NOT NULL,
|
||||
`uid` INT NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`passid`, `uid`),
|
||||
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
|
||||
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_apppasswords_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
|
||||
SET SQL_MODE=@OLD_SQL_MODE;
|
||||
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
||||
|
@ -29,4 +29,20 @@ ADD COLUMN `appname` VARCHAR(255) NOT NULL AFTER `uid`;
|
||||
ALTER TABLE `userloginkeys`
|
||||
ADD COLUMN `appicon` TINYTEXT NULL DEFAULT NULL AFTER `appname`;
|
||||
ALTER TABLE `apikeys`
|
||||
ADD COLUMN `type` VARCHAR(45) NOT NULL DEFAULT 'FULL' AFTER `notes`;
|
||||
ADD COLUMN `type` VARCHAR(45) NOT NULL DEFAULT 'FULL' AFTER `notes`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `apppasswords` (
|
||||
`passid` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`hash` VARCHAR(255) NOT NULL,
|
||||
`uid` INT(11) NOT NULL,
|
||||
`description` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`passid`, `uid`),
|
||||
UNIQUE INDEX `passid_UNIQUE` (`passid` ASC),
|
||||
INDEX `fk_apppasswords_accounts1_idx` (`uid` ASC),
|
||||
CONSTRAINT `fk_apppasswords_accounts1`
|
||||
FOREIGN KEY (`uid`)
|
||||
REFERENCES `accounthub`.`accounts` (`uid`)
|
||||
ON DELETE NO ACTION
|
||||
ON UPDATE NO ACTION)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARACTER SET = utf8;
|
11
langs/en/apppasswords.json
Normal file
11
langs/en/apppasswords.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"App Passwords": "App Passwords",
|
||||
"app passwords explained": "Use app passwords instead of your actual password when logging into apps with your {site_name} login. App passwords are required in some places when you have 2-factor authentication enabled.",
|
||||
"app password setup instructions": "Use the username and password below to log in to {app_name}. You'll only be shown this password one time.",
|
||||
"App name": "App name",
|
||||
"Generate password": "Generate password",
|
||||
"Revoke password": "Revoke password",
|
||||
"You don't have any app passwords.": "You don't have any app passwords.",
|
||||
"Done": "Done",
|
||||
"App passwords are not allowed here.": "App passwords are not allowed here."
|
||||
}
|
@ -19,6 +19,7 @@ class User {
|
||||
private $authsecret;
|
||||
private $has2fa = false;
|
||||
private $exists = false;
|
||||
private $apppasswords = [];
|
||||
|
||||
public function __construct(int $uid, string $username = "") {
|
||||
global $database;
|
||||
@ -32,6 +33,7 @@ class User {
|
||||
$this->authsecret = $user['authsecret'];
|
||||
$this->has2fa = !empty($user['authsecret']);
|
||||
$this->exists = true;
|
||||
$this->apppasswords = $database->select('apppasswords', 'hash', ['uid' => $this->uid]);
|
||||
} else {
|
||||
$this->uid = $uid;
|
||||
$this->username = $username;
|
||||
@ -107,6 +109,20 @@ class User {
|
||||
return password_verify($password, $this->passhash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given password against the user's app passwords.
|
||||
* @param string $apppassword
|
||||
* @return bool
|
||||
*/
|
||||
function checkAppPassword(string $apppassword): bool {
|
||||
foreach ($this->apppasswords as $hash) {
|
||||
if (password_verify($apppassword, $hash)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the user's password.
|
||||
* @global $database $database
|
||||
|
@ -93,6 +93,9 @@ if (!empty($_SESSION['check'])) {
|
||||
}
|
||||
} else {
|
||||
$error = $Strings->get("Password incorrect.", false);
|
||||
if ($user->checkAppPassword($_POST['password'])) {
|
||||
$error = $Strings->get("App passwords are not allowed here.", false);
|
||||
}
|
||||
Log::insert(LogType::LOGIN_FAILED, $user);
|
||||
}
|
||||
break;
|
||||
|
@ -10,6 +10,12 @@ use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\QrCode;
|
||||
|
||||
$user = new User($_SESSION['uid']);
|
||||
|
||||
if (!empty($_GET['delpass'])) {
|
||||
if ($database->has("apppasswords", ["AND" => ["uid" => $_SESSION['uid'], "passid" => $_GET['delpass']]])) {
|
||||
$database->delete("apppasswords", ["AND" => ["uid" => $_SESSION['uid'], "passid" => $_GET['delpass']]]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@ -138,5 +144,87 @@ $user = new User($_SESSION['uid']);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-10 col-md-6 col-lg-4 col-xl-4">
|
||||
<div class="card mb-4">
|
||||
<?php
|
||||
if (!empty($_GET['apppassword']) && $_GET['apppassword'] == "generate" && !empty($_POST['desc'])) {
|
||||
$code = strtoupper(substr(md5(mt_rand() . uniqid("", true)), 0, 20));
|
||||
$desc = htmlspecialchars($_POST['desc']);
|
||||
$chunk_code = str_replace(" ", "-", trim(chunk_split($code, 5, ' ')));
|
||||
$database->insert('apppasswords', ['uid' => $_SESSION['uid'], 'hash' => password_hash($chunk_code, PASSWORD_DEFAULT), 'description' => $desc]);
|
||||
?>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-shield-alt"></i> <?php $Strings->get("App Passwords"); ?></h5>
|
||||
<hr />
|
||||
|
||||
<?php $Strings->build("app password setup instructions", ["app_name" => $desc]); ?>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-baseline">
|
||||
<div><?php $Strings->get("username"); ?>:</div>
|
||||
<div class="text-monospace text-right"><?php echo $_SESSION['username']; ?></div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-baseline">
|
||||
<div><?php $Strings->get("password"); ?></div>
|
||||
<div class="text-monospace text-right"><?php echo $chunk_code; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a class="btn btn-success btn-block" href="app.php?page=security"><?php $Strings->get("Done"); ?></a>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
$activecodes = $database->select("apppasswords", ["passid", "description"], ["uid" => $_SESSION['uid']]);
|
||||
?>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="fas fa-shield-alt"></i> <?php $Strings->get("App Passwords"); ?></h5>
|
||||
<hr />
|
||||
<p class="card-text">
|
||||
<?php $Strings->build("app passwords explained", ["site_name" => $SETTINGS['site_title']]); ?>
|
||||
</p>
|
||||
<form action="app.php?page=security&apppassword=generate" method="POST">
|
||||
<input type="text" name="desc" class="form-control" placeholder="<?php $Strings->get("App name"); ?>" required />
|
||||
<button class="btn btn-success btn-block mt-2" type="submit">
|
||||
<?php $Strings->get("Generate password"); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item">
|
||||
<b><?php $Strings->get("App Passwords"); ?></b>
|
||||
</div>
|
||||
<?php
|
||||
if (count($activecodes) > 0) {
|
||||
foreach ($activecodes as $c) {
|
||||
?>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="">
|
||||
<?php echo $c['description']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-danger btn-sm m-1" href="app.php?page=security&delpass=<?php echo $c['passid']; ?>" data-toggle="tooltip" data-placement="bottom" title="<?php $Strings->get("Revoke password"); ?>">
|
||||
<i class='fas fa-trash'></i><noscript> <?php $Strings->get("Revoke password"); ?></noscript>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
} else {
|
||||
?>
|
||||
<div class="list-group-item">
|
||||
<?php $Strings->get("You don't have any app passwords."); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
Loading…
x
Reference in New Issue
Block a user