/** * 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; }