FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(customization): ship Page Builder auth customization core by windischb · Pull Request #173 · cocoar-dev/modgud · GitHub

feat(customization): ship Page Builder auth customization core - #173

Merged
windischb merged 34 commits into
developfrom
codex/customization-core
Aug 13, 2026
Merged

feat(customization): ship Page Builder auth customization core#173
windischb merged 34 commits into
developfrom
codex/customization-core

Conversation

Copy link
Copy Markdown
Contributor

Motivation

Ships the end-to-end Page Builder customization core for authentication pages, including compositions, page variants, application-scoped themes, email branding, preview/runtime alignment, and the amZettel login composition.

Also fixes the refresh-token response-loss incident tracked in Atlas as bug-token-500-replay-kill-logout: late metric failures can no longer turn a successful token mint into a 500, immediate retries receive a 30-second reuse leeway, and genuine replay remains isolated to the affected device authorization.

Surface touched

  • Authentication slice
  • OAuth/OIDC server
  • Admin API
  • Admin SPA
  • Plattform area
  • Realm provisioning / multi-tenancy
  • Docs
  • Build / CI / Docker

Tests

  • Unit tests added / updated for the changed code
  • Integration tests added / updated where applicable
  • dotnet test is fully green locally
  • Frontend changes verified in the browser

Local verification for the refresh-token fix:

  • Focused incident regression scenarios: 4/4 passed
  • Complete affected integration-test class: 18/18 passed
  • Unit suite: 1484/1484 passed
  • Full API suite: 587/589 passed. The two failing MagicLink tests reproduce unchanged on the original branch baseline.

Docs

  • docs updated where user-visible behavior changed
  • docs/roadmap.md updated if a roadmap item moved
  • N/A — internal-only change

Breaking changes

  • No breaking changes
  • Breaking change(s) described below

Notes

The OAuth hardening keeps the successful token response independent from observability lookups, configures a 30-second OpenIddict refresh-token reuse leeway, and aligns the custom reuse audit handler with that same window. Reuse after the window still triggers invalid_grant and revocation, while another device authorization remains usable.

windischb and others added 30 commits August 2, 2026 13:59
AddTenantedDataProtection deliberately renders ASP.NET Core's root
DataProtection key manager inert — the per-realm providers own the real key
rings. Its DataProtectionHostedService stayed registered though, and that
service resolves the root key ring on StartAsync to surface problems early.
Against an inert manager the read can never succeed, so every cold start
logged two ERROR lines describing a state we chose on purpose. For an IdP
that is expensive noise: an operator has to be able to trust that an error
at boot means something is genuinely broken.

Remove that one hosted-service registration. It has to happen after every
other registration: OpenIddict's builder calls AddDataProtection(), and the
TryAddEnumerable puts the check straight back precisely because we removed
it. Dropping it only inside AddTenantedDataProtection therefore leaves the
boot log unchanged. Program.cs calls the idempotent extension again right
before Build().

Only the root registration is touched. The per-tenant containers keep
normal key generation against MartenXmlRepository, so a genuine per-realm
key-ring failure still surfaces.

Verified on a cold start: zero ERR/FTL lines where there were two, and a
session cookie issued before the change still decrypts afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Vite bound ::1 only, so a proxy pointed at 127.0.0.1:4300 got connection
refused, and it rejected Host headers it did not recognise. Both stand in
the way of reaching the dev server through a local HTTPS proxy — which is
what WebAuthn, Secure cookies and OIDC redirect_uris want, rather than a
bare port. Browsers using localhost:4300 are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… boundary

Page Builder 3.0 stamps saved documents as schemaVersion 6, and the one
document change it carries is repeat.props.source -> props.contextPath. Our
server-side publish validator pinned the version at 5 and read the repeat
source only under its old name, so every document the new builder produced
would have been rejected by our own API — twice over, and with a message
pointing at neither cause.

Raise the accepted version and read contextPath first, falling back to
source. That mirrors the package's own migration, which skips when the new
key is already present. MinimumSchemaVersion stays at 4, so documents
stored before the rename keep validating, and the allowlist still bites
through the new spelling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ontract

3.0 removes `createAuthPageConfig()` and `createAuthPageDocument()`: the
package ships the runtime and nothing auth-specific. That is the right
boundary — the element, action and context allowlists decide what a
tenant-authored document may reach, so they belong to the IdP, not to a
generic authoring library.

