mongodb / laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)
https://www.mongodb.com/docs/drivers/php/laravel-mongodb/
MIT License
7.02k stars 1.43k forks source link

Issue with Sessions #2109

Closed codespikey closed 4 years ago

codespikey commented 4 years ago

Description:

I am having an issue with sessions in laravel when I set sessions driver to database. The sessions are being stored but are not working as they are suppose to be. I checked the same project on mysql database and everything was working perfectly.

Steps to reproduce

  1. New Laravel 8 + Fortify Project with MongoDb as database
  2. Set Sessions driver to database and Try to authenticate user
  3. Current session id and session_id in sessions collection will not match

Expected behaviour

I want to access active sessions for current active user and what it to show all the loggedin sessions by same user

Actual behaviour

I`m getting dozens of old sessions excluding the current session

PS. This issue is also causing csrf token mismatch error on post requests. Also I know that this branch is not stable but i don`t think the issue is related to l8 branch.

codespikey commented 4 years ago

Created a session handler for mongodb and now sessions are working fine. Let me know if there is an issue with the code

<?php

namespace App\Extensions;

use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Container\Container;
use Illuminate\Database\QueryException;
use Illuminate\Session\ExistenceAwareInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\InteractsWithTime;
use Jenssegers\Mongodb\Connection;
use SessionHandlerInterface;

class MongoDbSessionHandler implements ExistenceAwareInterface, SessionHandlerInterface
{
use InteractsWithTime;

/**
 * The database connection instance.
 *
 * @var Connection
 */
protected $connection;

/**
 * The name of the session table.
 *
 * @var string
 */
protected $table;

/**
 * The number of minutes the session should be valid.
 *
 * @var int
 */
protected $minutes;

/**
 * The container instance.
 *
 * @var \Illuminate\Contracts\Container\Container
 */
protected $container;

/**
 * The existence state of the session.
 *
 * @var bool
 */
protected $exists;

/**
 * Create a new database session handler instance.
 *
 * @param Connection $connection
 * @param string $table
 * @param int $minutes
 * @param \Illuminate\Contracts\Container\Container|null $container
 */
public function __construct(Connection $connection, $table, $minutes, Container $container = null)
{

    $this->table = $table;
    $this->minutes = $minutes;
    $this->container = $container;
    $this->connection = $connection;
}

/**
 * {@inheritdoc}
 */
public function open($savePath, $sessionName)
{
    return true;
}

/**
 * {@inheritdoc}
 */
public function close()
{
    return true;
}

/**
 * {@inheritdoc}
 */
public function read($sessionId)
{
    $session = (object) $this->getQuery()->where('id',$sessionId)->get();

    if($session->count() > 0) {
        $session = (object)$session->first();
    }else {
        return '';
    }

    if ($this->expired($session)) {
        $this->exists = true;

        return '';
    }

    if (isset($session->payload)) {
        $this->exists = true;

        return base64_decode($session->payload);
    }

    return '';
}

/**
 * Determine if the session is expired.
 *
 * @param  \stdClass  $session
 * @return bool
 */
protected function expired($session)
{
    return isset($session->last_activity) &&
        $session->last_activity < Carbon::now()->subMinutes($this->minutes)->getTimestamp();
}

/**
 * {@inheritdoc}
 */
public function write($sessionId, $data)
{
    $payload = $this->getDefaultPayload($data);

    if (! $this->exists) {
        $this->read($sessionId);
    }

    if ($this->exists) {
        $this->performUpdate($sessionId, $payload);
    } else {
        $this->performInsert($sessionId, $payload);
    }

    return $this->exists = true;
}

/**
 * Perform an insert operation on the session ID.
 *
 * @param  string  $sessionId
 * @param  string  $payload
 * @return bool|null
 */
protected function performInsert($sessionId, $payload)
{
    try {
        return $this->getQuery()->insert(Arr::set($payload, 'id', $sessionId));
    } catch (QueryException $e) {
        $this->performUpdate($sessionId, $payload);
    }
}

/**
 * Perform an update operation on the session ID.
 *
 * @param  string  $sessionId
 * @param  string  $payload
 * @return int
 */
protected function performUpdate($sessionId, $payload)
{
    return $this->getQuery()->where('id', $sessionId)->update($payload);
}

/**
 * Get the default payload for the session.
 *
 * @param  string  $data
 * @return array
 */
protected function getDefaultPayload($data)
{
    $payload = [
        'payload' => base64_encode($data),
        'last_activity' => $this->currentTime(),
    ];

    if (! $this->container) {
        return $payload;
    }

    return tap($payload, function (&$payload) {
        $this->addUserInformation($payload)
            ->addRequestInformation($payload);
    });
}

/**
 * Add the user information to the session payload.
 *
 * @param  array  $payload
 * @return $this
 */
protected function addUserInformation(&$payload)
{
    if ($this->container->bound(Guard::class)) {
        $payload['user_id'] = $this->userId();
    }

    return $this;
}

/**
 * Get the currently authenticated user's ID.
 *
 * @return mixed
 */
protected function userId()
{
    return $this->container->make(Guard::class)->id();
}

/**
 * Add the request information to the session payload.
 *
 * @param  array  $payload
 * @return $this
 */
protected function addRequestInformation(&$payload)
{
    if ($this->container->bound('request')) {
        $payload = array_merge($payload, [
            'ip_address' => $this->ipAddress(),
            'user_agent' => $this->userAgent(),
        ]);
    }

    return $this;
}

/**
 * Get the IP address for the current request.
 *
 * @return string
 */
protected function ipAddress()
{
    return $this->container->make('request')->ip();
}

/**
 * Get the user agent for the current request.
 *
 * @return string
 */
protected function userAgent()
{
    return substr((string) $this->container->make('request')->header('User-Agent'), 0, 500);
}

/**
 * {@inheritdoc}
 */
public function destroy($sessionId)
{
    $this->getQuery()->where('id', $sessionId)->delete();

    return true;
}

/**
 * {@inheritdoc}
 */
public function gc($lifetime)
{
    $this->getQuery()->where('last_activity', '<=', $this->currentTime() - $lifetime)->delete();
}

/**
 * Get a fresh query builder instance for the table.
 *
 * @return \Illuminate\Database\Query\Builder
 */
protected function getQuery()
{
    return $this->connection->table($this->table);
}

/**
 * Set the existence state for the session.
 *
 * @param  bool  $value
 * @return $this
 */
public function setExists($value)
{
    $this->exists = $value;

    return $this;
}
}