Skip to content

Commit 5f8c353

Browse files
committed
Added regenerate
1 parent 03dcd32 commit 5f8c353

File tree

5 files changed

+118
-3
lines changed

5 files changed

+118
-3
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ Unassign a Scope from an ApiKey
142142
php artisan apikey:scope:remove {apiKeyName} {scopeName}
143143
```
144144

145+
Regenerate an ApiKey (assign a new Bearer token)
146+
```shell
147+
php artisan apikey:regenerate {name}
148+
```
149+
145150
Delete a Scope
146151
```shell
147152
php artisan apikey:scope:delete {scopeName}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ApiSkeletons\Laravel\Doctrine\ApiKey\Console\Command;
6+
7+
use ApiSkeletons\Laravel\Doctrine\ApiKey\Entity\ApiKey;
8+
use Throwable;
9+
10+
// phpcs:disable SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint
11+
final class RegenerateApiKey extends Command
12+
{
13+
/**
14+
* The name and signature of the console command.
15+
*/
16+
protected $signature = 'apikey:regenerate {name}';
17+
18+
/**
19+
* The console command description.
20+
*/
21+
protected $description = 'Regenerate an apikey';
22+
23+
/**
24+
* Execute the console command.
25+
*/
26+
public function handle(): mixed
27+
{
28+
$name = $this->argument('name');
29+
30+
$apiKeyRepository = $this->apiKeyService->getEntityManager()
31+
->getRepository(ApiKey::class);
32+
33+
try {
34+
$apiKey = $apiKeyRepository->findOneBy(['name' => $name]);
35+
} catch (Throwable $e) {
36+
$this->error('ApiKey not found by name: ' . $name);
37+
38+
return 1;
39+
}
40+
41+
$apiKey = $apiKeyRepository->regenerate($apiKey);
42+
43+
$this->apiKeyService->getEntityManager()->flush();
44+
$this->printApiKeys([$apiKey]);
45+
46+
return 0;
47+
}
48+
}

src/Repository/ApiKeyRepository.php

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@
1313
use ApiSkeletons\Laravel\Doctrine\ApiKey\Exception\InvalidName;
1414
use DateTime;
1515
use Doctrine\ORM\EntityRepository;
16+
use Doctrine\ORM\ORMException;
1617
use Illuminate\Support\Str;
1718

1819
use function preg_match;
1920
use function request;
2021

2122
class ApiKeyRepository extends EntityRepository
2223
{
24+
/**
25+
* Create a new ApiKey entity
26+
*
27+
* @throws DuplicateName
28+
* @throws InvalidName
29+
* @throws ORMException
30+
*/
2331
public function generate(string $name): ApiKey
2432
{
2533
// Verify name is unique
@@ -33,9 +41,7 @@ public function generate(string $name): ApiKey
3341
throw new InvalidName('Please provide a valid name: [a-z0-9-]');
3442
}
3543

36-
do {
37-
$key = Str::random(64);
38-
} while ($this->findBy(['api_key' => $key]));
44+
$key = $this->generateKey();
3945

4046
$apiKey = new ApiKey();
4147
$apiKey
@@ -51,6 +57,31 @@ public function generate(string $name): ApiKey
5157
return $apiKey;
5258
}
5359

60+
/**
61+
* Assign a new api_key to an existing ApiKey entity
62+
*/
63+
public function regenerate(ApiKey $apiKey): ApiKey
64+
{
65+
$apiKey->setApiKey($this->generateKey());
66+
67+
return $apiKey;
68+
}
69+
70+
/**
71+
* Generate a unique api_key
72+
*/
73+
protected function generateKey(): string
74+
{
75+
do {
76+
$key = Str::random(64);
77+
} while ($this->findBy(['api_key' => $key]));
78+
79+
return $key;
80+
}
81+
82+
/**
83+
* Change the active status of an ApiKey entity
84+
*/
5485
public function updateActive(ApiKey $apiKey, bool $status): ApiKey
5586
{
5687
$apiKey
@@ -63,6 +94,11 @@ public function updateActive(ApiKey $apiKey, bool $status): ApiKey
6394
return $apiKey;
6495
}
6596

97+
/**
98+
* Add an existing scope to an existing ApiKey entity
99+
*
100+
* @throws DuplicateScopeForApiKey
101+
*/
66102
public function addScope(ApiKey $apiKey, Scope $scope): ApiKey
67103
{
68104
// Do not add scopes twice
@@ -80,6 +116,11 @@ public function addScope(ApiKey $apiKey, Scope $scope): ApiKey
80116
return $apiKey;
81117
}
82118

119+
/**
120+
* Remove a scope from an ApiKey
121+
*
122+
* @throws ApiKeyDoesNotHaveScope
123+
*/
83124
public function removeScope(ApiKey $apiKey, Scope $scope): ApiKey
84125
{
85126
$found = false;
@@ -104,11 +145,17 @@ public function removeScope(ApiKey $apiKey, Scope $scope): ApiKey
104145
return $apiKey;
105146
}
106147

148+
/**
149+
* Validate an API key name
150+
*/
107151
public function isValidName(string $name): bool
108152
{
109153
return (bool) preg_match('/^[a-z0-9-]{1,255}$/', $name);
110154
}
111155

156+
/**
157+
* Create a new entity for logging admin events whenever one is triggered
158+
*/
112159
protected function logAdminEvent(ApiKey $apiKey, string $eventName): AdminEvent
113160
{
114161
$adminEvent = (new AdminEvent())

src/ServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function boot(): void
3535
Console\Command\GenerateScope::class,
3636
Console\Command\PrintApiKey::class,
3737
Console\Command\PrintScope::class,
38+
Console\Command\RegenerateApiKey::class,
3839
Console\Command\RemoveScope::class,
3940
]);
4041
}

test/Feature/Repository/ApiKeyRepositoryTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ public function testGenerate(): void
3636
}
3737
}
3838

39+
public function testRegenerate(): void
40+
{
41+
$entityManager = $this->createDatabase(app('em'));
42+
$repository = $entityManager->getRepository(ApiKey::class);
43+
44+
$apiKey = $repository->generate('testing');
45+
$entityManager->flush();
46+
47+
$oldKey = $apiKey->getApiKey();
48+
$apiKey = $repository->regenerate($apiKey);
49+
50+
$this->assertNotEquals($oldKey, $apiKey->getApiKey());
51+
}
52+
3953
public function testGenerateValidatesName(): void
4054
{
4155
$this->expectException(InvalidName::class);

0 commit comments

Comments
 (0)