Documentation
¶
Overview ¶
Package credentials stores and resolves the OAuth token of one profile: access token, refresh token, expiry and type.
Resolution order:
- GITLAB_MCP_REFRESH_TOKEN in the environment (CI, automation).
- The OS keyring — Secret Service on Linux, Keychain on macOS, Credential Manager on Windows — keyed by the profile name.
- A file under the profile directory, restricted to the current account (0600, or an ACL on Windows), written only when the keyring was unavailable, and warned about on every use.
A missing keyring entry falls through quietly. A broken keyring (no session bus, no secret service) also falls through, so a headless machine still works, and its error is reported if nothing else is found.
GitLab rotates the refresh token on every refresh and revokes the old one at once, so a refresh token from the environment works exactly once. The pair it rotates into is saved to the keyring or the file stamped with a hash of the variable's value, and while the variable still holds that value the stored descendant is used in its place. Without that, the second start after a refresh would present a spent token.
Index ¶
Constants ¶
const ServiceName = "gitlab-mcp"
ServiceName is the keyring service identifier.
Variables ¶
var ErrKeyringSilent = errors.New("credentials: the profile records a token in the OS keyring and the " +
"keyring returned nothing. It is more likely locked or unreachable than empty: unlock it and try again. " +
"`gitlab-mcp login` writes a fresh token, which works around this rather than fixing it")
ErrKeyringSilent is what a keyring that answers "nothing" looks like when the profile says a token was stored in it.
The two are not the same and the advice differs. A missing token is fixed by logging in. A keyring that has the token and will not hand it over — locked, or a session bus that cannot reach the daemon — is fixed by unlocking it, and logging in again writes a second token beside the first without addressing the cause.
var ErrNotFound = errors.New("credentials: no token found; run `gitlab-mcp login`")
ErrNotFound means no token is stored anywhere.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
Get(service, account string) (string, error)
Set(service, account, secret string) error
Delete(service, account string) error
}
Backend is the keyring contract. Tests substitute an in-memory one.
type Store ¶
type Store struct {
Profile string
Keyring Backend
FilePath string
Env func(string) string
// Warn receives human-readable warnings; the plaintext fallback is
// announced through it on every use rather than once at login.
Warn func(string)
// ExpectKeyring says the profile records that a token was saved to
// the keyring. It changes only the error: with it, a keyring that
// answers "nothing" is reported as a keyring that will not answer
// rather than as a token that was never there.
ExpectKeyring bool
// contains filtered or unexported fields
}
Store resolves and saves the token for one profile.
func (*Store) Delete ¶
Delete removes the token from every store and reports every failure. Missing entries are fine. The environment is not a store.
func (*Store) EnvToken ¶ added in v4.1.3
EnvToken is the refresh token the environment gives, read through config.Lookup as every setting is, so spaces alone are none.
func (*Store) Resolve ¶
Resolve returns the token and where it came from.
The environment wins, except over a stored pair rotated from the very value it holds: that pair is the environment token's live successor. A token from the environment carries only the refresh token, so the first call refreshes it.
func (*Store) ResolveStored ¶
ResolveStored returns the token from the keyring or the file, ignoring the environment override: it is the token logout can revoke and delete.
func (*Store) Save ¶
Save stores the token in the keyring, or in the file when the keyring fails, and reports where it landed. A successful keyring save removes a stale plaintext copy, so there is one source of truth.
While the environment override is set, the saved pair is stamped as its successor (see Resolve); a pair saved without it is not.