Skip to content

Commit

Permalink
- Added multi auth database session handler
Browse files Browse the repository at this point in the history
- Renamed PasswordUpdateSchema to PasswordResetsSchema
  • Loading branch information
dash8x committed Apr 25, 2024
1 parent 2d78a37 commit c4c3048
Show file tree
Hide file tree
Showing 14 changed files with 620 additions and 4 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"javaabu/activitylog": "^1.1",
"laravel/ui": "^4.4",
"javaabu/passport": "^1.1",
"spatie/laravel-medialibrary": "^10.15 || ^11.0"
"spatie/laravel-medialibrary": "^10.15 || ^11.0",
"mobiledetect/mobiledetectlib": "^4.8"
},
"require-dev": {
"orchestra/testbench": "^7.0 || ^8.0 || ^9.0",
Expand Down
173 changes: 173 additions & 0 deletions src/Agent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

namespace Javaabu\Auth;

use Closure;
use Detection\MobileDetect;

/**
* @copyright Originally created by Jens Segers: https://github.com/jenssegers/agent
* Copied from https://github.com/laravel/jetstream
*/
class Agent extends MobileDetect
{
/**
* List of additional operating systems.
*
* @var array<string, string>
*/
protected static $additionalOperatingSystems = [
'Windows' => 'Windows',
'Windows NT' => 'Windows NT',
'OS X' => 'Mac OS X',
'Debian' => 'Debian',
'Ubuntu' => 'Ubuntu',
'Macintosh' => 'PPC',
'OpenBSD' => 'OpenBSD',
'Linux' => 'Linux',
'ChromeOS' => 'CrOS',
];

/**
* List of additional browsers.
*
* @var array<string, string>
*/
protected static $additionalBrowsers = [
'Opera Mini' => 'Opera Mini',
'Opera' => 'Opera|OPR',
'Edge' => 'Edge|Edg',
'Coc Coc' => 'coc_coc_browser',
'UCBrowser' => 'UCBrowser',
'Vivaldi' => 'Vivaldi',
'Chrome' => 'Chrome',
'Firefox' => 'Firefox',
'Safari' => 'Safari',
'IE' => 'MSIE|IEMobile|MSIEMobile|Trident/[.0-9]+',
'Netscape' => 'Netscape',
'Mozilla' => 'Mozilla',
'WeChat' => 'MicroMessenger',
];

/**
* Key value store for resolved strings.
*
* @var array<string, mixed>
*/
protected $store = [];

/**
* Get the platform name from the User Agent.
*
* @return string|null
*/
public function platform()
{
return $this->retrieveUsingCacheOrResolve('javaabu-auth.platform', function () {
return $this->findDetectionRulesAgainstUserAgent(
$this->mergeRules(MobileDetect::getOperatingSystems(), static::$additionalOperatingSystems)
);
});
}

/**
* Get the browser name from the User Agent.
*
* @return string|null
*/
public function browser()
{
return $this->retrieveUsingCacheOrResolve('javaabu-auth.browser', function () {
return $this->findDetectionRulesAgainstUserAgent(
$this->mergeRules(static::$additionalBrowsers, MobileDetect::getBrowsers())
);
});
}

/**
* Determine if the device is a desktop computer.
*
* @return bool
*/
public function isDesktop()
{
return $this->retrieveUsingCacheOrResolve('javaabu-auth.desktop', function () {
// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
if (
$this->getUserAgent() === static::$cloudFrontUA
&& $this->getHttpHeader('HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER') === 'true'
) {
return true;
}

return ! $this->isMobile() && ! $this->isTablet();
});
}

/**
* Match a detection rule and return the matched key.
*
* @return string|null
*/
protected function findDetectionRulesAgainstUserAgent(array $rules)
{
$userAgent = $this->getUserAgent();

foreach ($rules as $key => $regex) {
if (empty($regex)) {
continue;
}

if ($this->match($regex, $userAgent)) {
return $key ?: reset($this->matchesArray);
}
}

return null;
}

/**
* Retrieve from the given key from the cache or resolve the value.
*
* @param string $key
* @param \Closure():mixed $callback
* @return mixed
*/
protected function retrieveUsingCacheOrResolve(string $key, Closure $callback)
{
$cacheKey = $this->createCacheKey($key);

if (! is_null($cacheItem = $this->store[$cacheKey] ?? null)) {
return $cacheItem;
}

return tap(call_user_func($callback), function ($result) use ($cacheKey) {
$this->store[$cacheKey] = $result;
});
}

/**
* Merge multiple rules into one array.
*
* @param array $all
* @return array<string, string>
*/
protected function mergeRules(...$all)
{
$merged = [];

foreach ($all as $rules) {
foreach ($rules as $key => $value) {
if (empty($merged[$key])) {
$merged[$key] = $value;
} elseif (is_array($merged[$key])) {
$merged[$key][] = $value;
} else {
$merged[$key] .= '|'.$value;
}
}
}

return $merged;
}
}
15 changes: 15 additions & 0 deletions src/AuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

namespace Javaabu\Auth;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
use Javaabu\Auth\Http\Middlewares\RedirectIfActivated;
use Javaabu\Auth\Http\Middlewares\RedirectIfEmailVerificationNotNeeded;
use Javaabu\Auth\Http\Middlewares\RedirectIfNotActivated;
use Javaabu\Auth\PasswordUpdate\Middleware\RedirectIfPasswordUpdateNotRequired;
use Javaabu\Auth\PasswordUpdate\Middleware\RedirectIfPasswordUpdateRequired;
use Javaabu\Auth\Providers\EventServiceProvider;
use Javaabu\Auth\Session\MultiAuthDatabaseSessionHandler;

class AuthServiceProvider extends ServiceProvider
{
Expand All @@ -23,6 +26,18 @@ public function boot(): void
__DIR__.'/../config/config.php' => config_path('auth.php'),
], 'auth-config');
}

