Skip to content

Instantly share code, notes, and snippets.

@ssv445
Created January 29, 2020 07:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssv445/fb570ebec2e72dbc5e55d7c09b1aa976 to your computer and use it in GitHub Desktop.
Save ssv445/fb570ebec2e72dbc5e55d7c09b1aa976 to your computer and use it in GitHub Desktop.
<?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();
}
}
<?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);
}
}
<?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