fix: define the credential facade for private builds without enterprise

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75
This commit is contained in:
hugocasa
2026-09-07 15:48:03 +02:00
co-authored by Claude Opus 5
parent 6551d23642
commit 87b81bef37
5 changed files with 28 additions and 7 deletions
+1 -1
View File
@@ -1 +1 @@
5d9cd2b60b286840d1647103f89cf7953b8b2065
12224d76e0e75d0f7455ef0202e4b287567143a3
+6 -2
View File
@@ -1,7 +1,7 @@
#[cfg(feature = "private")]
#[allow(unused)]
pub use crate::git_sync_ee::*;
#[cfg(not(feature = "private"))]
#[cfg(not(all(feature = "private", feature = "enterprise")))]
use sqlx::{Pool, Postgres};
use url::Url;
@@ -17,7 +17,11 @@ pub async fn get_github_app_token_internal(
/// Server-held git credentials are an enterprise feature, so on this build a
/// repository URL authenticates with whatever it already carries.
#[cfg(not(feature = "private"))]
///
/// Gated on the pair rather than on `private` alone: `private` does not imply
/// `enterprise`, and the callers are plain (no `#[cfg]`), so a build with one
/// and not the other would find neither this nor the enterprise definition.
#[cfg(not(all(feature = "private", feature = "enterprise")))]
pub async fn with_stored_credential(
_db: &Pool<Postgres>,
_w_id: &str,
+5 -2
View File
@@ -441,8 +441,11 @@ pub struct GitCredentialStatus {
/// different credential here", which look identical at the API otherwise.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub token_fingerprint: Option<String>,
/// Rotation needs both a scope that permits it (`api` or `self_rotate`) and a
/// URL Windmill can write back to. `scopes` says which of the two is missing.
/// Whether Windmill renews this credential itself. That needs a scope which
/// permits it (`api` or `self_rotate`) and a credential this workspace holds:
/// a token carried in the repository URL is the operator's to manage, and one
/// resolved from an ancestor is the ancestor's, so neither is renewed here.
/// `scopes` therefore only tells the scope half apart.
pub rotatable: bool,
/// Unix timestamp (seconds) of the last check.
pub checked_at: i64,
@@ -41,6 +41,10 @@
let listError: string | undefined = $state(undefined)
let applying = $state(false)
let applyError: string | undefined = $state(undefined)
/** The token the current listing was made with. Editing the token afterwards
* leaves projects on screen that were never checked against it, and applying
* would store the new token for a project chosen under the old one. */
let listedToken = $state('')
// Shown alongside the GitHub App button and on the same terms, so the two
// read as one choice rather than one option and one absence.
@@ -56,6 +60,7 @@
let enabled = $derived(!!$enterpriseLicense)
let project = $derived(projects.find((p) => p.path_with_namespace === selectedProject))
let staleListing = $derived(projects.length > 0 && token !== listedToken)
async function listProjects() {
if (!ws) return
@@ -66,6 +71,7 @@
workspace: ws,
requestBody: { base_url: baseUrl, token, search: search || undefined }
})
listedToken = token
selectedProject = projects[0]?.path_with_namespace
if (projects.length === 0) {
listError = 'The token can see no project with at least the Developer role'
@@ -80,7 +86,7 @@
}
async function apply(close: (_: any) => void) {
if (!project || !token || applying) return
if (!project || !token || applying || staleListing) return
const chosen = project
const url = chosen.http_url_to_repo
applying = true
@@ -178,7 +184,12 @@
{#if listError}
<Alert type="error" title="Could not list projects" size="xs">{listError}</Alert>
{/if}
{#if projects.length > 0}
{#if staleListing}
<div class="text-xs font-normal text-secondary">
The token changed. List the projects again to choose one it can reach.
</div>
{/if}
{#if projects.length > 0 && !staleListing}
<div class="flex flex-col gap-y-1">
<div class="text-xs font-semibold text-emphasis">Project</div>
<Select
@@ -641,6 +641,9 @@
{:else if repo.credential.rotatable}
Repository token expires on {repo.credential.expires_at}. Renewing it automatically
requires an enterprise license.
{:else if (repo.credential.scopes ?? []).some((s) => s === 'api' || s === 'self_rotate')}
Repository token expires on {repo.credential.expires_at}. Windmill only renews a token it
holds, and this repository authenticates with one it does not.
{:else}
Repository token expires on {repo.credential.expires_at}. Give it the api or self_rotate
scope to let Windmill renew it automatically.