You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(auth): allowed_email_domains client surface + <SignIn> SSR sign-in errors (#418)
* feat(auth): add allowed_email_domains to auth settings (SDK + MCP + CLI)
Public client surface for the hosted-auth Workspace-domain allowlist.
Gateway enforcement lives in run402-private (feat/hosted-auth-domain-allowlist,
issue #440); this lets operators restrict hosted Google sign-in to specific
email domains, enforced server-side at token issuance.
- SDK: allowed_email_domains on AuthSettings/AuthSettingsResult, added to
AUTH_SETTINGS_FIELDS + validateAuthSettings (shape check only; the gateway
owns normalization + domain-syntax validation)
- MCP: allowed_email_domains on the auth_settings tool schema + rendered output
- CLI: run402 auth settings --allowed-email-domains <csv|none> ('none' clears,
mirroring the existing --preferred null sentinel since parseFlag drops "")
- docs: llms-cli.txt command + R402_AUTH_DOMAIN_NOT_ALLOWED (403) catalog entry
- tests: 3 SDK + 1 MCP cases; full suite green
Refs #415
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(astro): <SignIn> renders hosted sign-in errors server-side (no JS)
Companion to the gateway hosted-auth-signin-error-ssr change
(run402-private#442): a failed hosted OAuth sign-in returns to the sign-in
page with a server-readable ?r402_auth_error=<code>, and <SignIn> renders the
message with zero client JS and zero consumer code.
- sign-in-methods.ts: googleOauthHtml emits errorReturnTo on the start link;
AUTH_ERROR_PARAM + messageForAuthError (specific copy for user-actionable
codes, generic fallback otherwise) + .r402-auth-error styling.
- SignIn.astro: reads ?r402_auth_error from Astro.url, renders a role="alert"
block in the multi-method branch; emits its own URL as errorReturnTo. The
password-only default branch is untouched, so the byte-identical default
render contract (6.1/6.3) is preserved.
- tests: alert render + absent + generic-fallback + builder/unit cases.
Full astro suite 285/285; tsc build clean.
- README: "Hosted sign-in errors render themselves" DX section.
Refs #415
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: astro/README.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -312,6 +312,19 @@ Every auth error carries a structured envelope with a `next_actions[].fix` paylo
312
312
313
313
Two pre-existing codes were enriched with `fix` payloads in this release (names unchanged): `R402_AUTH_CSRF_ORIGIN_MISMATCH` (submit from a Run402 component / same-origin form) and `R402_AUTH_PRERENDERED` (`export const prerender = false;`). At deploy time, `run402 doctor` statically detects a `createResponseFromTenantAssertion` call whose function lacks the `auth.sessionMint` capability and emits `R402_DOCTOR_AUTH_SESSION_MINT_CAPABILITY_MISSING` with the exact spec edit — catching the footgun before it becomes a runtime 403.
A failed hosted OAuth sign-in — e.g. a Google account whose domain is not in the project's `allowed_email_domains` (set via `run402 auth settings --allowed-email-domains …`) — is returned to your sign-in page with a **server-readable**`?r402_auth_error=<code>` query param, and `<SignIn>` renders the message for you. Server-side, **no client JS, zero extra code**:
318
+
319
+
```astro
320
+
---
321
+
import { SignIn } from "@run402/astro/components";
322
+
---
323
+
<SignIn returnTo="/admin" methods={["google"]} />
324
+
```
325
+
326
+
A `@gmail.com` user rejected from a Workspace-restricted admin tool lands back on this page with "This site is restricted to approved email domains. Sign in with your work account." rendered above the form. `<SignIn>` emits its own URL as the error-return target on the OAuth start link, so the round-trip is automatic — you never touch a query param or the URL hash. Known codes (`domain_not_allowed`, `account_exists_requires_link`, `identity_already_linked`) get specific copy; anything else (transient/infra) falls back to a generic "Sign-in could not be completed. Please try again." The block carries `role="alert"` and the `.r402-auth-error` class — restyle via your own CSS or the `--r402-error-fg` / `--r402-error-bg` / `--r402-error-border` custom properties. With no error param the render is byte-identical to before — nothing extra ships.
327
+
315
328
## `<Run402Picture>` — runtime CMS images
316
329
317
330
For images coming from a DB row at SSR time (the common CMS pattern), use `<Run402Picture asset={page.hero_asset}>`. The `asset` prop is the `AssetRef` JSONB that `r.assets.put()` returned at upload time — store the whole object, not just the URL, then render directly.
0 commit comments