The GitHub Actions job "Direct Backport Push" on texera.git/main has succeeded. Run started by GitHub user github-merge-queue[bot] (triggered by github-merge-queue[bot]).
Head commit for run: 186652ba9b1abb731a520e6ef97b461826803153 / Neil Ketteringham <[email protected]> feat(auth): add ORCID login (#7664) ### What changes were proposed in this PR? Closes #7516 by adding ORCID login as an optional feature, disabled by default. https://github.com/user-attachments/assets/3c1fe0f8-898b-4e22-b5ec-5a775042ed60 ORCID differs from the existing OIDC provider (Google) in two ways, and those two differences drive nearly all of this diff. **1. No email address.** This PR uses ORCID's authorization-code flow with the `/authenticate` scope, which returns an iD and a name and no email. (ORCID does support OpenID Connect — there's an `openid` scope and an `id_token` — but even under `openid` it doesn't assert an address.) Many Texera features require a valid email, so an ORCID account is provisioned `INACTIVE` with a NULL email and the address is collected at first sign-in by the prompt that #7758 already shipped: `AuthService.loginWithExistingToken` hands out no user while the `email` claim is null, and the dialog it opens is not dismissable — cancelling signs out. This PR adds no part of that flow and changes none of it; it only produces the account shape the prompt was built for. The rules that prompt enforces (all existing `PUT /auth/email` behaviour, listed here because they are what makes an ORCID account safe to create without an address): - an address held by an account that already has a credential (LOCAL/Google/ORCID) is refused with 409 - an address held by a contributor placeholder is claimed: the ORCID identity moves onto the placeholder's uid, and the row created at login is discarded - an account that already has an address can't replace it The attach is single-step, following repo precedent: `AuthResource.register` already claims a placeholder on an unverified, typed address, and there is no email verification anywhere in the codebase today. Adding verification is out of scope here but worth doing. **2. Not a single-step handoff.** Because this is a plain OAuth 2.0 authorization-code flow rather than the OIDC path Google takes, login can't be resolved in one clean step. The frontend gets a dedicated callback component that resolves the code and passes it to the backend before routing to the homepage. The CSRF `state` parameter is now verified there — the login page was already writing it to `sessionStorage`, but nothing read it back. - **`ExternalAuthProvisioner` gains a sibling entry point**, rather than the existing one widening. `ExternalProfile` keeps main's contract (`email: String`, provider-verified), and a new `ExternalIdentity(providerType, providerId, name)` covers a provider that vouches for no address, provisioned through `loginOrProvisionIdentityOnly`. Such a login is deliberately never matched to an existing account: the only address available for matching would be one the user typed, and linking on that is the takeover `ExternalProfile` warns about. `GoogleAuthResource` is unchanged. - **`refresh` no longer blanks a field the provider didn't assert.** A returning ORCID login carries no address, and by then the account may well have one collected through the prompt. - **`TexeraWebApplication`** registers the new resource. - **`ConfigResource` / `GuiConfig`** carry the `orcidLogin` flag to the login page. ### Config and how to enable `user-sys.orcid.{clientId,clientSecret,baseUrl,redirectUri}`, `GUI_LOGIN_ORCID_LOGIN`, and both k8s values files. `baseUrl` defaults to the ORCID sandbox. `GET /auth/orcid/config` returns 503 when any setting is missing, so the button stays disabled and no error toast appears. `redirectUri` is served to the login page by `GET /auth/orcid/config` rather than derived in the browser, so the authorize leg and the token exchange cannot disagree — ORCID requires them to match byte-for-byte. Serving ORCID locally needs the dev server on the IPv4 loopback (`ng serve --host 127.0.0.1`), because ORCID rejects `localhost` as a registered redirect URI and `ng serve` binds `localhost`/`::1` by default. This is a per-developer flag, not a repo-wide default: `angular.json` is unchanged. ### Any related issues, documentation, discussions? Closes #7516 ### How was this PR tested? New specs on both sides. The consent screen and token exchange are the one part that cannot be unit tested, so `exchangeCode` is a protected seam the specs override — as `GoogleAuthResourceSpec` does with `verifiedPayload` — and the real flow was driven by hand against the ORCID sandbox. - **`OrcidAuthResourceSpec` (new)**: provisioning from an authenticated iD — emailless INACTIVE account plus its `auth_provider` row, idempotent on a second login, the iD standing in for a private name. Refusals: a response naming no iD, a blank code, each missing config setting. - **`ExternalAuthProvisionerSpec`**: identity-only provisioning, two emailless accounts staying separate on a NULL email (`"user".email` is UNIQUE, which in Postgres doesn't constrain repeated NULLs), and a later-collected address surviving a refresh. - **`GoogleAuthResourceSpec` / `AuthResourceSpec`**: unchanged by this PR, run as regression over the provisioning refactor. - **Frontend**: `orcid-callback.component.spec.ts` (new) for the state check and every refusal path; `auth.service.spec.ts` for the `orcidAuth` endpoint and its error propagation; the login page's redirect and button gating. ``` sbt "WorkflowExecutionService/testOnly org.apache.texera.web.resource.auth.*" cd frontend && npx ng test --watch=false sbt scalafmtCheckAll && cd frontend && npx tsc -p tsconfig.json --noEmit && yarn format:ci ``` By hand, against the ORCID sandbox. Register `http://127.0.0.1:4200/callback/orcid` on a sandbox application (ORCID rejects `localhost`), then: ``` export USER_SYS_ORCID_CLIENT_ID=APP-XXXXXXXXXXXX export USER_SYS_ORCID_CLIENT_SECRET=... # read once per JVM, so export before starting export GUI_LOGIN_ORCID_LOGIN=true bin/local-dev.sh up # migrations + jOOQ codegen cd frontend && npx ng serve --host 127.0.0.1 ``` Sign in with ORCID at `http://127.0.0.1:4200/login`, consent, supply an address at the prompt, and reload to confirm you are not asked again. Refusals: a tampered `state` on the callback URL returns you to `/login`; an address belonging to a credentialed account keeps the dialog open. With the credentials unset, the button stays disabled and no error toast appears. **Migration**: applied to a database whose enum lacked `ORCID` under both runners this repo uses (`bin/local-dev.sh` keeps `SET search_path`; the Liquibase runner in `sql/docker-compose.yml` strips it, which is why the type is schema-qualified), then re-applied to confirm idempotence. ### Was this PR authored or co-authored using generative AI tooling? Co-Authored with Claude Opus 5 --------- Signed-off-by: Neil Ketteringham <[email protected]> Co-authored-by: Claude Opus 5 (1M context) <[email protected]> Co-authored-by: Yicong Huang <[email protected]> Report URL: https://github.com/apache/texera/actions/runs/33709243953 With regards, GitHub Actions via GitBox
