feat: wire ATProto OAuth login via revolution/laravel-bluesky
Replaces the Fortify scaffolding (torn out in 6352493) with session-based
auth backed by ATProto OAuth. Data-minimization is the core stance — the
users table stores only `did` (primary key), encrypted `refresh_token`,
and `iss`; handle + full OAuthSession live in Laravel session storage
and are refetched on every login.
- Users table rewritten (did as string PK, no id/name/email/password).
- User model uses WithBluesky trait + minimal tokenForBluesky().
- AuthenticatedSessionController + single-invoke OAuthCallbackController.
Both explicitly setScopes() to override the package's hardcoded
transition:* defaults.
- OAuthSessionUpdated + OAuthSessionRefreshing listeners keep DB and
session store in sync; refresh race is handled by Laravel's default
database session driver serializing per-user requests.
- Granular repo:app.skyreader.* scopes (no transition:generic).
- Custom /oauth-client-metadata.json + /oauth-jwks.json routes +
OAuthConfig::clientMetadataUsing override so the client_id/URLs
point at our canonical paths (not the package's /bluesky/oauth/*).
- / route intercepts ?iss callback in dev (package hardcodes the dev
redirect to 127.0.0.1:8000).
- Inertia shares auth.handle from the session; User type + UserInfo +
UserMenuContent + AppHeader rewritten (handle + initials, no avatar).
- Settings profile + delete-user flow removed pending ATProto-native
profile surface; settings redirects to /settings/appearance.
- Pest tests cover login render, POST redirect, callback happy path,
and logout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>