mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
* fix: skip expiry notifications for app embed and SDK tokens Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: share app token label prefixes between mint sites and the check Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: skip expiry alerts for impersonation and test-connection tokens Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
4.6 KiB
4.6 KiB
Auth surface: facts that are easy to get wrong
Symbols, not line numbers, are cited: they drift less.
- Credential precedence (
windmill-api-auth/src/auth.rsextract_token):Authorization: Bearer→tokencookie →?token=query param. A URL with?token=is a credential on every route, but an existing cookie silently wins over it. AUTH_CACHEcaches a token's identity for 120 s. Deleting a token row does not purge it: the DB trigger (migrations/20260316000001_token_hash_pk_swap.up.sql) notifies only forlabel = 'session'rows, anddelete_tokennever callsinvalidate_token_from_cache.- Sessions are
tokenrows withlabel='session'plus the HttpOnlytokencookie, minted only bycreate_session_token(windmill-api-users/src/users.rs).GET /api/users/refresh_tokenmints one for any non-job token but returns plain text, no redirect. tokens/impersonate(superadmin) returns a multi-use token and sets no cookie.- A token's label decides whether its expiry raises alerts. When
delete_expired_itemsremoves an expiredtokenrow, the monitor emails the owner and raises a critical alert (if enabled); rows registered byregister_token_expiry_notificationalso get an "expiring soon" warning first. Neither happens whenis_user_token(windmill-common/src/auth.rs) reserves the label, so a token the system mints for itself, whether from the backend or from the frontend throughtokens/create, needs a reserved label. Anephemeral-prefix needs no other change (keep it clear ofis_server_minted_labelif minted throughtokens/create); a new prefix also goes into the SQL and Svelte mirrors that function's doc lists. - Every superadmin route refuses a job token:
require_super_admin(windmill-api-auth/src/lib.rs) errors onauthed.job_id.is_some(). A script that needsusers/create,tokens/impersonate,set_login_type, … must use a dedicated superadmin user token stored as a secret, never$WM_TOKEN. Token scopes cannot narrow superadmin routes. login_type(passwordtable) is a free-formVARCHAR(50). Password login and password reset requirelogin_type = 'password';set_passwordalso acceptspending_oauthand turns the account into apasswordone in the same statement (an account created ahead of its owner gets its first credential that way, or through the OAuth claim below).- Login links (
login_linktable,POST /users/login_linkssuperadmin-only,GET /auth/login_link/{token}unauthenticated): single-use, ≤15 min, a session cookie and a 302 to a same-originrd.require_login_typeon the mint refuses (409) an account whoselogin_typehas moved on — the way a caller re-entering an account it created stops being able to once the owner has a password or a provider. - Pre-approved trial offer (
cloud_trial_offer, cloud-only routes under/users/cloud_trial_offer): written by a superadmin at provisioning, consumed by{consumed: true}or by the portal's refusal;…/gois the one Windmill→portal hop that mints a portal login, over the sameCUSTOMER_SERVICE_TOKENtrust the onboarding hook uses (users_ee.rs, the portal's admin token). It never expires on its own. - OAuth login (
oauth2_ee.rslogin_externally, decision inexisting_login_decision) matches an existing account by lowercased email only. Same provider → login; apending_oauthaccount (seePENDING_OAUTH_LOGIN_TYPE) is claimed by the first login whose address the provider itself asserted and did not mark unverified —login_typebecomes the client key and the hash is nulled; otherwiserequire_preexisting_user_for_oauthdecides: on, every existing account is loggable-into by any provider; off, "exists but with a different login type". A new account getslogin_type = <client key>. - OAuth email trust:
LoginUserInfo.email_verifiedis read leniently (bool or "true"/"false" strings) and is only consulted for the claim above; only GitHub is filtered toprimary && verified; a missing email is fabricated fromnameas<name>@windmill.devand reacheslogin_externallywithemail_asserted = false. GET /api/oauth/login/{client}is an unauthenticated 302 to the provider — a plain link from any page starts SSO.CLOUD_HOSTEDis presence-tested (windmill-common/src/worker.rs):CLOUD_HOSTED=falsestill enables cloud mode. Of the routes above only the cloud trial offer and onboarding profile routes are cloud-gated; for the rest, cloud only adds quotas.CREATE_WORKSPACE_REQUIRE_SUPERADMINdefaults totruewhen unset; only the literal"true"enables it when set.