Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .github/workflows/aurora.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
name: Aurora CI

# The Aurora add-on, checked in its development harness, `frontend/aurora`.
# Two jobs: the checks, in one job rather than one per check, since each would
# install Aurora and build its library packages first, which is most of the
# run; and a real sign-in through Dex, which also needs the backend.

on:
workflow_call:
inputs:
node-version:
required: true
type: string
python-version:
required: true
type: string
plone-version:
required: true
type: string
working-directory:
required: false
type: string
default: frontend/aurora

jobs:
aurora:
name: "Aurora: Lint, i18n, unit tests and build"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

# The steps of Aurora's own `.github/actions/node_env_setup`: pnpm comes
# from the harness's `packageManager`, through Corepack.
- name: Use Node.js ${{ inputs.node-version }}
uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}

- name: Install and enable Corepack
run: |
npm install --global corepack@latest
corepack enable

- name: Get pnpm store directory
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-aurora-pnpm-store-${{ hashFiles(format('{0}/pnpm-lock.yaml', inputs.working-directory)) }}
restore-keys: |
${{ runner.os }}-aurora-pnpm-store-

# Checks Aurora out at the tag in `mrs.developer.json`, installs, and
# builds Aurora's library packages.
- name: Install
run: make install

- name: Lint and typecheck
run: make lint

- name: Catalogues match identity-core's
run: make ci-i18n

- name: Unit tests
run: make ci-test

- name: Build
run: make build

acceptance:
name: "Aurora: Sign-in through Dex"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Setup the backend
uses: plone/meta/.github/actions/setup_backend_uv@2.x
with:
python-version: ${{ inputs.python-version }}
plone-version: ${{ inputs.plone-version }}
working-directory: backend

- name: Use Node.js ${{ inputs.node-version }}
uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}

- name: Install and enable Corepack
run: |
npm install --global corepack@latest
corepack enable

- name: Get pnpm store directory
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"

- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-aurora-pnpm-store-${{ hashFiles(format('{0}/pnpm-lock.yaml', inputs.working-directory)) }}
restore-keys: |
${{ runner.os }}-aurora-pnpm-store-

- name: Install
run: make install

- name: Build Aurora
run: pnpm build

- name: Install Playwright's browser
run: pnpm exec playwright install --with-deps chromium

# The three services, as `make acceptance-*-start` starts them, in the
# background of this job.
- name: Start the backend, Dex and Aurora
run: |
nohup make acceptance-backend-start > backend.log 2>&1 &
docker run -d --name aurora-acceptance-dex -p 5556:5556 \
-v "$GITHUB_WORKSPACE/backend/tests/_resources/dex/config.yaml:/etc/dex/config.yaml:ro" \
ghcr.io/dexidp/dex:v2.44.0 dex serve /etc/dex/config.yaml
PLONE_API_PATH=http://localhost:55001/plone nohup pnpm start:prod > aurora.log 2>&1 &
for url in \
http://localhost:55001/plone/++api++/@login-providers \
http://127.0.0.1:5556/dex/.well-known/openid-configuration \
http://localhost:3000/; do
timeout 300 bash -c "until curl -sf -o /dev/null $url; do sleep 2; done" \
|| { echo "$url never answered"; exit 1; }
done

- name: Register the Dex provider
run: make acceptance-provider

- name: Acceptance tests
run: make acceptance-test

- name: Service logs
if: failure()
run: |
tail -n 100 backend.log aurora.log
docker logs aurora-acceptance-dex | tail -n 100

- name: Upload the Playwright traces
if: failure()
uses: actions/upload-artifact@v7
with:
name: aurora-acceptance-results
path: ${{ inputs.working-directory }}/acceptance/results
retention-days: 7
1 change: 1 addition & 0 deletions .github/workflows/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ jobs:
frontend:
- 'frontend/**'
- '.github/workflows/frontend*'
- '.github/workflows/aurora*'
changelog-backend:
- 'backend/**'
changelog-frontend:
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ jobs:
contents: read
packages: write

# The Aurora add-on. Gated on the same paths as the Volto frontend: both
# build on `frontend/packages/identity-core`.
aurora:
uses: ./.github/workflows/aurora.yml
needs:
- config
with:
node-version: ${{ needs.config.outputs.node-version }}
python-version: ${{ needs.config.outputs.python-version }}
plone-version: ${{ needs.config.outputs.plone-version }}
if: ${{ needs.config.outputs.frontend == 'true' }}
permissions:
contents: read

