Add app passwords (close #15)

master
Skylar Ittner 5 years ago
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) {

Binary file not shown.

@ -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;

@ -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…
Cancel
Save