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.
|
or <a href="/admin/syndication">edit</a> them.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h2>Tokens</h2>
|
||||||
|
<p>
|
||||||
|
View and <a href="/admin/tokens">revoke</a> issued Micropub tokens.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Bio</h2>
|
<h2>Bio</h2>
|
||||||
<p>
|
<p>
|
||||||
Edit your <a href="/admin/bio">bio</a>.
|
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\PasskeysController;
|
||||||
use App\Http\Controllers\Admin\PlacesController as AdminPlacesController;
|
use App\Http\Controllers\Admin\PlacesController as AdminPlacesController;
|
||||||
use App\Http\Controllers\Admin\SyndicationTargetsController;
|
use App\Http\Controllers\Admin\SyndicationTargetsController;
|
||||||
|
use App\Http\Controllers\Admin\TokensController;
|
||||||
use App\Http\Controllers\ArticlesController;
|
use App\Http\Controllers\ArticlesController;
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
use App\Http\Controllers\BookmarksController;
|
use App\Http\Controllers\BookmarksController;
|
||||||
|
|
@ -147,6 +148,12 @@ Route::middleware(MyAuthMiddleware::class)->prefix('admin')->group(function () {
|
||||||
Route::delete('/{clientId}', [ClientsController::class, 'destroy']);
|
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
|
// Bio
|
||||||
Route::prefix('bio')->group(function () {
|
Route::prefix('bio')->group(function () {
|
||||||
Route::get('/', [BioController::class, 'show'])->name('admin.bio.show');
|
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