Commit 857bcf0
Add AT Protocol (Bluesky) authentication provider (#102)
* Add AT Protocol (Bluesky) authentication provider
Implements a spec-compliant atproto OAuth login provider alongside the
existing Google/Twitch/Patreon providers. atproto's flow is heavier than
classic OAuth2, so this also extends the provider abstraction to support
async, multi-step flows without changing existing providers' behavior.
Highlights:
- Full identity discovery with no hardcoded Bluesky/PDS hosts:
handle -> DID (HTTPS .well-known/atproto-did, then DNS _atproto.<handle>
over DoH) -> DID document (did:plc via the PLC directory, did:web via the
domain) -> PDS (#atproto_pds service) -> authorization server
(.well-known/oauth-protected-resource -> oauth-authorization-server).
- PKCE, DPoP-bound tokens (RFC 9449, with use_dpop_nonce retry), and Pushed
Authorization Requests (PAR), as required by the atproto OAuth profile.
- Public OAuth client: serves its own client-metadata.json (the client_id),
no client secret. Enabled via ATPROTO_ENABLED; PLC/DoH endpoints overridable
via ATPROTO_PLC_URL / ATPROTO_DOH_URL.
- Transient PKCE verifier + DPoP key + discovered endpoints are persisted in
an encrypted, short-lived flow cookie across the redirect round-trip.
- Handle-entry form shown when no identifier is supplied (standard atproto UX).
- OAuthProvider gains optional authorize()/exchange()/handleExtraRoute() hooks;
shared user/session creation extracted into finishLogin().
- Power-strip + profile UI: "Continue with Bluesky" button and provider icons.
- Tests covering metadata, handle form, PAR+DPoP+PKCE (with nonce retry),
callback token exchange/session/credential, and state-mismatch rejection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015SgFKgH1gqfP2Zx3yrnzY2
* Configure atproto via factory config object instead of env vars
atproto is a public OAuth client with no secrets, so per the project's
configuration philosophy (env holds only secrets/per-deployment values) all of
its settings now live in the createStartupAPI factory config rather than env:
- enabled, clientName, plcUrl, dohUrl moved onto ProviderOptions (alongside the
existing Patreon-specific campaignId); enablement is providers.atproto.enabled.
- isAtprotoEnabled() now takes ProviderOptions; AtprotoProvider.create reads
clientName/plcUrl/dohUrl from options.
- getActiveProviders(env, providerConfigs) is config-aware; providerConfigs is
threaded through handleSSR and handleAdmin so the power-strip/profile/admin UIs
list atproto when enabled.
- Removed ATPROTO_* fields from StartupAPIEnv and wrangler.jsonc; regenerated
worker-configuration.d.ts.
- README + tests updated to enable atproto via the config object.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015SgFKgH1gqfP2Zx3yrnzY2
* Enable atproto by presence of its config key, not enabled: true
atproto has no env credentials, so the natural equivalent of "enabled because
credentials are present" is "enabled because the config key is present".
`providers: { atproto: {} }` now enables it; `enabled: false` remains as an
explicit opt-out for dynamically-built config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015SgFKgH1gqfP2Zx3yrnzY2
* Bump version to 0.4.0
Minor release for the new AT Protocol (Bluesky) authentication provider.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4102412 commit 857bcf0
18 files changed
Lines changed: 1211 additions & 214 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
| 74 | + | |
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
| |||
99 | 101 | | |
100 | 102 | | |
101 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
102 | 144 | | |
103 | 145 | | |
104 | 146 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
212 | 212 | | |
213 | 213 | | |
214 | 214 | | |
| 215 | + | |
| 216 | + | |
215 | 217 | | |
216 | 218 | | |
217 | 219 | | |
| |||
221 | 223 | | |
222 | 224 | | |
223 | 225 | | |
| 226 | + | |
224 | 227 | | |
225 | 228 | | |
226 | 229 | | |
| |||
257 | 260 | | |
258 | 261 | | |
259 | 262 | | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
260 | 272 | | |
261 | 273 | | |
262 | 274 | | |
| |||
585 | 597 | | |
586 | 598 | | |
587 | 599 | | |
| 600 | + | |
588 | 601 | | |
589 | 602 | | |
590 | 603 | | |
| |||
773 | 786 | | |
774 | 787 | | |
775 | 788 | | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
776 | 799 | | |
777 | 800 | | |
778 | 801 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
433 | 433 | | |
434 | 434 | | |
435 | 435 | | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
436 | 440 | | |
437 | 441 | | |
438 | 442 | | |
| |||
0 commit comments