Add admin page to list and revoke Micropub tokens
Gives a way to actually use the revocation capability built up over the last few commits from the admin side, not just self-service via the client. Lists client_id/scope/issue time per token (never the raw token itself, since only its hash is stored) with a revoke button for active ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L
This commit is contained in:
parent
9c9a6392c8
commit
9d6cf6c815
5 changed files with 159 additions and 0 deletions
33
app/Http/Controllers/Admin/TokensController.php
Normal file
33
app/Http/Controllers/Admin/TokensController.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\MicropubToken;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class TokensController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show a list of issued Micropub tokens.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$tokens = MicropubToken::latest()->get();
|
||||
|
||||
return view('admin.tokens.index', compact('tokens'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a Micropub token.
|
||||
*/
|
||||
public function revoke(MicropubToken $token): RedirectResponse
|
||||
{
|
||||
$token->revoke();
|
||||
|
||||
return redirect('/admin/tokens');
|
||||
}
|
||||
}
|
||||
27
resources/views/admin/tokens/index.blade.php
Normal file
27
resources/views/admin/tokens/index.blade.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
@extends('master')
|
||||
|
||||
@section('title')List Tokens « Admin CP « @stop
|
||||
|
||||
@section('content')
|
||||
<h1>Micropub Tokens</h1>
|
||||
@if($tokens->isEmpty())
|
||||
<p>No tokens have been issued.</p>
|
||||
@else
|
||||
<ul>
|
||||
@foreach($tokens as $token)
|
||||
<li>
|
||||
{{ $token->client_id }} — scope: {{ $token->scope }} — issued {{ $token->created_at->diffForHumans() }}
|
||||
@if($token->isRevoked)
|
||||
— revoked {{ $token->revoked_at->diffForHumans() }}
|
||||
@else
|
||||
<form action="/admin/tokens/{{ $token->id }}/revoke" method="post" style="display:inline">
|
||||
{{ csrf_field() }}
|
||||
{{ method_field('PUT') }}
|
||||
<button type="submit" name="revoke">Revoke</button>
|
||||
</form>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@stop
|
||||
|
|
@ -47,6 +47,11 @@
|
|||
or <a href="/admin/syndication">edit</a> them.
|
||||
</p>
|
||||
|
||||
<h2>Tokens</h2>
|
||||
<p>
|
||||
View and <a href="/admin/tokens">revoke</a> issued Micropub tokens.
|
||||
</p>
|
||||
|
||||
<h2>Bio</h2>
|
||||
<p>
|
||||
Edit your <a href="/admin/bio">bio</a>.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\NotesController as AdminNotesController;
|
|||
use App\Http\Controllers\Admin\PasskeysController;
|
||||
use App\Http\Controllers\Admin\PlacesController as AdminPlacesController;
|
||||
use App\Http\Controllers\Admin\SyndicationTargetsController;
|
||||
use App\Http\Controllers\Admin\TokensController;
|
||||
use App\Http\Controllers\ArticlesController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\BookmarksController;
|
||||
|
|
@ -147,6 +148,12 @@ Route::middleware(MyAuthMiddleware::class)->prefix('admin')->group(function () {
|
|||
Route::delete('/{clientId}', [ClientsController::class, 'destroy']);
|
||||
});
|
||||
|
||||
// Micropub Tokens
|
||||
Route::prefix('tokens')->group(function () {
|
||||
Route::get('/', [TokensController::class, 'index']);
|
||||
Route::put('/{token}/revoke', [TokensController::class, 'revoke']);
|
||||
});
|
||||
|
||||
// Bio
|
||||
Route::prefix('bio')->group(function () {
|
||||
Route::get('/', [BioController::class, 'show'])->name('admin.bio.show');
|
||||
|
|
|
|||
87
tests/Feature/Admin/TokensTest.php
Normal file
87
tests/Feature/Admin/TokensTest.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Admin;
|
||||
|
||||
use App\Models\MicropubToken;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TokensTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
#[Test]
|
||||
public function index_requires_authentication(): void
|
||||
{
|
||||
$response = $this->get('/admin/tokens');
|
||||
$response->assertRedirect();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function index_lists_issued_tokens(): void
|
||||
{
|
||||
$user = User::factory()->make();
|
||||
$token = MicropubToken::create([
|
||||
'token_hash' => hash('sha256', 'a-token'),
|
||||
'client_id' => 'https://quill.p3k.io',
|
||||
'me' => 'https://jonnybarnes.uk',
|
||||
'scope' => 'create update',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->get('/admin/tokens');
|
||||
$response->assertOk();
|
||||
$response->assertSeeText($token->client_id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function revoke_requires_authentication(): void
|
||||
{
|
||||
$token = MicropubToken::create([
|
||||
'token_hash' => hash('sha256', 'a-token'),
|
||||
'client_id' => 'https://quill.p3k.io',
|
||||
'me' => 'https://jonnybarnes.uk',
|
||||
'scope' => 'create',
|
||||
]);
|
||||
|
||||
$response = $this->put("/admin/tokens/{$token->id}/revoke");
|
||||
$response->assertRedirect();
|
||||
|
||||
$this->assertFalse($token->fresh()->isRevoked);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function revoke_marks_the_token_as_revoked(): void
|
||||
{
|
||||
$user = User::factory()->make();
|
||||
$token = MicropubToken::create([
|
||||
'token_hash' => hash('sha256', 'a-token'),
|
||||
'client_id' => 'https://quill.p3k.io',
|
||||
'me' => 'https://jonnybarnes.uk',
|
||||
'scope' => 'create',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)->put("/admin/tokens/{$token->id}/revoke");
|
||||
|
||||
$this->assertTrue($token->fresh()->isRevoked);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function revoke_redirects_to_index(): void
|
||||
{
|
||||
$user = User::factory()->make();
|
||||
$token = MicropubToken::create([
|
||||
'token_hash' => hash('sha256', 'a-token'),
|
||||
'client_id' => 'https://quill.p3k.io',
|
||||
'me' => 'https://jonnybarnes.uk',
|
||||
'scope' => 'create',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->put("/admin/tokens/{$token->id}/revoke");
|
||||
|
||||
$response->assertRedirect('/admin/tokens');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue