├── .gitignore ├── src ├── User.php ├── RememberToken.php ├── Contracts │ └── Authenticatable.php ├── EloquentAuthenticatable.php ├── UserProvider.php ├── RememberAllServiceProvider.php ├── GenericUser.php ├── DatabaseUserProvider.php ├── EloquentUserProvider.php └── SessionGuard.php ├── phpunit.xml ├── database └── migrations │ └── 2018_08_20_121912_create_remember_tokens_table.php ├── LICENSE ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /src/User.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Contracts/Authenticatable.php: -------------------------------------------------------------------------------- 1 | hasMany(RememberToken::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | ./tests/ 17 | 18 | 19 | 20 | 21 | ./src/ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /database/migrations/2018_08_20_121912_create_remember_tokens_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('token', 100); 19 | $table->integer('user_id'); 20 | $table->timestamps(); 21 | $table->dateTime('expires_at'); 22 | 23 | $table->unique(['token', 'user_id']); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('remember_tokens'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Barchart, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/UserProvider.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'web' => [ 21 | 'driver' => 'rememberall', 22 | 'provider' => 'users', 23 | 'expire' => 10080, // optional token expiration time, in minutes (7 days is the default) 24 | ], 25 | ], 26 | ``` 27 | 28 | #### Eloquent 29 | For Eloquent, you also need to update your model. Just replace Laravel's default `User` model with the following: 30 | ```php 31 | use Barchart\Laravel\RememberAll\User as Authenticatable; 32 | 33 | class User extends Authenticatable 34 | { 35 | 36 | } 37 | ``` 38 | 39 | If you're not extending off of Laravel's base `User` model and instead extending directly off of Eloquent's `Model`, replace Laravel's default `Authenticatable` and `AuthenticatableContract` with the following: 40 | ```php 41 | use Barchart\Laravel\RememberAll\EloquentAuthenticatable as Authenticatable; 42 | use Barchart\Laravel\RememberAll\Contracts\Authenticatable as AuthenticatableContract; 43 | 44 | class User extends Model implements AuthenticatableContract 45 | { 46 | use Authenticatable; 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /src/RememberAllServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/../database/migrations'); 18 | 19 | Auth::extend('rememberall', function ($app, $name, $config) { 20 | $provider = $app['auth']->createUserProvider($config['provider'] ?? null); 21 | 22 | $guard = new SessionGuard($name, $provider, $app['session.store'], request(), $config['expire'] ?? null); 23 | 24 | if (method_exists($guard, 'setCookieJar')) { 25 | $guard->setCookieJar($app['cookie']); 26 | } 27 | 28 | if (method_exists($guard, 'setDispatcher')) { 29 | $guard->setDispatcher($app['events']); 30 | } 31 | 32 | if (method_exists($guard, 'setRequest')) { 33 | $guard->setRequest($app->refresh('request', $guard, 'setRequest')); 34 | } 35 | 36 | return $guard; 37 | }); 38 | 39 | Auth::provider('database', function ($app, array $config) { 40 | $connection = $app['db']->connection(); 41 | 42 | return new DatabaseUserProvider($connection, $app['hash'], $config['table']); 43 | }); 44 | 45 | Auth::provider('eloquent', function ($app, array $config) { 46 | return new EloquentUserProvider($app['hash'], $config['model']); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/GenericUser.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 26 | } 27 | 28 | /** 29 | * Get the name of the unique identifier for the user. 30 | * 31 | * @return string 32 | */ 33 | public function getAuthIdentifierName() 34 | { 35 | return 'id'; 36 | } 37 | 38 | /** 39 | * Get the unique identifier for the user. 40 | * 41 | * @return mixed 42 | */ 43 | public function getAuthIdentifier() 44 | { 45 | $name = $this->getAuthIdentifierName(); 46 | 47 | return $this->attributes[$name]; 48 | } 49 | 50 | /** 51 | * Get the password for the user. 52 | * 53 | * @return string 54 | */ 55 | public function getAuthPassword() 56 | { 57 | return $this->attributes['password']; 58 | } 59 | 60 | /** 61 | * Get the "remember me" token value. 62 | * 63 | * @return string 64 | */ 65 | public function getRememberToken() 66 | { 67 | return $this->attributes[$this->getRememberTokenName()]; 68 | } 69 | 70 | /** 71 | * Set the "remember me" token value. 72 | * 73 | * @param string $value 74 | * @return void 75 | */ 76 | public function setRememberToken($value) 77 | { 78 | $this->attributes[$this->getRememberTokenName()] = $value; 79 | } 80 | 81 | /** 82 | * Get the column name for the "remember me" token. 83 | * 84 | * @return string 85 | */ 86 | public function getRememberTokenName() 87 | { 88 | return 'remember_token'; 89 | } 90 | 91 | /** 92 | * Dynamically access the user's attributes. 93 | * 94 | * @param string $key 95 | * @return mixed 96 | */ 97 | public function __get($key) 98 | { 99 | return $this->attributes[$key]; 100 | } 101 | 102 | /** 103 | * Dynamically set an attribute on the user. 104 | * 105 | * @param string $key 106 | * @param mixed $value 107 | * @return void 108 | */ 109 | public function __set($key, $value) 110 | { 111 | $this->attributes[$key] = $value; 112 | } 113 | 114 | /** 115 | * Dynamically check if a value is set on the user. 116 | * 117 | * @param string $key 118 | * @return bool 119 | */ 120 | public function __isset($key) 121 | { 122 | return isset($this->attributes[$key]); 123 | } 124 | 125 | /** 126 | * Dynamically unset a value on the user. 127 | * 128 | * @param string $key 129 | * @return void 130 | */ 131 | public function __unset($key) 132 | { 133 | unset($this->attributes[$key]); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/DatabaseUserProvider.php: -------------------------------------------------------------------------------- 1 | getGenericUser( 21 | $this->conn->table($this->table)->find($identifier) 22 | ); 23 | 24 | $query = $this->conn->table($this->table) 25 | ->select($this->table.'.*') 26 | ->leftJoin('remember_tokens', 'remember_tokens.user_id', '=', $this->table.'.'.$user->getAuthIdentifierName()) 27 | ->where($this->table.'.'.$user->getAuthIdentifierName(), $identifier) 28 | ->where('remember_tokens.token', $token) 29 | ->where('remember_tokens.expires_at', '<', Carbon::now()) 30 | ->first(); 31 | 32 | return $query ? $user : null; 33 | } 34 | 35 | /** 36 | * Add a token value for the "remember me" session. 37 | * 38 | * @param string $value 39 | * @param int $expire 40 | * @return void 41 | */ 42 | public function addRememberToken($identifier, $value, $expire) 43 | { 44 | $this->conn->table('remember_tokens')->insert([ 45 | 'token' => $value, 46 | 'user_id' => $identifier, 47 | 'created_at' => Carbon::now(), 48 | 'updated_at' => Carbon::now(), 49 | 'expires_at' => Carbon::now()->addMinutes($expire), 50 | ]); 51 | } 52 | 53 | /** 54 | * Replace "remember me" token with new token. 55 | * 56 | * @param string $token 57 | * @param string $newToken 58 | * @param int $expire 59 | * 60 | * @return void 61 | */ 62 | public function replaceRememberToken($identifier, $token, $newToken, $expire) 63 | { 64 | $this->conn->table('remember_tokens') 65 | ->where('user_id', $identifier) 66 | ->where('token', $token) 67 | ->update([ 68 | 'token' => $newToken, 69 | 'expires_at' => Carbon::now()->addMinutes($expire), 70 | ]); 71 | } 72 | 73 | /** 74 | * Delete the specified "remember me" token for the given user. 75 | * 76 | * @param mixed $identifier 77 | * @param string $token 78 | * @return null 79 | */ 80 | public function deleteRememberToken($identifier, $token) 81 | { 82 | $this->conn->table('remember_tokens') 83 | ->where('user_id', $identifier) 84 | ->where('token', $token) 85 | ->delete(); 86 | } 87 | 88 | /** 89 | * Purge old or expired "remember me" tokens. 90 | * 91 | * @param mixed $identifier 92 | * @param bool $expired 93 | * @return null 94 | */ 95 | public function purgeRememberTokens($identifier, $expired = false) 96 | { 97 | $query = $this->conn->table('remember_tokens') 98 | ->where('user_id', $identifier); 99 | 100 | if ($expired) { 101 | $query->where('expires_at', '<', Carbon::now()); 102 | } 103 | 104 | $query->delete(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/EloquentUserProvider.php: -------------------------------------------------------------------------------- 1 | getModelByIdentifier($identifier)) { 21 | return null; 22 | } 23 | 24 | $rememberTokens = $model->rememberTokens()->where('expires_at', '>', Carbon::now())->get(); 25 | 26 | foreach ($rememberTokens as $rememberToken) { 27 | if (hash_equals($rememberToken->token, $token)) { 28 | return $model; 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Add a token value for the "remember me" session. 35 | * 36 | * @param string $value 37 | * @param int $expire 38 | * @return void 39 | */ 40 | public function addRememberToken($identifier, $value, $expire) 41 | { 42 | $model = $this->getModelByIdentifier($identifier); 43 | 44 | if ($model) { 45 | $model->rememberTokens()->create([ 46 | 'token' => $value, 47 | 'expires_at' => Carbon::now()->addMinutes($expire), 48 | ]); 49 | } 50 | } 51 | 52 | /** 53 | * Replace "remember me" token with new token. 54 | * 55 | * @param string $token 56 | * @param string $newToken 57 | * @param int $expire 58 | * 59 | * @return void 60 | */ 61 | public function replaceRememberToken($identifier, $token, $newToken, $expire) 62 | { 63 | $model = $this->getModelByIdentifier($identifier); 64 | 65 | if ($model) { 66 | $model->rememberTokens()->where('token', $token)->update([ 67 | 'token' => $newToken, 68 | 'expires_at' => Carbon::now()->addMinutes($expire), 69 | ]); 70 | } 71 | } 72 | 73 | /** 74 | * Delete the specified "remember me" token for the given user. 75 | * 76 | * @param mixed $identifier 77 | * @param string $token 78 | * @return null 79 | */ 80 | public function deleteRememberToken($identifier, $token) 81 | { 82 | $model = $this->getModelByIdentifier($identifier); 83 | 84 | if ($model && $token = $model->rememberTokens()->where('token', $token)->first()) { 85 | $token->delete(); 86 | } 87 | } 88 | 89 | /** 90 | * Purge old or expired "remember me" tokens. 91 | * 92 | * @param mixed $identifier 93 | * @param bool $expired 94 | * @return null 95 | */ 96 | public function purgeRememberTokens($identifier, $expired = false) 97 | { 98 | $model = $this->getModelByIdentifier($identifier); 99 | 100 | if ($model) { 101 | $query = $model->rememberTokens(); 102 | 103 | if ($expired) { 104 | $query->where('expires_at', '<', Carbon::now()); 105 | } 106 | 107 | $query->delete(); 108 | } 109 | } 110 | 111 | /** 112 | * Gets the user based on their unique identifier. 113 | * 114 | * @param mixed $identifier 115 | * @return \Illuminate\Contracts\Auth\Authenticatable|null 116 | */ 117 | protected function getModelByIdentifier($identifier) 118 | { 119 | $model = $this->createModel(); 120 | 121 | return $model->where($model->getAuthIdentifierName(), $identifier)->first(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/SessionGuard.php: -------------------------------------------------------------------------------- 1 | expire = $expire ?: 10080; 31 | } 32 | 33 | /** 34 | * Get the currently authenticated user. 35 | * 36 | * @return \Illuminate\Contracts\Auth\Authenticatable|null 37 | */ 38 | public function user() 39 | { 40 | if ($this->loggedOut) { 41 | return; 42 | } 43 | 44 | // If we've already retrieved the user for the current request we can just 45 | // return it back immediately. We do not want to fetch the user data on 46 | // every call to this method because that would be tremendously slow. 47 | if (! is_null($this->user)) { 48 | return $this->user; 49 | } 50 | 51 | $id = $this->session->get($this->getName()); 52 | 53 | // First we will try to load the user using the identifier in the session if 54 | // one exists. Otherwise we will check for a "remember me" cookie in this 55 | // request, and if one exists, attempt to retrieve the user using that. 56 | if (! is_null($id)) { 57 | if ($this->user = $this->provider->retrieveById($id)) { 58 | $this->fireAuthenticatedEvent($this->user); 59 | } 60 | } 61 | 62 | // If the user is null, but we decrypt a "recaller" cookie we can attempt to 63 | // pull the user data on that cookie which serves as a remember cookie on 64 | // the application. Once we have a user we can return it to the caller. 65 | $recaller = $this->recaller(); 66 | 67 | if (is_null($this->user) && ! is_null($recaller)) { 68 | $this->user = $this->userFromRecaller($recaller); 69 | 70 | if ($this->user) { 71 | $this->replaceRememberToken($this->user, $recaller->token()); 72 | 73 | $this->updateSession($this->user->getAuthIdentifier()); 74 | 75 | $this->fireLoginEvent($this->user, true); 76 | } 77 | } 78 | 79 | return $this->user; 80 | } 81 | 82 | protected function replaceRememberToken(AuthenticatableContract $user, $token) 83 | { 84 | $this->provider->replaceRememberToken( 85 | $user->getAuthIdentifier(), $token, $newToken = $this->getNewToken(), $this->expire 86 | ); 87 | 88 | $this->queueRecallerCookie($user, $newToken); 89 | } 90 | 91 | /** 92 | * Log a user into the application. 93 | * 94 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 95 | * @param bool $remember 96 | * @return void 97 | */ 98 | public function login(AuthenticatableContract $user, $remember = false) 99 | { 100 | $this->updateSession($user->getAuthIdentifier()); 101 | 102 | // If the user should be permanently "remembered" by the application we will 103 | // queue a permanent cookie that contains the encrypted copy of the user 104 | // identifier. We will then decrypt this later to retrieve the users. 105 | if ($remember) { 106 | $token = $this->createRememberToken($user); 107 | 108 | $this->queueRecallerCookie($user, $token); 109 | } 110 | 111 | // If we have an event dispatcher instance set we will fire an event so that 112 | // any listeners will hook into the authentication events and run actions 113 | // based on the login and logout events fired from the guard instances. 114 | $this->fireLoginEvent($user, $remember); 115 | 116 | $this->setUser($user); 117 | } 118 | 119 | /** 120 | * Create a new "remember me" token for the user. 121 | * 122 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 123 | * @return void 124 | */ 125 | protected function createRememberToken(AuthenticatableContract $user) 126 | { 127 | $this->provider->addRememberToken($user->getAuthIdentifier(), $token = $this->getNewToken(), $this->expire); 128 | 129 | $this->provider->purgeRememberTokens($user->getAuthIdentifier(), true); 130 | 131 | return $token; 132 | } 133 | 134 | /** 135 | * Creates a new token for "remember me" sessions. 136 | * 137 | * @return string 138 | */ 139 | protected function getNewToken() 140 | { 141 | return Str::random(60); 142 | } 143 | 144 | /** 145 | * Log the user out of the application. 146 | * 147 | * @return void 148 | */ 149 | public function logout() 150 | { 151 | $user = $this->user(); 152 | 153 | // If we have an event dispatcher instance, we can fire off the logout event 154 | // so any further processing can be done. This allows the developer to be 155 | // listening for anytime a user signs out of this application manually. 156 | $this->clearUserDataFromStorage(); 157 | 158 | if (isset($this->events)) { 159 | $this->events->dispatch(new LogoutEvent($this->name, $user)); 160 | } 161 | 162 | // Once we have fired the logout event we will clear the users out of memory 163 | // so they are no longer available as the user is no longer considered as 164 | // being signed into this application and should not be available here. 165 | $this->user = null; 166 | 167 | $this->loggedOut = true; 168 | } 169 | 170 | /** 171 | * Remove the user data from the session and cookies. 172 | * 173 | * @return void 174 | */ 175 | protected function clearUserDataFromStorage() 176 | { 177 | $this->session->remove($this->getName()); 178 | 179 | $recaller = $this->recaller(); 180 | 181 | if (! is_null($recaller)) { 182 | $this->getCookieJar()->queue($this->getCookieJar() 183 | ->forget($this->getRecallerName())); 184 | 185 | $this->provider->deleteRememberToken($recaller->id(), $recaller->token()); 186 | } 187 | } 188 | 189 | /** 190 | * Invalidate other sessions for the current user. 191 | * 192 | * The application must be using the AuthenticateSession middleware. 193 | * 194 | * @param string $password 195 | * @param string $attribute 196 | * @return bool|null 197 | */ 198 | public function logoutOtherDevices($password, $attribute = 'password') 199 | { 200 | if (! $this->user()) { 201 | return; 202 | } 203 | 204 | $this->provider->purgeRememberTokens($this->user()->getAuthIdentifier()); 205 | 206 | return parent::logoutOtherDevices($password, $attribute); 207 | } 208 | 209 | /** 210 | * Queue the recaller cookie into the cookie jar. 211 | * 212 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 213 | * @return void 214 | */ 215 | protected function queueRecallerCookie(AuthenticatableContract $user, $token = null) 216 | { 217 | if (is_null($token)) { 218 | $token = $this->createRememberToken($user); 219 | } 220 | 221 | $this->getCookieJar()->queue($this->createRecaller( 222 | $user->getAuthIdentifier().'|'.$token.'|'.$user->getAuthPassword() 223 | )); 224 | } 225 | 226 | /** 227 | * Create a "remember me" cookie for a given ID. 228 | * 229 | * @param string $value 230 | * @return \Symfony\Component\HttpFoundation\Cookie 231 | */ 232 | protected function createRecaller($value) 233 | { 234 | return $this->getCookieJar()->make($this->getRecallerName(), $value, $this->expire); 235 | } 236 | } --------------------------------------------------------------------------------