Skip to content

feat(quant_api): Log in to Quant (OAuth connect) for 2.3.0 - #263

Merged
stooit merged 34 commits into
2.xfrom
feat/oauth-connect
Nov 9, 2026
Merged

stooit merged 34 commits into
2.xfrom
feat/oauth-connect

Conversation

@stooit

@stooit stooit commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Part of 2.3.0. Includes #262 (fix/content-paths); merge that first.

"Log in to Quant": connect a Drupal 11 site by approving it in the Quant dashboard and choosing its project, instead of copying a token. The login is limited to that project, kept outside config, refreshed on cron, and a copied database does not publish with the original site's login.

Changes from the staging rehearsal and the release review

  • Log out ends the login in the dashboard (POST /api/oauth/revoke, best effort; an older dashboard lets the token expire). Change project and a failed project pick end the login they replace or discard.
  • A copy never revokes the original's login. Log out on a copy of another site's database only forgets the login locally.
  • The editor sees a login that needs renewing. After Quant refuses a refresh, the settings page shows a warning instead of "Connected", the button reads "Log in to Quant again", and the status report says "The Quant login needs renewing". An expired access token with a refresh token stored is not reported.
  • An unreachable endpoint no longer breaks the settings page. ConnectException is not a RequestException; the client now catches every Guzzle error and shows a fixed message. The exception text (which names the endpoint URL and can carry credentials) goes to the log with URL user information removed. The endpoint override warning redacts credentials too.
  • One success message after a project pick, not two; messages are built on page views only, so a stale "copy" error no longer shows after "This is the same site".
  • Tests run with strict config schema checks again.

Fixed for 2.3.0 after the rehearsal (second pass)

  • Failed pushes are visible. Pushes run after the response, so a failure was only in the log. Each failure is kept for the site (short reason, never the error text) until a later push of that page succeeds. Editors see a warning on their next page view (cached pages are invalidated when the list changes) and the status report lists the pages.
  • A save no longer logs the site out when quant_api_dashboard_url points outside quantgov.cloud: a logged-in site shows the platform of its login.
  • Blank or invalid pagination selectors no longer raise PHP warnings on every push.
  • Review fixes: Log out on a copied database never revokes the original site's login; replaced and discarded logins are revoked; an expired access token with a refresh token is not reported as needing a new login; the endpoint override warning redacts credentials.

Needs the dashboard

The revoke endpoint is on the dashboard develop branch (staging) now. Until production has it, Log out falls back to forgetting the login locally.

How it was tested

  • Staging (dash.stage.quantgov.cloud): login, project pick, push, cron refresh (new token, still project-limited), Log out (old token 200 before, 401 after), a login revoked in the dashboard (detected on cron, warning shown, "Log in to Quant again" renews it), endpoint down (plain message, no crash).
  • Whole module suite: 187 pass, 1 skipped; phpcs clean on changed files.

stooit added 25 commits November 8, 2026 14:02
Adds quant_api.credentials (settings.php override, then the OAuth connection,
then the typed token), the platform and auth_method settings with schema, and
an update hook that sets the platform from an existing endpoint. The client,
quant_search and the seed form no longer read api_token from config.
The editor approves the site in the Quant dashboard (client drupal-quant, S256
PKCE, state bound to the session and user) and picks a project. The module
limits the token to that project, reads the project's write token and keeps
both outside config. Change project starts a new login; the old project stays
until the new choice. Cron refreshes the token with the portal's rotation
rules; a database copy never refreshes the original site's tokens. Multi-domain
sites stay on token connect. Adds drush quant:auth-status, kernel tests for the
refresh rules and a functional test against a fake dashboard.
An unconnected site sent requests with empty credentials, for example a
PATCH /v1/unpublish when a search page was deleted. Every call now fails
closed before any request, with one log line that says why.
…d endpoint, and a rotated write token recovers

- A copy of the database (fingerprint mismatch) gets no write token in
  login mode, and the settings page says to log in on the copy.
- The platform chosen for a login is saved only when the login completes,
  so a cancelled login never re-points a working token connection.
- In login mode the endpoint is the platform's own host; a custom endpoint
  (a settings.php override aside) no longer receives the hidden token.
- Cron re-reads the project write token with the login after a refresh and
  at least daily, so a token rotated in the dashboard recovers.
…rite token

- A copy (fingerprint mismatch) logs an error that names the differing
  components, shows on the status report and the settings page, and offers
  "This is the same site" (configure quant), which stores the fingerprint
  again so the site publishes. The fingerprint docblock is corrected.
- A 401 from the content API re-reads the project write token with the
  login, at most once every 15 minutes, so a token rotated in the dashboard
  recovers on the next publish.
…ver-side dashboard

- $settings['quant_api_endpoint_url'] names the content API endpoint in
  every mode (for example a stage host behind basic auth). It is never
  editable in the UI; the settings page and drush show it without its
  userinfo, and an HTTP middleware moves userinfo from any request URI into
  an Authorization header before the request is sent, so it never reaches a
  message or a log.
