Add public-client (CIMD) OAuth support

Public clients authenticate with PKCE and no client secret, using a
hosted Client ID Metadata Document (CIMD) as the client_id.

Add a no-secret token-endpoint mode, derive the state-token salt and the
PKCE verifier from a server-side key so the verifier never appears in
the signed-but-unencrypted state, and expose Registration.PublicClient,
Registry.PublicClients and the CIMD metadata path for provider wiring.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-05-29 12:23:57 +02:00
parent 8f40f460d4
commit cd8ddd8db5
5 changed files with 380 additions and 50 deletions

View File

@@ -115,6 +115,25 @@ func (r *Registry) All() []*Registration {
return out
}
// PublicClients returns every Registration flagged PublicClient (CIMD,
// no client_secret). probod uses this to auto-register their OAuth2
// connectors with a deployment-derived client_id and state-signing key.
// Order is not stable.
func (r *Registry) PublicClients() []*Registration {
r.mu.RLock()
defer r.mu.RUnlock()
var out []*Registration
for _, reg := range r.providers {
if reg.PublicClient {
out = append(out, reg)
}
}
return out
}
// ProviderDisplayName returns the human-readable label for the
// provider, falling back to the raw constant string when no display
// name is registered.

View File

@@ -46,6 +46,14 @@ type Registration struct {
// request and replays the verifier on the token exchange. Default
// false; non-PKCE providers are unaffected.
RequiresPKCE bool
// PublicClient marks an OAuth2 provider that authenticates as a public
// client (no client_secret) via PKCE, using the Client ID Metadata
// Document (CIMD) flow. probod auto-registers such providers with no
// operator credentials: the client_id is the deployment's hosted CIMD
// URL (baseURL + connector.CIMDMetadataPath) and the state token is
// signed with a server-derived key. Set TokenEndpointAuth to "none"
// alongside this.
PublicClient bool
// BuildAuthURL derives the authorization URL from an operator-supplied
// integration slug, for providers (e.g. Vercel) whose AuthURL embeds
// it as a path segment. It must construct the URL with net/url and