Neilk1021 opened a new pull request, #8314:
URL: https://github.com/apache/texera/pull/8314

   Adds Sign in with Apple as a third identity provider alongside local and 
Google sign-in, following the shape the ORCID work established: one more button 
in the login card's `social-buttons` block, one more provider resource on the 
backend, and no new login surface.
   
   Off by default. `gui.login.apple-login` gates the button, and the flow needs 
a Services ID (`user-sys.apple.clientId`) that only an operator can supply, so 
a plain checkout is unaffected.
   
   ## Backend
   
   `AppleAuthResource` (`/auth/apple`) exposes the client id and verifies Apple 
identity tokens:
   
   - `GET /auth/apple/clientid` — mirrors `GoogleAuthResource`, so the Services 
ID never lands in a config payload.
   - `POST /auth/apple/login` — takes the raw identity token as `text/plain` 
and verifies it against Apple's published JWKS 
(`https://appleid.apple.com/auth/keys`), pinning the issuer, the audience to 
our Services ID, and RS256. A malformed or unverifiable credential becomes a 
401 rather than escaping as a 500.
   
   Provisioning goes through `ExternalAuthProvisioner`, extended here to handle 
an identity with no email address. Three Apple-specific details drove that:
   
   - **`sub` is the provider id.** It is stable per user but scoped to the 
Apple developer *team* — transferring the app rotates it for every user, and 
`transfer_sub` is only available for 60 days after such a move. Worth knowing 
before any team transfer.
   - **An unverified address is refused, an absent one is not.** Mapping an 
address Apple did not verify onto an existing account would be a takeover. But 
Apple omits `email` entirely for Sign in with Apple at Work & School accounts, 
so refusing on absence would lock those users out of a provider the deployment 
has enabled; they are provisioned identity-only and asked for an address once 
inside.
   - **Apple types its claims inconsistently.** `email_verified` arrives as 
either a JSON boolean or a quoted string, and Apple documents both. Reading 
only one shape silently yields `false`, which would reject legitimate logins — 
`booleanClaim` handles both.
   
   Apple sends the display name only on a user's first authorization, outside 
the identity token, so it never reaches this endpoint and would be unsigned and 
untrusted anyway. The address stands in, as it already does for a Google 
account with no name, and `sub` stands in when there is no address either. 
Apple supplies no avatar.
   
   ## Frontend
   
   `AppleAuthService` owns Apple's SDK: it injects `appleid.auth.js` from 
Apple's CDN on first use, configures it with `usePopup: true`, and resolves 
with the identity token. `usePopup` is load-bearing twice over — it keeps the 
token in the page, and it stops Apple posting a form to `redirectURI`, which 
would navigate away from the SPA. The script is fetched on click, so a visitor 
who only uses the password form never calls Apple at all.
   
   `appleLogin` joins the anonymous `/config/pre-login` payload, since the 
login page has to decide whether to draw the button before anyone is signed in.
   
   ### Why the button is ours rather than Apple's rendered one
   
   Apple's SDK can render its own button, and this branch used it for a while. 
It had to be abandoned: the SDK derives the label's size from the button's 
height (a 13px label inside a `viewBox` scaled by `height / 30`, so a 44px 
button yields ~19px type) and offers only three logo widths, all keyed to 
height. Neither can be set independently, so the button could not be matched to 
the Google button beside it.
   
   Apple permits a custom button provided it carries their logo artwork and one 
of their three approved titles. So the button is a plain `<button>` with 
`assets/logos/apple-logo-white.svg` and a "Continue with Apple" label, styled 
to sit flush with Google's: same 328px width, 14px label, and a height no 
smaller than Google's, which is what Apple's "no smaller than other sign-in 
buttons" rule requires. The logo is a 16×44 box carrying the glyph's own clear 
space, so it is neither cropped nor given extra vertical padding.
   
   Label alignment follows `.orcid-login`: `flex: 1; text-align: center` with 
no compensation for the logo's width, which centres the label in the space to 
the right of the mark — what Google's button does — rather than across the 
whole button.
   
   The reasoning behind each value is in the template and SCSS comments, since 
these are the kind of numbers that get "tidied" into something non-compliant 
later.
   
   ## Database
   
   `sql/updates/43.sql` adds `APPLE` to `auth_provider.provider_type`. Nothing 
inserts an `APPLE` row in the same transaction, because Postgres forbids using 
a new enum value in the transaction that adds it — the value is only declared, 
and the first Apple login writes it. The type is schema-qualified because the 
two migration runners disagree about the search path.
   
   ## Tests
   
   - `AppleAuthResourceSpec` — 11 cases over token verification and claim 
mapping, with the network seam overridden rather than signing real tokens: 
verified/unverified/absent email, both claim encodings, malformed credentials, 
and the 401 paths.
   - `apple-auth.service.spec.ts` — 9 cases over the SDK mechanics: the client 
id fetched before the script is injected, the pinned CDN url, memoization 
across calls, a retry after a failed load, `usePopup`/`redirectURI` in the init 
argument, and popup dismissal resolving as "no token".
   - `texera-login.component.spec.ts` — the Apple button and click flow, 
including two that assert the compliance-sensitive bits: the label is one of 
Apple's permitted titles, and the logo `src` is Apple's asset rather than an 
icon-set glyph.
   
   Also folded in a fix for a pre-existing failure in that spec: its `render()` 
helper set only `localLogin` and `googleLogin`, while `MockGuiConfigService` 
defaults `appleLogin: true` and the divider condition reads all three, so the 
"only local login" divider assertion failed. The helper now takes all three 
flags, and there are new cases for the Apple-only divider path, which nothing 
covered.
   
   Full frontend suite passes: 210 files, 5472 tests.
   
   ## Testing
   
   Verified end to end against a real Apple app and Services ID. Apple will not 
accept `http://localhost` as a redirect URI and requires HTTPS on a domain 
registered against the Services ID, so the flow cannot be exercised on a plain 
local checkout. I routed a local dev server through an ngrok tunnel to get a 
stable HTTPS domain Apple would accept:
   
   1. Registered the ngrok domain against the Services ID in the Apple 
developer console, with the return URL pointing at the tunnel origin, and 
served Apple's domain-association file from `/.well-known/` (hence the 
`angular.json` asset glob).
   2. Set `USER_SYS_APPLE_CLIENT_ID` to the Services ID and 
`GUI_LOGIN_APPLE_LOGIN=true`.
   3. Ran the Angular dev server bound so the tunnel host is accepted, and 
opened the tunnel URL rather than localhost — `redirectURI` derives from 
`window.location.origin`, so it has to be the tunnel origin for Apple to match 
its registration.
   4. Signed in through Apple's popup and confirmed the identity token reaches 
`POST /auth/apple/login`, verifies against Apple's JWKS, provisions the user, 
and returns a session that lands on the workflow page.
   5. Re-ran with the popup dismissed to confirm no error is surfaced, and with 
an account whose address Apple had not verified to confirm the 401.
   
   The first sign-in and a subsequent one were both exercised, which matters 
because Apple only sends the display name on the first authorization.
   
   ## Before merging
   
   - `frontend/angular.json` carries my ngrok host in `allowedHosts`, which is 
developer-local config in a shared file. It needs removing or moving behind a 
local override before this merges.
   - `frontend/.well-known/.gitkeep` is a placeholder for Apple's 
domain-association file; a deployment enabling Apple has to supply the real 
file.
   - Committing Apple's logo artwork may warrant an entry under `licenses/` or 
`licenses-3rd-party-code/`. The ORCID work committed its brand PNG without one, 
so there may be no established practice here — flagging for whoever knows the 
policy.
   


-- 
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