storybook:
name: "Storybook"
uses: ./.github/workflows/tmp-frontend-storybook.yml
Expand Down Expand Up @@ -115,6 +129,7 @@ jobs:
- backend
- docs
- frontend
- aurora
- storybook
- deploy-docs
steps:
Expand All @@ -127,5 +142,6 @@ jobs:
echo '| backend | ${{ needs.backend.result }} |' >> $GITHUB_STEP_SUMMARY
echo '| docs | ${{ needs.docs.result }} |' >> $GITHUB_STEP_SUMMARY
echo '| frontend | ${{ needs.frontend.result }} |' >> $GITHUB_STEP_SUMMARY
echo '| aurora | ${{ needs.aurora.result }} |' >> $GITHUB_STEP_SUMMARY
echo '| storybook | ${{ needs.storybook.result }} |' >> $GITHUB_STEP_SUMMARY
echo '| deploy-docs | ${{ needs.deploy-docs.result }} |' >> $GITHUB_STEP_SUMMARY
15 changes: 14 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ doing so:
entries both catalogues define, and `workspace:*` only for packages both
workspaces have.

The Aurora add-on renders Aurora's `/login` with its own page,
`aurora-identity/routes/login.tsx`. Aurora's registry cannot replace a route,
and the `loginActions` slot it offers sits inside its password `<Form>`, where
the add-on's forms cannot go. So `aurora-identity/index.ts` swaps the file
`@plone/cmsui` registered for `/login` (`lib/routes.ts`), and warns at build
time when that file is no longer there. Upgrading Aurora means checking that
`AURORA_LOGIN_FILE` still names it.

The Aurora version is pinned by `frontend/aurora/mrs.developer.json`. Aurora's
upgrade guides live in its checkout, at
`frontend/aurora/core/docs/upgrade-guide/`.

`frontend/core` and `frontend/aurora/core` are **not ours**. They are
`mrs.developer` checkouts of Volto and Aurora, excluded by their harness's
`.gitignore`. Never edit them, never cite them as this project's convention,
Expand Down Expand Up @@ -94,7 +106,8 @@ Run these before proposing a commit. CI runs the same ones.
| `make check` | root | `make format` then `make lint`, both halves |
| `make test` | root | `make backend-test` and `make frontend-test` |
| `make check-imports` | `backend/` | The core/server layer boundary |
| `make aurora-lint`, `make aurora-test` | root | The Aurora add-on. Needs `make aurora-install`; **not run by CI yet** |
| `make aurora-lint`, `make aurora-test`, `make aurora-build` | root | The Aurora add-on. Needs `make aurora-install`; CI runs them in `.github/workflows/aurora.yml` |
| `make acceptance-test` | `frontend/aurora` | A real sign-in through Dex, with Playwright. Needs the services its `acceptance-*` targets start; CI runs it too |
| `make docs-build` | root | Sphinx with `-W`, warnings as errors |
| `make vale` | `docs/` | Prose style. **Errors must be zero**; warnings are advisory |

Expand Down
138 changes: 138 additions & 0 deletions docs/docs/concepts/frontends.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
myst:
html_meta:
"description": "Why the pas.plugins.identity frontend is split into a shared core and one add-on per frontend, for Volto and Plone Aurora."
"property=og:description": "Why the pas.plugins.identity frontend is split into a shared core and one add-on per frontend, for Volto and Plone Aurora."
"property=og:title": "About the two frontends"
"keywords": "Plone, pas.plugins.identity, Volto, Plone Aurora, identity-core, frontend"
---

(concepts-frontends)=

# About the two frontends

Plone has two React frontends: Volto, and its successor, Plone Aurora.
They run different versions of React, route differently, and translate with different libraries.
A site runs one of them.
The sign-in it offers is the same either way, because the package ships one add-on for each, built on a shared core.

`@plone-collective/identity-core`
: What both frontends share: the REST payload types, a table of every REST path, the helpers that need no framework, the login components, and the card a provider's redirect lands on.

`@plone-collective/volto-identity`
: The Volto add-on.
Everything this package's frontend does: sign-in, the identities page, the profile, consent, and the control panels.

`@plone-collective/aurora-identity`
: The Plone Aurora add-on.
Sign-in: the login page, starting a sign-in with a provider, and finishing it.

<!-- frontend/packages/identity-core/src/index.ts, frontend/packages/aurora-identity/index.ts -->

```{mermaid}
:config: {"flowchart": {"htmlLabels": false}}
flowchart TB
core["identity-core<br/>types, endpoints, helpers,<br/>login components"]
volto["volto-identity<br/>React 18, Redux, react-intl"]
aurora["aurora-identity<br/>React 19, React Router, i18next"]
backend["pas.plugins.identity<br/>REST API"]
volto --> core
aurora --> core
volto -->|"through Volto's API proxy"| backend
aurora -->|"from Aurora's server"| backend
```

## What the core may not import

The core imports no frontend framework.
Not Volto, not Aurora, not Redux, not a router, and not an i18n library.
React, `react-aria-components` and `@plone/components` are allowed, because both frontends have them.
An ESLint rule rejects anything else.

