closed #138 added new cookie based login
This commit is contained in:
@@ -13,6 +13,10 @@ use DB;
|
||||
|
||||
class Controller {
|
||||
|
||||
// cookie specific keys (names)
|
||||
const COOKIE_NAME_STATE = 'cookie';
|
||||
const COOKIE_PREFIX_CHARACTER = 'char';
|
||||
|
||||
/**
|
||||
* @var \Base
|
||||
*/
|
||||
@@ -114,6 +118,200 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get cookies "state" information
|
||||
* -> whether user accepts cookies
|
||||
* @return bool
|
||||
*/
|
||||
protected function getCookieState(){
|
||||
return (bool)count( $this->getCookieByName(self::COOKIE_NAME_STATE) );
|
||||
}
|
||||
|
||||
/**
|
||||
* search for existing cookies
|
||||
* -> either a specific cookie by its name
|
||||
* -> or get multiple cookies by their name (search by prefix)
|
||||
* @param $cookieName
|
||||
* @param bool $prefix
|
||||
* @return array
|
||||
*/
|
||||
protected function getCookieByName($cookieName, $prefix = false){
|
||||
$data = [];
|
||||
|
||||
if(!empty($cookieName)){
|
||||
$cookieData = (array)$this->getF3()->get('COOKIE');
|
||||
if($prefix === true){
|
||||
// look for multiple cookies with same prefix
|
||||
foreach($cookieData as $name => $value){
|
||||
if(strpos($name, $cookieName) === 0){
|
||||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
}elseif( isset($cookieData[$cookieName]) ){
|
||||
// look for a single cookie
|
||||
$data[$cookieName] = $cookieData[$cookieName];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update logged in cookie by character model
|
||||
* -> store validation data in DB
|
||||
* @param Model\CharacterModel $character
|
||||
*/
|
||||
protected function setLoginCookie(Model\CharacterModel $character){
|
||||
|
||||
if( $this->getCookieState() ){
|
||||
$expireSeconds = (int) $this->getF3()->get('PATHFINDER.LOGIN.COOKIE_EXPIRE');
|
||||
$expireSeconds *= 24 * 60 * 60;
|
||||
|
||||
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
|
||||
$expireTime = new \DateTime('now', $timezone);
|
||||
|
||||
// add cookie expire time
|
||||
$expireTime->add(new \DateInterval('PT' . $expireSeconds . 'S'));
|
||||
|
||||
// unique "selector" -> to facilitate database look-ups (small size)
|
||||
// -> This is preferable to simply using the database id field,
|
||||
// which leaks the number of active users on the application
|
||||
$selector = bin2hex(mcrypt_create_iv(12, MCRYPT_DEV_URANDOM));
|
||||
|
||||
// generate unique "validator" (strong encryption)
|
||||
// -> plaintext set to user (cookie), hashed version of this in DB
|
||||
$size = mcrypt_get_iv_size(MCRYPT_CAST_256, MCRYPT_MODE_CFB);
|
||||
$validator = bin2hex(mcrypt_create_iv($size, MCRYPT_DEV_URANDOM));
|
||||
|
||||
// generate unique cookie token
|
||||
$token = hash('sha256', $validator);
|
||||
|
||||
// get unique cookie name for this character
|
||||
$name = md5($character->name);
|
||||
|
||||
$authData = [
|
||||
'characterId' => $character,
|
||||
'selector' => $selector,
|
||||
'token' => $token,
|
||||
'expires' => $expireTime->format('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$authenticationModel = $character->rel('characterTokens');
|
||||
$authenticationModel->copyfrom($authData);
|
||||
$authenticationModel->save();
|
||||
|
||||
$cookieValue = implode(':', [$selector, $validator]);
|
||||
|
||||
// get cookie name -> save new one OR update existing cookie
|
||||
$cookieName = 'COOKIE.' . self::COOKIE_PREFIX_CHARACTER . '_' . $name;
|
||||
$this->getF3()->set($cookieName, $cookieValue, $expireSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get characters from given cookie data
|
||||
* -> validate cookie data
|
||||
* -> validate characters
|
||||
* @param array $cookieData
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getCookieCharacters($cookieData = []){
|
||||
$characters = [];
|
||||
|
||||
if(
|
||||
$this->getCookieState() &&
|
||||
!empty($cookieData)
|
||||
){
|
||||
/**
|
||||
* @var $characterAuth Model\CharacterAuthenticationModel
|
||||
*/
|
||||
$characterAuth = Model\BasicModel::getNew('CharacterAuthenticationModel');
|
||||
|
||||
$timezone = new \DateTimeZone( $this->getF3()->get('TZ') );
|
||||
$currentTime = new \DateTime('now', $timezone);
|
||||
|
||||
foreach($cookieData as $name => $value){
|
||||
// remove invalid cookies
|
||||
$invalidCookie = false;
|
||||
|
||||
$data = explode(':', $value);
|
||||
if(count($data) === 2){
|
||||
// cookie data is well formatted
|
||||
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1]);
|
||||
|
||||
// validate expire data
|
||||
// validate token
|
||||
if(
|
||||
!$characterAuth->dry() &&
|
||||
strtotime($characterAuth->expires) >= $currentTime->getTimestamp() &&
|
||||
hash_equals($characterAuth->token, hash('sha256', $data[1]))
|
||||
){
|
||||
// cookie information is valid
|
||||
// -> try to update character information from CREST
|
||||
// e.g. Corp has changed, this also ensures valid "access_token"
|
||||
/**
|
||||
* @var $character Model\CharacterModel
|
||||
*/
|
||||
$character = $characterAuth->characterId;
|
||||
$updateStatus = $character->updateFromCrest();
|
||||
|
||||
// check if character still has user (is not the case of "ownerHash" changed
|
||||
// check if character is still authorized to log in (e.g. corp/ally or config has changed
|
||||
// -> do NOT remove cookie on failure. This can be a temporary problem (e.g. CREST is down,..)
|
||||
if(
|
||||
empty($updateStatus) &&
|
||||
$character->hasUserCharacter() &&
|
||||
$character->isAuthorized()
|
||||
){
|
||||
$characters[$name] = $character;
|
||||
}
|
||||
}else{
|
||||
$invalidCookie = true;
|
||||
}
|
||||
$characterAuth->reset();
|
||||
}else{
|
||||
$invalidCookie = true;
|
||||
}
|
||||
|
||||
// remove invalid cookie
|
||||
if($invalidCookie){
|
||||
$this->getF3()->clear('COOKIE.' . $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $characters;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether a user is currently logged in
|
||||
* @param \Base $f3
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkLogTimer($f3){
|
||||
$loginCheck = false;
|
||||
|
||||
if($f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) > 0){
|
||||
// check logIn time
|
||||
$logInTime = new \DateTime();
|
||||
$logInTime->setTimestamp( $f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) );
|
||||
$now = new \DateTime();
|
||||
|
||||
$timeDiff = $now->diff($logInTime);
|
||||
|
||||
$minutes = $timeDiff->days * 60 * 24 * 60;
|
||||
$minutes += $timeDiff->h * 60;
|
||||
$minutes += $timeDiff->i;
|
||||
|
||||
if($minutes <= $f3->get('PATHFINDER.TIMER.LOGGED')){
|
||||
$loginCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $loginCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current character model
|
||||
* @param int $ttl
|
||||
@@ -127,12 +325,15 @@ class Controller {
|
||||
$characterId = (int)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTER_ID);
|
||||
if($characterId){
|
||||
/**
|
||||
* @var $characterModel \Model\CharacterModel
|
||||
* @var $characterModel Model\CharacterModel
|
||||
*/
|
||||
$characterModel = Model\BasicModel::getNew('CharacterModel');
|
||||
$characterModel->getById($characterId, $ttl);
|
||||
|
||||
if( !$characterModel->dry() ){
|
||||
if(
|
||||
!$characterModel->dry() &&
|
||||
$characterModel->hasUserCharacter()
|
||||
){
|
||||
$character = &$characterModel;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user