mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 16:02:23 +00:00
A second way in for a guest (companion to windmill#10929): a JWT the embedding customer's own backend mints and signs, carried on the app's share link and verified against a key the workspace admin configured. It needs no identity-provider round-trip, so it works inside an iframe where popups and third-party cookies do not. Bearer prefix jwt_guest_, stateless: verified per request, cached until exp, no token row. A JWT guest is the same identity as a signed-in guest: no usr row, no password row, no seat, confined to the one app app_path names. Every guest gate applies: the plan, the workspace switch (enforced once at the auth door via the sentinel), the app mode (guest_app_admits), and "no account at all" (has_any_account). The claim's workspace_id must equal the route's workspace, and a workspace-less route never accepts it. Claims honoured: email, workspace_id, app_path, exp (mandatory); nbf/iat validated when present; the accepted lifetime is capped at 24h. Algorithms: RS256/384/512, PS256/384/512, ES256/384; HS* is refused. The key is a per-workspace setting, a PEM public key or a JWKS URL (at most one, a DB CHECK enforces it), Enterprise-plan gated like the guest switch. The JWKS URL is validated against private ranges and the fetch is pinned to the validated address. Counting: a JWT guest is recorded in guest_activity (once per email, workspace and day, cached), marked jwt_entry, and not in unique_ext_jwt_token. A first-seen users.login_guest audit carries the entry kind. Narrower than jwt_ext_ by design: that key is instance-level and can assert admin, groups and folders; a guest key is scoped to one workspace and only ever mints guests. An app-only user a customer routes through jwt_ext_ today is counted; through a guest JWT they become a free guest, the intended pricing change, split out as guest_jwt_count in the telemetry so it can be measured. Changes on the parent branch, additive: ApiAuthed.credential_expiry (a credential's own expiry when it has no token row); guest_derived_token_constraints caps on it; guest_session_scopes moved to windmill-api-auth::scopes and has_any_account to windmill-common::users so the mint and the JWT arm share one copy; the signed-in mint's login_guest audit now carries entry=idp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3