diff --git a/.changeset/social-sign-in-buttons-post.md b/.changeset/social-sign-in-buttons-post.md new file mode 100644 index 00000000..b4424b88 --- /dev/null +++ b/.changeset/social-sign-in-buttons-post.md @@ -0,0 +1,13 @@ +--- +'ePDS': patch +--- + +The "Sign in with Google" and "Sign in with GitHub" buttons now work, and each client app chooses whether to show them. + +**Affects:** End users, Client app developers, Operators + +**End users:** Clicking either button used to end on a "not found" page. If a sign-in cannot be started, the page now says so and the button can be pressed again. Only one sign-in can be in progress at a time, so pressing a second option while the first is starting no longer breaks the first. + +**Client app developers:** Social buttons are now opt-in per client, like the ATProto/Bluesky button. Add `"epds_social_providers": ["google"]` (and/or `"github"`) to your client metadata to show them. Clients that do not list a provider see no social buttons, even when the operator has configured it. + +**Operators:** The buttons now POST `{ provider, callbackURL: '/auth/complete' }` to `/api/auth/sign-in/social` instead of linking to it with a GET, which better-auth answers with 404. The page then follows the returned URL only when it is an https URL on that provider's authorization host (`accounts.google.com` for Google, `github.com` for GitHub); any other response shows the sign-in error instead. Configuring a provider no longer adds its button to every client's login page; only clients that list it in `epds_social_providers` get it. Because the buttons never worked before this change (every click returned 404), no client loses a working sign-in. Branding CSS that targeted `a.btn-google` / `a.btn-github` should target `.btn-google` / `.btn-github` instead. diff --git a/docs/configuration.md b/docs/configuration.md index 94427468..3f04a465 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -169,8 +169,10 @@ card when both `PDS_TERMS_OF_SERVICE_URL` and `PDS_PRIVACY_POLICY_URL` ### Social providers (optional) -Both ID and SECRET must be set to enable a provider. When set, social login -buttons appear on the login page. +Both ID and SECRET must be set to enable a provider. A provider's button +appears on the login page only for OAuth clients that also list it in their +client metadata (`"epds_social_providers": ["google"]`), so enabling a +provider does not change the login page of clients that did not ask for it. | Variable | Description | | ---------------------- | -------------------------------------------------------------------------------------------- | diff --git a/docs/tutorial.md b/docs/tutorial.md index 9b33f34e..39c0dd56 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -381,6 +381,24 @@ Constraints: - If `epds_handle_login_url` is unset or invalid, the button is not rendered. Existing clients see no behaviour change. +#### Optional: offer Google or GitHub sign-in + +If the PDS operator has configured a social provider (see +[Social providers](./configuration.md#social-providers-optional)), opt in +by listing it in your client metadata: + +```json +{ + "epds_social_providers": ["google"] +} +``` + +The login page then renders that provider's button above the email form. +A button appears only when both the operator configured the provider and +your client lists it, so a provider enabled for one app does not appear on +other apps' login pages. Accepted values: `"google"`, `"github"`. Unknown +values are ignored; if the field is unset, no social buttons are rendered. + #### Optional: custom CSS and favicon for ePDS pages (trusted clients) If your app is in the PDS operator's `PDS_OAUTH_TRUSTED_CLIENTS`, you can diff --git a/packages/auth-service/src/__tests__/__helpers__/login-page-script.ts b/packages/auth-service/src/__tests__/__helpers__/login-page-script.ts new file mode 100644 index 00000000..bc61aa3e --- /dev/null +++ b/packages/auth-service/src/__tests__/__helpers__/login-page-script.ts @@ -0,0 +1,268 @@ +/** + * Runs the login page's real inline script in a `node:vm` context against a + * minimal stand-in DOM, so tests can click its buttons and submit its forms. + * There is no DOM test environment in this repo, and asserting on the + * script's source text alone would not show that a handler is wired up. + * + * Only the elements the sign-in handlers use are real fakes; everything else + * the script touches (OTP boxes, steps, heartbeat UI) absorbs reads, writes + * and calls. + */ +import * as vm from 'node:vm' +import { vi, type Mock } from 'vitest' +import type { renderLoginPage } from '../../routes/login-page.js' + +export type LoginPageOptions = Parameters[0] + +/** + * renderLoginPage options for a plain email-step page with both social + * buttons requested; tests override what they need. + */ +export const LOGIN_PAGE_OPTIONS: LoginPageOptions = { + flowId: 'flow-1', + clientId: 'https://app.example.com/client-metadata.json', + clientName: 'Example', + branding: { epds_social_providers: ['google', 'github'] }, + customCss: null, + customFaviconUrl: null, + customFaviconUrlDark: null, + loginHint: '', + initialStep: 'email', + otpAlreadySent: false, + csrfToken: 'csrf', + authBasePath: '/api/auth', + pdsPublicUrl: 'https://pds.example.com', + otpLength: 6, + otpCharset: 'numeric', + heartbeatEnabled: false, +} + +/** The page's main inline script (the one that owns the login flow). */ +export function mainScript(html: string): string { + const scripts = [...html.matchAll(/