<!-- frontend/.eslintrc.js, the override for packages/identity-core/** -->

A component still needs to translate a message, link to a page, and draw an icon, and each frontend does those differently.
So the core's components ask for them through `useIdentityUI()`, and each add-on wraps its pages in a provider that answers:

| Asked for | Volto's answer | Aurora's answer |
|---|---|---|
| Translating a message | `react-intl` | `i18next` |
| Linking within the site | Volto's router link | React Router's link |
| Icons | Volto's icons | Quanta's icons, from `@plone/icons` |
| The password-reset page | `/passwordreset` | `/reset-password` |

<!-- frontend/packages/identity-core/src/components/IdentityUI/IdentityUI.tsx, frontend/packages/volto-identity/src/components/IdentityUI/VoltoIdentityUI.tsx, frontend/packages/aurora-identity/components/IdentityUI/AuroraIdentityUI.tsx -->

Without a provider the components still render, in English, with plain links and plain icons.

## One set of translations

The core's messages are declared with `defineMessages`, and their translations live in the core's gettext catalogues.
Volto reads those catalogues as they are.
For Aurora, `pnpm i18n` in the harness writes them into the Aurora add-on's `locales/<lang>/common.json`, so a translation is made once and reaches both frontends.

<!-- frontend/packages/identity-core/locales/, frontend/packages/aurora-identity/scripts/i18n.mjs, frontend/packages/aurora-identity/lib/i18n.ts -->

## One look, two themes

The core's styles are plain CSS, since Aurora has no Sass compiler.
They read `--identity-*` custom properties, which the core defines with values that suit Volto.
The Aurora add-on redefines the ones a Quanta page shows the difference in: the accent, the state colours, the surfaces, and the width of a provider button.
A site restyles either frontend the same way, by redefining the properties.

<!-- frontend/packages/identity-core/src/styles.css, frontend/packages/aurora-identity/components/IdentityUI/AuroraIdentityUI.css -->

## Where Aurora's add-on calls the backend from

Volto's browser reaches the backend through Volto's API proxy, which rewrites every request into a virtual-host URL naming the site's public address.
Aurora's add-on calls the backend from Aurora's server, in the loaders and actions of its routes, so it builds that URL itself.
The backend builds the callback URL it gives a provider from that address, so the provider sends the visitor back to Aurora, not to the backend.

<!-- frontend/packages/aurora-identity/lib/backend.ts -->

Starting a sign-in sets the backend's flow cookie, and finishing it reads that cookie back.
The add-on passes it to the browser on the way out and back to the backend on the way in, and sends nothing else of the browser's.

The callback path is `/login-identity` in both frontends.
It is the redirect URI registered with every provider, so a site moving from Volto to Aurora keeps its provider registrations.

<!-- frontend/packages/aurora-identity/lib/paths.ts -->

## Why Aurora's login page is replaced

Aurora's own login page draws a password form and offers a slot inside it for other add-ons.
This package's ways in are forms of their own, and HTML does not allow a form inside a form.
Aurora's registry can add a route but not replace one, so the add-on keeps Aurora's `/login` route and changes the file it renders.
The page keeps Aurora's frame: the close link, the logo and hero slots, and the heading.
Inside it is the core's login form, the same one the Volto add-on shows.

<!-- frontend/packages/aurora-identity/lib/routes.ts, frontend/packages/aurora-identity/routes/login.tsx -->

If a later Aurora release moves its login page, the add-on leaves Aurora's page in place and warns while the site is built.

## Settings

Both add-ons have the same two login settings, with the same defaults.
Each reads them where its frontend reads settings at run time.

| Setting | Default | Volto | Aurora |
|---|---|---|---|
| Offer Plone's password form | Off | `RAZZLE_IDENTITY_SHOW_PLONE_LOGIN` | `IDENTITY_SHOW_PLONE_LOGIN` |
| Start a sole provider at once | On | `RAZZLE_IDENTITY_REDIRECT_TO_SOLE_PROVIDER` | `IDENTITY_REDIRECT_TO_SOLE_PROVIDER` |

<!-- frontend/packages/volto-identity/src/helpers/showPloneLogin.ts, frontend/packages/volto-identity/src/helpers/redirectToSoleProvider.ts, frontend/packages/aurora-identity/lib/settings.ts -->

The environment variable wins over `config.settings.identity` in both.
A login page visited with `?choose` shows the options, whatever the second setting says.

## What Aurora does not have yet

The Aurora add-on covers signing in.
The identities page, the profile, the consent screen and the control panels exist in the Volto add-on only.
Neither the Aurora add-on nor the core is published to npm yet.

## Related

- {doc}`mental-model`
- {doc}`layers`
- {doc}`../contributing`
1 change: 1 addition & 0 deletions docs/docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Start with {doc}`mental-model`. It names everything the other pages assume.
mental-model
identities
layers
frontends
email-verification
users-as-content
profiles-and-groups
Expand Down
Loading
Loading