Session::extend('multi_auth_database', function (Application $app) {
$table = config('session.table');

$lifetime = config('session.lifetime');

$connection = config('session.connection');

$db_connection = $app->make('db')->connection($connection);

return new MultiAuthDatabaseSessionHandler($db_connection, $table, $lifetime, $app);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php

namespace Javaabu\Auth\PasswordUpdate;
namespace Javaabu\Auth;

use Illuminate\Database\Schema\Blueprint;

class PasswordUpdateSchema
class PasswordResetsSchema
{
/**
* Adds the columns needed for email verification
Expand Down
36 changes: 36 additions & 0 deletions src/Session/MultiAuthDatabaseSessionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Javaabu\Auth\Session;

use Illuminate\Contracts\Auth\Guard;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Session\DatabaseSessionHandler;

class MultiAuthDatabaseSessionHandler extends DatabaseSessionHandler
{
protected function addUserInformation(&$payload)
{
if ($this->container->bound(Guard::class) && ($user_type = $this->userType())) {
$payload['user_id'] = $this->userId();
$payload['user_type'] = $this->userType();
}

return $this;
}

/**
* Get the currently authenticated user.
*
* @return mixed
*/
protected function userType(): ?string
{
$user = $this->container->make(Guard::class)->user();

if ($user instanceof Model) {
return $user->getMorphClass();
}

return null;
}
}
57 changes: 57 additions & 0 deletions src/Session/Session.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Javaabu\Auth\Session;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Javaabu\Auth\Agent;

class Session extends Model
{
public $incrementing = false;

public $timestamps = false;

/**
* The attributes that are cast to native types
*
* @var array
*/
protected $casts = [
'last_activity' => 'datetime'
];


public function __construct(array $attributes = [])
{
$this->table = config('session.table');
$this->connection = config('session.connection');

parent::__construct($attributes);
}

public function getAgentAttribute(): Agent
{
return $this->createAgent();
}

public function getIsCurrentDeviceAttribute(): bool
{
return $this->id === request()->session()->getId();
}

public function user(): MorphTo
{
return $this->morphTo('user');
}

/**
* Create a new agent instance from the given session.
*
* @return Agent
*/
protected function createAgent(): Agent
{
return tap(new Agent(), fn ($agent) => $agent->setUserAgent($this->user_agent));
}
}
21 changes: 21 additions & 0 deletions src/Session/SessionSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Javaabu\Auth\Session;

use Illuminate\Database\Schema\Blueprint;

class SessionSchema
{
/**
* Adds the columns needed for email verification
*/
public static function columns(Blueprint $table): void
{
$table->string('id')->primary();
$table->nullableMorphs('user');
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
}
}
26 changes: 26 additions & 0 deletions src/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Carbon\Carbon;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
Expand All @@ -18,6 +19,7 @@
use Javaabu\Auth\Notifications\VerifyEmail;
use Javaabu\Auth\PasswordUpdate\PasswordUpdatable;
use Javaabu\Auth\PasswordUpdate\PasswordUpdatableContract;
use Javaabu\Auth\Session\Session;
use Javaabu\Helpers\AdminModel\AdminModel;
use Javaabu\Helpers\AdminModel\IsAdminModel;
use Javaabu\Helpers\Media\AllowedMimeTypes;
Expand Down Expand Up @@ -594,4 +596,28 @@ public function getListNameAttribute(): string
{
return __(':name (:email)', ['name' => $this->name, 'email' => $this->email]);
}

/**
* Get the sessions of the user
*/
public function sessions(): MorphMany
{
return $this->morphMany(Session::class, 'user');
}

/**
* Delete the other browser session records from storage.
*
* @return void
*/
public function deleteOtherSessionRecords()
{
if (config('session.driver') !== 'multi_auth_database') {
return;
}

$this->sessions()
->where('id', '!=', request()->session()->getId())
->delete();
}
}

0 comments on commit c4c3048

Please sign in to comment.