Skip to content

Add the Aurora add-on and its development harness - #135

Draft
sneridagh wants to merge 1 commit into
issue-132-phase-2from
issue-132-phase-3
Draft

sneridagh wants to merge 1 commit into
issue-132-phase-2from
issue-132-phase-3

Conversation

@sneridagh

Copy link
Copy Markdown
Member

Third phase of #132, stacked on #134. Draft: please don't merge. Each phase gets its own PR, built on the previous one, and none is merged until the whole series has been reviewed.

A vertical slice of sign-in for Plone Aurora: the login page lists the identity providers, a button starts a sign-in, and the provider's redirect back ends in a signed-in Aurora session. It shares identity-core with the Volto add-on.

Verified end to end

I ran a real OIDC sign-in through Aurora against a local Dex identity provider. The setup:

  • a throwaway Plone site on port 8081, with a Dex provider configured as in the backend's docker tests;
  • a throwaway Dex container;
  • the Aurora harness on port 3001.

The sequence:

  1. /login serves a Dex button under Aurora's own password form, in the server-rendered HTML.
  2. The button goes to /login-identity/start/dex. That answers with a redirect to Dex carrying redirect_uri=http://localhost:3001/login-identity, and Set-Cookie: __pas_identity_flow=…; Path=/; HttpOnly; SameSite=Lax.
  3. After signing in at Dex, the browser came back to /login-identity, then landed on / with Log Out in Aurora's header.

The failure paths, checked against the server's answers:

Request Answer
?error=access_denied "The provider refused the sign-in."
No code "This sign-in link is incomplete."
A forged code/state "That sign-in link is no longer valid. Please start again."
An unknown provider Redirect to "That sign-in option is not available right now."
A malformed provider id 404

How Aurora talks to the backend

Volto's browser reaches the backend through Volto's proxy, which rewrites every request into a virtual-host URL naming the public origin. Aurora's routes call the backend from the server, so aurora-identity/lib/backend.ts does what that proxy did:

  • Naming the public origin. backendUrl() builds the same VirtualHostBase/<proto>/<host>:<port>/<site>/++api++/VirtualHostRoot URL. The backend builds the callback URL from the portal URL; without this it would give the provider http://localhost:8080/Plone/login-identity.
  • Relaying the flow cookie. Starting a sign-in sets __pas_identity_flow (state, PKCE verifier, nonce), and finishing it reads that cookie back. The start route passes the backend's Set-Cookie to the browser unchanged. It names no domain, so the browser files it under Aurora's host. The callback forwards that one cookie and nothing else; Aurora's own session cookie is not the backend's to see.
  • Linking. The callback also sends Aurora's session token as Authorization, so a signed-in user linking a second identity is linked to their own account.

The add-on

Piece What it does
config/server.ts A rootLoaderData utility that fetches @login-providers for /login only, so the buttons are in the served page and no other page pays for them
slots/LoginProviders.tsx Core's ProviderButton per provider in the loginActions slot, the email "provider" left out. A whole-page navigation, because the start route redirects off-site
routes/start.ts Validates the provider id against the backend's PROVIDER_ID_PATTERN, starts the flow, follows only an http(s) authorize URL
routes/callback.tsx Handles a provider's code and a magic link's token alike, stores the session with Aurora's setAuthOnResponse, with an expiry read from the token. Returns to came_from only when it is a path on this site
components/IdentityUI/AuroraIdentityUI.tsx Aurora's IdentityUIProvider: i18next and the router Link

The callback path is /login-identity, the same as Volto's. It is the redirect URI registered with each provider, so it has to be the same whichever frontend serves the site.

Translations

Core's gettext catalogues stay the one source of truth. pnpm i18n in frontend/aurora writes them into aurora-identity/locales/<lang>/common.json, under one identity key:

Language Messages
de, es, pt-BR 24 each
en none: it falls back to the English defaults, as in Volto

Message ids are English sentences full of the . and : i18next treats as separators. translateWith() therefore:

  • looks keys up with keySeparator: '\u001f' and nsSeparator: false;
  • fills {placeholders} with core's interpolate, because i18next expects {{name}}.

Its tests run a real i18next instance and check that Aurora's own nested keys are not disturbed. make ci-i18n in the harness fails when the committed files are behind core's.

Changes to identity-core

  • CallbackCard: the card a provider redirect lands on. Volto's Callback now renders it too: its 17 existing tests pass unchanged, and the messages keep their ids, so the existing translations came along. It adds an unavailable reason, reusing the translated "That sign-in option is not available right now."
  • expiryFromToken, beside useridFromToken, both now reading the claims through one function.
  • Plain CSS instead of SCSS. Aurora has no Sass compiler. The files used nesting alone and were compiled once by sass; all 19 comment blocks were checked to survive.

