Skip to content

Commit

Permalink
feat: add events to guard (#6)
Browse files Browse the repository at this point in the history
* fix: update new default max log size to 6kb as buffer size was lower than originally expected

* fix: add auth and login events to LITokenGuard
  • Loading branch information
chintan-liveintent authored Nov 14, 2024
1 parent bfd8694 commit 619f86b
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 14 deletions.
4 changes: 2 additions & 2 deletions config/liveintent.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@

// The size of the message before a new log statement is used.
// Note:
// This is necessary due to the docker logs 16kb stream buffer which caused elastic search logs to be split,
// This is necessary due to the docker logs 6kb stream buffer which caused elastic search logs to be split,
// which caused kibana to not be able to properly display log entries that ended up being too large.
'message_max_size_bytes' => 13000,
'message_max_size_bytes' => 5500,

'log_session_info' => false,
'log_token_info' => true,
Expand Down
163 changes: 157 additions & 6 deletions src/Auth/LITokenGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,59 @@
use Illuminate\Http\Request;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Failed;
use Illuminate\Support\Facades\Log;
use Illuminate\Auth\Events\Validated;
use Lcobucci\JWT\Signer\Ecdsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Illuminate\Auth\Events\Attempting;
use Illuminate\Auth\Events\Authenticated;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Auth\Authenticatable;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\StrictValidAt;

/**
* Updated from discussions found here: https://laracasts.com/discuss/channels/laravel/login-event
*/
class LITokenGuard
{
/**
* The event dispatcher instance.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected Dispatcher $events;
protected \Symfony\Component\HttpFoundation\Request|Request $request;

/**
* Create a new internal token guard instance.
* @throws Throwable
*/
public function __construct(
private String $name,
private LITokenUserProvider $userProvider,
) {
}

/**
* Get the user for the incoming request.
*
* @param Request $request
* @return Authenticatable|null
*/
public function user(Request $request): ?Authenticatable
public function user(): ?Authenticatable
{
if (! $bearerToken = $request->bearerToken()) {
$this->fireAttemptEvent(['token' => $this->getBearerToken()]);
if (!$bearerToken = $this->getBearerToken()) {
Log::debug('NoBearerTokenPresent');
$this->fireFailedEvent(null, ['token' => $bearerToken]);

return null;
}

$maybeUser = null;

try {
$signer = Sha256::create();
$publicKey = InMemory::plainText(config('liveintent.auth.li_token.public_key'));
Expand All @@ -59,11 +80,141 @@ public function user(Request $request): ?Authenticatable

// Finally, we'll use the configured user provider to retrieve
// an authenticatable instance so the app can work with it.
return $this->userProvider->retrieveByLIToken($liToken);
$maybeUser = $this->userProvider->retrieveByLIToken($liToken);
if ($maybeUser) {
$this->fireValidatedEvent($maybeUser);
$this->fireAuthenticatedEvent($maybeUser);
$this->fireLoginEvent($maybeUser);
} else {
$this->fireFailedEvent($maybeUser, ['token' => $bearerToken]);
}

return $maybeUser;

} catch (Throwable $e) {
Log::debug('ErrorValidatingLIToken', [ 'message' => $e->getMessage() ]);
Log::debug('ErrorValidatingLIToken: ' . $e->getMessage());
$this->fireFailedEvent($maybeUser, ['token' => $bearerToken]);
}

return null;
return null;
}

/**
* Get the event dispatcher instance.
*
* @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getDispatcher(): Dispatcher
{
return $this->events;
}

/**
* Set the event dispatcher instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function setDispatcher(Dispatcher $events): void
{
$this->events = $events;
}

/**
* Get the current request instance.
*
* @return \Symfony\Component\HttpFoundation\Request|null
*/
public function getRequest()
{
/** @psalm-suppress RedundantPropertyInitializationCheck */
if (!isset($this->request)) {
$this->request = Request::createFromGlobals();
}

return $this->request;
}

/**
* Set the current request instance.
*
* @param \Symfony\Component\HttpFoundation\Request|Request|null $request
* @return $this
*/
public function setRequest($request)
{
$this->request = $request;

return $this;
}

protected function getBearerToken(): null|string
{
$request = $this->getRequest();
if ($request instanceof Request) {
return $request->bearerToken();
}

$authorizationHeader = $request->headers->get('Authorization') ?? '';

return trim(str_ireplace('Bearer', '', $authorizationHeader));
}

/**
* Fire the attempt event with the arguments.
*
* @param array $credentials
* @param bool $remember
* @return void
*/
protected function fireAttemptEvent(array $credentials, $remember = false)
{
$this->events->dispatch(new Attempting($this->name, $credentials, $remember));
}

/**
* Fires the validated event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireValidatedEvent($user)
{
$this->events->dispatch(new Validated($this->name, $user));
}

/**
* Fire the login event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
protected function fireLoginEvent($user, $remember = false)
{
$this->events->dispatch(new Login($this->name, $user, $remember));
}

/**
* Fire the authenticated event if the dispatcher is set.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireAuthenticatedEvent($user)
{
$this->events->dispatch(new Authenticated($this->name, $user));
}

/**
* Fire the failed authentication attempt event with the given arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
* @return void
*/
protected function fireFailedEvent($user, array $credentials)
{
$this->events->dispatch(new Failed($this->name, $user, $credentials));
}
}
2 changes: 1 addition & 1 deletion src/Auth/LITokenPersistentUserProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function persist(Authenticatable $user)
{
$model = $this->createModel();

Log::debug('PersistingUser', ['user_id' => $user->getAuthIdentifier()]);
Log::debug('PersistingUser user_id: ' . $user->getAuthIdentifier());

// If the user has defined an override method for how the
// authenticatable should be persisted, we'll use that
Expand Down
3 changes: 3 additions & 0 deletions src/Database/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
/**
* @template TModel of \Illuminate\Database\Eloquent\Model
* @extends \LiveIntent\LaravelCommon\Database\AbstractFactory<TModel>
*
* @psalm-suppress ImplementedParamTypeMismatch
* @method \LiveIntent\LaravelCommon\Auth\User create(array $attributes = [], ?\Illuminate\Database\Eloquent\Model $parent = null)
*/
class UserFactory extends AbstractFactory
{
Expand Down
3 changes: 2 additions & 1 deletion src/Http/Middleware/LogWithRequestContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class LogWithRequestContext
public function __construct()
{
$this->ignorePaths = config('liveintent.logging.ignore_paths', []);
$this->messageMaxSizeBytes = config('liveintent.logging.message_max_size_bytes', 13000);
$this->messageMaxSizeBytes = config('liveintent.logging.message_max_size_bytes', 5500);
$this->shouldLogSessionInfo = config('liveintent.logging.log_session_info', false);
$this->shouldLogTokenInfo = config('liveintent.logging.log_token_info', true);
$this->shouldLogRequestHeaders = config('liveintent.logging.log_request_headers', true);
Expand Down Expand Up @@ -145,6 +145,7 @@ protected function getUnverifiedToken(Request $request): ?Plain
return $unverifiedToken;
} catch (Exception $exception) {
// token was provided, but not parseable, ignore and continue
Log::debug('ErrorParsingBearerToken', ['exception' => $exception->getMessage()]);
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/LaravelCommon.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ class LaravelCommon
public static function registerAuthGuard()
{
Auth::extend('li_token', function ($app, $_name, array $config) {
return new RequestGuard(function ($request) use ($config) {
return new RequestGuard(function ($request) use ($app, $config) {
/** @psalm-suppress InvalidArgument */
return (new LITokenGuard(
Auth::createUserProvider($config['provider'])
))->user($request);
$guard = new LITokenGuard(
'li_token',
Auth::createUserProvider($config['provider']),
);

$guard->setDispatcher($app['events']);
$guard->setRequest($request);

return $guard->user();
}, $app['request']);
});
}
Expand Down

0 comments on commit 619f86b

Please sign in to comment.