feat: pluggable auth providers — add AT Protocol, refactor GitHub/Google#398
feat: pluggable auth providers — add AT Protocol, refactor GitHub/Google#398simnaut wants to merge 4 commits intoemdash-cms:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 5f8b24e The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
All contributors have signed the CLA ✍️ ✅ |
Scope checkThis PR changes 2,616 lines across 55 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new pluggable auth provider system in emdash, adds AT Protocol authentication as the first plugin-based provider, and refactors GitHub/Google OAuth into the same provider interface so all login methods are rendered uniformly in the login page and setup wizard.
Changes:
- Add
AuthProviderDescriptor+ route injection +virtual:emdash/auth-providersregistry generation. - Add
@emdash-cms/plugin-atprotoauth provider (routes, OAuth client, handle/DID verification, admin UI components). - Refactor admin login/setup UI to render configured providers dynamically and add a public
/_emdash/api/auth/modeendpoint.
Reviewed changes
Copilot reviewed 52 out of 55 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds workspace links and new dependencies for the AT Protocol plugin and demo site updates. |
| packages/plugins/atproto/tests/resolve-handle.test.ts | Adds tests for independent handle→DID verification error paths. |
| packages/plugins/atproto/tests/plugin.test.ts | Updates plugin descriptor tests to new expected entrypoint/format/storage. |
| packages/plugins/atproto/tests/oauth-client.test.ts | Adds tests for AT Protocol OAuth client metadata and singleton behavior. |
| packages/plugins/atproto/tests/auth.test.ts | Adds tests for atproto() auth provider descriptor contract/config passthrough. |
| packages/plugins/atproto/src/routes/setup-admin.ts | Implements setup-step route to initiate AT Protocol OAuth and persist setup state. |
| packages/plugins/atproto/src/routes/login.ts | Implements login initiation route for AT Protocol OAuth. |
| packages/plugins/atproto/src/routes/client-metadata.ts | Serves /.well-known/atproto-client-metadata.json for AT Protocol OAuth. |
| packages/plugins/atproto/src/routes/callback.ts | Implements AT Protocol OAuth callback, user provisioning, allowlist enforcement, setup finalization, session creation. |
| packages/plugins/atproto/src/resolve-handle.ts | Adds independent handle resolution via DNS-over-HTTPS + well-known HTTP. |
| packages/plugins/atproto/src/oauth-client.ts | Adds OAuth client singleton + DB-backed state/session storage support for Workers. |
| packages/plugins/atproto/src/env.d.ts | References core locals types for plugin route context typing. |
| packages/plugins/atproto/src/db-store.ts | Adds DB-backed store implementation for atcute OAuth state/sessions. |
| packages/plugins/atproto/src/auth.ts | Exposes atproto() returning an AuthProviderDescriptor (adminEntry, routes, public routes). |
| packages/plugins/atproto/src/admin.tsx | Adds provider UI components (LoginButton/LoginForm/SetupStep) for admin app. |
| packages/plugins/atproto/package.json | Exports new auth/admin/routes modules and declares deps/peers. |
| packages/core/tsconfig.json | Includes new *-admin.tsx auth provider admin components in TS compilation. |
| packages/core/src/virtual-modules.d.ts | Adds typing for virtual:emdash/auth-providers and config authProviders. |
| packages/core/src/index.ts | Re-exports new auth provider types. |
| packages/core/src/auth/types.ts | Defines AuthProviderDescriptor, AuthRouteDescriptor, and admin export contract types. |
| packages/core/src/auth/providers/google.ts | Adds google() provider descriptor returning adminEntry for UI integration. |
| packages/core/src/auth/providers/google-admin.tsx | Adds Google provider LoginButton component. |
| packages/core/src/auth/providers/github.ts | Adds github() provider descriptor returning adminEntry for UI integration. |
| packages/core/src/auth/providers/github-admin.tsx | Adds GitHub provider LoginButton component. |
| packages/core/src/auth/mode.ts | Re-exports new auth provider types from core auth mode module. |
| packages/core/src/astro/routes/PluginRegistry.tsx | Passes authProviders registry into the admin app. |
| packages/core/src/astro/routes/api/setup/status.ts | Adjusts setup status commentary for external auth mode. |
| packages/core/src/astro/routes/api/setup/index.ts | Adjusts setup state persistence comments for provider-based flows. |
| packages/core/src/astro/routes/api/setup/admin.ts | Merges admin info into existing setup state (preserve step 1 values). |
| packages/core/src/astro/routes/api/auth/oauth/[provider]/callback.ts | Makes OAuth callback setup-aware (first user becomes admin; finalize setup). |
| packages/core/src/astro/routes/api/auth/oauth/[provider].ts | Improves OAuth initiation error redirect target and missing-env error messaging. |
| packages/core/src/astro/routes/api/auth/mode.ts | Adds public endpoint exposing active auth mode + provider list + signupEnabled. |
| packages/core/src/astro/middleware/auth.ts | Merges provider-supplied public route bypass prefixes into middleware checks; whitelists /_emdash/api/auth/mode. |
| packages/core/src/astro/middleware.ts | Adds fast-path bypass for non-/_emdash injected provider routes like /.well-known/*. |
| packages/core/src/astro/integration/vite-config.ts | Adds virtual module IDs and generator for auth providers registry. |
| packages/core/src/astro/integration/virtual-modules.ts | Implements generateAuthProvidersModule() to bundle provider adminEntry exports. |
| packages/core/src/astro/integration/runtime.ts | Adds authProviders to EmDashConfig. |
| packages/core/src/astro/integration/routes.ts | Injects core auth mode route + provider-declared routes. |
| packages/core/src/astro/integration/index.ts | Wires authProviders through resolved config and injects provider routes. |
| packages/core/src/api/setup-complete.ts | Introduces shared idempotent finalizeSetup() helper. |
| packages/core/src/api/schemas/setup.ts | Adds schemas for AT Protocol login/setup request bodies. |
| packages/core/src/api/route-utils.ts | Public re-export bundle for plugin route handlers (parse/api helpers + finalizeSetup). |
| packages/core/package.json | Exposes new public exports for provider descriptors/admin components + route utils/schemas; adds optional peer for atproto plugin. |
| packages/auth/src/oauth/consumer.ts | Extracts shared findOrCreateOAuthUser() and updates callback path to use it. |
| packages/auth/src/index.ts | Re-exports findOrCreateOAuthUser + CanSelfSignup type. |
| packages/admin/src/lib/auth-provider-context.tsx | Adds React context for provider UI modules and helper hooks (list ordering). |
| packages/admin/src/lib/api/index.ts | Re-exports fetchAuthMode(). |
| packages/admin/src/lib/api/client.ts | Adds fetchAuthMode() client for public auth mode endpoint. |
| packages/admin/src/index.ts | Re-exports auth provider context utilities from admin package. |
| packages/admin/src/components/SetupWizard.tsx | Refactors setup wizard to offer passkey + provider methods and show URL error banners. |
| packages/admin/src/components/LoginPage.tsx | Refactors login page to render providers dynamically and fetch auth mode from public endpoint. |
| packages/admin/src/App.tsx | Wires auth provider registry into admin app via context provider. |
| demos/simple/package.json | Adds atproto plugin to demo dependencies. |
| demos/simple/astro.config.mjs | Configures authProviders: [github(), google(), atproto()] in the demo. |
| .changeset/stale-knives-fix.md | Changeset documenting the new pluggable auth provider system and AT Protocol support. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
08c0312 to
173d8ed
Compare
173d8ed to
d64888e
Compare
|
I have read the CLA Document and I hereby sign the CLA |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
|
Great! The test failures are just an outdated lockfile. I have a few things I'd like addressing, but I need to dig into it a bit deeper. I'll leave another review in a few hours. |
packages/core/src/astro/routes/api/auth/oauth/[provider]/callback.ts
Outdated
Show resolved
Hide resolved
| * | ||
| * @example ["did:plc:abc123", "did:web:example.com"] | ||
| */ | ||
| allowedDIDs?: string[]; |
There was a problem hiding this comment.
What's the default if none of these allowlists are set? I think the ideal would be to forbid self-signup in that case, except for the admin set up during onboarding
There was a problem hiding this comment.
The explicit check to prevent sign up when no allowedDIDs and allowedHandles are defined is added in up5f8b24e7d02ecfd956a96f270d5162e62aaf0520.
| @@ -0,0 +1,99 @@ | |||
| /** | |||
| * Database-backed store for AT Protocol OAuth state and sessions. | |||
There was a problem hiding this comment.
Rather than this approach where we're manually handling SQL, could AuthProviderDescriptor include storage like plugins do?
There was a problem hiding this comment.
Check out 5f8b24e, let me know if this is what you are looking for!
…rage, update terminology Addresses all 10 review comments on PR emdash-cms#398: - Split auth provider into @emdash-cms/auth-atproto (npm-installable), keep syndication plugin in @emdash-cms/plugin-atproto (marketplace) - Add `storage` field to AuthProviderDescriptor, reuse plugin storage infrastructure instead of manual SQL table creation - Rename "AT Protocol" → "Atmosphere", remove "PDS" from user-facing strings - Forbid self-signup when no allowlists configured (except first admin) - Fix core callback.ts import to use #db alias - Fix env.d.ts to reference emdash/locals package Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rage, update terminology Addresses all 10 review comments on PR emdash-cms#398: - Split auth provider into @emdash-cms/auth-atproto (npm-installable), keep syndication plugin in @emdash-cms/plugin-atproto (marketplace) - Add `storage` field to AuthProviderDescriptor, reuse plugin storage infrastructure instead of manual SQL table creation - Rename "AT Protocol" → "Atmosphere", remove "PDS" from user-facing strings - Forbid self-signup when no allowlists configured (except first admin) - Fix core callback.ts import to use #db alias - Fix env.d.ts to reference emdash/locals package
9213a5b to
2bf86c2
Compare
2bf86c2 to
75c4152
Compare
…rage, update terminology Addresses all 10 review comments on PR emdash-cms#398: - Split auth provider into @emdash-cms/auth-atproto (npm-installable), keep syndication plugin in @emdash-cms/plugin-atproto (marketplace) - Add `storage` field to AuthProviderDescriptor, reuse plugin storage infrastructure instead of manual SQL table creation - Rename "AT Protocol" → "Atmosphere", remove "PDS" from user-facing strings - Forbid self-signup when no allowlists configured (except first admin) - Fix core callback.ts import to use #db alias - Fix env.d.ts to reference emdash/locals package
Introduces a pluggable auth provider system and uses it to add AT Protocol authentication as the first plugin-based provider. GitHub and Google OAuth are refactored from hardcoded buttons into the same provider interface. - AuthProviderDescriptor interface with admin UI, routes, and public routes - virtual:emdash/auth-providers Vite module for distributing provider components - Shared findOrCreateOAuthUser() in @emdash-cms/auth for consistent signup gating - AT Protocol auth via @atcute/oauth-node-client with PKCE (public client) - allowedDIDs and allowedHandles config with independent handle verification via DNS-over-HTTPS + HTTP well-known (never trusts PDS handle claims) - Default role for new signups changed to Subscriber - First user becomes Admin during setup regardless of provider
- Replace countUsers() with setup_complete option flag check in both GitHub/Google and ATProto OAuth callbacks to prevent concurrent callbacks from both claiming first-user admin role - Memoize ensureTable() in db-store.ts with a module-level boolean so CREATE TABLE IF NOT EXISTS only runs once per process
…rage, update terminology Addresses all 10 review comments on PR emdash-cms#398: - Split auth provider into @emdash-cms/auth-atproto (npm-installable), keep syndication plugin in @emdash-cms/plugin-atproto (marketplace) - Add `storage` field to AuthProviderDescriptor, reuse plugin storage infrastructure instead of manual SQL table creation - Rename "AT Protocol" → "Atmosphere", remove "PDS" from user-facing strings - Forbid self-signup when no allowlists configured (except first admin) - Fix core callback.ts import to use #db alias - Fix env.d.ts to reference emdash/locals package
75c4152 to
5f8b24e
Compare
What does this PR do?
Introduces a pluggable auth provider system and uses it to add AT Protocol authentication as the first plugin-based provider. GitHub and Google OAuth are refactored from hardcoded buttons into the same provider interface. All auth methods — passkey, AT Protocol, GitHub, Google — are equal options on the login page and setup wizard.
Auth provider descriptor
Every provider implements
AuthProviderDescriptor:Each descriptor declares an
adminEntry(React components for login/setup UI), optionalroutes(Astro route handlers), andpublicRoutes(middleware bypass prefixes). Components are distributed to the admin app via avirtual:emdash/auth-providersVite virtual module.What changed
New pluggable infrastructure (core):
AuthProviderDescriptor/AuthRouteDescriptortypes inauth/types.tsauthProvidersconfig field onEmDashConfigvirtual:emdash/auth-providersvirtual module withgenerateAuthProvidersModule()injectAuthProviderRoutes()for generic route injection from descriptorsAuthProviderContextin admin for distributing provider UI components/_emdash/api/auth/modeendpoint returning available providersemdash/api/route-utilspublic export for API utilities used by plugin routesfinalizeSetup()shared helper for setup completion (idempotent, used by all auth callbacks)Shared OAuth user creation (
@emdash-cms/auth):findOrCreateOAuthUser()extracted as a shared export — handles OAuth account lookup, email auto-linking, and user creation withcanSelfSignuppolicyLogin page:
OAUTH_PROVIDERSarrayuseAuthProviderList()LoginButtonshow in a button grid; those withLoginFormexpand inline on clickSetup wizard:
AT Protocol (new —
@emdash-cms/plugin-atproto):atproto()returnsAuthProviderDescriptorwith routes for login, callback, setup-admin, and client metadata@emdash-cms/plugin-atproto/routes/*), not in core — removing the plugin removes the routesLoginButton(compact butterfly icon),LoginForm(handle input),SetupStepexported from@emdash-cms/plugin-atproto/admin@atcute/oauth-node-clientwith PKCE (public client, no key management needed)allowedDIDsallowlist for access control by permanent DID identifiersallowedHandlesallowlist with wildcard support for org-level domain gating (e.g.,*.mycompany.com). Handle ownership is independently verified via DNS-over-HTTPS and HTTP well-known resolution using@atcute/identity-resolver— the PDS's handle claim is never trusted directlyfindOrCreateOAuthUser()withcanSelfSignuppolicy for consistent signup gatingGitHub / Google (refactored to descriptors):
github()andgoogle()returnAuthProviderDescriptorwithadminEntrypointing to-admin.tsxcomponentsLoginButtoncomponents with provider icons, reusing core's existing[provider]OAuth routesOAuth error handling:
Refererto redirect errors back to the originating page (setup wizard vs login)Configuration
AT Protocol options:
New routes (AT Protocol)
POST /_emdash/api/auth/atproto/login— initiate OAuth flow (handle → PDS authorization URL)GET /_emdash/api/auth/atproto/callback— handle PDS redirect, create/find user, establish sessionPOST /_emdash/api/setup/atproto-admin— setup wizard flowGET /.well-known/atproto-client-metadata.json— client metadata (PDS fetches this)Type of change
Checklist
pnpm typecheckpassespnpm --silent lint:json | jq '.diagnostics | length'returns 0pnpm testpasses (or targeted tests for my change)pnpm formathas been runAI-generated code disclosure
Screenshots / test output
Test results (2026-04-08)
emdash (core): 2096 tests, 113 files — all passing
@emdash-cms/plugin-atproto: 76 tests, 7 files — all passing
@emdash-cms/auth: 41 tests, 2 files — all passing
@emdash-cms/admin:⚠️ Browser tests failing due to Playwright infrastructure issue (pre-existing
@vitest/browser-playwrightsetup error, not a regression from this PR).