The config is now ours (authPageConfig.ts), built from the shape the
package's IDP_INTEGRATION.md describes. The starting documents are ours
too, and rather than re-author them by hand — which would have silently
changed what a realm gets on "create page" — scripts/freeze-auth-documents
captured the existing derivation against the last pre-3.0 build and froze
it as JSON. What a realm gets is what it got before.

Migrated with them, since neither is an automatic ingest migration:
- 14 `visibleWhen: { source: 'state' }` conditions now read
  `runtime.viewState` from the runtime context, which every caller already
  passed. Page State bindings keep the name; there were none.
- 2 repeaters name their array through `contextPath`, and the documents are
  stamped schemaVersion 6.
- The page root no longer carries `minHeight`, in the style or in Root Page
  Code. 3.0 drops size on the root because the page is exactly its host
  container, and every new page opened with a warning about a line that did
  nothing. The container supplies the height already.

Renames applied: `config.fields` → `dataContract`, `config.elements` →
`elementTypes`, `PageVisualFont.source` → `src`. Note that
`normalizePageSchema`'s option key stays `elements` — only the PageConfig
property moved. Removed props: the renderer's `viewState` and the builder's
`previewState`, both redundant with the context. `availableStates` becomes
`runtime.viewState` with `allowedValues`, which is what restores the
dropdown in the condition editor; `previewFixtures` becomes host-owned
`previewViewports`; `stylePresets` is gone with nothing to replace it.

All six @cocoar/vue-* packages pinned to 3.0.0 as the guide asks, so an
authoring package cannot pair with an older renderer.

Verified end to end against the dev instance: create login page → save
(accepted by the schemaVersion 6 publish boundary) → activate in realm
settings → the runtime renders the document through CoarPageRenderer with
the SES page-script worker running. No console errors, no backend errors,
type-check and production build clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reference is the real page at https://app.amzettel.at, and the question
it answers is whether a realm can rebuild its own login without us shipping
anything tenant-specific. It can: the split screen, the brand panel and the
passwordless form are ordinary authored content, going in through the same
API the admin UI uses.

The brand panel is a composition — a visual-markup node with the wordmark,
tagline and the shopping list that ticks itself off. Login and logout can
pin the same immutable version, so updating it is one publish rather than an
edit per page. The form is the flow the reference shows: an email address, a
mailed code, a passkey alternative. The code step is not a second page but
the same document reacting to runtime.viewState.

Colour, radii and fonts are deliberately NOT in the document. They come from
the application theme, which is why amZettel is modelled as what it is — an
application of the realm, on its own subdomain, inheriting the realm's active
login variant. The same document renders in another application's colours
without touching a node.

Two things this surfaced:

- The visual-markup vocabulary was six variables, too thin for a real brand
  panel. It is now a documented set (surface, ink shades, lines, brand,
  radius, shadow, easing, fonts) fed from the application theme. The names
  are generic on purpose: the predecessor was a stylePreset with one
  tenant's background baked into our stylesheet, which only that tenant
  could use — and which 3.0 removed anyway.

- LoginView decided whether the page owns the language switcher by looking
  for the node id `login-language-switcher`, so a page that named its
  switcher anything else got a second one drawn by the host. It now matches
  on the `auth:toggle-language` action, which is the published contract; node
  ids belong to the author.

Verified against the reference at 1280 and on the phone viewport, where the
panel hides itself and the form takes the screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wordmark rendered far smaller than the reference. Inside the sealed
visual-markup iframe, `vw` resolves against the iframe — this pane — not the
viewport, so the original design's `clamp(2.6rem, 4.5vw, 3.4rem)` measured
4.5% of 563px and collapsed onto its clamp minimum. The panel is 44% wide,
which makes the equivalent ~10vw.

Worth remembering for any visual-markup content: viewport units inside the
sealed frame are pane units.

The seed script now publishes a new composition version when the panel
changed, rather than silently reusing the old one — published versions are
immutable, and a page stays pinned to what it materialized.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
windischb merged commit 9a62eed into develop Aug 13, 2026
11 of 13 checks passed
windischb deleted the codex/customization-core branch August 13, 2026 13:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants


Back | FazBrowse Home | New Git URL