diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index e608f93c4a..3a69668f99 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -13,6 +13,7 @@ import { sendUserToast } from '$lib/toast' import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte' import ResourceForm from './ResourceForm.svelte' + import { managedCredentialHost } from './git_sync/managedCredential' import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte' import Alert from './common/alert/Alert.svelte' import { resource } from 'runed' @@ -149,6 +150,7 @@ let loadingSchema = $derived(resourceTypeResource.loading) let current = $derived(selected ? states[selected]?.draft : undefined) + let managedHost = $derived(managedCredentialHost(current?.args)) let resourceToEdit: Resource | undefined = $derived( selected ? fetchedResources[selected] : undefined ) @@ -410,12 +412,12 @@ {/if} - {#if current?.args?.managed_credential} + {#if managedHost} The URL below carries no credential. Windmill renews the token before it expires and hands it to this workspace's sync jobs, and forks of this workspace use it without storing their own copy. To replace it, pick the project again with the - {current.args.managed_credential === 'gitlab' ? 'GitLab' : 'git'} button below. + {managedHost === 'gitlab' ? 'GitLab' : 'git'} button below. {/if} diff --git a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte index 911c956943..a958ffc7f8 100644 --- a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte @@ -24,6 +24,7 @@ import Toggle from '$lib/components/Toggle.svelte' import EEOnly from '$lib/components/EEOnly.svelte' import { ResourceService, VariableService } from '$lib/gen' + import { managedCredentialHost } from './managedCredential' let { idx = null, @@ -277,7 +278,7 @@ // Extract git URL from resource value const value = resource.value as Record isGithubApp = value?.is_github_app === true - managedCredential = value?.managed_credential ?? undefined + managedCredential = managedCredentialHost(value) // A newly added sync connection defaults to pulling from Git only // when the repository is app-backed (instant webhook delivery). // Polling is opt-in for token repositories, and fork/dev workspaces diff --git a/frontend/src/lib/components/git_sync/managedCredential.ts b/frontend/src/lib/components/git_sync/managedCredential.ts new file mode 100644 index 0000000000..3eccc8767a --- /dev/null +++ b/frontend/src/lib/components/git_sync/managedCredential.ts @@ -0,0 +1,25 @@ +/** A git repository resource value, as far as this predicate cares. */ +type GitRepositoryValue = { url?: string; managed_credential?: string } | undefined | null + +/** True when the remote authenticates itself, i.e. it carries a `user@` or + * `user:password@` userinfo component. Mirrors the server's rule, which is what + * decides whether it attaches the stored credential at all. */ +function urlCarriesCredential(url: string | undefined): boolean { + return /:\/\/[^/@]+@/.test(url ?? '') +} + +/** + * The host whose token Windmill holds for this repository, or undefined when it + * holds none. + * + * A URL that carries its own credential wins over the marker: the server skips + * the stored credential for such a URL, so honouring a stale marker here would + * have the UI promise renewal for a token nothing renews. That happens whenever + * someone puts a token back in the URL without clearing the marker, which is why + * this is checked rather than trusting the marker alone. + */ +export function managedCredentialHost(value: GitRepositoryValue): string | undefined { + const host = value?.managed_credential + if (!host || host === 'none') return undefined + return urlCarriesCredential(value?.url) ? undefined : host +}