Documentation
¶
Index ¶
- Variables
- func ContextWithEmail(ctx context.Context, email string) context.Context
- func EmailFromContext(ctx context.Context) string
- type AdminUserStore
- type AuthCodeEntry
- type AuthCodeStore
- type Claims
- type ClientEntry
- type ClientLoadEntry
- type ClientPersister
- type ClientStore
- func (s *ClientStore) AddRedirectURI(clientID, uri string)
- func (s *ClientStore) Get(clientID string) (*ClientEntry, bool)
- func (s *ClientStore) IsKiteClient(clientID string) bool
- func (s *ClientStore) LoadFromDB() error
- func (s *ClientStore) Register(redirectURIs []string, clientName string) (clientID, clientSecret string, err error)
- func (s *ClientStore) RegisterKiteClient(clientID string, redirectURIs []string)
- func (s *ClientStore) SetLogger(logger *slog.Logger)
- func (s *ClientStore) SetPersister(p ClientPersister)
- func (s *ClientStore) ValidateRedirectURI(clientID, uri string) bool
- type Config
- type ConsentRecorder
- type GoogleSSOConfig
- type Handler
- func (h *Handler) AuthServerMetadata(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Authorize(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Close()
- func (h *Handler) GenerateBrowserLoginURL(apiKey, email, redirect string) string
- func (h *Handler) GoogleSSOEnabled() bool
- func (h *Handler) HandleAdminLogin(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleAdminMFAEnroll(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleAdminMFAVerify(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleBrowserAuthCallback(w http.ResponseWriter, r *http.Request, requestToken string)
- func (h *Handler) HandleBrowserLogin(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleEmailLookup(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleGoogleCallback(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleGoogleLogin(w http.ResponseWriter, r *http.Request)
- func (h *Handler) HandleKiteOAuthCallback(w http.ResponseWriter, r *http.Request, requestToken string)
- func (h *Handler) HandleLoginChoice(w http.ResponseWriter, r *http.Request)
- func (h *Handler) JWTManager() *JWTManager
- func (h *Handler) LoadClientsFromDB() error
- func (h *Handler) MintAdminMFAToken(email string) (string, error)
- func (h *Handler) Register(w http.ResponseWriter, r *http.Request)
- func (h *Handler) RequireAdminMFA(next http.Handler) http.Handler
- func (h *Handler) RequireAuth(next http.Handler) http.Handler
- func (h *Handler) RequireAuthBrowser(next http.Handler) http.Handler
- func (h *Handler) ResourceMetadata(w http.ResponseWriter, r *http.Request)
- func (h *Handler) SetAuthCookie(w http.ResponseWriter, email string) error
- func (h *Handler) SetClientPersister(p ClientPersister, logger *slog.Logger)
- func (h *Handler) SetConsentRecorder(rec ConsentRecorder)
- func (h *Handler) SetGoogleSSO(cfg *GoogleSSOConfig)
- func (h *Handler) SetHTTPClient(c *http.Client)
- func (h *Handler) SetKiteTokenChecker(checker KiteTokenChecker)
- func (h *Handler) SetRegistry(r KeyRegistry)
- func (h *Handler) SetUserStore(store AdminUserStore)
- func (h *Handler) Token(w http.ResponseWriter, r *http.Request)
- type JWTManager
- func (j *JWTManager) GenerateToken(email, clientID string) (string, error)
- func (j *JWTManager) GenerateTokenWithExpiry(email, clientID string, expiry time.Duration) (string, error)
- func (j *JWTManager) SetPreviousSecret(previous string)
- func (j *JWTManager) ValidateToken(tokenString string, audiences ...string) (*Claims, error)
- type KeyRegistry
- type KiteExchanger
- type KiteTokenChecker
- type RegistryEntry
- type Signer
Constants ¶
This section is empty.
Variables ¶
var ErrAuthCodeStoreFull = errors.New("auth code store is full")
ErrAuthCodeStoreFull is returned when the auth code store has reached its capacity.
Functions ¶
func ContextWithEmail ¶
ContextWithEmail returns a new context with the given email set. Useful for testing handlers that depend on EmailFromContext.
func EmailFromContext ¶
EmailFromContext extracts the authenticated email from the request context.
Types ¶
type AdminUserStore ¶
type AdminUserStore interface {
GetRole(email string) string
GetStatus(email string) string
VerifyPassword(email, password string) (bool, error)
// EnsureGoogleUser auto-creates a trader account on first Google SSO login.
// Existing users are left unchanged (admins keep admin role).
EnsureGoogleUser(email string)
// HasTOTP reports whether the given user has an enrolled TOTP secret.
// Used by the MFA gate to decide between enrollment vs verification.
HasTOTP(email string) bool
// SetTOTPSecret persists a TOTP secret for the user (encrypted at rest).
// Returns error if the user is not admin or no encryption key is wired.
SetTOTPSecret(email, plaintextSecret string) error
// VerifyTOTP checks the supplied 6-digit code against the stored secret.
// Returns (false, nil) for any non-match (wrong code, not enrolled).
VerifyTOTP(email, code string) (bool, error)
// ClearTOTPSecret removes a user's TOTP enrollment (admin recovery flow).
ClearTOTPSecret(email string) error
}
AdminUserStore provides user lookup, password verification, and auto-provisioning for login. Implemented by users.Store to avoid direct import of the users package.
MFA methods (HasTOTP/SetTOTPSecret/VerifyTOTP/ClearTOTPSecret) gate admin actions per docs/access-control.md §8. Implementations must reject SetTOTPSecret on non-admin users (defence in depth).
type AuthCodeEntry ¶
type AuthCodeEntry struct {
ClientID string
CodeChallenge string // S256-hashed code_challenge from the client
RedirectURI string
Email string // set at callback for normal flow (global credentials)
RequestToken string // set at callback for deferred exchange (per-user Kite credentials)
ExpiresAt time.Time
}
AuthCodeEntry stores data associated with an issued authorization code.
type AuthCodeStore ¶
type AuthCodeStore struct {
// contains filtered or unexported fields
}
AuthCodeStore is a thread-safe in-memory store for OAuth authorization codes.
func NewAuthCodeStore ¶
func NewAuthCodeStore() *AuthCodeStore
NewAuthCodeStore creates a store and starts a background cleanup goroutine.
func (*AuthCodeStore) Close ¶
func (s *AuthCodeStore) Close()
Close stops the background cleanup goroutine. Safe to call multiple times.
func (*AuthCodeStore) Consume ¶
func (s *AuthCodeStore) Consume(code string) (*AuthCodeEntry, bool)
Consume retrieves and deletes an authorization code (one-time use).
func (*AuthCodeStore) Generate ¶
func (s *AuthCodeStore) Generate(entry *AuthCodeEntry) (string, error)
Generate creates a new random authorization code and stores the entry.
type Claims ¶
type Claims struct {
jwt.RegisteredClaims
}
Claims represents the JWT claims for an MCP access token.
type ClientEntry ¶
type ClientEntry struct {
ClientSecret string
RedirectURIs []string
ClientName string
CreatedAt time.Time
IsKiteAPIKey bool // true if client_id is a user's Kite API key (per-user credentials)
}
ClientEntry stores a dynamically registered client.
type ClientLoadEntry ¶
type ClientLoadEntry struct {
ClientID string
ClientSecret string
RedirectURIs string // JSON-encoded []string
ClientName string
CreatedAt time.Time
IsKiteAPIKey bool
}
ClientLoadEntry represents a client loaded from persistence.
type ClientPersister ¶
type ClientPersister interface {
SaveClient(clientID, clientSecret, redirectURIsJSON, clientName string, createdAt time.Time, isKiteKey bool) error
LoadClients() ([]*ClientLoadEntry, error)
DeleteClient(clientID string) error
}
ClientPersister provides optional persistence for OAuth clients.
type ClientStore ¶
type ClientStore struct {
// contains filtered or unexported fields
}
ClientStore is a thread-safe in-memory store for dynamically registered OAuth clients. Optionally backed by a ClientPersister for persistence via SetPersister.
func (*ClientStore) AddRedirectURI ¶
func (s *ClientStore) AddRedirectURI(clientID, uri string)
AddRedirectURI adds a redirect URI to an existing client if not already present. Capped at maxRedirectURIs per client to prevent abuse.
func (*ClientStore) Get ¶
func (s *ClientStore) Get(clientID string) (*ClientEntry, bool)
Get retrieves a client by ID. Returns a copy to prevent callers from mutating shared state.
func (*ClientStore) IsKiteClient ¶
func (s *ClientStore) IsKiteClient(clientID string) bool
IsKiteClient returns true if the client_id was registered as a Kite API key client.
func (*ClientStore) LoadFromDB ¶
func (s *ClientStore) LoadFromDB() error
LoadFromDB populates the in-memory store from the persister.
func (*ClientStore) Register ¶
func (s *ClientStore) Register(redirectURIs []string, clientName string) (clientID, clientSecret string, err error)
Register creates a new client with a random ID and secret.
func (*ClientStore) RegisterKiteClient ¶
func (s *ClientStore) RegisterKiteClient(clientID string, redirectURIs []string)
RegisterKiteClient auto-registers a client where client_id is a Kite API key. No client_secret is stored — validation happens via Kite's GenerateSession at token exchange.
func (*ClientStore) SetLogger ¶
func (s *ClientStore) SetLogger(logger *slog.Logger)
SetLogger sets the logger for persistence error reporting.
func (*ClientStore) SetPersister ¶
func (s *ClientStore) SetPersister(p ClientPersister)
SetPersister enables write-through persistence for OAuth clients.
func (*ClientStore) ValidateRedirectURI ¶
func (s *ClientStore) ValidateRedirectURI(clientID, uri string) bool
ValidateRedirectURI checks if the URI is registered for the client.
type Config ¶
type Config struct {
KiteAPIKey string // Kite API key for generating login URLs (optional: per-user credentials via oauth_client_id)
JWTSecret string
ExternalURL string // e.g. https://kite-mcp-server.fly.dev
TokenExpiry time.Duration
Logger *slog.Logger
}
Config holds OAuth 2.1 configuration.
type ConsentRecorder ¶
type ConsentRecorder func(email, ipAddress, userAgent string)
ConsentRecorder captures the DPDP Act 2023 consent-grant event that occurs when a user completes the Kite OAuth flow — at callback time we know the email (via Kite token exchange) plus the IP/UA/timestamp, which is the minimum record the Data Protection Board may request in an audit.
Implementations must be non-blocking and best-effort: the OAuth callback should not fail just because consent logging is unavailable. Implementations are expected to swallow or log their own errors — the callback ignores the return value.
type GoogleSSOConfig ¶
type GoogleSSOConfig struct {
ClientID string
ClientSecret string
RedirectURL string
Endpoint string // Optional: override token endpoint URL (for testing). Empty = Google default.
UserInfoURL string // Optional: override userinfo URL (for testing). Empty = Google default.
}
GoogleSSOConfig holds configuration for Google OAuth SSO.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler implements all OAuth 2.1 HTTP endpoints.
Endpoint handlers are grouped by flow across sibling files:
- handlers.go core wiring (Handler struct, NewHandler, setters, metadata, Register, helpers)
- handlers_oauth.go OAuth 2.1 Authorize / email prompt / Token
- handlers_callback.go Kite OAuth callback (HandleKiteOAuthCallback)
- handlers_browser.go browser login flow (HandleBrowserLogin / HandleBrowserAuthCallback / HandleLoginChoice)
- handlers_admin.go admin password login (HandleAdminLogin)
func NewHandler ¶
func NewHandler(cfg *Config, signer Signer, exchanger KiteExchanger) *Handler
NewHandler creates a new OAuth handler. Config must be validated first.
func (*Handler) AuthServerMetadata ¶
func (h *Handler) AuthServerMetadata(w http.ResponseWriter, r *http.Request)
AuthServerMetadata serves RFC 8414 OAuth Authorization Server Metadata.
func (*Handler) Authorize ¶
func (h *Handler) Authorize(w http.ResponseWriter, r *http.Request)
Authorize handles GET /oauth/authorize — validates params and redirects to Kite login.
func (*Handler) Close ¶
func (h *Handler) Close()
Close releases resources held by the handler (e.g., background goroutines).
func (*Handler) GenerateBrowserLoginURL ¶
GenerateBrowserLoginURL generates a Kite login URL for browser-based auth. The email and redirect path are signed together and passed through as redirect_params, so the callback can look up per-user credentials for the token exchange.
func (*Handler) GoogleSSOEnabled ¶
GoogleSSOEnabled returns true if Google SSO is configured.
func (*Handler) HandleAdminLogin ¶
func (h *Handler) HandleAdminLogin(w http.ResponseWriter, r *http.Request)
HandleAdminLogin serves and processes the admin password login form. GET: renders admin_login.html with CSRF token. POST: validates CSRF, checks admin role + active status + bcrypt password. On success: sets auth cookie and redirects to /admin/ops.
func (*Handler) HandleAdminMFAEnroll ¶
func (h *Handler) HandleAdminMFAEnroll(w http.ResponseWriter, r *http.Request)
HandleAdminMFAEnroll handles enrollment.
GET — fresh flow: generate a new TOTP secret, render the enrollment form (manual-entry secret + otpauth:// link). POST — verify the supplied code matches the freshly-generated secret; if so, persist the secret on the user store and mint the MFA cookie.
The secret is round-tripped through a hidden form field rather than stored server-side at GET time. Only the SUCCESSFUL POST persists. This avoids retaining a half-enrolled secret if the user abandons the flow.
func (*Handler) HandleAdminMFAVerify ¶
func (h *Handler) HandleAdminMFAVerify(w http.ResponseWriter, r *http.Request)
HandleAdminMFAVerify handles ongoing verification (after enrollment).
GET — render the 6-digit code form. If the user hasn't enrolled yet, redirect to /enroll. POST — verify the code against the stored secret. On success, mint the MFA cookie.
func (*Handler) HandleBrowserAuthCallback ¶
func (h *Handler) HandleBrowserAuthCallback(w http.ResponseWriter, r *http.Request, requestToken string)
HandleBrowserAuthCallback handles the Kite callback for browser login flow. Called when flow=browser in the callback query params. Sets a JWT cookie and redirects to the target page (e.g. /admin/ops).
func (*Handler) HandleBrowserLogin ¶
func (h *Handler) HandleBrowserLogin(w http.ResponseWriter, r *http.Request)
HandleBrowserLogin serves a login form or redirects to Kite login for browser-based auth. If an email query param is provided, looks up stored credentials and redirects to Kite. Otherwise, serves a login form where the user enters their email. CSRF protection: GET sets a random token as an HttpOnly cookie and hidden form field; POST verifies the cookie matches the form value.
func (*Handler) HandleEmailLookup ¶
func (h *Handler) HandleEmailLookup(w http.ResponseWriter, r *http.Request)
HandleEmailLookup handles POST /oauth/email-lookup — looks up registry for user's email, then redirects to Kite login with the registered app's API key.
func (*Handler) HandleGoogleCallback ¶
func (h *Handler) HandleGoogleCallback(w http.ResponseWriter, r *http.Request)
HandleGoogleCallback handles the OAuth callback from Google.
func (*Handler) HandleGoogleLogin ¶
func (h *Handler) HandleGoogleLogin(w http.ResponseWriter, r *http.Request)
HandleGoogleLogin redirects the user to the Google consent screen.
func (*Handler) HandleKiteOAuthCallback ¶
func (h *Handler) HandleKiteOAuthCallback(w http.ResponseWriter, r *http.Request, requestToken string)
HandleKiteOAuthCallback handles the Kite callback for MCP OAuth flow. Called when flow=oauth in the callback query params.
func (*Handler) HandleLoginChoice ¶
func (h *Handler) HandleLoginChoice(w http.ResponseWriter, r *http.Request)
HandleLoginChoice serves a unified login page offering Kite login and admin login. If the user already has a valid dashboard cookie, redirects to the dashboard.
func (*Handler) JWTManager ¶
func (h *Handler) JWTManager() *JWTManager
JWTManager returns the JWT manager for external use.
func (*Handler) LoadClientsFromDB ¶
LoadClientsFromDB loads persisted OAuth clients into the in-memory store.
func (*Handler) MintAdminMFAToken ¶
MintAdminMFAToken issues a short-lived JWT marking the user's MFA gate as passed. Used by both enroll and verify success paths and by tests. Exposed (not unexported) because the dashboard middleware occasionally needs to mint via a different code path.
func (*Handler) Register ¶
func (h *Handler) Register(w http.ResponseWriter, r *http.Request)
Register handles POST /oauth/register.
func (*Handler) RequireAdminMFA ¶
RequireAdminMFA is the middleware that gates admin paths. Preconditions: the upstream admin auth middleware has already set email-in-ctx (via ContextWithEmail). RequireAdminMFA checks:
- email present (else 401 — defensive; should never trip in prod)
- kite_admin_mfa cookie present and valid for THIS email
If the cookie is missing/invalid, the user is redirected to /auth/admin-mfa/enroll (un-enrolled) or /verify (enrolled). The original path is preserved as the redirect target so they land back where they were after the gate clears.
func (*Handler) RequireAuth ¶
RequireAuth returns middleware that validates Bearer JWT tokens on requests. Unauthenticated requests get a 401 with WWW-Authenticate pointing to the resource metadata.
func (*Handler) RequireAuthBrowser ¶
RequireAuthBrowser checks for a valid JWT cookie (audience=dashboard). Unlike RequireAuth, this does NOT check Kite token expiry — the browser auth flow has its own re-auth path via HandleBrowserAuthCallback.
RequireAuthBrowser returns middleware for browser-based auth. Tries Bearer token first, then falls back to a JWT cookie. If neither is valid, redirects to the Kite login flow.
func (*Handler) ResourceMetadata ¶
func (h *Handler) ResourceMetadata(w http.ResponseWriter, r *http.Request)
ResourceMetadata serves RFC 9728 OAuth Protected Resource Metadata.
func (*Handler) SetAuthCookie ¶
func (h *Handler) SetAuthCookie(w http.ResponseWriter, email string) error
SetAuthCookie sets a JWT cookie for browser-based dashboard auth.
func (*Handler) SetClientPersister ¶
func (h *Handler) SetClientPersister(p ClientPersister, logger *slog.Logger)
SetClientPersister enables persistence for the OAuth client store.
func (*Handler) SetConsentRecorder ¶
func (h *Handler) SetConsentRecorder(rec ConsentRecorder)
SetConsentRecorder registers a callback that persists a DPDP Act 2023 consent-grant event. The Handler invokes it once per successful OAuth callback, after the email is known. When nil (default), consent recording is disabled — appropriate for DevMode or clients that don't need the audit trail. Production deployments must wire this to a durable store.
func (*Handler) SetGoogleSSO ¶
func (h *Handler) SetGoogleSSO(cfg *GoogleSSOConfig)
SetGoogleSSO enables Google SSO for admin login.
func (*Handler) SetHTTPClient ¶
SetHTTPClient sets a custom HTTP client for Google OAuth operations (token exchange + userinfo). When nil (default), the standard library clients are used.
func (*Handler) SetKiteTokenChecker ¶
func (h *Handler) SetKiteTokenChecker(checker KiteTokenChecker)
SetKiteTokenChecker registers a callback that checks Kite token validity. When set, RequireAuth returns 401 if the Kite token has expired, forcing mcp-remote to re-authenticate (which includes a fresh Kite login).
func (*Handler) SetRegistry ¶
func (h *Handler) SetRegistry(r KeyRegistry)
SetRegistry sets the key registry for zero-config onboarding. When set, generic OAuth clients can be matched to Kite apps by email.
func (*Handler) SetUserStore ¶
func (h *Handler) SetUserStore(store AdminUserStore)
SetUserStore sets the user store for admin password-based login.
type JWTManager ¶
type JWTManager struct {
// contains filtered or unexported fields
}
JWTManager handles token generation and validation.
Dual-key rotation: secret signs new tokens; previousSecret is accepted during verification only. To rotate without invalidating live sessions:
- Set OAUTH_JWT_SECRET_PREVIOUS = current secret
- Set OAUTH_JWT_SECRET = freshly generated new secret
- Restart. Existing JWTs (signed with the now-previous key) verify via the fallback. New tokens sign with the new key.
- After all live JWTs have expired (default 24h MCP / 7d dashboard), unset OAUTH_JWT_SECRET_PREVIOUS. The rotation is complete.
previousSecret may be empty — in which case there is no fallback and rotation = invalidation, the pre-PR-DR behaviour.
func NewJWTManager ¶
func NewJWTManager(secret string, expiry time.Duration) *JWTManager
NewJWTManager creates a new JWT manager. previousSecret is optional — pass empty string when no rotation is in progress.
func (*JWTManager) GenerateToken ¶
func (j *JWTManager) GenerateToken(email, clientID string) (string, error)
GenerateToken creates a signed JWT for the given email and client.
func (*JWTManager) GenerateTokenWithExpiry ¶
func (j *JWTManager) GenerateTokenWithExpiry(email, clientID string, expiry time.Duration) (string, error)
GenerateTokenWithExpiry creates a signed JWT with a custom expiry duration.
func (*JWTManager) SetPreviousSecret ¶
func (j *JWTManager) SetPreviousSecret(previous string)
SetPreviousSecret installs a second-chance verify key for graceful rotation. Tokens signed with this key validate; new tokens still sign with the primary secret. Pass empty to clear the rotation slot once the migration window has elapsed.
func (*JWTManager) ValidateToken ¶
func (j *JWTManager) ValidateToken(tokenString string, audiences ...string) (*Claims, error)
ValidateToken parses and validates the JWT, returning claims if valid. If audiences are provided, validates that the token's audience matches at least one.
Dual-key behaviour: tries the primary secret first. On signature failure, retries with previousSecret if set. Lets graceful rotations keep live sessions alive across a secret swap. Verifications that succeed via the previous key still pass — there is no warning header or claim flag, since reading already-issued tokens is precisely what the fallback exists for.
type KeyRegistry ¶
type KeyRegistry interface {
HasEntries() bool
GetByEmail(email string) (*RegistryEntry, bool)
GetSecretByAPIKey(apiKey string) (apiSecret string, ok bool)
}
KeyRegistry provides access to the pre-registered Kite app credentials. Implemented by registry.Store to avoid direct import.
type KiteExchanger ¶
type KiteExchanger interface {
ExchangeRequestToken(requestToken string) (email string, err error)
ExchangeWithCredentials(requestToken, apiKey, apiSecret string) (email string, err error)
GetCredentials(email string) (apiKey, apiSecret string, ok bool)
GetSecretByAPIKey(apiKey string) (apiSecret string, ok bool)
}
KiteExchanger exchanges a Kite request_token for user identity and caches the access token.
type KiteTokenChecker ¶
KiteTokenChecker checks whether the Kite trading token for a given email is still valid. Returns true if the token is valid (or no token cached yet), false if expired.
type RegistryEntry ¶
type RegistryEntry struct {
APIKey string
APISecret string
RegisteredBy string // admin email who registered the app
}
RegistryEntry is a thin projection of a pre-registered Kite app, used inside the oauth package to avoid importing the registry package directly.