protected function processLogin(array $post = [])
{
$state = State::instance();
if (empty($post['username']) || empty($post['passphrase'])) {
$this->lens('login', ['post_response' => ['message' => \__('Please fill out the form entirely'), 'status' => 'error']]);
}
$airBrake = Gears::get('AirBrake');
if (IDE_HACKS) {
$airBrake = new AirBrake();
}
if ($airBrake->failFast($post['username'], $_SERVER['REMOTE_ADDR'])) {
$this->lens('login', ['post_response' => ['message' => \__('You are doing that too fast. Please wait a few seconds and try again.'), 'status' => 'error']]);
} elseif (!$airBrake->getFastExit()) {
$delay = $airBrake->getDelay($post['username'], $_SERVER['REMOTE_ADDR']);
if ($delay > 0) {
\usleep($delay * 1000);
}
}
try {
$userID = $this->airship_auth->login($post['username'], new HiddenString($post['passphrase']));
} catch (InvalidMessage $e) {
$this->log('InvalidMessage Exception on Login; probable cause: password column was corrupted', LogLevel::CRITICAL, ['exception' => \Airship\throwableToArray($e)]);
$this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
}
if (!empty($userID)) {
$userID = (int) $userID;
$user = $this->acct->getUserAccount($userID);
if ($user['enable_2factor']) {
if (empty($post['two_factor'])) {
$post['two_factor'] = '';
}
$gauth = $this->twoFactorPreamble($userID);
$checked = $gauth->validateCode($post['two_factor'], \time());
if (!$checked) {
$fails = $airBrake->getFailedLoginAttempts($post['username'], $_SERVER['REMOTE_ADDR']) + 1;
// Instead of the password, seal a timestamped and
// signed message saying the password was correct.
// We use a signature with a key local to this Airship
// so attackers can't just spam a string constant to
// make the person decrypting these strings freak out
// and assume the password was compromised.
//
// False positives are bad. This gives the sysadmin a
// surefire way to reliably verify that a log entry is
// due to two-factor authentication failing.
$message = '**Note: The password was correct; ' . ' invalid 2FA token was provided.** ' . (new \DateTime('now'))->format(\AIRSHIP_DATE_FORMAT);
$signed = Base64UrlSafe::encode(Asymmetric::sign($message, $state->keyring['notary.online_signing_key'], true));
$airBrake->registerLoginFailure($post['username'], $_SERVER['REMOTE_ADDR'], $fails, new HiddenString($signed . $message));
$this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
}
}
if ($user['session_canary']) {
$_SESSION['session_canary'] = $user['session_canary'];
} elseif ($this->config('password-reset.logout')) {
$_SESSION['session_canary'] = $this->acct->createSessionCanary($userID);
}
// Regenerate session ID:
Session::regenerate(true);
$_SESSION['userid'] = (int) $userID;
if (!empty($post['remember'])) {
$autoPilot = Gears::getName('AutoPilot');
if (IDE_HACKS) {
$autoPilot = new AutoPilot();
}
$httpsOnly = (bool) $autoPilot::isHTTPSConnection();
Cookie::setcookie('airship_token', Symmetric::encrypt($this->airship_auth->createAuthToken($userID), $state->keyring['cookie.encrypt_key']), \time() + ($state->universal['long-term-auth-expire'] ?? self::DEFAULT_LONGTERMAUTH_EXPIRE), '/', $state->universal['session_config']['cookie_domain'] ?? '', $httpsOnly ?? false, true);
}
\Airship\redirect($this->airship_cabin_prefix);
} else {
$fails = $airBrake->getFailedLoginAttempts($post['username'], $_SERVER['REMOTE_ADDR']) + 1;
// If the server is setup (with an EncryptionPublicKey) and the
// number of failures is above the log threshold, this will
// encrypt the password guess with the public key so that only
// the person in possession of the secret key can decrypt it.
$airBrake->registerLoginFailure($post['username'], $_SERVER['REMOTE_ADDR'], $fails, new HiddenString($post['passphrase']));
$this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
}
}