mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 08:07:03 +00:00
feat: surface guest JWT as the embed method in the app deploy drawer
The deploy drawer explained the secret-URL embed but not the guest JWT path, so the primary way to embed an app for a customer's own authenticated users was undiscoverable. For a guest-mode app with guests enabled, show how to mint a `jwt_guest_` token and append `guest.<jwt>` to the app URL, with a copyable iframe template pre-filled with this app's workspace_id and app_path, and a note that new guest emails are refused past the instance's free allowance (the live count is shown just above). Also log a guest JWT allowance refusal at warn, not info: the caller gets a bare 401 (the reason must not leak to an unauthenticated caller), so the log is the admin's signal that the instance hit its guest cap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
daf9553896
commit
c20f549fe0
@@ -799,7 +799,9 @@ async fn admit_and_record_guest_jwt(db: &DB, w_id: &str, email: &str, app_path:
|
||||
match windmill_common::workspaces::guest_admission(&mut *tx, email).await {
|
||||
Ok(()) => {}
|
||||
Err(e @ windmill_common::error::Error::PermissionDenied(_)) => {
|
||||
tracing::info!("guest JWT not admitted for {w_id}: {e:#}");
|
||||
// The guest hits a bare 401 (the reason must not leak to an unauthenticated caller);
|
||||
// warn so an admin sees the cap in logs, since it is the actionable signal here.
|
||||
tracing::warn!("guest JWT refused (guest allowance) for {w_id}: {e:#}");
|
||||
GUEST_JWT_REFUSED_CACHE.insert(cache_key, std::time::Instant::now());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,10 @@
|
||||
}${customPath}`
|
||||
)
|
||||
|
||||
// The app URL a guest JWT rides on: append `guest.<jwt>` and the viewer authenticates the
|
||||
// token as a seatless guest. Uses the custom URL when set, else the public secret URL.
|
||||
let guestJwtBase = $derived(customPath !== undefined ? fullCustomUrl : secretUrlHref)
|
||||
|
||||
// When embedding a raw app in an iframe inside another Windmill app (or any
|
||||
// cross-origin-isolated page), the embedded document must set COEP. The
|
||||
// `wm_coep` flag opts the public app into the cross-origin isolation headers.
|
||||
@@ -500,8 +504,8 @@
|
||||
Anyone your identity provider authenticates can open this app without a Windmill account.
|
||||
They join no workspace. Members of this workspace can open it too.
|
||||
{#if guestUsage}
|
||||
{guestUsage.guest_count} of {guestUsage.free_allowance} free guests used across this
|
||||
instance in the last {guestUsage.window_days} days; beyond that, {guestUsage.metered
|
||||
{guestUsage.guest_count} of {guestUsage.free_allowance} free guests used across this instance
|
||||
in the last {guestUsage.window_days} days; beyond that, {guestUsage.metered
|
||||
? 'every four guests count as one seat'
|
||||
: 'new guests are refused until the count drops'}.
|
||||
{/if}
|
||||
@@ -543,6 +547,34 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if policy.execution_mode == 'guest' && guestAccessEnabled && guestJwtBase}
|
||||
<div class="mt-4 border-t pt-3 flex flex-col gap-2">
|
||||
<div class="text-xs font-semibold text-emphasis">
|
||||
Embed for your own authenticated users (guest JWT)
|
||||
</div>
|
||||
<div class="text-xs text-secondary">
|
||||
To open this app for a user your own product already authenticates, mint a short-lived JWT
|
||||
in your backend and append it to the app URL as <code>guest.<jwt></code>. Each token
|
||||
is its own seatless guest, confined to this app — no shared secret and no Windmill
|
||||
account, unlike the plain secret URL above.
|
||||
</div>
|
||||
<div class="text-xs text-secondary">
|
||||
Sign it with the workspace's guest JWT key (Workspace settings → Guests), carrying
|
||||
<code>email</code>, <code>workspace_id</code> = <code>{opWs}</code>,
|
||||
<code>app_path</code> = <code>{appPath}</code> and <code>exp</code> (at most 24h ahead).
|
||||
</div>
|
||||
<ClipboardPanel
|
||||
content={toEmbedSnippet(`${guestJwtBase}/guest.YOUR_GUEST_JWT`)}
|
||||
size="md"
|
||||
/>
|
||||
<div class="text-2xs text-secondary">
|
||||
Replace <code>YOUR_GUEST_JWT</code> with the token your backend signs per user. Past the instance's
|
||||
free guest allowance a new guest email is refused (see the count above); guests already seen
|
||||
in the window keep working.
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-4">
|
||||
{#if !($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
<Alert type="warning" title="Admin only" size="xs">
|
||||
|
||||
Reference in New Issue
Block a user