From 9d6cf6c815e5fd1f4eb0e41c7dff0c89e04cf47f Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Thu, 13 Aug 2026 16:18:52 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_014625MfkGZ7GVdbqKme4a8L --- .../Controllers/Admin/TokensController.php | 33 +++++++ resources/views/admin/tokens/index.blade.php | 27 ++++++ resources/views/admin/welcome.blade.php | 5 ++ routes/web.php | 7 ++ tests/Feature/Admin/TokensTest.php | 87 +++++++++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 app/Http/Controllers/Admin/TokensController.php create mode 100644 resources/views/admin/tokens/index.blade.php create mode 100644 tests/Feature/Admin/TokensTest.php diff --git a/app/Http/Controllers/Admin/TokensController.php b/app/Http/Controllers/Admin/TokensController.php new file mode 100644 index 00000000..1b5348f9 --- /dev/null +++ b/app/Http/Controllers/Admin/TokensController.php @@ -0,0 +1,33 @@ +get(); + + return view('admin.tokens.index', compact('tokens')); + } + + /** + * Revoke a Micropub token. + */ + public function revoke(MicropubToken $token): RedirectResponse + { + $token->revoke(); + + return redirect('/admin/tokens'); + } +} diff --git a/resources/views/admin/tokens/index.blade.php b/resources/views/admin/tokens/index.blade.php new file mode 100644 index 00000000..cb7edda9 --- /dev/null +++ b/resources/views/admin/tokens/index.blade.php @@ -0,0 +1,27 @@ +@extends('master') + +@section('title')List Tokens « Admin CP « @stop + +@section('content') +

Micropub Tokens

+ @if($tokens->isEmpty()) +

No tokens have been issued.

+ @else + + @endif +@stop diff --git a/resources/views/admin/welcome.blade.php b/resources/views/admin/welcome.blade.php index 269ccdc5..663cfdc4 100644 --- a/resources/views/admin/welcome.blade.php +++ b/resources/views/admin/welcome.blade.php @@ -47,6 +47,11 @@ or edit them.

+

Tokens

+

+ View and revoke issued Micropub tokens. +

+

Bio

Edit your bio. diff --git a/routes/web.php b/routes/web.php index fbba6329..e8924f32 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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'); diff --git a/tests/Feature/Admin/TokensTest.php b/tests/Feature/Admin/TokensTest.php new file mode 100644 index 00000000..0c415296 --- /dev/null +++ b/tests/Feature/Admin/TokensTest.php @@ -0,0 +1,87 @@ +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'); + } +}