CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Send permissive CORS headers from the fake AS

atproto OAuth public clients run in the browser and reach the fake
AS via `fetch`, which enforces same-origin policy. Without CORS
headers the browser blocked successful 200 responses from
JavaScript before the OAuth flow could progress.

A small axum middleware now answers OPTIONS preflight requests
with `204 No Content` plus permissive `Allow-Origin / Methods /
Headers` and tags every other response with
`Access-Control-Allow-Origin: *` and
`Access-Control-Expose-Headers: DPoP-Nonce` (the only custom
response header a client needs to read, per RFC 9449 §8.2). The
fake AS is a single-tenant test fixture, so reflecting any origin
is appropriate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+39 -2
+39 -2
src/commands/test/oauth/client/fake_as/endpoints.rs
··· 4 4 use axum::{ 5 5 Router, 6 6 body::Bytes, 7 - extract::State, 8 - http::{HeaderMap, Method, StatusCode, Uri}, 7 + extract::{Request, State}, 8 + http::{HeaderMap, HeaderValue, Method, StatusCode, Uri}, 9 + middleware::{Next, from_fn}, 9 10 response::{IntoResponse, Json, Redirect, Response}, 10 11 routing::{get, post}, 11 12 }; ··· 147 148 .route("/oauth/par", post(par)) 148 149 .route("/oauth/authorize", get(authorize)) 149 150 .route("/oauth/token", post(token)) 151 + .layer(from_fn(cors_middleware)) 150 152 .with_state(state) 153 + } 154 + 155 + /// Permissive CORS middleware for browser-based public clients. 156 + /// 157 + /// atproto OAuth public clients run in the browser and fetch the 158 + /// well-known endpoints, PAR, and token endpoints via `fetch`. Without 159 + /// CORS headers the browser blocks the response from JavaScript. The 160 + /// fake AS is a single-tenant test fixture — origin restrictions are 161 + /// not meaningful — so we reflect any origin and expose `DPoP-Nonce` 162 + /// (the only custom response header a client needs to read, per RFC 163 + /// 9449 §8.2). 164 + async fn cors_middleware(req: Request, next: Next) -> Response { 165 + if req.method() == Method::OPTIONS { 166 + let mut resp = StatusCode::NO_CONTENT.into_response(); 167 + let h = resp.headers_mut(); 168 + h.insert("access-control-allow-origin", HeaderValue::from_static("*")); 169 + h.insert( 170 + "access-control-allow-methods", 171 + HeaderValue::from_static("GET, POST, OPTIONS"), 172 + ); 173 + h.insert( 174 + "access-control-allow-headers", 175 + HeaderValue::from_static("*"), 176 + ); 177 + h.insert("access-control-max-age", HeaderValue::from_static("86400")); 178 + return resp; 179 + } 180 + let mut resp = next.run(req).await; 181 + let h = resp.headers_mut(); 182 + h.insert("access-control-allow-origin", HeaderValue::from_static("*")); 183 + h.insert( 184 + "access-control-expose-headers", 185 + HeaderValue::from_static("DPoP-Nonce"), 186 + ); 187 + resp 151 188 } 152 189 153 190 async fn did_json(