mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-13 08:05:23 +00:00
feat: let apps hide the viewer login status on public urls (#11089)
* feat: let apps hide the viewer login status on public urls Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTWrfHeqcFMH8qWdsP6kEr * fix: apply the login status setting on deploy and regenerate mcp tools Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTWrfHeqcFMH8qWdsP6kEr * fix: save the login status toggle immediately like its sibling toggles Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTWrfHeqcFMH8qWdsP6kEr --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e651b4cd63
commit
6056ec7148
@@ -33969,6 +33969,12 @@ components:
|
||||
the app bundle so `windmill-client` calls run as the viewer. Must
|
||||
be a subset of the server's curated allowlist (jobs:run, jobs:read,
|
||||
users:read, resources:read, variables:read).
|
||||
hide_login_status:
|
||||
type: boolean
|
||||
description: >
|
||||
When true, the app's public and custom URLs do not show the viewer's
|
||||
login status (the user they are signed in as, or that they are
|
||||
signed out) in the top-left corner. Absent or false shows it.
|
||||
|
||||
ListableApp:
|
||||
type: object
|
||||
|
||||
@@ -521,6 +521,9 @@ pub struct Policy {
|
||||
/// `FRONTEND_SDK_ALLOWED_SCOPES`; absent means no credential (the default).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub frontend_sdk_scopes: Option<Vec<String>>,
|
||||
/// Display only: hides the viewer's login status badge on the public viewer.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hide_login_status: Option<bool>,
|
||||
}
|
||||
|
||||
impl Policy {
|
||||
@@ -4580,6 +4583,7 @@ async fn upload_s3_file_from_app(
|
||||
allowed_s3_keys: None,
|
||||
sandbox: None,
|
||||
frontend_sdk_scopes: None,
|
||||
hide_login_status: None,
|
||||
})
|
||||
} else {
|
||||
let policy_o = sqlx::query_scalar!(
|
||||
@@ -4995,6 +4999,7 @@ async fn get_on_behalf_authed_from_app(
|
||||
allowed_s3_keys: Some(force_allowed_s3_keys),
|
||||
sandbox: None,
|
||||
frontend_sdk_scopes: None,
|
||||
hide_login_status: None,
|
||||
}
|
||||
} else {
|
||||
// TODO: improve db query to not return uneeded fields
|
||||
@@ -5019,6 +5024,7 @@ async fn get_on_behalf_authed_from_app(
|
||||
allowed_s3_keys: None,
|
||||
sandbox: None,
|
||||
frontend_sdk_scopes: None,
|
||||
hide_login_status: None,
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -1265,7 +1265,7 @@ is, a different one moves it there and archives the old path"),
|
||||
},
|
||||
"execution_mode": {
|
||||
"type": "string",
|
||||
"description": "Who the app's runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Either way `anonymous`, which makes the app publicly executable, is never assumed. Possible values: viewer, publisher, anonymous"
|
||||
"description": "Who may open the app, and who its runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Neither `anonymous`, which makes the app publicly executable, nor `guest`, which opens it to anyone the identity provider authenticates, is ever assumed. A guest is only admitted where the workspace also has `guest_access_enabled`, which is checked when the session is minted and again on every guest request. Possible values: viewer, publisher, guest, anonymous"
|
||||
},
|
||||
"on_behalf_of": {
|
||||
"type": "string"
|
||||
@@ -1283,6 +1283,10 @@ is, a different one moves it there and archives the old path"),
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Raw apps: author-declared scopes for the frontend SDK token. Takes effect only when `sandbox` is also true — an unsandboxed bundle runs with the viewer's own session, so no token is advertised or minted for it and this list stays inert. On a sandboxed app a non-empty list lets viewers mint (after consenting) a short-lived token carrying their own identity restricted to these scopes, handed to the app bundle so `windmill-client` calls run as the viewer. Must be a subset of the server's curated allowlist (jobs:run, jobs:read, users:read, resources:read, variables:read).\n"
|
||||
},
|
||||
"hide_login_status": {
|
||||
"type": "boolean",
|
||||
"description": "When true, the app's public and custom URLs do not show the viewer's login status (the user they are signed in as, or that they are signed out) in the top-left corner. Absent or false shows it.\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1380,7 +1384,7 @@ is, a different one moves it there and archives the old path"),
|
||||
},
|
||||
"execution_mode": {
|
||||
"type": "string",
|
||||
"description": "Who the app's runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Either way `anonymous`, which makes the app publicly executable, is never assumed. Possible values: viewer, publisher, anonymous"
|
||||
"description": "Who may open the app, and who its runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Neither `anonymous`, which makes the app publicly executable, nor `guest`, which opens it to anyone the identity provider authenticates, is ever assumed. A guest is only admitted where the workspace also has `guest_access_enabled`, which is checked when the session is minted and again on every guest request. Possible values: viewer, publisher, guest, anonymous"
|
||||
},
|
||||
"on_behalf_of": {
|
||||
"type": "string"
|
||||
@@ -1398,6 +1402,10 @@ is, a different one moves it there and archives the old path"),
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Raw apps: author-declared scopes for the frontend SDK token. Takes effect only when `sandbox` is also true — an unsandboxed bundle runs with the viewer's own session, so no token is advertised or minted for it and this list stays inert. On a sandboxed app a non-empty list lets viewers mint (after consenting) a short-lived token carrying their own identity restricted to these scopes, handed to the app bundle so `windmill-client` calls run as the viewer. Must be a subset of the server's curated allowlist (jobs:run, jobs:read, users:read, resources:read, variables:read).\n"
|
||||
},
|
||||
"hide_login_status": {
|
||||
"type": "boolean",
|
||||
"description": "When true, the app's public and custom URLs do not show the viewer's login status (the user they are signed in as, or that they are signed out) in the top-left corner. Absent or false shows it.\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -649,6 +649,24 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<Toggle
|
||||
options={{ right: "Show the viewer's login status" }}
|
||||
checked={!policy.hide_login_status}
|
||||
on:change={(e) => {
|
||||
policy.hide_login_status = e.detail ? undefined : true
|
||||
if (savedApp && !newApp) {
|
||||
setPublishState(e.detail ? 'Login status shown' : 'Login status hidden')
|
||||
}
|
||||
}}
|
||||
disabled={!savedApp}
|
||||
/>
|
||||
<div class="text-xs text-secondary mt-1">
|
||||
The public and custom URLs show who the viewer is signed in as, or that they are signed out,
|
||||
in the top-left corner.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Alert type="info" title="Only latest deployed app is publicly available">
|
||||
You will still need to deploy the app to make visible the latest changes
|
||||
|
||||
@@ -57,6 +57,10 @@
|
||||
// Use workspace from props or from app.workspace_id (for custom path responses)
|
||||
let effectiveWorkspace = $derived(workspace ?? app?.workspace_id)
|
||||
|
||||
// The setting lives on the app, so the badge waits for it while loading
|
||||
// instead of flashing on an app that hides it.
|
||||
let showLoginStatus = $derived(app ? !app.policy?.hide_login_status : notExists || noPermission)
|
||||
|
||||
// On the public surfaces (untrusted distribution) runnable-authored html/svg needs
|
||||
// the viewer's approval before it renders, unless the app sandbox isolates it. The
|
||||
// in-workspace viewer renders it verbatim. See getAppMarkupTrust.
|
||||
@@ -117,13 +121,15 @@
|
||||
<div class="flex gap-1 items-center"><User size={14} />{child}</div>
|
||||
{/snippet}
|
||||
|
||||
<div class="z-50 text-2xs text-primary absolute top-3 left-2"
|
||||
>{#if $userStore}
|
||||
{@render userInfo($userStore.username)}
|
||||
{:else if globalUser}
|
||||
{@render userInfo(globalUser.email)}
|
||||
{:else}<UserRoundX size={14} />{/if}
|
||||
</div>
|
||||
{#if showLoginStatus}
|
||||
<div class="z-50 text-2xs text-primary absolute top-3 left-2"
|
||||
>{#if $userStore}
|
||||
{@render userInfo($userStore.username)}
|
||||
{:else if globalUser}
|
||||
{@render userInfo(globalUser.email)}
|
||||
{:else}<UserRoundX size={14} />{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if notExists}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"execution_mode": {
|
||||
"type": "string",
|
||||
"description": "Who the app's runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Either way `anonymous`, which makes the app publicly executable, is never assumed. Possible values: viewer, publisher, anonymous"
|
||||
"description": "Who may open the app, and who its runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Neither `anonymous`, which makes the app publicly executable, nor `guest`, which opens it to anyone the identity provider authenticates, is ever assumed. A guest is only admitted where the workspace also has `guest_access_enabled`, which is checked when the session is minted and again on every guest request. Possible values: viewer, publisher, guest, anonymous"
|
||||
},
|
||||
"on_behalf_of": {
|
||||
"type": "string"
|
||||
@@ -1290,6 +1290,10 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Raw apps: author-declared scopes for the frontend SDK token. Takes effect only when `sandbox` is also true \u2014 an unsandboxed bundle runs with the viewer's own session, so no token is advertised or minted for it and this list stays inert. On a sandboxed app a non-empty list lets viewers mint (after consenting) a short-lived token carrying their own identity restricted to these scopes, handed to the app bundle so `windmill-client` calls run as the viewer. Must be a subset of the server's curated allowlist (jobs:run, jobs:read, users:read, resources:read, variables:read).\n"
|
||||
},
|
||||
"hide_login_status": {
|
||||
"type": "boolean",
|
||||
"description": "When true, the app's public and custom URLs do not show the viewer's login status (the user they are signed in as, or that they are signed out) in the top-left corner. Absent or false shows it.\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1387,7 +1391,7 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
},
|
||||
"execution_mode": {
|
||||
"type": "string",
|
||||
"description": "Who the app's runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Either way `anonymous`, which makes the app publicly executable, is never assumed. Possible values: viewer, publisher, anonymous"
|
||||
"description": "Who may open the app, and who its runnables execute as. Optional, and what omitting it means depends on the operation: creating an app defaults it to `publisher` (runs on behalf of the app's publisher and requires an authenticated viewer), while updating one keeps the mode the app is already deployed under. Neither `anonymous`, which makes the app publicly executable, nor `guest`, which opens it to anyone the identity provider authenticates, is ever assumed. A guest is only admitted where the workspace also has `guest_access_enabled`, which is checked when the session is minted and again on every guest request. Possible values: viewer, publisher, guest, anonymous"
|
||||
},
|
||||
"on_behalf_of": {
|
||||
"type": "string"
|
||||
@@ -1405,6 +1409,10 @@ export const mcpEndpointTools: EndpointTool[] = [
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Raw apps: author-declared scopes for the frontend SDK token. Takes effect only when `sandbox` is also true \u2014 an unsandboxed bundle runs with the viewer's own session, so no token is advertised or minted for it and this list stays inert. On a sandboxed app a non-empty list lets viewers mint (after consenting) a short-lived token carrying their own identity restricted to these scopes, handed to the app bundle so `windmill-client` calls run as the viewer. Must be a subset of the server's curated allowlist (jobs:run, jobs:read, users:read, resources:read, variables:read).\n"
|
||||
},
|
||||
"hide_login_status": {
|
||||
"type": "boolean",
|
||||
"description": "When true, the app's public and custom URLs do not show the viewer's login status (the user they are signed in as, or that they are signed out) in the top-left corner. Absent or false shows it.\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user