Skip to content

Commit cf5c140

Browse files
committed
init commit.
1 parent c71fcb5 commit cf5c140

File tree

9 files changed

+363
-7
lines changed

9 files changed

+363
-7
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,32 @@
66
## Installing
77

88
```shell
9-
$ composer require vendor/package -vvv
9+
$ composer require overtrue/laravel-passport-cache-token -vvv
1010
```
1111

1212
## Usage
1313

14-
TODO
14+
Thanks to Laravel's automatic package discovery mechanism, you don't need to do any additional operations.
15+
16+
Of course, you can also control the cache strategy freely, just need to configure the following in the configuration file:
17+
18+
//config/passport.php
19+
```php
20+
return [
21+
//...
22+
'cache' => [
23+
// Cache key prefix
24+
'prefix' => 'passport_token_',
25+
26+
// The life time of token cache,
27+
// Unit: second
28+
'expires_in' => 300,
29+
30+
// Cache tags
31+
'tags' => [],
32+
],
33+
];
34+
```
1535

1636
## Contributing
1737

composer.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
{
2-
"name": "overtrue/php-package",
3-
"description": "A PHP package template repository.",
2+
"name": "overtrue/laravel-passport-cache-token",
3+
"description": "Make laravel/passport token cacheable.",
44
"license": "MIT",
55
"authors": [
66
{
77
"name": "vendor",
88
"email": "name@domain.com"
99
}
1010
],
11-
"require": {},
11+
"require": {
12+
"laravel/framework": "^7.14",
13+
"laravel/passport": "^9.2",
14+
"orchestra/testbench": "^5.3"
15+
},
1216
"require-dev": {
1317
"brainmaestro/composer-git-hooks": "^2.7",
1418
"friendsofphp/php-cs-fixer": "^2.15",
@@ -17,14 +21,19 @@
1721
},
1822
"autoload": {
1923
"psr-4": {
20-
"Vendor\\Package\\": "src"
24+
"Overtrue\\LaravelPassportCacheToken\\": "./src"
25+
}
26+
},
27+
"autoload-dev": {
28+
"psr-4": {
29+
"Tests\\": "./tests"
2130
}
2231
},
2332
"extra": {
2433
"hooks": {
2534
"pre-commit": [
2635
"composer test",
27-
"composer fix-style"
36+
"composer check-style"
2837
],
2938
"pre-push": [
3039
"composer test",

src/.gitkeep

Whitespace-only changes.

src/CacheTokenRepository.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace Overtrue\LaravelPassportCacheToken;
4+
5+
use Illuminate\Database\Eloquent\Collection;
6+
use Laravel\Passport\Passport;
7+
use Laravel\Passport\Token;
8+
use Illuminate\Support\Facades\Cache;
9+
use Laravel\Passport\TokenRepository;
10+
11+
class CacheTokenRepository extends TokenRepository
12+
{
13+
/**
14+
* @var string
15+
*/
16+
protected $cacheKey;
17+
18+
/**
19+
* @var int
20+
*/
21+
protected $expiresInSeconds;
22+
23+
/**
24+
* @var array
25+
*/
26+
protected $cacheTags;
27+
28+
/**
29+
* @param string $cacheKey
30+
* @param int $expiresInSeconds
31+
* @param array $tags
32+
*/
33+
public function __construct(string $cacheKey = null, int $expiresInSeconds = null, array $tags = [])
34+
{
35+
$this->cacheKey = $cacheKey ?? 'passport_token_';
36+
$this->expiresInSeconds = $expiresInSeconds ?? 5 * 60;
37+
$this->cacheTags = $tags;
38+
}
39+
40+
/**
41+
* Get a token by the given ID.
42+
*
43+
* @param string $id
44+
*
45+
* @return \Laravel\Passport\Token
46+
*/
47+
public function find($id): Token
48+
{
49+
return Cache::remember($this->cacheKey . $id, \now()->addSeconds($this->expiresInSeconds), function () use ($id) {
50+
return Passport::token()->where('id', $id)->first();
51+
});
52+
}
53+
54+
/**
55+
* Get a token by the given user ID and token ID.
56+
*
57+
* @param string $id
58+
* @param int $userId
59+
*
60+
* @return \Laravel\Passport\Token|null
61+
*/
62+
public function findForUser($id, $userId)
63+
{
64+
return Cache::remember($this->cacheKey . $id, \now()->addSeconds($this->expiresInSeconds), function () use ($id, $userId) {
65+
return Passport::token()->where('id', $id)->where('user_id', $userId)->first();
66+
});
67+
}
68+
69+
/**
70+
* Get the token instances for the given user ID.
71+
*
72+
* @param mixed $userId
73+
*
74+
* @return \Illuminate\Database\Eloquent\Collection
75+
*/
76+
public function forUser($userId): Collection
77+
{
78+
return Cache::remember($this->cacheKey . $userId, \now()->addSeconds($this->expiresInSeconds), function () use ($userId) {
79+
return Passport::token()->where('user_id', $userId)->get();
80+
});
81+
}
82+
83+
/**
84+
* Get a valid token instance for the given user and client.
85+
*
86+
* @param \Illuminate\Database\Eloquent\Model $user
87+
* @param \Laravel\Passport\Client $client
88+
*
89+
* @return \Laravel\Passport\Token|null
90+
*/
91+
public function getValidToken($user, $client)
92+
{
93+
return Cache::remember($this->cacheKey . $user->getKey(), \now()->addSeconds($this->expiresInSeconds), function () use ($client, $user) {
94+
return $client->tokens()
95+
->whereUserId($user->getKey())
96+
->where('revoked', 0)
97+
->where('expires_at', '>', \now())
98+
->first();
99+
});
100+
}
101+
}

src/CacheTokenServiceProvider.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Overtrue\LaravelPassportCacheToken;
4+
5+
use Illuminate\Support\ServiceProvider;
6+
use Laravel\Passport\TokenRepository;
7+
8+
class CacheTokenServiceProvider extends ServiceProvider
9+
{
10+
public function register()
11+
{
12+
$this->app->singleton(TokenRepository::class, function () {
13+
return new CacheTokenRepository(
14+
\config('passport.cache.prefix'),
15+
\config('passport.cache.expires_in'),
16+
\config('passport.cache.tags', [])
17+
);
18+
});
19+
}
20+
}

tests/.gitkeep

Whitespace-only changes.

tests/FeatureTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Tests;
4+
5+
use Illuminate\Contracts\Hashing\Hasher;
6+
use Illuminate\Contracts\Routing\Registrar;
7+
use Illuminate\Support\Facades\Auth;
8+
use Illuminate\Support\Str;
9+
use Laravel\Passport\Client;
10+
use Laravel\Passport\ClientRepository;
11+
12+
class FeatureTest extends TestCase
13+
{
14+
public function test_user_can_create_tokens()
15+
{
16+
$this->withoutExceptionHandling();
17+
18+
$password = 'foobar123';
19+
$user = new User();
20+
$user->email = 'foo@gmail.com';
21+
$user->password = $this->app->make(Hasher::class)->make($password);
22+
$user->save();
23+
24+
/** @var Client $client */
25+
$client = Client::create([
26+
'user_id' => null,
27+
'name' => 'Foo',
28+
'secret' => Str::random(40),
29+
'redirect' => 'http://localhost/',
30+
'personal_access_client' => false,
31+
'password_client' => true,
32+
'revoked' => false,
33+
]);
34+
35+
$response = $this->post(
36+
'/oauth/token',
37+
[
38+
'grant_type' => 'password',
39+
'client_id' => $client->id,
40+
'client_secret' => $client->secret,
41+
'username' => $user->email,
42+
'password' => $password,
43+
]
44+
);
45+
46+
$response->assertOk();
47+
}
48+
49+
public function test_it_can_cache_token()
50+
{
51+
$password = 'foobar123';
52+
$user = new User();
53+
$user->email = 'foo@gmail.com';
54+
$user->password = $this->app->make(Hasher::class)->make($password);
55+
$user->save();
56+
57+
/** @var Client $client */
58+
app(ClientRepository::class)->createPersonalAccessClient($user->id, 'Personal Token Client', 'http://localhost');
59+
60+
/** @var Registrar $router */
61+
$router = $this->app->make(Registrar::class);
62+
63+
$token = $user->createToken('test')->accessToken;
64+
65+
$router->get('/foo', function () {
66+
return 'bar';
67+
})->middleware('auth:api');
68+
69+
$query = $this->getQueryLog(function () use ($token, $user, $router) {
70+
$this->getJson('/foo')->assertStatus(401);
71+
$this->withHeader('Authorization', 'Bearer ' . $token)->getJson('/foo')->assertSuccessful()->assertSee('bar');
72+
});
73+
74+
$this->assertCount(3, $query);
75+
76+
// token cached
77+
$query = $this->getQueryLog(function () use ($token, $user, $router) {
78+
$router->get('/me', function () {
79+
return Auth::user();
80+
})->middleware('auth:api');
81+
82+
$this->withHeader('Authorization', 'Bearer ' . $token)->getJson('/me')->assertSuccessful()->assertJsonFragment([
83+
'id' => $user->id,
84+
'email' => $user->email,
85+
]);
86+
87+
$this->withHeader('Authorization', 'Bearer ' . $token)->getJson('/me')->assertOk();
88+
$this->withHeader('Authorization', 'Bearer ' . $token)->getJson('/me')->assertOk();
89+
$this->withHeader('Authorization', 'Bearer ' . $token)->getJson('/me')->assertOk();
90+
$this->withHeader('Authorization', 'Bearer ' . $token)->getJson('/me')->assertOk();
91+
});
92+
93+
$this->assertCount(0, $query);
94+
}
95+
96+
protected function getQueryLog(\Closure $callback): \Illuminate\Support\Collection
97+
{
98+
$sqls = \collect([]);
99+
\DB::listen(function ($query) use ($sqls) {
100+
$sqls->push(['sql' => $query->sql, 'bindings' => $query->bindings]);
101+
});
102+
103+
$callback();
104+
105+
return $sqls;
106+
}
107+
}

tests/TestCase.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Tests;
4+
5+
use Illuminate\Contracts\Config\Repository;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Foundation\Testing\RefreshDatabase;
8+
use Illuminate\Support\Facades\Schema;
9+
use Laravel\Passport\Passport;
10+
use Laravel\Passport\PassportServiceProvider;
11+
use Overtrue\LaravelPassportCacheToken\CacheTokenServiceProvider;
12+
13+
abstract class TestCase extends \Orchestra\Testbench\TestCase
14+
{
15+
use RefreshDatabase;
16+
17+
public const KEYS = __DIR__ . '/keys';
18+
public const PUBLIC_KEY = self::KEYS . '/oauth-public.key';
19+
public const PRIVATE_KEY = self::KEYS . '/oauth-private.key';
20+
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->artisan('migrate:fresh');
26+
27+
Passport::routes();
28+
29+
@unlink(self::PUBLIC_KEY);
30+
@unlink(self::PRIVATE_KEY);
31+
32+
$this->artisan('passport:keys');
33+
34+
Schema::create('users', function (Blueprint $table) {
35+
$table->increments('id');
36+
$table->string('email')->unique();
37+
$table->string('password');
38+
$table->dateTime('created_at');
39+
$table->dateTime('updated_at');
40+
});
41+
}
42+
43+
protected function tearDown(): void
44+
{
45+
Schema::dropIfExists('users');
46+
47+
parent::tearDown();
48+
}
49+
50+
protected function getEnvironmentSetUp($app)
51+
{
52+
$config = $app->make(Repository::class);
53+
54+
$config->set('auth.defaults.provider', 'users');
55+
56+
if (($userClass = $this->getUserClass()) !== null) {
57+
$config->set('auth.providers.users.model', $userClass);
58+
}
59+
60+
$config->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']);
61+
62+
$app['config']->set('database.default', 'testbench');
63+
64+
$app['config']->set('passport.storage.database.connection', 'testbench');
65+
66+
$app['config']->set('database.connections.testbench', [
67+
'driver' => 'sqlite',
68+
'database' => ':memory:',
69+
'prefix' => '',
70+
]);
71+
72+
$app['config']->set('app.key', 'base64:L7o69H49J7GM4qVX/lfI2DOPW8fqCfw+kiahKsxL62g=');
73+
}
74+
75+
protected function getPackageProviders($app)
76+
{
77+
return [PassportServiceProvider::class, CacheTokenServiceProvider::class];
78+
}
79+
80+
/**
81+
* Get the Eloquent user model class name.
82+
*
83+
* @return string|null
84+
*/
85+
protected function getUserClass()
86+
{
87+
return User::class;
88+
}
89+
}

0 commit comments

Comments
 (0)