Documentation
¶
Overview ¶
Package auth provides agent token authentication for MCPProxy.
It implements token generation, validation, HMAC-based hashing, and context propagation for distinguishing admin vs agent access. Agent tokens use the "mcp_agt_" prefix and are secured with HMAC-SHA256 hashing before storage.
Index ¶
- Constants
- func AgentDeniedServerOp(op string) bool
- func AuthorizeServerOp(ac *AuthContext, op string) bool
- func GenerateToken() (string, error)
- func GetOrCreateHMACKey(dataDir string) ([]byte, error)
- func HashToken(token string, key []byte) string
- func TokenPrefix(token string) string
- func ValidatePermissions(perms []string) error
- func ValidateTokenFormat(token string) bool
- func WithAuthContext(ctx context.Context, ac *AuthContext) context.Context
- type AgentToken
- type AuthContext
Constants ¶
const ( PermRead = "read" PermWrite = "write" PermDestructive = "destructive" )
Permission constants define the allowed permission tiers.
const ( AuthTypeAdmin = "admin" // API key admin (personal edition) AuthTypeAgent = "agent" // Agent token authentication AuthTypeUser = "user" // Regular OAuth-authenticated user (server edition) AuthTypeAdminUser = "admin_user" // OAuth-authenticated admin (server edition) )
Auth type constants.
const ( ServerOpAdd = "add" ServerOpRemove = "remove" ServerOpUpdate = "update" ServerOpPatch = "patch" ServerOpEnable = "enable" ServerOpDisable = "disable" ServerOpRestart = "restart" ServerOpRefresh = "refresh" ServerOpDiscoverTools = "discover_tools" ServerOpQuarantine = "quarantine" ServerOpUnquarantine = "unquarantine" ServerOpAddFromRegistry = "add_from_registry" ServerOpLogin = "login" ServerOpLogout = "logout" ServerOpConfigToSecret = "config_to_secret" ServerOpApproveTools = "approve_tools" ServerOpBlockTools = "block_tools" // REST-only mutating operations that reach server/security/config state // through routes OTHER than /api/v1/servers/{id}. They are denied to agents // so the /servers gate cannot be bypassed via a sibling endpoint // (config apply rewrites mcpServers; registry add creates a server; the // security scanner mutates a server's approval state). ServerOpConfigWrite = "config_write" ServerOpScan = "scan" ServerOpSecurityApprove = "security_approve" ServerOpSecurityReject = "security_reject" ServerOpSecretWrite = "secret_write" ServerOpDiagnosticsFix = "diagnostics_fix" )
Server-mutating operation identifiers.
These name the write operations on upstream MCP servers that are exposed on BOTH the MCP `upstream_servers` / `quarantine_security` tools and the REST `/api/v1/servers/...` surface. They are the single source of truth for which operations agent tokens (non-admin) may NOT perform, so the two surfaces can never drift apart again (issues #877/#878).
The string values for the operations that also exist on the MCP `upstream_servers` tool intentionally match that tool's operation names (see internal/server/mcp.go: operationAdd/operationRemove and the literal "update"/"patch"/"enable"/"disable"/"restart"/"refresh"/"add_from_registry" dispatch), so the MCP denylist can consume this policy without changing its observable behavior. REST-only operations (quarantine, login, …) use their own identifiers; the MCP surface never passes those strings here, so listing them is a no-op for MCP and closes the REST gap.
const MaxTokens = 100
MaxTokens is the maximum number of agent tokens allowed.
const TokenPrefixStr = "mcp_agt_"
Token prefix used for all agent tokens.
Variables ¶
This section is empty.
Functions ¶
func AgentDeniedServerOp ¶
AgentDeniedServerOp reports whether the named operation is forbidden to agent tokens (non-admin auth contexts). It is case-sensitive: callers pass the canonical ServerOp* identifiers, never user-controlled free text.
func AuthorizeServerOp ¶
func AuthorizeServerOp(ac *AuthContext, op string) bool
AuthorizeServerOp is the shared gate both surfaces use. It returns true when the caller is permitted to perform op. An admin context (API key or OS-authenticated socket) is always allowed. A nil context means the request arrived without an AuthContext — the auth middleware only leaves it nil in test/no-config passthrough scenarios that never occur once a real *config.Config is loaded, so it is treated as allowed here (the middleware, not this policy, is responsible for rejecting unauthenticated production requests). Any present non-admin context (agent token) is denied when op is on the denylist.
func GenerateToken ¶
GenerateToken creates a new agent token with the mcp_agt_ prefix followed by 64 hex characters (32 random bytes). Total length: 72 chars.
func GetOrCreateHMACKey ¶
GetOrCreateHMACKey reads the HMAC key from <dataDir>/.token_key. If the file does not exist, it generates a 32-byte random key, writes it with 0600 permissions, and returns it.
func HashToken ¶
HashToken computes HMAC-SHA256 of the token using the given key and returns the hex-encoded digest.
func TokenPrefix ¶
TokenPrefix returns the first 12 characters of the token for display purposes.
func ValidatePermissions ¶
ValidatePermissions checks that the given permissions list is valid. It must contain "read" and only contain valid permission values.
func ValidateTokenFormat ¶
ValidateTokenFormat checks that a token has the correct format: mcp_agt_ prefix followed by exactly 64 hex characters (72 chars total).
func WithAuthContext ¶
func WithAuthContext(ctx context.Context, ac *AuthContext) context.Context
WithAuthContext returns a new context with the given AuthContext attached.
Types ¶
type AgentToken ¶
type AgentToken struct {
Name string `json:"name"`
TokenHash string `json:"token_hash"`
TokenPrefix string `json:"token_prefix"` // first 12 chars of the raw token
AllowedServers []string `json:"allowed_servers"`
Permissions []string `json:"permissions"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
Revoked bool `json:"revoked"`
UserID string `json:"user_id,omitempty"` // Owner user ID (server edition)
ProfilePin string `json:"profile_pin,omitempty"` // Profile this token is pinned to (Profiles v2 T3)
}
AgentToken represents a stored agent token record.
func (*AgentToken) IsExpired ¶
func (t *AgentToken) IsExpired() bool
IsExpired returns true if the token has passed its expiry time.
func (*AgentToken) IsRevoked ¶
func (t *AgentToken) IsRevoked() bool
IsRevoked returns true if the token has been revoked.
type AuthContext ¶
type AuthContext struct {
Type string // "admin", "agent", "user", or "admin_user"
AgentName string // Name of the agent token (empty for admin)
TokenPrefix string // First 12 chars of raw token (empty for admin)
AllowedServers []string // Servers this token can access (nil = all for admin)
Permissions []string // Permission tiers (nil = all for admin)
ProfilePin string // Profile this agent token is pinned to (Profiles v2 T3; empty if unpinned)
// Multi-user OAuth fields (server edition). Empty for non-user auth types.
UserID string // User's unique ULID identifier
Email string // User's email from OAuth provider
DisplayName string // User's display name
Role string // "admin" or "user" (empty for API key / agent token auth)
Provider string // OAuth provider used (e.g., "google", "github")
}
AuthContext carries authentication identity through request context.
func AdminContext ¶
func AdminContext() *AuthContext
AdminContext returns an AuthContext representing full admin access (API key auth).
func AdminUserContext ¶ added in v0.21.0
func AdminUserContext(userID, email, displayName, provider string) *AuthContext
AdminUserContext returns an AuthContext for an OAuth-authenticated admin user.
func AuthContextFromContext ¶
func AuthContextFromContext(ctx context.Context) *AuthContext
AuthContextFromContext extracts the AuthContext from the context. Returns nil if no AuthContext is present.
func UserContext ¶ added in v0.21.0
func UserContext(userID, email, displayName, provider string) *AuthContext
UserContext returns an AuthContext for a regular OAuth-authenticated user.
func (*AuthContext) CanAccessServer ¶
func (ac *AuthContext) CanAccessServer(name string) bool
CanAccessServer checks whether this context is allowed to access the named server. Admin contexts have unrestricted access. Agent contexts check their AllowedServers list, where "*" is treated as a wildcard granting access to all servers.
func (*AuthContext) GetUserID ¶ added in v0.21.0
func (ac *AuthContext) GetUserID() string
GetUserID returns the user's unique identifier, or empty string for non-user auth.
func (*AuthContext) HasPermission ¶
func (ac *AuthContext) HasPermission(perm string) bool
HasPermission checks whether this context includes the given permission. Admin contexts have all permissions. Agent contexts check their Permissions list.
func (*AuthContext) IsAdmin ¶
func (ac *AuthContext) IsAdmin() bool
IsAdmin returns true if this is an admin authentication context. Both API key admins ("admin") and OAuth-authenticated admins ("admin_user") are considered admin.
func (*AuthContext) IsAuthenticated ¶ added in v0.21.0
func (ac *AuthContext) IsAuthenticated() bool
IsAuthenticated returns true if this context has any authentication type set.
func (*AuthContext) IsUser ¶ added in v0.21.0
func (ac *AuthContext) IsUser() bool
IsUser returns true if this is an OAuth-authenticated user context. Both regular users ("user") and admin users ("admin_user") are considered users.