Neilk1021 opened a new pull request, #7664:
URL: https://github.com/apache/texera/pull/7664
### What changes were proposed in this PR?
Closes #7516 by adding ORCID login as an optional feature, disabled by
default.
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 after signing in with ORCID the user is prompted by a modal
to attach one.
The rules that prompt enforces:
- 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, we can't
resolve login 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.
#### Schema change required to build this branch
`sql/updates/38.sql` adds `ORCID` to `provider_type_enum`, with the matching
`changelog.xml` changeSet and the `texera_ddl.sql` enum. **Anyone building this
branch must apply the migration and re-run `DAO/jooqGenerate`**, or
`ProviderTypeEnum.ORCID` won't exist and the build fails. (Migration was
renumbered 36 → 38 after upstream took
both 36 and 37.)
### Shared code this touches
These are edits to existing paths, not new ORCID-only code, so they're the
parts worth
review attention:
- **`ExternalProfile.email` widens to `Option[String]`.** Signature change
in shared
provisioning code that Google flows through as well. Google still passes
`Some(email)`; its linking behaviour is unchanged.
- **`loginWithExistingToken` ordering.** It no longer logs out immediately
for an
emailless INACTIVE user; it asks for the address first, and the reissued
token
re-enters and takes the registration path. This affects all providers.
Motivation is
concrete: `bin/k8s/values.yaml` ships `USER_SYS_INVITE_ONLY=true`, and
without the
reorder the admin receives a request with a null address, which
`/gmail/notify-unauthorized` rejects.
- **`User.email` is now optional in TypeScript**, which surfaced two real
spots:
`FlarumService` (identifies accounts by email) and the admin edit field.
- **`AdminUserResource.updateUser` refuses to activate an account with no
email.** New
guard on an existing admin endpoint — a REGULAR account without an address
builds
dataset paths as `null/<name>/…`.
### Config and how to enable
`user-sys.orcid.{clientId,clientSecret,baseUrl,redirectUri}`,
`GUI_LOGIN_ORCID_LOGIN`, and both k8s values files. `/auth/orcid/config`
returns 503 when unconfigured, so the button stays disabled.
To try it locally: register a sandbox app with the redirect URI. Note that
**ORCID rejects `localhost`**, so local testing needs `ng serve --host
127.0.0.1`.
### 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 the exchange 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.
- **`AuthResourceSpec`**: the `PUT /auth/email` contract — stores and
reissues the token; refuses
malformed, blank, already-set, and addresses owned by a credentialed
account; adopts a contributor
placeholder, including when the caller has a `user_last_active_time` row
(that FK has no
`ON DELETE CASCADE`, so the adoption used to throw).
- **`ExternalAuthProvisionerSpec`**: identity-only provisioning, two
emailless accounts staying
separate, a later-collected address surviving a refresh.
**`AdminUserResourceSpec`**: no activation
without an address.
- **Frontend**: `orcid-callback.component.spec.ts` (new) for the `state`
check and every refusal
path; `auth.service.spec.ts` for the prompt and its invite-only ordering;
plus the modal,
`user.service.spec.ts`, and the login page's redirect.
```sh
AMBER_TEST_FILTER=skip-integration sbt "WorkflowExecutionService/testOnly
org.apache.texera.web.resource.auth.*
org.apache.texera.web.resource.dashboard.admin.user.*"
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:
```sh
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 # ng serve binds
localhost/::1 by default
```
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 4.8
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]