Feat: Implement JWT bearer token authentication

Protects image upload, delete, and tag-update endpoints behind
Bearer token auth. Public read endpoints remain open. Angular SPA
gains a login page, auth interceptor, and route guard for /upload.

- JWTAuthProvider (HS256, sub/iat/exp, secrets.compare_digest)
- POST /api/v1/auth/token login endpoint
- require_auth FastAPI dependency on all write routes
- AuthService, LoginComponent, authInterceptor, authGuard
- Detail page hides write controls for unauthenticated visitors
- 43 unit tests passing; integration tests require Docker stack

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 19:12:38 +00:00
parent d91a65abe5
commit 5fbbc1e67f
36 changed files with 3998 additions and 42 deletions

View File

@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ImageRecord, ImageService } from '../services/image.service';
import { AuthService } from '../auth/auth.service';
@Component({
selector: 'app-detail',
@@ -20,10 +21,10 @@ import { ImageRecord, ImageService } from '../services/image.service';
<h3>Tags</h3>
<div class="chips">
<span *ngFor="let tag of image.tags" class="chip">
{{ tag }} <button (click)="removeTag(tag)">×</button>
{{ tag }} <button *ngIf="auth.isAuthenticated()" (click)="removeTag(tag)">×</button>
</span>
</div>
<div class="add-tag">
<div class="add-tag" *ngIf="auth.isAuthenticated()">
<input
[(ngModel)]="newTagInput"
placeholder="Add tag…"
@@ -34,7 +35,7 @@ import { ImageRecord, ImageService } from '../services/image.service';
<p class="tag-error" *ngIf="tagError">{{ tagError }}</p>
</section>
<button class="delete-btn" (click)="showDeleteDialog = true">Delete Image</button>
<button *ngIf="auth.isAuthenticated()" class="delete-btn" (click)="showDeleteDialog = true">Delete Image</button>
<div class="dialog-overlay" *ngIf="showDeleteDialog">
<div class="dialog">
@@ -75,6 +76,7 @@ export class DetailComponent implements OnInit {
constructor(
public imageService: ImageService,
public auth: AuthService,
private route: ActivatedRoute,
public router: Router,
private cdr: ChangeDetectorRef,