- $settings['quant_api_dashboard_internal_url'] is the dashboard address for
  server-to-server calls (token, refresh, projects, limit, write token); the
  browser redirect keeps quant_api_dashboard_url. Default: the same.
The dashboard token response names the user but not the organisations, so a
real login found no projects. The test dashboard now answers like the real
one. A dashboard fixed in settings.php also sets the starting platform.
Without the Redirect module, a changed alias (for example Pathauto after a
title change) left a 404 at the old URL on the static site. The old alias
now gets a 301 to the new one, for published nodes and terms only. With the
Redirect module on, its own redirect entity is pushed as before.
Installing the module failed strict config schema checks for
views.view.quant_metadata_node.
…leted

A deleted published node or term sent its internal-path redirect again,
because the entity is still published during delete. /node/N kept a 301
to a page that no longer exists. Deleted content now unpublishes its
internal paths (the prefixed one only, for one deleted translation).
The file_link formatter settings of views.view.quant_metadata_file carried
a copy of Views UI options, which fails strict config schema checks.
Log out deleted the token only in Drupal, so it stayed valid on the portal
until it expired. The module now calls POST /api/oauth/revoke first (best
effort: an older dashboard or a failed call is logged, and the local
connection is deleted in every case).
After Quant refused a refresh (for example a revoked login), the settings
page still showed "Connected" and the reconnect note sat below it. The
page message is now the warning, and the status report shows "The Quant
login needs renewing". After a project pick the page shows one success
message, not two. The status is built on a page view only: built during a
submit, it showed stale after the redirect (for example the copied-site
error right after "This is the same site").
…n needs renewing

The warning asked the editor to log in again, but the only button was
"Change project".
…ettings page

Guzzle's ConnectException is not a RequestException, so ping(), project()
and search() let it through and the Quant API settings page failed with an
error page. They now catch every Guzzle error. The editor sees a fixed
message instead of the exception text, which names the endpoint URL and can
carry credentials; the log keeps the cause with URL user information
removed. The settings page shows one error, not two.
The core schema fixes from fix/content-paths make the shipped views valid.
…s; review fixes

From the release review:
- Log out (or a token save) on a copy of another site's database revoked the
  original site's login. A copy now only forgets the login locally.
- Change project and a failed project pick now end the login they replace
  or discard, instead of leaving it valid at the dashboard.
- An expired access token with a refresh token stored no longer shows
  "The Quant login needs renewing": the next request renews it.
- The settings page decides on its success and error messages from a session
  flag and the client's own unreachable flag, not from other queued
  messages, so a token save shows "Connected" again.
- The endpoint override warning no longer shows credentials in the URL.
With a dashboard override outside quantgov.cloud (for example a local
QuantGov dashboard), the platform radio defaulted to QuantCDN while the
saved platform was QuantGov, so saving the settings form for another reason
logged the site out.
Pushes run after the response is sent (and from the queue on cron), so a
failed push was only in the log. Each failure is now kept for the site, with
a short reason and never the error text, until a later push of the same page
succeeds. Editors (configure quant or access content overview) see a warning
on their next page view, and the status report lists the pages. Also: a
blank or invalid pagination selector no longer raises PHP warnings.
From review: a cached editor page did not show a new failure (the list now
has a cache tag, invalidated when it changes); a file that Quant already had
(MD5 matches) never cleared its failure; concurrent queue workers could lose
entries (the list changes under a lock); the message pointed editors without
access to the status report.
…ts in CI

CI (Drupal 11.1) failed: config/install/quant.settings.yml held 0/1 for keys
the schema types as boolean. Drupal 11.1 installs module config as trusted
data, without casting it, so the strict schema check saw integers. Drupal
11.4 casts on save, which is why a local 11.4 run passed. The install config
now holds true/false, and update 9009 casts integers that existing sites
stored.

CI now starts a PHP web server and sets SIMPLETEST_BASE_URL, so the
functional tests run instead of failing on the missing base URL.
CI (Drupal 11.1, which installs module config without casting it) still
failed on quant.token_settings. A new kernel test checks every config/install
and config/optional file of quant and its submodules against its schema, as
the files are on disk, and lists every mismatch in one run. It found:
- quant.token_settings disable/strict and the Quant Search entity settings
  stored 0/1 for booleans: now true/false; update 9009 casts them on
  existing sites.
- The quant_metadata Views field option kept 0 for each unticked key (from
  checkboxes), against its list-of-strings schema: the shipped views keep
  only the ticked keys, the field drops unticked keys on save, and update
  9010 filters saved views.
CI also installs drupal/webform, so the Quant Forms tests can run.
@stooit
stooit merged commit 0e3fe2f into 2.x Nov 9, 2026
2 checks passed
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