feat(auth): Add ORCID Login - #7664
Conversation
Upstream added sql/updates/36.sql (prefix dataset paths with "datasets/") and 37.sql (model metadata tables), so this branch's ORCID migration was renumbered 36 -> 38 and sql/changelog.xml now carries all three changeSets in order. sql/texera_ddl.sql merged cleanly: provider_type_enum keeps 'ORCID' alongside upstream's new model tables. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Automated Reviewer SuggestionsBased on the
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #7664 +/- ##
============================================
- Coverage 90.12% 90.03% -0.10%
- Complexity 4420 4424 +4
============================================
Files 1176 1183 +7
Lines 47001 47233 +232
Branches 5271 5305 +34
============================================
+ Hits 42359 42525 +166
- Misses 2880 2925 +45
- Partials 1762 1783 +21
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 407 | 0.248 | 23,058/40,377/40,377 us | 🔴 +29.5% / 🔴 +147.0% |
| 🔴 | bs=100 sw=10 sl=64 | 938 | 0.572 | 105,497/130,331/130,331 us | 🔴 +12.4% / 🔴 +22.4% |
| 🔴 | bs=1000 sw=10 sl=64 | 1,071 | 0.654 | 921,024/1,044,473/1,044,473 us | 🔴 +7.0% / 🟢 -5.7% |
Baseline details
Latest main 3cb3479 from same runner
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 407 tuples/sec | 478 tuples/sec | 779.28 tuples/sec | -14.9% | -47.8% |
| bs=10 sw=10 sl=64 | MB/s | 0.248 MB/s | 0.292 MB/s | 0.476 MB/s | -15.1% | -47.9% |
| bs=10 sw=10 sl=64 | p50 | 23,058 us | 18,704 us | 12,712 us | +23.3% | +81.4% |
| bs=10 sw=10 sl=64 | p95 | 40,377 us | 31,183 us | 16,345 us | +29.5% | +147.0% |
| bs=10 sw=10 sl=64 | p99 | 40,377 us | 31,183 us | 19,050 us | +29.5% | +112.0% |
| bs=100 sw=10 sl=64 | throughput | 938 tuples/sec | 1,010 tuples/sec | 1,017 tuples/sec | -7.1% | -7.8% |
| bs=100 sw=10 sl=64 | MB/s | 0.572 MB/s | 0.616 MB/s | 0.621 MB/s | -7.1% | -7.9% |
| bs=100 sw=10 sl=64 | p50 | 105,497 us | 95,751 us | 100,048 us | +10.2% | +5.4% |
| bs=100 sw=10 sl=64 | p95 | 130,331 us | 115,960 us | 106,477 us | +12.4% | +22.4% |
| bs=100 sw=10 sl=64 | p99 | 130,331 us | 115,960 us | 114,739 us | +12.4% | +13.6% |
| bs=1000 sw=10 sl=64 | throughput | 1,071 tuples/sec | 1,104 tuples/sec | 1,048 tuples/sec | -3.0% | +2.2% |
| bs=1000 sw=10 sl=64 | MB/s | 0.654 MB/s | 0.674 MB/s | 0.639 MB/s | -3.0% | +2.3% |
| bs=1000 sw=10 sl=64 | p50 | 921,024 us | 910,529 us | 976,350 us | +1.2% | -5.7% |
| bs=1000 sw=10 sl=64 | p95 | 1,044,473 us | 976,287 us | 1,022,084 us | +7.0% | +2.2% |
| bs=1000 sw=10 sl=64 | p99 | 1,044,473 us | 976,287 us | 1,053,520 us | +7.0% | -0.9% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,491.61,200,128000,407,0.248,23058.39,40377.41,40377.41
1,100,10,64,20,2132.28,2000,1280000,938,0.572,105497.40,130330.91,130330.91
2,1000,10,64,20,18674.88,20000,12800000,1071,0.654,921024.47,1044473.00,1044473.00|
/request-review @aglinxinyuan |
|
This PR is huge! 2000+ LOC and 50 files. Without breaking it into smaller PRs, can you check if all the changes are necessary? AI tend to write huge changes, let's try to compact it before anyone starts review |
I think this is a big topic, as it changes the account serving model. I do believe we have many features that rely on emails. Especially we have communication features (comment, share, future email notifications for executions etc.) I would strongly suggest we make email NON optional for all accounts. The current model essentially allows a partial account without an email. There are two concerns with the current model:
I feel this step could just be required before they sign up with ORCID. for example,
Even further: a better way is to always require an email or an oauth with an email to login. And after that user can optionally link their ORCID in account settings. |
|
In order to review this, we will also need a gif/recording to see how it works on the UI. Please attach one in the PR description. |
Sure! But much of the PR was hand-rolled and then reviewed with AI, so idk how much can actually be cut, as most of it is dependent on each other. |
If I recall correctly, the way it's implemented signs out the user if they refuse to provide an email, thus preventing them from using the service until one is provided. However, the guards on this could be stronger.
I should mention that this is already a problem we have with the current log-in system. Addition verification seemed out of scope for this specific PR, but I agree it needs to be added. |
That is the intent with the Modal, it's not meant to be an optional step, rather its meant to basically say "hey, you're not done, do this to finish setting up your account." |
|
I agree with @Yicong-Huang. I'm pretty confident that you don't need 2000 lines to make it work. If you need 2000 lines for each provider, it will be very hard to scale. |
Wasn't saying that all future providers will need 2k lines, more so that there are required features that service similar to ORCID require to function, I'll split those into smaller PRs and submit them first. |
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
/authenticatescope, which returns an iD and a name and no email. (ORCID does support OpenID Connect — there's anopenidscope and an id_token — but even underopenidit 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:
is refused with 409
moves onto the placeholder's uid, and the row created at login is discarded
The attach is single-step, following repo precedent:
AuthResource.registeralready 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 herebut 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
stateparameter 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.sqladdsORCIDtoprovider_type_enum, with the matchingchangelog.xmlchangeSet and thetexera_ddl.sqlenum. Anyone building this branch must apply the migration and re-runDAO/jooqGenerate, orProviderTypeEnum.ORCIDwon't exist and the build fails. (Migration was renumbered 36 → 38 after upstream tookboth 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.emailwidens toOption[String]. Signature change in sharedprovisioning code that Google flows through as well. Google still passes
Some(email); its linking behaviour is unchanged.loginWithExistingTokenordering. It no longer logs out immediately for anemailless 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.yamlshipsUSER_SYS_INVITE_ONLY=true, and without thereorder the admin receives a request with a null address, which
/gmail/notify-unauthorizedrejects.User.emailis now optional in TypeScript, which surfaced two real spots:FlarumService(identifies accounts by email) and the admin edit field.AdminUserResource.updateUserrefuses to activate an account with no email. Newguard 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/configreturns 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 needsng 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
protectedseam the specs override — asGoogleAuthResourceSpecdoeswith
verifiedPayload— and the real flow was driven by hand against the ORCID sandbox.OrcidAuthResourceSpec(new): provisioning from an authenticated iD — emailless INACTIVEaccount plus its
auth_providerrow, idempotent on a second login, the iD standing in for aprivate name. Refusals: a response naming no iD, a blank code, each missing config setting.
AuthResourceSpec: thePUT /auth/emailcontract — stores and reissues the token; refusesmalformed, blank, already-set, and addresses owned by a credentialed account; adopts a contributor
placeholder, including when the caller has a
user_last_active_timerow (that FK has noON DELETE CASCADE, so the adoption used to throw).ExternalAuthProvisionerSpec: identity-only provisioning, two emailless accounts stayingseparate, a later-collected address surviving a refresh.
AdminUserResourceSpec: no activationwithout an address.
orcid-callback.component.spec.ts(new) for thestatecheck and every refusalpath;
auth.service.spec.tsfor the prompt and its invite-only ordering; plus the modal,user.service.spec.ts, and the login page's redirect.By hand, against the ORCID sandbox. Register
http://127.0.0.1:4200/callback/orcidon a sandboxapplication (ORCID rejects
localhost), then:Sign in with ORCID at
http://127.0.0.1:4200/login, consent, supply an address at the prompt, andreload to confirm you are not asked again. Refusals: a tampered
stateon the callback URL returnsyou to
/login; an address belonging to a credentialed account keeps the dialog open. With thecredentials unset, the button stays disabled and no error toast appears.
Migration: applied to a database whose enum lacked
ORCIDunder both runners this repo uses(
bin/local-dev.shkeepsSET search_path; the Liquibase runner insql/docker-compose.ymlstripsit, 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