mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
974b0265f85f9cfac65bbd2f6ebbcb0e966e3301
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9320312eac |
feat: cap user token expiration with an instance setting (#11159)
* feat: cap user token expiration with an instance setting Adds `max_token_expiration_days`, an instance-wide ceiling on how far ahead a token created through `POST /users/tokens/create` may expire. With it set, that route refuses a token with no expiration and one that expires past the window; absent or non-positive, nothing changes. Only the user-facing handler enforces it. Server-side mints (native trigger webhook tokens, app embed tokens, sessions) pick a lifetime the caller never chooses and go straight to `create_token_internal`, so they stay uncapped, as does the superadmin `impersonate` route. Service accounts are exempt, in the workspace the token targets or in any workspace for a global token, so unattended automation can keep longer-lived credentials. The token form now surfaces the API error instead of only logging it, and offers "Expires In" in MCP mode as well: that mode always sent no expiration, which the cap refuses, leaving MCP URLs impossible to generate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: shorten over-long token expirations instead of refusing them Refusing a non-compliant request breaks the callers that cannot comply. The CLI authorization page, `wmill user create-token` and the editor's language-server token each pick a lifetime — usually none at all — with no way to read the setting, so a cap made browser login hang and the editor lose its LSP root rather than stopping the long-lived tokens the setting is aimed at. `cap_token_expiration` now returns the expiration to store, shortening a request that asks for too long or for none. The policy still holds absolutely, no caller can break, and there is no clock-skew boundary where an expiration exactly at the ceiling flips to an error. The token form needed no changes at all, so its MCP and error-toast edits are gone with it. Also drops the Enterprise badge on the setting, which nothing enforced, notes the mint paths in docs/auth-surface.md, and pins that `tokens/impersonate` and the second-workspace case stay outside the exemption. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: drop the unintended token-form change and correct the exemption docs The token form needed no change once the ceiling shortens rather than refuses, but the earlier revert restored from the index, which already held the staged edit, so the MCP expiration field and the error toast stayed on the branch with a comment justifying them by a refusal that no longer happens. docs/auth-surface.md claimed a service-account row in any workspace exempts outright; that only holds for a workspace-less token, which has no workspace to match. A ceiling written as a string, which the YAML instance config and config sync can both produce, now has a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: offer only expirations within the ceiling in the token form The server shortens a token that asks for longer than `max_token_expiration_days` or for no expiration, which the token form could not tell anyone: a user picking "No expiration" got the ceiling silently. The form now reads the setting and, with one set, drops "No expiration" and every choice above it, adds the ceiling itself as "N days (maximum)" and selects it, and says the instance limits tokens to N days. MCP mode hides the expiration field and always sent none, so with a ceiling the field now shows there too and keeps its value across the toggle. Without a ceiling the form is unchanged. Reading it needs no superadmin: the setting joins the keys any logged-in user can read through `GET /settings/global/{key}`. It holds a policy, not a secret. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: make the token form and the server agree on what counts as a ceiling The form parsed `max_token_expiration_days` more loosely than `cap_token_expiration`, so the two could disagree on whether a ceiling exists at all. A `7.0` from the YAML instance config, or a string such as "7.0" or "1e1", made the form hide "No expiration" and announce a 7-day limit while the server capped nothing; a value between chrono's and JavaScript's date limits preselected an expiration the server could not parse. Both now read the same thing as a ceiling: a whole number of days from 1 to 1,000,000, stored as an integer, an integral float or a string of digits. `parseMaxTokenExpirationDays` holds the frontend's copy, and the instance settings validation uses it too, so the settings page no longer accepts a value the server would ignore. The bound replaces the date-range guard on both sides. Also corrects the rationale for shortening rather than refusing: the setting is now readable by any logged-in user, so those callers do not read it rather than cannot, and CLIs already installed never will. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: cap service-account tokens like everyone else's The ticket exempted service accounts from `max_token_expiration_days`, but their tokens are the long-lived ones a rotation policy is meant to bound, and the exemption let any workspace admin get an uncapped token by impersonating one. It also left the token form unable to agree with the server: an admin impersonating a service account was offered only capped choices while the server would have kept any. `cap_token_expiration` now takes just the requested expiration, with no per-caller lookup, and the service-account query and its cache entry are gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: cap superadmin impersonation tokens and pin the frontend parser `POST /users/tokens/impersonate` wrote its own token row with whatever expiration the superadmin sent, so it was the one route left that could mint a token that never expires with `max_token_expiration_days` set. The ceiling only decides the stored expiration (the auth lookup never reads the setting), so leaving it uncapped meant exactly that. It now goes through `cap_token_expiration` like `create_token`; nothing in Windmill calls it, so no caller changes. Also adds `tokenExpiration.test.ts`, pinning which stored values `parseMaxTokenExpirationDays` reads as a ceiling against the server's reading, and documents that tokens existing when the setting is turned on or lowered keep their expiration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject a max_token_expiration_days the token routes cannot read The settings API and config sync stored any value for the key, and the token routes can only read an unparseable one as no ceiling. A typo such as `7.5` or "7.0" was accepted and silently turned the policy off. `parse_max_token_expiration_days` in windmill-common is now the single server reading of the setting: null or empty clears it, a whole number of days within the bound is the ceiling, anything else is an error. The settings write hook and `sync_global_settings_declarative` reject that error, and `cap_token_expiration` reads through the same function, logging a value written around both. Tests: the parser's accept/clear/reject table (the same table as the frontend parser's), the settings API refusing 7.5, and config sync refusing "7.0". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reserve the CLI login token label so its expiry does not email the user With a token expiration ceiling, the token the CLI authorization page mints now expires, so every `wmill` login earned an "expiring soon" and an "expired and deleted" email and critical alert. The CLI already signs in again on its own when that token stops working, so those notifications ask the user to do nothing. The page now labels it `cli-login:<username>` (previously `cli-<username>`), reserved in `is_user_token` and its SQL and Svelte mirrors: no expiry notifications, and the label cannot be edited. A colon-terminated namespace like `embed_app:` and `impersonation:` keeps hand-made labels clear of it. Not in `is_server_minted_label`, since the page mints through `/users/tokens/create`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: skip the expiring-soon warning for tokens that were short-lived from the start A token whose whole lifetime fits in the 7-day warning window got its "expiring soon" email (and critical alert, when enabled) minutes after it was created, about a lifetime its creator had just picked. With an expiration ceiling of 7 days or less that is every token created from the form or `wmill token create`. `register_token_expiry_notification` no longer queues a warning for such a token. The window is now `TOKEN_EXPIRY_WARNING_DAYS`, shared with `check_expiring_tokens`, so shortening the warning window can never leave tokens of an intermediate lifetime with no warning at all. The "expired and deleted" notice still goes out for every user token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: exempt service-account tokens from the expiration ceiling again Service accounts are the identity automation that needs a long-lived credential runs as, so their tokens are exempt from `max_token_expiration_days` once more: a service account in the workspace the token names, or in any workspace for a workspace-less token. `tokens/impersonate` checks the impersonated account, so a superadmin minting a token for a service account gets the same exemption. The token form applies the same rule for the account it is running as, when that account is a service account in the current workspace, which is what an admin impersonating one sees; otherwise it would offer only capped choices while the server keeps any. Any workspace admin can create and impersonate a service account to hold an uncapped token, so the ceiling bounds personal tokens; the doc comment and docs/auth-surface.md say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: decide the token form's service-account exemption from the token's workspace The form treated the account as a service account only when it was one in the workspace the app was on, while the server checks the workspace the token is for, or any workspace for a workspace-less token. With an email that is a service account in one workspace and an ordinary member of another, picking the other workspace in MCP mode offered "No expiration" and the server silently stored the ceiling; the reverse hid the exemption. `GET /workspaces/users` now returns each membership's `is_service_account` (its query already joins the `usr` row), and the form applies the server's rule to the token's own workspace. The selection becomes a derived value held within the ceiling, so switching to a capped workspace never leaves an unoffered choice selected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5639187fec |
feat(auth): 2 h login links and a click-to-sign-in page for emailed ones (#11203)
* feat(auth): 2 h login links and a click-to-sign-in page for emailed ones
Raise the login link cap from 15 min to 2 h, so a link sent by email still
works when it is read.
A link minted with `confirm: true` is a /user/login_link page instead of
the API path. Loading the page does nothing; its button POSTs to
/api/auth/login_link/{token}, which spends the link and answers where to go.
Mail scanners that open links on delivery no longer burn them. Links minted
without `confirm` still sign in on open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(auth): keep the login link page to design-system components
A tokenless visit bounced off a raw <p>; send it to the page a spent link
already bounces to, and show the modal's own spinner while it goes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9d348f84c7 |
fix: skip expiry notifications for app embed and SDK tokens (#11169)
* 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> |
||
|
|
91e6dc39ce |
feat: pre-approved cloud accounts: login links, OAuth adoption, setup, and the trial bridge (#10875)
* feat: single-use login links and oauth-claimable pending accounts * docs: capture the auth surface facts behind login links * fix: accept stringified email_verified from oauth userinfo * docs: describe the oauth claim rule in the auth surface notes * fix: harden login-link redirects and sweep expired links * chore: bump ee-repo-ref * fix: keep expired login links a day so an open still reads as expired * fix: refuse login links for superadmin and devops accounts * fix: re-check the account's roles when a login link is opened * feat: pre-approved cloud accounts finish their setup and start their trial from Windmill * feat: dev-only localStorage opt-in to the cloud UI on localhost * feat: finish-setup entry in the desktop settings menu * style: pulse the settings row while account setup is pending; shorter, blue finish-setup entry * fix: list the configured providers in the finish-setup modal * fix: open the finish-setup modal after the menu has closed * feat: finish-setup provider sign-in keeps the session when the provider asserts another address * chore: pin the EE companion commit * fix: plain toast for the finish-setup refusal * style: format the dev cloud override Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: onboarding skips the source question an invite already answered Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: type the finish-setup icons and login_type as the frontend uses them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: invited accounts get a workspace name, hub picks and starter prompts from their invite Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the workspace form reads the invite's name itself, so the picker prefills it too Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: an empty workspace offers the projects its invite picked, one click from importing Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * style: picked projects get identical import buttons Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: a pinned sidebar banner until an invited account has credentials of its own Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * style: the account-setup row speaks the rail's language, tinted not filled Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * chore: pin ee-repo-ref to the import fix Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * refactor: picked projects live in the template picker only; account-setup row moves to the rail footer Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: review round — no portal login for job tokens, finish-setup failures keep the session, prompt labels deduped Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — trial start is a POST, profile cache follows the session, setup row on MenuButton Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — no password road where password login is off, cache note on the login form, trial refusal surfaced Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — set_password guarded on its read, refusal stays on the page, docs and formatting Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — popup OAuth clears the profile cache, portal helper crate-private, refusal toast stays Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: a refused trial is recorded inline in the rail, not in a day-long toast Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the refusal notice uses the rail's button and has a collapsed form Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — SSO can finish account setup, with the same mismatch refusal as OAuth Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: SSO finish-setup rides in RelayState and the refusal notice is a status region Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: keep the finish-setup cookie beside RelayState, hoist the status region, pin session-keyed profile cache Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: empty live region for the trial refusal, drop the setup cookie once adopted, telemetry inventory Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the trial refusal survives the responsive sidebar swap Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the trial refusal is shown to the account it answers, modal open prop is required Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: an invited account skips the whole onboarding survey Its source is the invite and its use case was researched before the invite went out, so neither question is asked: the known source is recorded and onboarding opens on naming the workspace. Accounts without an invite profile see the survey exactly as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: an invited account with a workspace leaves onboarding before anything paints Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: account-setup state resets on sign-out, onboarding shows a loading state while it settles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * style: keep the refresh doc comment on refresh Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: profile lists are distinct, and the offer table notes what a users-import does to it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * chore: update ee-repo-ref to 1ba6fe83451f0a1f8fafe04b7187087d51e0f769 This commit updates the EE repository reference after PR #750 was merged in windmill-ee-private. Previous ee-repo-ref: be42722d09832ffff709a1f710f3e97e34d513b2 New ee-repo-ref: 1ba6fe83451f0a1f8fafe04b7187087d51e0f769 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |