The GitHub Actions job "Required Checks" on texera.git/main has failed.
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/33709244157

With regards,
GitHub Actions via GitBox

Reply via email to