package auth
import (
"context"
"sync"
"gitcode-mcp/internal/config"
)
type Result struct {
Present bool
Token string
Source string
StoreMode string
ErrorClass string
Remediation string
}
type CredentialResolver struct {
mu sync.Mutex
provider config.CredentialProvider
result *Result
}
func NewCredentialResolver(src config.Source) *CredentialResolver {
return &CredentialResolver{
provider: config.DefaultCredentialProvider(src),
}
}
func NewCredentialResolverWithProvider(provider config.CredentialProvider) *CredentialResolver {
return &CredentialResolver{provider: provider}
}
func (r *CredentialResolver) Resolve(ctx context.Context, eff config.EffectiveConfig) Result {
if ctx.Err() != nil {
return cancelledResolution()
}
if !r.mu.TryLock() {
return Result{Source: "unavailable", ErrorClass: "credential-resolution-busy", Remediation: "Credential resolution is in progress; retry the explicit diagnostic or live operation."}
}
defer r.mu.Unlock()
if r.result != nil {
return *r.result
}
eff = normalizeEffectiveConfig(eff)
secret, status, _ := r.provider.Resolve(ctx, eff)
if ctx.Err() != nil {
return cancelledResolution()
}
result := Result{
Present: status.Present,
Token: secret.Value(),
Source: status.Source,
StoreMode: status.StoreMode,
ErrorClass: status.ErrorClass,
Remediation: status.Remediation,
}
r.result = &result
return result
}
func cancelledResolution() Result {
return Result{Source: "unavailable", ErrorClass: "credential-resolution-cancelled", Remediation: "Credential resolution was cancelled; retry only if still needed."}
}
func (r *CredentialResolver) Status(ctx context.Context, eff config.EffectiveConfig) Result {
return r.Resolve(ctx, eff)
}
func normalizeEffectiveConfig(eff config.EffectiveConfig) config.EffectiveConfig {
if eff.CredentialPolicy.Store == "" {
eff.CredentialPolicy.Store = "auto"
}
if eff.CredentialPolicy.KeyringService == "" {
eff.CredentialPolicy.KeyringService = "gitcode-mcp"
}
if eff.CredentialPolicy.KeyringAccount == "" {
eff.CredentialPolicy.KeyringAccount = "token"
}
return eff
}