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]

Reply via email to