Two harnesses, one packages/

frontend/aurora is its own pnpm workspace: React 19, pnpm 11, Aurora 1.0.0-alpha.15. Both harnesses install identity-core, so its node_modules points at whichever installed last. Each problem below was reproduced before it was fixed:

Problem, as reproduced Fix
With Aurora installed last, Volto's production bundle carried React 19.3.0 beside 18.2.0 volto-identity/razzle.extend.js aliases core's peers to Volto's copies, skipping any Volto aliases itself. A no-op in a project installing the published packages
Aurora resolving core's React 18 frontend/aurora/harness/vite.extend.js, a private dev-only add-on, deduplicates core's peers and lets Vite serve ../packages
Typechecking core against the other harness's types Each package's typecheck uses a tsconfig.typecheck.json mapping core's peers to its own copies, and the add-ons include core's sources
A first attempt put those mappings in tsconfig.json; Volto turns an add-on's tsconfig paths into webpack aliases (getAliasesFromTSConfig), so react resolved to @types/react and Storybook stopped building Mappings moved to tsconfig.typecheck.json
Storybook's packages/** glob followed aurora-identity's node_modules into Aurora's own stories Narrowed to packages/*/src
Core's jsdom: catalog: failed the Aurora install Core uses catalog: and workspace:* only for entries both workspaces have. Its i18n runs Volto's i18n.cjs by path

After the fixes, the production bundles of both orders were checked:

Installed last Volto bundle Aurora bundle
Aurora React 18 only React 19 only
Volto React 18 only React 19 only

AGENTS.md documents all of these under "Two harnesses share frontend/packages".

Other changes

  • Root make targets aurora-install, aurora-start, aurora-build, aurora-lint and aurora-test. They are opt-in: CI does not run them yet, and AGENTS.md and contributing.md say so.
  • Harness lint runs from frontend/ with --config, because ESLint ignores files outside a config's base path.
  • Docs and news: a fifth news scope, frontend/packages/aurora-identity/news/, and updated tables in AGENTS.md and contributing.md.
  • Left out of the template's output: .cookieplone.json held a local email address. Also left out: the template's GitHub workflows, Dockerfile, Playwright and Storybook, which belong in the CI and polish pass.

Not in this PR

  • CI jobs for the Aurora harness, and a Playwright test of the flow above.
  • Aurora pages for identities, profile, consent and applications (phase 4). After linking, the callback returns to came_from, since there is no identities page yet.
  • The magic-link and password forms on Aurora's login page, which keeps its own password form for now.
  • Styling the buttons to Aurora's theme, and Aurora's own icons in place of core's plain ones.
  • A docs page on the frontend split.

Checks

Check Result
Tests Core 19 files / 260 tests (+8 CallbackCard, +4 expiryFromToken). Volto 70 / 682, unchanged. Aurora add-on 3 / 19
make -C frontend lint, make aurora-lint Pass
ci-i18n, both harnesses Pass
make docs-build; Vale Pass; 0 errors
make -C frontend storybook-build Passes: 37 core stories
pnpm build (Volto), make aurora-build Pass, in both install orders

Third phase of #132: a vertical slice of sign-in for Plone Aurora, from the
login page's provider buttons to a stored session, sharing identity-core with
the Volto add-on.

- frontend/aurora is a harness generated from Cookieplone's aurora_addon
  template, its own pnpm workspace beside the Volto one. A dev-only harness
  add-on deduplicates identity-core's peers in Vite.
- frontend/packages/aurora-identity lists the providers in the login page's
  loginActions slot (fetched by a rootLoaderData utility on /login only),
  starts a sign-in at /login-identity/start/<provider> and finishes it at
  /login-identity, storing the token in Aurora's session cookie. Backend
  calls name Aurora's public origin through a virtual-host URL, so the
  provider sends the user back to Aurora, and the backend's flow cookie is
  relayed to the browser and back.
- Translations come from identity-core's gettext catalogues, written into
  i18next's common.json by `pnpm i18n`.
- identity-core gains CallbackCard, used by both frontends, and
  expiryFromToken. Its styles are plain CSS, compiled once from the SCSS.
- Both harnesses install identity-core, so each corrects for the other:
  volto-identity's razzle.extend.js aliases core's peers to Volto's copies,
  typechecking maps them in tsconfig.typecheck.json (never tsconfig.json,
  which Volto turns into webpack aliases), and Storybook's globs stay
  inside packages/*/src.
- Root make targets aurora-install, aurora-start, aurora-build, aurora-lint
  and aurora-test drive the harness. CI does not run them yet.

This branch has not been deployed

No deployments
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.

1 participant