Created
January 29, 2020 07:35
-
-
Save ssv445/fb570ebec2e72dbc5e55d7c09b1aa976 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Sk\Session; | |
use App\Sk\SkApi; | |
use Carbon\Carbon; | |
use App\Sk\SkModel; | |
use App\Sk\SkPayload; | |
use App\Sk\User\User; | |
use App\Sk\SkException; | |
use Webpatser\Uuid\Uuid; | |
use WhichBrowser\Parser; | |
use App\Sk\Session\Session; | |
use Illuminate\Support\Str; | |
use App\Sk\Avails\AvailLink; | |
use Illuminate\Support\Facades\App; | |
use App\Sk\Session\SessionValidation; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Database\Eloquent\SoftDeletes; | |
use Illuminate\Database\Eloquent\ModelNotFoundException; | |
class Session extends SkModel | |
{ | |
use SoftDeletes; | |
/** | |
* The table associated with the model. | |
* | |
* @var string | |
*/ | |
protected $table = 'sessions'; | |
/** | |
* The attributes that are mass assignable. | |
* | |
* @var array | |
*/ | |
protected $fillable = [ | |
'key', | |
// 'secret', | |
'user_id', | |
'otp', | |
'ip_address', | |
'platform', | |
'platform_signature', | |
'last_active_at', | |
'mode' | |
]; | |
protected $hidden = [ | |
'otp', | |
'secret', | |
'created_at', | |
'deleted_at', | |
'updated_at', | |
'user' | |
]; | |
/** | |
* The primary key for the model. | |
* | |
* @var string | |
*/ | |
protected $primaryKey = 'key'; | |
/** | |
* The attributes that should be mutated to dates. | |
* | |
* @var array | |
*/ | |
protected $dates = ['last_active_at']; | |
/** | |
* Indicates if the IDs are auto-incrementing. | |
* | |
* @var bool | |
*/ | |
public $incrementing = false; | |
public function getKeyAttribute() | |
{ | |
if (empty($this->attributes['key'])) { | |
return $this->attributes['key'] = (string) Uuid::generate(); | |
} | |
return $this->attributes['key']; | |
} | |
public function setOtpAttribute($value) | |
{ | |
$this->attributes['otp'] = doHash($value); | |
} | |
public function getSecretAttribute() | |
{ | |
return $this->attributes['secret'] = empty($this->attributes["secret"]) | |
? crypt((string) Uuid::generate(), mt_rand()) | |
: $this->attributes["secret"]; | |
} | |
public function user() | |
{ | |
return $this->belongsTo('App\Sk\User\User', 'user_id'); | |
} | |
/** | |
* create a new Session | |
* @param \stdClass $data | |
*/ | |
public function doCreate(User $user, SkPayload $data) | |
{ | |
try { | |
(new SessionValidation($data))->validateCreate(); | |
$data->key = $this->key; | |
$data->user_id = $user->id; | |
$data->otp = generateOtp(); | |
$this->fill((array) $data)->save(); | |
//send OTP (on mobile ?) | |
$user->sendOtp($data->otp); | |
return $this; | |
} catch (\Exception $e) { | |
SkException::throwException( | |
trans('sk.session/creation_failed'), | |
$e | |
); | |
} | |
} | |
/** | |
* verify otp and mark session active | |
* @return Session | |
**/ | |
public function verifyOtp(SkPayload $data) | |
{ | |
try { | |
(new SessionValidation($data))->validateVerifyOtp(); | |
if ($this->otp != doHash($data->otp)) { | |
throw new SkException(trans('sk.session/invalid_otp')); | |
} | |
//IMP: secret will be auto generated by model, when reading it | |
$this->secret; | |
$this->otp = null; | |
$this->last_active_at = now(); | |
$this->notification_token = $data->notification_token ?? null; | |
$this->save(); | |
//Also mark it logged in | |
$this->setLoggedIn(); | |
return $this; | |
} catch (\Exception $e) { | |
SkException::throwException( | |
trans('sk.session/verify_otp_failed'), | |
$e | |
); | |
} | |
} | |
/** | |
* | |
* @return Session | |
**/ | |
public function authorize($key, SkPayload $data) | |
{ | |
//append secret to payload & verify hash | |
$toBeHashed = $data->key . $this->secret . $data->timestamp; | |
$expected = hash('sha256', $toBeHashed); | |
if ($data->hash != $expected) { | |
throw new SkException( | |
'Authorization invalid. Please login again.', | |
419 | |
); | |
} | |
//update the last active time of user in session | |
$this->updateLastActiveAt(); | |
// set the current session & user | |
$this->setLoggedIn(); | |
return $this; | |
} | |
public function setLoggedIn() | |
{ | |
// set the session Entity | |
SkApi::setLoggedIn($this); | |
//App::setLocale($this->user->default_language); | |
} | |
/** | |
* update last active at | |
**/ | |
public function updateLastActiveAt() | |
{ | |
try { | |
if ($this->last_active_at instanceof Carbon) { | |
if ($this->last_active_at < now()->subSeconds(30)) { | |
$this->last_active_at = now(); | |
} | |
} else { | |
$this->last_active_at = now(); | |
} | |
$this->save(); | |
} catch (\Exception $e) { | |
SkException::throwException("Could not update last active", $e); | |
} | |
} | |
/** | |
* get active session of login user | |
*/ | |
public function getAllSessionOfLoginUser() | |
{ | |
$currentSession = SkApi::getLoggedIn(); | |
$sessions = $this->where('user_id', $currentSession->user_id) | |
->whereNotNull('secret') | |
->latest('last_active_at') | |
->get() | |
->each(function ($items) { | |
$items->append('platformSignDetail'); | |
}); | |
return $sessions; | |
} | |
/* | |
* logout from other devices | |
* expect current device | |
*/ | |
public function logoutFromOtherDevices() | |
{ | |
try { | |
$currentSession = SkApi::getLoggedIn(); | |
$this->where('key', '!=', $currentSession->key) | |
->where('user_id', $currentSession->user_id) | |
->delete(); | |
} catch (\Exception $e) { | |
throw $e; | |
} | |
} | |
public function getPlatformSignDetailAttribute() | |
{ | |
$parsedPlatformSign = new Parser( | |
$this->attributes['platform_signature'] | |
); | |
$platformSign = new \stdClass(); | |
$platformSign->browser = $parsedPlatformSign->browser->name; | |
$platformSign->os = $parsedPlatformSign->os->name; | |
$platformSign->icon = 'sm_' . Str::slug($platformSign->browser, '_'); | |
$platformSign->device = ucfirst($parsedPlatformSign->device->type); | |
$platformSign->deviceModel = $parsedPlatformSign->device->model; | |
if ($this->attributes['platform'] == SESSION_PLATFORM_APP) { | |
$platformSign->icon = 'sm_session_device_mobile'; | |
} | |
return $platformSign; | |
} | |
public static function getActiveRegistrationTokens($userId) | |
{ | |
return self::where('user_id', $userId) | |
->whereNotNull('registration_token') | |
->where( | |
'last_active_at', | |
'>=', | |
now()->subDays(config('sm.session.expire_days')) | |
) | |
->pluck('registration_token') | |
->toArray(); | |
} | |
static function createSessionForUser(User $user) | |
{ | |
$session = new Session(); | |
$session->key; | |
$session->secret; | |
$session->user_id = $user->id; | |
$session->ip_address = '0.0.0.0'; | |
$session->platform = 0; | |
$session->platform_signature = 'Testing Session for cli'; | |
$session->onboarding = 0; | |
$session->save(); | |
return $session; | |
} | |
public function createSessionForAvailMode(SkPayload $data) | |
{ | |
try{ | |
(new SessionValidation($data))->validateCreateForAvailUser(); | |
$data->key = $this->key; | |
$data->secret = $this->secret; | |
$data->mode = SESSION_MODE_AVAIL; | |
$data->last_active_at = now(); | |
$this->fill((array) $data)->save(); | |
return $this; | |
} catch (\Exception $e) { | |
throw new SkException('sk.avail_link/session_creation_failed'); | |
} | |
} | |
public function loginAvailUser(SkPayload $data, $key = null) | |
{ | |
try{ | |
$session = Session::findOrFail($key); | |
} catch (ModelNotFoundException $e) { | |
$session = $this->createSessionForAvailMode($data); | |
} | |
// if session expired then update last active date | |
if($session->last_active_at->addDays(config('sm.session.expire_days'))->lte(now())) | |
{ | |
$session->updateLastActiveAt(); | |
} | |
//let him login, and return the session | |
$session->setLoggedIn(); | |
return $session; | |
} | |
public function doOnboardingRequired(){ | |
if(!$this->onboarding){ | |
return false; | |
} | |
//FIXME: do we need to check with other attributes as well? | |
if(empty($this->user->name)){ | |
return true; | |
} | |
$this->onboarding = false; | |
$this->save(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Sk\Session; | |
use App\Sk\SkPayload; | |
use App\Sk\SkValidation; | |
class SessionValidation extends SkValidation | |
{ | |
/** | |
* fields | |
*/ | |
const FIELD_KEY = 'key'; | |
const FIELD_SECRET = 'secret'; | |
const FIELD_EMAIL = 'email'; | |
const FIELD_USER_ID = 'user_id'; | |
const FIELD_OTP = 'otp'; | |
const FIELD_IP_ADDRESS = 'ip_address'; | |
const FIELD_PLATFORM_SIGNATURE = 'platform_signature'; | |
public function __construct(SkPayload $data) | |
{ | |
$this->data = $data; | |
$this->rules = [ | |
self::FIELD_KEY => 'required|string|exists:sessions,key', | |
self::FIELD_USER_ID => 'required|numeric|exists:users,id', | |
self::FIELD_IP_ADDRESS => 'required|string', | |
self::FIELD_PLATFORM_SIGNATURE => 'required|string', | |
self::FIELD_OTP => 'required|numeric' | |
]; | |
} | |
/** | |
* validate given data for create action | |
*/ | |
public function validateCreate() | |
{ | |
$fields = [ | |
self::FIELD_IP_ADDRESS, | |
self::FIELD_PLATFORM_SIGNATURE | |
]; | |
$this->validate($fields, $this->data); | |
} | |
/** | |
* validate given data for create action | |
*/ | |
public function validateVerifyOtp() | |
{ | |
$fields = [self::FIELD_KEY, self::FIELD_OTP]; | |
$this->validate($fields, $this->data); | |
} | |
/* | |
* validate given data for delete action | |
*/ | |
public function validateLogout() | |
{ | |
$fields = [self::FIELD_KEY]; | |
$this->validate($fields, $this->data); | |
} | |
public function validateCreateForAvailUser() | |
{ | |
$fields = [ | |
self::FIELD_USER_ID, | |
self::FIELD_IP_ADDRESS, | |
self::FIELD_PLATFORM_SIGNATURE | |
]; | |
$this->validate($fields, $this->data); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Sk; | |
use App\Sk\SkException; | |
use Illuminate\Support\Facades\Validator; | |
class SkValidation | |
{ | |
/** | |
* validate fields of given data with given rules | |
*/ | |
public function validate(array $fields, $data) | |
{ | |
$applicableRules = array(); | |
foreach ($fields as $field) { | |
$applicableRules[$field] = $this->rules[$field] ?? []; | |
} | |
if (count($applicableRules)) { | |
$this->doValidateRules($data, $applicableRules); | |
} | |
} | |
protected function doValidateRules($data, $rules) | |
{ | |
$validator = Validator::make((array) $data, $rules); | |
if ($validator->fails()) { | |
$errors = $validator->errors(); | |
$validation = []; | |
foreach ($rules as $field => $rule) { | |
if ($errors->get($field)) { | |
$validation[$field] = $errors->get($field); | |
} | |
} | |
$e = new SkException(implode("\n", $errors->all()), 400); | |
$e->validation = $validation; | |
throw $e; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment