auth

package
v0.1.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 27, 2026 License: Apache-2.0 Imports: 43 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EffectiveUserID

func EffectiveUserID(ctx context.Context) string

EffectiveUserID returns a stable user identifier from context. Delegates to the internal auth package.

func InjectTokens

func InjectTokens(ctx context.Context, tokens *scyauth.Token) context.Context

InjectTokens stores OAuth tokens in context so that MCPAuthToken and downstream MCP clients can forward the logged-in user's token. External auth middleware (outside this module) should call this after authenticating the user from a session or JWT.

func InjectUser

func InjectUser(ctx context.Context, subject string) context.Context

InjectUser stores user identity in context using the agently-core auth key. External middleware (outside this module) can call this to bridge their own auth context into the context key that EffectiveUserID reads.

func MCPAuthToken

func MCPAuthToken(ctx context.Context, useIDToken bool) string

MCPAuthToken selects a single token string suitable for outbound MCP calls. Delegates to the internal auth package.

func NewCreatedByUserTokenProvider added in v0.1.7

func NewCreatedByUserTokenProvider(cfg *Config, dao *datly.Service) token.Provider

NewCreatedByUserTokenProvider returns a store-backed token provider suitable for scheduler created_by_user_id auth restoration. It only restores tokens already persisted in user_oauth_token; it does not enable any broader auth flow.

func NewTokenStoreAdapter

func NewTokenStoreAdapter(store TokenStore, users UserService) token.TokenStore

NewTokenStoreAdapter wraps a service/auth.TokenStore to satisfy token.TokenStore.

func Protect

func Protect(cfg *Config, sessions *Manager, opts ...ProtectOption) func(http.Handler) http.Handler

func ProtectWithTokenProvider

func ProtectWithTokenProvider(cfg *Config, sessions *Manager, tp token.Provider, opts ...ProtectOption) func(http.Handler) http.Handler

Protect returns middleware that extracts auth credentials from the request (Bearer token or session cookie) and populates the request context with authenticated user identity and tokens.

Requests to /v1/api/auth/* and OPTIONS are passed through without auth. When JWT auth is configured, Bearer tokens are cryptographically verified.

ProtectWithTokenProvider is like Protect but also stores session tokens in the given token.Provider so they are available for subsequent requests from that user.

func WithAuthExtensions added in v0.1.3

func WithAuthExtensions(base http.Handler, runtime *Runtime) http.Handler

func WithAuthProtection added in v0.1.3

func WithAuthProtection(base http.Handler, runtime *Runtime) http.Handler

Types

type Config added in v0.1.3

type Config struct {
	Enabled                 bool     `yaml:"enabled" json:"enabled"`
	CookieName              string   `yaml:"cookieName" json:"cookieName"`
	SessionTTLHours         int      `yaml:"sessionTTLHours,omitempty" json:"sessionTTLHours,omitempty"`
	TokenRefreshLeadMinutes int      `yaml:"tokenRefreshLeadMinutes,omitempty" json:"tokenRefreshLeadMinutes,omitempty"`
	DefaultUsername         string   `yaml:"defaultUsername" json:"defaultUsername"`
	IpHashKey               string   `yaml:"ipHashKey" json:"ipHashKey"`
	TrustedProxies          []string `yaml:"trustedProxies" json:"trustedProxies"`
	RedirectPath            string   `yaml:"redirectPath" json:"redirectPath"`
	OAuth                   *OAuth   `yaml:"oauth" json:"oauth"`
	Local                   *Local   `yaml:"local" json:"local"`
	JWT                     *JWT     `yaml:"jwt,omitempty" json:"jwt,omitempty"`
}

Config defines global authentication settings for public embedders.

func DecodeConfigFromRoot added in v0.1.8

func DecodeConfigFromRoot(root *wscfg.Root) (*Config, error)

DecodeConfigFromRoot decodes the `auth:` section from an already- loaded workspace Root. Returns (nil, nil) when the root is nil or the auth section is empty — callers treat that as "auth disabled". Env-template expansion and the "effectively empty" check live here so the two entry points (LoadConfig / DecodeConfigFromRoot) behave identically.

func LoadConfig added in v0.1.8

func LoadConfig(workspaceRoot string) (*Config, error)

LoadConfig reads `<workspaceRoot>/config.yaml` and decodes the `auth:` section into a *Config. Returns (nil, nil) when no auth section is present or all fields are zero.

Callers that have already loaded the workspace `Root` (e.g. the executor bootstrap that also reads `default:` / `mcpServer`) should prefer DecodeConfigFromRoot to avoid re-reading and re-parsing config.yaml.

func LoadWorkspaceConfig deprecated added in v0.1.3

func LoadWorkspaceConfig(workspaceRoot string) (*Config, error)

LoadWorkspaceConfig is a thin compatibility shim over LoadConfig.

Deprecated: use LoadConfig for new code, or DecodeConfigFromRoot when the workspace Root is already in hand. Kept so external callers that still reference the old name keep compiling; will be removed once the tree is fully migrated.

func (*Config) IsBearerAccepted added in v0.1.3

func (c *Config) IsBearerAccepted() bool

func (*Config) IsCookieAccepted added in v0.1.3

func (c *Config) IsCookieAccepted() bool

func (*Config) IsJWTAuth added in v0.1.3

func (c *Config) IsJWTAuth() bool

func (*Config) IsLocalAuth added in v0.1.3

func (c *Config) IsLocalAuth() bool

func (*Config) Validate added in v0.1.3

func (c *Config) Validate() error

type DatlyUserService added in v0.1.5

type DatlyUserService struct {
	// contains filtered or unexported fields
}

func NewDatlyUserService added in v0.1.5

func NewDatlyUserService(dao *datly.Service) *DatlyUserService

func (*DatlyUserService) GetBySubjectAndProvider added in v0.1.5

func (s *DatlyUserService) GetBySubjectAndProvider(ctx context.Context, subject, provider string) (*User, error)

func (*DatlyUserService) GetByUsername added in v0.1.5

func (s *DatlyUserService) GetByUsername(ctx context.Context, username string) (*User, error)

func (*DatlyUserService) UpdateHashIPByID added in v0.1.5

func (s *DatlyUserService) UpdateHashIPByID(ctx context.Context, id, hash string) error

func (*DatlyUserService) UpdatePreferences added in v0.1.5

func (s *DatlyUserService) UpdatePreferences(ctx context.Context, username string, patch *PreferencesPatch) error

func (*DatlyUserService) Upsert added in v0.1.5

func (s *DatlyUserService) Upsert(ctx context.Context, user *User) error

func (*DatlyUserService) UpsertWithProvider added in v0.1.5

func (s *DatlyUserService) UpsertWithProvider(ctx context.Context, username, displayName, email, provider, subject string) (string, error)

type ExpiringTokenScanner added in v0.1.7

type ExpiringTokenScanner interface {
	// ScanExpiring returns all stored tokens whose expiry is before horizon
	// and that carry a refresh token. Only tokens that can actually be
	// refreshed need to be returned.
	ScanExpiring(ctx context.Context, horizon time.Time) ([]*OAuthToken, error)
}

ExpiringTokenScanner is an optional extension of TokenStore for implementations that can efficiently query the store for tokens expiring before a given horizon. It is used by the background refresh watcher to proactively refresh tokens for users who are idle (no active in-memory session) before they expire. TokenStore implementations that do not embed a queryable DB may omit this.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler serves auth-related HTTP endpoints. It is designed to be mounted under /v1/api/auth/* on the application router.

func NewHandler

func NewHandler(cfg *Config, sessions *Manager, opts ...HandlerOption) *Handler

NewHandler creates an auth HTTP handler.

func (*Handler) Register

func (h *Handler) Register(mux *http.ServeMux)

Register mounts auth routes on the given mux.

func (*Handler) RegisterPreferences

func (h *Handler) RegisterPreferences(mux *http.ServeMux)

RegisterPreferences mounts preference endpoints on the given mux. These endpoints require the auth middleware to populate user context.

type HandlerOption

type HandlerOption func(*Handler)

HandlerOption customises the auth Handler.

func WithTokenProvider

func WithTokenProvider(tp token.Provider) HandlerOption

WithTokenProvider injects a shared token lifecycle manager.

func WithTokenStore

func WithTokenStore(ts TokenStore) HandlerOption

WithTokenStore injects an OAuth token store.

func WithUserService

func WithUserService(us UserService) HandlerOption

WithUserService injects a user service.

type JWT added in v0.1.3

type JWT struct {
	Enabled       bool     `yaml:"enabled" json:"enabled"`
	RSA           []string `yaml:"rsa,omitempty" json:"rsa,omitempty"`
	HMAC          string   `yaml:"hmac,omitempty" json:"hmac,omitempty"`
	CertURL       string   `yaml:"certURL,omitempty" json:"certURL,omitempty"`
	RSAPrivateKey string   `yaml:"rsaPrivateKey,omitempty" json:"rsaPrivateKey,omitempty"`
}

type JWTService

type JWTService struct {
	// contains filtered or unexported fields
}

JWTService wraps scy's JWT signer and verifier for the agently-core auth layer.

func NewJWTService

func NewJWTService(cfg *JWT) *JWTService

NewJWTService creates a JWT service from the given config. Call Init() before use to load keys.

func NewJWTServiceFromConfigValues added in v0.1.1

func NewJWTServiceFromConfigValues(enabled bool, rsa []string, hmac, certURL, rsaPrivateKey string) *JWTService

NewJWTServiceFromConfigValues creates a JWT service from raw config values for callers outside agently-core that cannot import the internal auth package.

func (*JWTService) Init

func (j *JWTService) Init(ctx context.Context) error

Init loads keys from scy resources. Safe to call multiple times.

func (*JWTService) PublicKeys

func (j *JWTService) PublicKeys() (map[string]*rsa.PublicKey, error)

PublicKeys returns the loaded RSA public keys (for diagnostics/testing).

func (*JWTService) Sign

func (j *JWTService) Sign(ttl time.Duration, claims interface{}) (string, error)

Sign creates a signed JWT token with the given claims and TTL. Returns an error if no signer is configured.

func (*JWTService) Verify

func (j *JWTService) Verify(ctx context.Context, tokenString string) (*UserInfo, error)

Verify validates a JWT token string and returns the parsed claims. Returns an error if the token is invalid, expired, or signature verification fails.

type Local added in v0.1.3

type Local struct {
	Enabled bool `yaml:"enabled" json:"enabled"`
}

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages user sessions with an in-memory cache and optional persistent store.

func NewManager

func NewManager(ttl time.Duration, store SessionStore) *Manager

NewManager creates a session manager with the given TTL. If store is nil, sessions are stored only in memory.

func (*Manager) ActiveSessions

func (m *Manager) ActiveSessions() []*Session

ActiveSessions returns a snapshot of all non-expired sessions in memory.

func (*Manager) Delete

func (m *Manager) Delete(ctx context.Context, id string)

Delete removes a session.

func (*Manager) Get

func (m *Manager) Get(ctx context.Context, id string) *Session

Get retrieves a session by ID. Returns nil if not found or expired.

func (*Manager) Put

func (m *Manager) Put(ctx context.Context, s *Session)

Put stores a session.

func (*Manager) PutAsync added in v0.1.8

func (m *Manager) PutAsync(ctx context.Context, s *Session)

PutAsync stores the session in memory immediately and persists it to the backing store out-of-band. Use this on latency-sensitive HTTP auth paths where the request should not block on durable session persistence.

type OAuth added in v0.1.3

type OAuth struct {
	Mode          string       `yaml:"mode" json:"mode"`
	Name          string       `yaml:"name" json:"name"`
	Label         string       `yaml:"label" json:"label"`
	UsePopupLogin bool         `yaml:"usePopupLogin,omitempty" json:"usePopupLogin,omitempty"`
	Client        *OAuthClient `yaml:"client" json:"client"`
}

type OAuthClient added in v0.1.3

type OAuthClient struct {
	ConfigURL    string   `yaml:"configURL" json:"configURL"`
	DiscoveryURL string   `yaml:"discoveryURL" json:"discoveryURL"`
	JWKSURL      string   `yaml:"jwksURL" json:"jwksURL"`
	RedirectURI  string   `yaml:"redirectURI" json:"redirectURI"`
	ClientID     string   `yaml:"clientID" json:"clientID"`
	Scopes       []string `yaml:"scopes" json:"scopes"`
	Issuer       string   `yaml:"issuer" json:"issuer"`
	Audiences    []string `yaml:"audiences" json:"audiences"`
}

type OAuthToken

type OAuthToken struct {
	Username     string    `json:"username"`
	Provider     string    `json:"provider"`
	AccessToken  string    `json:"accessToken"`
	IDToken      string    `json:"idToken,omitempty"`
	RefreshToken string    `json:"refreshToken,omitempty"`
	ExpiresAt    time.Time `json:"expiresAt,omitempty"`
}

OAuthToken represents a stored OAuth token set for a user/provider pair.

type PreferencesPatch

type PreferencesPatch struct {
	DisplayName        *string                           `json:"displayName,omitempty"`
	Timezone           *string                           `json:"timezone,omitempty"`
	DefaultAgentRef    *string                           `json:"defaultAgentRef,omitempty"`
	DefaultModelRef    *string                           `json:"defaultModelRef,omitempty"`
	DefaultEmbedderRef *string                           `json:"defaultEmbedderRef,omitempty"`
	AgentPrefs         map[string]map[string]interface{} `json:"agentPrefs,omitempty"`
}

PreferencesPatch describes a partial update to user preferences.

type ProtectOption

type ProtectOption func(*protectConfig)

ProtectOption customises the Protect middleware.

func WithJWTService

func WithJWTService(j *JWTService) ProtectOption

WithJWTService injects a pre-initialised JWTService for Bearer token verification.

type Runtime added in v0.1.3

type Runtime struct {
	// contains filtered or unexported fields
}

func NewRuntime added in v0.1.3

func NewRuntime(ctx context.Context, workspaceRoot string, dao *datly.Service) (*Runtime, error)

func (*Runtime) JWTService added in v0.1.3

func (r *Runtime) JWTService() *JWTService

type Session

type Session struct {
	ID       string         `json:"id"`
	UserID   string         `json:"userId,omitempty"`
	Username string         `json:"username"`
	Email    string         `json:"email,omitempty"`
	Subject  string         `json:"subject,omitempty"`
	Provider string         `json:"provider,omitempty"`
	Tokens   *scyauth.Token `json:"-"`
	// TransientRefreshRetryAt suppresses repeated refresh attempts/log spam
	// after a temporary token-endpoint failure. In-memory only.
	TransientRefreshRetryAt time.Time `json:"-"`
	CreatedAt               time.Time `json:"createdAt"`
	ExpiresAt               time.Time `json:"expiresAt"`
}

Session represents an authenticated user session.

Identity model:

  • UserID = canonical agently users.id when available
  • Subject = raw oauth/jwt subject
  • Username = jwt.preferred_username or jwt.name — display name only
  • Email = jwt.email — display / contact only

UserID is the preferred stable identity for session-owned auth, persistence, foreign-key joins, and token ownership. Subject is preserved as the raw IdP identity for provider lookups and diagnostics.

func (*Session) EffectiveUserID added in v0.1.7

func (s *Session) EffectiveUserID() string

EffectiveUserID returns jwt.sub as the stable user identity. Falls back to Subject, then Email, then Username for sessions without a canonical stored user id (e.g. local/anonymous mode).

func (*Session) IsExpired

func (s *Session) IsExpired() bool

IsExpired returns true when the session has passed its expiry time.

type SessionRecord

type SessionRecord struct {
	ID           string    `json:"id"`
	UserID       string    `json:"userId,omitempty"`
	Username     string    `json:"username"`
	Email        string    `json:"email,omitempty"`
	Subject      string    `json:"subject,omitempty"`
	Provider     string    `json:"provider,omitempty"`
	AccessToken  string    `json:"accessToken,omitempty"`
	IDToken      string    `json:"idToken,omitempty"`
	RefreshToken string    `json:"refreshToken,omitempty"`
	CreatedAt    time.Time `json:"createdAt"`
	ExpiresAt    time.Time `json:"expiresAt"`
}

SessionRecord is the persistent form of a session for external stores.

type SessionStore

type SessionStore interface {
	Get(ctx context.Context, id string) (*SessionRecord, error)
	Upsert(ctx context.Context, rec *SessionRecord) error
	Delete(ctx context.Context, id string) error
}

SessionStore is a pluggable backend for persistent session storage.

type SessionStoreDAO

type SessionStoreDAO struct {
	// contains filtered or unexported fields
}

SessionStoreDAO uses Datly to persist sessions.

func NewSessionStoreDAO

func NewSessionStoreDAO(dao *datly.Service) *SessionStoreDAO

NewSessionStoreDAO constructs a Datly-backed session store.

func (*SessionStoreDAO) Delete

func (s *SessionStoreDAO) Delete(ctx context.Context, id string) error

Delete removes a session by id.

func (*SessionStoreDAO) Get

Get loads a session by id.

func (*SessionStoreDAO) Upsert

func (s *SessionStoreDAO) Upsert(ctx context.Context, rec *SessionRecord) error

Upsert inserts or updates a session record.

type TokenStore

type TokenStore interface {
	Get(ctx context.Context, username, provider string) (*OAuthToken, error)
	Put(ctx context.Context, token *OAuthToken) error
	Delete(ctx context.Context, username, provider string) error

	// TryAcquireRefreshLease atomically attempts to acquire a distributed lease
	// for refreshing the token identified by (username, provider).
	TryAcquireRefreshLease(ctx context.Context, username, provider, owner string, ttl time.Duration) (version int64, acquired bool, err error)

	// ReleaseRefreshLease releases a previously acquired lease.
	ReleaseRefreshLease(ctx context.Context, username, provider, owner string) error

	// CASPut atomically updates the token only if the current version matches
	// expectedVersion and the lease is held by owner.
	CASPut(ctx context.Context, token *OAuthToken, expectedVersion int64, owner string) (swapped bool, err error)
}

TokenStore abstracts encrypted OAuth token persistence. Implementations may use scy-backed secrets, database storage, etc.

type TokenStoreDAO

type TokenStoreDAO struct {
	// contains filtered or unexported fields
}

TokenStoreDAO is a Datly-backed TokenStore with Blowfish encryption.

func NewTokenStoreDAO

func NewTokenStoreDAO(dao *datly.Service, salt string) *TokenStoreDAO

NewTokenStoreDAO creates a Datly-backed token store.

func (*TokenStoreDAO) CASPut

func (s *TokenStoreDAO) CASPut(ctx context.Context, token *OAuthToken, expectedVersion int64, owner string) (bool, error)

CASPut atomically updates the token only if the current version matches expectedVersion and the lease is held by owner. On success, bumps version and clears the lease. Returns (true, nil) if the swap succeeded.

func (*TokenStoreDAO) Delete

func (s *TokenStoreDAO) Delete(ctx context.Context, username, provider string) error

Delete removes a token by upserting an empty enc_token.

func (*TokenStoreDAO) Get

func (s *TokenStoreDAO) Get(ctx context.Context, username, provider string) (*OAuthToken, error)

Get loads and decrypts a token from DB.

func (*TokenStoreDAO) Put

func (s *TokenStoreDAO) Put(ctx context.Context, token *OAuthToken) error

Put encrypts and saves a token via the Datly write handler.

func (*TokenStoreDAO) ReleaseRefreshLease

func (s *TokenStoreDAO) ReleaseRefreshLease(ctx context.Context, username, provider, owner string) error

ReleaseRefreshLease releases a previously acquired lease, resetting the row to idle. The owner check ensures we only release our own lease.

func (*TokenStoreDAO) ScanExpiring added in v0.1.7

func (s *TokenStoreDAO) ScanExpiring(ctx context.Context, horizon time.Time) ([]*OAuthToken, error)

ScanExpiring returns all stored tokens expiring before horizon that carry a refresh token. Called by the background watcher to refresh tokens for idle users who have no active in-memory session.

func (*TokenStoreDAO) TryAcquireRefreshLease

func (s *TokenStoreDAO) TryAcquireRefreshLease(ctx context.Context, username, provider, owner string, ttl time.Duration) (int64, bool, error)

TryAcquireRefreshLease atomically attempts to acquire a distributed lease for refreshing the token identified by (username, provider). The lease is granted only when the row is idle or has an expired lease. All timestamp comparisons use the DB server's CURRENT_TIMESTAMP to avoid clock-skew issues.

type User

type User struct {
	ID          string                 `json:"id,omitempty"`
	Username    string                 `json:"username"`
	Email       string                 `json:"email,omitempty"`
	DisplayName string                 `json:"displayName,omitempty"`
	Provider    string                 `json:"provider,omitempty"`
	Subject     string                 `json:"subject,omitempty"`
	Preferences map[string]interface{} `json:"preferences,omitempty"`
}

User represents a registered user.

type UserInfo added in v0.1.3

type UserInfo struct {
	Subject string
	Email   string
}

UserInfo carries minimal identity extracted from auth context or JWT claims.

func RuntimeUserFromContext added in v0.1.3

func RuntimeUserFromContext(ctx context.Context) *UserInfo

type UserService

type UserService interface {
	GetByUsername(ctx context.Context, username string) (*User, error)
	GetBySubjectAndProvider(ctx context.Context, subject, provider string) (*User, error)
	Upsert(ctx context.Context, user *User) error
	UpsertWithProvider(ctx context.Context, username, displayName, email, provider, subject string) (string, error)
	UpdateHashIPByID(ctx context.Context, id, hash string) error
	UpdatePreferences(ctx context.Context, username string, patch *PreferencesPatch) error
}

UserService abstracts user CRUD. Implementations may use Datly, SQL, or an in-memory store.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL