diff --git a/functions/_middleware.ts b/functions/_middleware.ts index b6337790..b5a47725 100644 --- a/functions/_middleware.ts +++ b/functions/_middleware.ts @@ -3,13 +3,13 @@ // Content negotiation for AI agents: a request for a normal docs URL with // `Accept: text/markdown` gets that page's Markdown sibling instead of HTML, // on the same URL a person would visit. The `.md` URLs themselves (added in -// #654) keep working unchanged — this adds the "ask the canonical URL for +// #654) keep working unchanged: this adds the "ask the canonical URL for // Markdown" path on top, which is what agent-friendliness scanners check for. // // Cloudflare's dashboard-level "Markdown for Agents" (AI Crawl Control) does // the same thing, but only for a zone on Cloudflare DNS with a Pro/Business -// plan — shorebird.dev isn't (DNS lives elsewhere, just CNAMed to Pages), so -// this reimplements the negotiation ourselves. Recipe: +// plan, and shorebird.dev isn't (DNS lives elsewhere, just CNAMed to Pages), +// so this reimplements the negotiation ourselves. Recipe: // https://acceptmarkdown.com/recipes/cloudflare-workers import { markdownSiblingPath } from '../src/utils/markdown-path'; @@ -20,7 +20,7 @@ interface Env { // Static assets never have a Markdown or JSON sibling; skip negotiation for // them entirely. `/.well-known/*` is skipped too: those are single-format -// discovery/config files (RFC 8615) with their own declared Content-Type — +// discovery/config files (RFC 8615) with their own declared Content-Type; // they don't participate in the markdown/html/json negotiation this // middleware does for docs pages, and running them through it would 406 // a request that correctly sends that file's own declared Accept type, @@ -81,6 +81,7 @@ const AGENT_LINK_HEADERS = [ '; rel="agent"', '; rel="service-doc"', '; rel="service-desc"; type="application/json"', + '; rel="oauth-authorization-server"', '; rel="alternate"; type="text/plain"', '; rel="search"; type="application/opensearchdescription+xml"', '; rel="author"; type="text/plain"', @@ -117,7 +118,7 @@ const JSON_404_BODY = JSON.stringify( ); // The build already renders a proper Markdown 404 page (via the -// [...slug].md.ts route from #654, since 404.md is a normal docs entry) — +// [...slug].md.ts route from #654, since 404.md is a normal docs entry): // fetch that instead of hand-maintaining a second copy of its link list // here, which would drift from the real page over time. async function fetch404Markdown(assets: Fetcher, url: URL): Promise { @@ -189,8 +190,8 @@ function negotiate(acceptHeader: string | null): Preference { if (markdown < 0 && html < 0 && json < 0) { // Only 406 when the client explicitly rejected everything (an // unqualified `*/*;q=0`). A client that just didn't list one of our - // three representations — e.g. a health check sending - // `Accept: text/plain` — gets the default HTML rather than a hard + // three representations (e.g. a health check sending + // `Accept: text/plain`) gets the default HTML rather than a hard // failure; RFC 9110 §12.5.1 permits serving a non-preferred // representation instead of 406 for exactly this reason. const rejectsEverything = entries.some( diff --git a/public/auth.md b/public/auth.md index f4765ba7..7b1a883b 100644 --- a/public/auth.md +++ b/public/auth.md @@ -1,6 +1,6 @@ -# Shorebird auth.md — Authentication guide for agents and automated tooling +# Shorebird auth.md: Authentication guide for agents and automated tooling This document describes how AI agents, scripts, and CI/CD pipelines authenticate with Shorebird services. @@ -46,15 +46,21 @@ creating an `sb_api_*` API key, as described under the credential issuance step; once issued, it's the only artifact an agent needs to authenticate. -Registration page: `https://console.shorebird.dev` (Account > API Keys) — this -is an authenticated web console, not a callable API. A human (or an agent -driving a browser) must sign in there to issue a key; there is no anonymous, +Registration page: `https://console.shorebird.dev` (Account > API Keys). This is +an authenticated web console, not a callable API: a human (or an agent driving a +browser) must sign in there to issue a key, and there is no anonymous, unauthenticated, or programmatic registration path. ## Protected resources - **API base URL**: `https://api.shorebird.dev/api/v1` - **OpenAPI specification**: `https://api.shorebird.dev/openapi.json` +- **Authorization server metadata (RFC 8414)**: + `https://auth.shorebird.dev/.well-known/oauth-authorization-server`. This is + published by the authorization server itself, not mirrored here: RFC 8414 + resolves it from the issuer (`https://auth.shorebird.dev`), so a copy at this + domain would not be found by standards-based discovery and could drift from + the real one. - **API catalog**: `https://docs.shorebird.dev/.well-known/api-catalog` - **Agent card**: `https://docs.shorebird.dev/.well-known/agent.json` - **Reachability verification**: Verify endpoint status at