Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/adapter-manifest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'seamless-auth-api': minor
---

Publish an adapter manifest so server adapters can be driven by data instead of a hand-maintained route list (#372).

- `GET /.well-known/seamless-adapter.json` describes every route a server adapter exposes: which held token it sends (`none`, `preAuth`, `registration`, `access`, `refresh`), which tokens a response issues or clears, which body fields reach the browser under the adapter's cookie transport, and which routes support external message delivery. The same document is committed as `adapter-manifest.json` and regenerated by `npm run generate:api`.
- Routes declare this with a new `adapter` option on `defineRoute`. Registration fails when a public route does not say whether adapters expose it, when an ephemeral route does not say which token it takes, or when a route that always returns a token does not say what it issues.
- The four OTP send routes and the magic-link request now accept `POST`. They send a message, and a `GET` can be triggered cross-site without a CORS preflight. The `GET` forms still work and are marked deprecated.
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Direct provider wiring currently lives in [src/config/directMessaging.ts](/Users

- Prefer changing controllers/services over adding logic directly in routes.
- When adding or updating routes, use `schemas`, not `schema`, so request parsing and OpenAPI generation both work.
- Public, ephemeral, and token-issuing routes must declare `adapter` in `defineRoute` (see [src/lib/adapterManifest.ts](/Users/brandoncorbett/git/seamless-auth-api/src/lib/adapterManifest.ts)). It feeds `/.well-known/seamless-adapter.json`, which server adapters follow instead of a hard-coded route list. Run `npm run generate:api` after route changes so `openapi.json` and `adapter-manifest.json` stay committed in step.
- If a route requires auth, prefer the `auth` option in `createRouter` definitions. That keeps middleware wiring and OpenAPI security metadata aligned.
- If a route also needs admin checks or rate limiting, combine `auth` with extra `middleware`.
- Be careful around token response shapes and bearer auth. Browser-cookie auth mode has been removed.
Expand Down
232 changes: 232 additions & 0 deletions adapter-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
{
"schemaVersion": 1,
"apiVersion": "0.17.0",
"session": {
"subject": "sub",
"token": "token",
"refreshToken": "refreshToken",
"ttl": "ttl",
"refreshTtl": "refreshTtl"
},
"routes": [
{ "method": "GET", "path": "/admin/auth-events", "credential": "access" },
{ "method": "GET", "path": "/admin/auth-events/export", "credential": "access" },
{ "method": "GET", "path": "/admin/auth-events/integrity", "credential": "access" },
{ "method": "GET", "path": "/admin/credential-count", "credential": "access" },
{ "method": "GET", "path": "/admin/enrollment", "credential": "access" },
{ "method": "POST", "path": "/admin/enrollment/invites", "credential": "access" },
{ "method": "GET", "path": "/admin/organizations", "credential": "access" },
{ "method": "POST", "path": "/admin/organizations", "credential": "access" },
{ "method": "DELETE", "path": "/admin/organizations/{organizationId}", "credential": "access" },
{ "method": "GET", "path": "/admin/organizations/{organizationId}", "credential": "access" },
{ "method": "PATCH", "path": "/admin/organizations/{organizationId}", "credential": "access" },
{
"method": "GET",
"path": "/admin/organizations/{organizationId}/members",
"credential": "access"
},
{
"method": "POST",
"path": "/admin/organizations/{organizationId}/members",
"credential": "access"
},
{
"method": "DELETE",
"path": "/admin/organizations/{organizationId}/members/{userId}",
"credential": "access"
},
{
"method": "PATCH",
"path": "/admin/organizations/{organizationId}/members/{userId}",
"credential": "access"
},
{
"method": "DELETE",
"path": "/admin/organizations/{organizationId}/oauth-providers/{providerId}/retirement",
"credential": "access"
},
{
"method": "PUT",
"path": "/admin/organizations/{organizationId}/oauth-providers/{providerId}/retirement",
"credential": "access"
},
{ "method": "GET", "path": "/admin/reports/authentication-coverage", "credential": "access" },
{ "method": "GET", "path": "/admin/review-accounts", "credential": "access" },
{ "method": "GET", "path": "/admin/sessions", "credential": "access" },
{ "method": "GET", "path": "/admin/sessions/{userId}", "credential": "access" },
{ "method": "DELETE", "path": "/admin/sessions/{userId}/revoke-all", "credential": "access" },
{ "method": "DELETE", "path": "/admin/sessions/by-id/{id}", "credential": "access" },
{ "method": "DELETE", "path": "/admin/users", "credential": "access" },
{ "method": "GET", "path": "/admin/users", "credential": "access" },
{ "method": "POST", "path": "/admin/users", "credential": "access" },
{ "method": "GET", "path": "/admin/users/{userId}", "credential": "access" },
{ "method": "PATCH", "path": "/admin/users/{userId}", "credential": "access" },
{ "method": "GET", "path": "/admin/users/{userId}/anomalies", "credential": "access" },
{
"method": "POST",
"path": "/admin/users/{userId}/recovery/device-replacement",
"credential": "access"
},
{ "method": "POST", "path": "/admin/users/import", "credential": "access" },
{ "method": "GET", "path": "/internal/auth-events/grouped", "credential": "access" },
{ "method": "GET", "path": "/internal/auth-events/login-stats", "credential": "access" },
{ "method": "GET", "path": "/internal/auth-events/summary", "credential": "access" },
{ "method": "GET", "path": "/internal/auth-events/timeseries", "credential": "access" },
{ "method": "GET", "path": "/internal/metrics/dashboard", "credential": "access" },
{ "method": "GET", "path": "/internal/metrics/funnel", "credential": "access" },
{ "method": "GET", "path": "/internal/metrics/sign-ins", "credential": "access" },
{ "method": "GET", "path": "/internal/security/anomalies", "credential": "access" },
{
"method": "POST",
"path": "/login",
"credential": "none",
"issues": "preAuth",
"body": { "pick": ["message", "identifierType", "loginMethods"] }
},
{
"method": "DELETE",
"path": "/logout",
"credential": "access",
"clears": ["access", "registration", "refresh"]
},
{
"method": "DELETE",
"path": "/logout/all",
"credential": "access",
"clears": ["access", "registration", "refresh"]
},
{ "method": "POST", "path": "/magic-link", "credential": "preAuth", "delivery": true },
{ "method": "GET", "path": "/magic-link/check", "credential": "preAuth", "issues": "session" },
{ "method": "GET", "path": "/magic-link/verify/{token}", "credential": "none" },
{
"method": "POST",
"path": "/oauth/{providerId}/callback",
"credential": "none",
"issues": "session"
},
{ "method": "POST", "path": "/oauth/{providerId}/start", "credential": "none" },
{ "method": "GET", "path": "/oauth/providers", "credential": "none" },
{ "method": "GET", "path": "/organizations", "credential": "access" },
{ "method": "POST", "path": "/organizations", "credential": "access" },
{ "method": "GET", "path": "/organizations/{organizationId}", "credential": "access" },
{ "method": "PATCH", "path": "/organizations/{organizationId}", "credential": "access" },
{ "method": "GET", "path": "/organizations/{organizationId}/members", "credential": "access" },
{ "method": "POST", "path": "/organizations/{organizationId}/members", "credential": "access" },
{
"method": "DELETE",
"path": "/organizations/{organizationId}/members/{userId}",
"credential": "access"
},
{
"method": "PATCH",
"path": "/organizations/{organizationId}/members/{userId}",
"credential": "access"
},
{
"method": "POST",
"path": "/organizations/{organizationId}/switch",
"credential": "access",
"issues": "access"
},
{
"method": "POST",
"path": "/otp/generate-email-otp",
"credential": "registration",
"delivery": true
},
{
"method": "POST",
"path": "/otp/generate-login-email-otp",
"credential": "preAuth",
"delivery": true
},
{
"method": "POST",
"path": "/otp/generate-login-phone-otp",
"credential": "preAuth",
"delivery": true
},
{
"method": "POST",
"path": "/otp/generate-phone-otp",
"credential": "registration",
"delivery": true
},
{
"method": "POST",
"path": "/otp/verify-email-otp",
"credential": "registration",
"issues": "session"
},
{
"method": "POST",
"path": "/otp/verify-login-email-otp",
"credential": "preAuth",
"issues": "session"
},
{
"method": "POST",
"path": "/otp/verify-login-phone-otp",
"credential": "preAuth",
"issues": "session"
},
{
"method": "POST",
"path": "/otp/verify-phone-otp",
"credential": "registration",
"issues": "session"
},
{ "method": "POST", "path": "/refresh", "credential": "refresh", "issues": "session" },
{ "method": "POST", "path": "/registration/phone", "credential": "access" },
{ "method": "POST", "path": "/registration/phone/verify", "credential": "access" },
{
"method": "POST",
"path": "/registration/register",
"credential": "none",
"issues": "registration"
},
{ "method": "DELETE", "path": "/sessions", "credential": "access" },
{ "method": "GET", "path": "/sessions", "credential": "access" },
{ "method": "DELETE", "path": "/sessions/{id}", "credential": "access" },
{ "method": "GET", "path": "/step-up/status", "credential": "access" },
{ "method": "POST", "path": "/step-up/webauthn/finish", "credential": "access" },
{ "method": "POST", "path": "/step-up/webauthn/start", "credential": "access" },
{ "method": "GET", "path": "/system-config/admin", "credential": "access" },
{ "method": "PATCH", "path": "/system-config/admin", "credential": "access" },
{ "method": "GET", "path": "/system-config/oauth-providers", "credential": "access" },
{ "method": "POST", "path": "/system-config/oauth-providers", "credential": "access" },
{ "method": "DELETE", "path": "/system-config/oauth-providers/{id}", "credential": "access" },
{ "method": "PATCH", "path": "/system-config/oauth-providers/{id}", "credential": "access" },
{ "method": "GET", "path": "/system-config/public", "credential": "none" },
{ "method": "GET", "path": "/system-config/roles", "credential": "access" },
{ "method": "POST", "path": "/totp/disable", "credential": "access" },
{ "method": "POST", "path": "/totp/enroll/start", "credential": "access" },
{ "method": "POST", "path": "/totp/enroll/verify", "credential": "access" },
{ "method": "GET", "path": "/totp/status", "credential": "access" },
{
"method": "POST",
"path": "/totp/verify-login",
"credential": "preAuth",
"issues": "session"
},
{ "method": "POST", "path": "/totp/verify-mfa", "credential": "access" },
{ "method": "DELETE", "path": "/users/credentials", "credential": "access" },
{ "method": "POST", "path": "/users/credentials", "credential": "access" },
{
"method": "DELETE",
"path": "/users/delete",
"credential": "access",
"clears": ["access", "registration", "refresh"]
},
{ "method": "GET", "path": "/users/me", "credential": "access", "clears": ["preAuth"] },
{
"method": "POST",
"path": "/webauthn/login/finish",
"credential": "preAuth",
"issues": "session"
},
{ "method": "POST", "path": "/webauthn/login/start", "credential": "preAuth" },
{ "method": "POST", "path": "/webauthn/register/finish", "credential": "access" },
{ "method": "GET", "path": "/webauthn/register/start", "credential": "access" }
]
}
Loading
Loading