/**
* Authenticate a user by a long-term authentication token (e.g. a cookie).
*
* @param string $token
* @return mixed int
* @throws LongTermAuthAlert
*/
public function loginByToken(string $token = '') : int
{
$table = $this->db->escapeIdentifier($this->tableConfig['table']['longterm']);
$f = ['selector' => $this->db->escapeIdentifier($this->tableConfig['fields']['longterm']['selector']), 'userid' => $this->tableConfig['fields']['longterm']['userid'], 'validator' => $this->tableConfig['fields']['longterm']['validator']];
try {
$decoded = Base64::decode($token);
} catch (\RangeException $ex) {
throw new LongTermAuthAlert(\trk('errors.security.invalid_persistent_token'));
}
if ($decoded === false) {
throw new LongTermAuthAlert(\trk('errors.security.invalid_persistent_token'));
} elseif (Binary::safeStrlen($decoded) !== self::LONG_TERM_AUTH_BYTES) {
throw new LongTermAuthAlert(\trk('errors.security.invalid_persistent_token'));
}
\Sodium\memzero($token);
$sel = Binary::safeSubstr($decoded, 0, self::SELECTOR_BYTES);
$val = CryptoUtil::raw_hash(Binary::safeSubstr($decoded, self::SELECTOR_BYTES));
\Sodium\memzero($decoded);
$record = $this->db->row('SELECT * FROM ' . $table . ' WHERE ' . $f['selector'] . ' = ?', Base64::encode($sel));
if (empty($record)) {
\Sodium\memzero($val);
throw new LongTermAuthAlert(\trk('errors.security.invalid_persistent_token'));
}
$stored = \Sodium\hex2bin($record[$f['validator']]);
\Sodium\memzero($record[$f['validator']]);
if (!\hash_equals($stored, $val)) {
\Sodium\memzero($val);
\Sodium\memzero($stored);
throw new LongTermAuthAlert(\trk('errors.security.invalid_persistent_token'));
}
\Sodium\memzero($stored);
\Sodium\memzero($val);
$userID = (int) $record[$f['userid']];
$_SESSION['session_canary'] = $this->db->cell('SELECT session_canary FROM airship_users WHERE userid = ?', $userID);
return $userID;
}