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')
+
No tokens have been issued.
+ @else ++ View and revoke issued Micropub tokens. +
+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'); + } +}