auth

package
v0.60.1 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// SessionCookieName is the HTTP cookie name for session tokens.
	SessionCookieName = "stella_session"

	// SessionDuration is the default session lifetime.
	SessionDuration = 7 * 24 * time.Hour
)
View Source
const (
	RoleAdmin = "admin"
	RoleUser  = "user"
)

RoleAdmin and RoleUser are the built-in role IDs.

Variables

View Source
var (
	ErrNotFound           = errors.New("not found")
	ErrInvalidCredentials = errors.New("invalid credentials")
	ErrAlreadyConsumed    = errors.New("already consumed")
	ErrExpired            = errors.New("expired")
)

Sentinel errors used across auth domain.

View Source
var (
	ErrRateLimitIP           = errors.New("too many requests from this IP, try again later")
	ErrRateLimitUsername     = errors.New("too many failed attempts for this account, try again in 30 seconds")
	ErrRateLimitRegistration = errors.New("too many registration attempts for this email, try again in 30 seconds")
)

Rate limiting errors.

View Source
var ErrNoSession = errors.New("no session cookie")

ErrNoSession is returned when no session cookie is present.

Functions

func CheckPassword

func CheckPassword(hash, plain string) error

CheckPassword verifies a plaintext password against a bcrypt hash.

func ClearSessionCookie

func ClearSessionCookie(w http.ResponseWriter)

ClearSessionCookie removes the session cookie from the response. Setting Secure=true is harmless on HTTP and ensures the cookie is properly cleared on HTTPS deployments.

func DeriveHMACKey added in v0.38.0

func DeriveHMACKey(vaultKey, purpose string) ([]byte, error)

DeriveHMACKey derives a per-purpose HMAC key from the vault key. Used by StateManager in the oidc package.

func GetSessionCookie

func GetSessionCookie(r *http.Request) (string, error)

GetSessionCookie extracts the session ID from the request cookie. Returns ErrNoSession if the cookie is missing or empty.

func HMACSign added in v0.38.0

func HMACSign(key, msg []byte) []byte

HMACSign returns an HMAC-SHA256 signature of msg using key.

func HMACVerify added in v0.38.0

func HMACVerify(key, msg, sig []byte) bool

HMACVerify returns true if sig is a valid HMAC-SHA256 of msg under key.

func HashPassword

func HashPassword(plain string) (string, error)

func IsLinkCode

func IsLinkCode(s string) bool

IsLinkCode returns true if the string looks like a valid link code format (6 alphanumeric characters). This is a quick check before attempting Consume.

func NewSessionID

func NewSessionID() string

NewSessionID generates a cryptographically random hex-encoded session ID (32 bytes = 64 hex characters).

func SetBcryptCostForTesting added in v0.25.4

func SetBcryptCostForTesting(cost int) func()

SetBcryptCostForTesting overrides the bcrypt work factor and returns a reset func.

func SetSessionCookie

func SetSessionCookie(w http.ResponseWriter, sessionID string, secure bool)

SetSessionCookie writes the session cookie to the response. The Secure flag is set when secure is true (i.e., not localhost/dev).

Types

type Action

type Action string

Action is a string alias for authorization actions.

type AuthProvider added in v0.38.0

type AuthProvider interface {
	// Name returns the stable provider identifier used in route paths and DB records.
	Name() string

	// LoginURL generates the IdP redirect URL for the given state.
	LoginURL(ctx context.Context, state AuthState) (string, error)

	// HandleCallback validates the callback request, exchanges the code for
	// tokens, verifies the ID token, and returns the normalised identity.
	// Implementations must verify email_verified == true and reject missing emails.
	HandleCallback(ctx context.Context, r *http.Request, state AuthState) (*ExternalIdentity, error)
}

AuthProvider is the abstraction over all login methods (OIDC, LocalProvider, GitHub OAuth, reverse proxy, etc.). Business code only depends on this interface.

type AuthService added in v0.38.0

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

AuthService composes the auth stores and owns business-level transactions such as ProcessOIDCLogin. It is the only place that coordinates cross-store writes inside a single DB transaction.

func NewAuthService added in v0.38.0

func NewAuthService(
	db *pgxpool.Pool,
	users UserStore,
	logins LoginIdentityStore,
	sessions SessionStore,
) *AuthService

NewAuthService creates an AuthService with all required stores.

func (*AuthService) CreateSessionForUser added in v0.40.0

func (s *AuthService) CreateSessionForUser(ctx context.Context, userID string, sessionMgr *SessionManager) (OIDCLoginResult, error)

CreateSessionForUser creates a login session for a user that has already been authenticated by Stella itself, such as local password login.

func (*AuthService) Logout added in v0.38.0

func (s *AuthService) Logout(ctx context.Context, rawToken string) error

Logout deletes the session identified by rawToken.

func (*AuthService) PrincipalFromToken added in v0.38.0

func (s *AuthService) PrincipalFromToken(ctx context.Context, rawToken string) (*Principal, error)

PrincipalFromToken resolves a Principal from a raw session token.

func (*AuthService) ProcessOIDCLogin added in v0.38.0

func (s *AuthService) ProcessOIDCLogin(ctx context.Context, ext ExternalIdentity, sessionMgr *SessionManager) (OIDCLoginResult, error)

ProcessOIDCLogin is the single transaction entry point for external identity callbacks. It resolves or creates the user and login identity, then creates a new session.

func (*AuthService) UpdateUserAgeKeys added in v0.38.0

func (s *AuthService) UpdateUserAgeKeys(ctx context.Context, userID, publicKey, privateKey string) error

UpdateUserAgeKeys stores vault age keys for userID in the OIDC user table.

type AuthState added in v0.38.0

type AuthState struct {
	State        string
	CodeVerifier string
	ProviderName string
}

AuthState carries the PKCE code verifier and CSRF state string between the login redirect and the callback. It is embedded in a signed cookie.

type AuthStore

type AuthStore interface {
	// User-Agent assignments
	AssignAgent(ctx context.Context, userID string, agentID string) error
	RemoveAgent(ctx context.Context, userID string, agentID string) error
	ListUserAgentIDs(ctx context.Context, userID string) ([]string, error)
	ListAgentUserIDs(ctx context.Context, agentID string) ([]string, error)

	// API tokens (auth_user_token)
	CreateUserToken(ctx context.Context, token UserToken) (UserToken, error)
	GetUserTokenByHash(ctx context.Context, tokenHash string) (UserToken, error)
	GetActiveUserTokenByHash(ctx context.Context, tokenHash string) (UserToken, error)
	GetActiveAutoUserToken(ctx context.Context, userID string) (UserToken, error)
	RotateUserToken(ctx context.Context, id string) (int64, error)
	RevokeUserToken(ctx context.Context, id string) (int64, error)
	UpdateUserTokenLastUsed(ctx context.Context, id string) (int64, error)
}

AuthStore provides access to user-agent assignments and API tokens.

type AuthStores added in v0.38.0

type AuthStores struct {
	Users       UserStore
	Logins      LoginIdentityStore
	Sessions    SessionStore
	Credentials CredentialStore
}

AuthStores groups the stores needed for a single transactional login flow.

type ChannelIdentity added in v0.38.0

type ChannelIdentity struct {
	ID         string    `json:"id"`
	UserID     string    `json:"user_id"`
	Platform   string    `json:"platform"`
	ExternalID string    `json:"external_id"`
	Name       string    `json:"name"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

ChannelIdentity is a messaging-platform identity stored in channel_identity. It replaces the old Identity type for channel-linked accounts (Telegram, Slack, etc.).

type ChannelIdentityStore added in v0.38.0

type ChannelIdentityStore interface {
	CreateChannelIdentity(ctx context.Context, i ChannelIdentity) (ChannelIdentity, error)
	GetChannelIdentity(ctx context.Context, id string) (ChannelIdentity, error)
	GetChannelIdentityByPlatform(ctx context.Context, platform, externalID string) (ChannelIdentity, error)
	ListChannelIdentitiesByUser(ctx context.Context, userID string) ([]ChannelIdentity, error)
	UpdateChannelIdentityExternalID(ctx context.Context, id, externalID string) error
	DeleteChannelIdentity(ctx context.Context, id string) error
}

ChannelIdentityStore provides CRUD for channel_identity (messaging platform identities).

type Credential added in v0.38.0

type Credential struct {
	ID           string    `json:"id"`
	UserID       string    `json:"user_id"`
	PasswordHash string    `json:"-"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

Credential stores the bcrypt password hash for local password login. It lives in auth_credential and is keyed by user_id (one per user).

type CredentialService added in v0.38.0

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

CredentialService manages local password credentials stored in auth_credential. These credentials are used by Stella's local password login flow.

func NewCredentialService added in v0.38.0

func NewCredentialService(store CredentialStore) *CredentialService

NewCredentialService creates a CredentialService backed by the given store.

func (*CredentialService) HasCredential added in v0.38.0

func (s *CredentialService) HasCredential(ctx context.Context, userID string) (bool, error)

HasCredential reports whether a credential row exists for userID.

func (*CredentialService) SetPassword added in v0.38.0

func (s *CredentialService) SetPassword(ctx context.Context, userID, plainPassword string) error

SetPassword creates or replaces the local password for a user.

func (*CredentialService) VerifyPassword added in v0.38.0

func (s *CredentialService) VerifyPassword(ctx context.Context, userID, plainPassword string) error

VerifyPassword checks plainPassword against the stored hash for userID. Returns ErrNotFound if no credential exists, ErrInvalidCredentials on mismatch.

type CredentialStore added in v0.38.0

type CredentialStore interface {
	CreateCredential(ctx context.Context, c Credential) (Credential, error)
	GetCredentialByUserID(ctx context.Context, userID string) (Credential, error)
	UpdateCredentialHash(ctx context.Context, userID, passwordHash string) error
	DeleteCredential(ctx context.Context, userID string) error
}

CredentialStore provides CRUD for auth_credential (local password hashes).

type ExternalIdentity added in v0.38.0

type ExternalIdentity struct {
	Provider   string
	Subject    string
	Email      string
	Name       string
	AvatarURL  string
	Claims     map[string]any
	OAuthToken *OAuthToken // set by OAuth providers; nil for OIDC/local
}

ExternalIdentity is the normalised identity returned by an AuthProvider after a successful callback. It contains all information needed to upsert a User, LoginIdentity, and Membership in ProcessOIDCLogin.

type LinkCodeStore

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

LinkCodeStore manages single-use link codes for channel account linking. Codes expire after 5 minutes. The default constructor keeps them in-memory; the shared constructor persists them to the Stella DB for cross-process use.

func NewLinkCodeStore

func NewLinkCodeStore() *LinkCodeStore

NewLinkCodeStore creates a new link code store.

func NewSharedLinkCodeStore

func NewSharedLinkCodeStore(ctx context.Context, db *pgxpool.Pool) (*LinkCodeStore, error)

NewSharedLinkCodeStore creates a link code store backed by the shared Stella DB so admin and channel subprocesses can exchange codes across processes.

func (*LinkCodeStore) Consume

func (s *LinkCodeStore) Consume(code string) (string, string, bool)

Consume looks up a link code and returns the associated user ID and platform if valid. The code is consumed (deleted) on success. Returns ("", "", false) if the code is invalid or expired.

func (*LinkCodeStore) Generate

func (s *LinkCodeStore) Generate(userID string, platform string) string

Generate creates a new 6-character alphanumeric link code for the given user and platform. Returns the code string.

type LoginIdentity added in v0.38.0

type LoginIdentity struct {
	ID              string         `json:"id"`
	UserID          string         `json:"user_id"`
	Provider        string         `json:"provider"`
	ProviderSubject string         `json:"provider_subject"`
	Email           string         `json:"email"`
	Name            string         `json:"name"`
	AvatarURL       string         `json:"avatar_url"`
	RawClaims       map[string]any `json:"raw_claims,omitempty"`
	CreatedAt       time.Time      `json:"created_at"`
	UpdatedAt       time.Time      `json:"updated_at"`
}

LoginIdentity is an OIDC login identity stored in auth_identity. It is named LoginIdentity (not Identity) to avoid a conflict with the pre-existing channel Identity type during the migration period.

type LoginIdentityStore added in v0.38.0

type LoginIdentityStore interface {
	CreateLoginIdentity(ctx context.Context, i LoginIdentity) (LoginIdentity, error)
	GetLoginIdentityByProvider(ctx context.Context, provider, providerSubject string) (LoginIdentity, error)
	ListLoginIdentitiesByUser(ctx context.Context, userID string) ([]LoginIdentity, error)
	UpdateLoginIdentity(ctx context.Context, i LoginIdentity) error
}

LoginIdentityStore provides CRUD for auth_identity (OIDC login identities).

type OAuthToken added in v0.43.0

type OAuthToken struct {
	AccessToken           string
	RefreshToken          string
	ExpiresIn             int // seconds until access token expires
	RefreshTokenExpiresIn int // seconds until refresh token expires
}

OAuthToken carries the raw token from an OAuth login callback so the server can reuse it for tool access when login and tool providers share the same app.

type OIDCLoginResult added in v0.38.0

type OIDCLoginResult struct {
	User         User
	Session      Session
	IsNewUser    bool
	SessionToken string // raw token for the session cookie (not stored in DB)
}

OIDCLoginResult holds everything the HTTP handler needs after a successful login callback.

type PermissionChecker added in v0.38.0

type PermissionChecker interface {
	Can(ctx context.Context, principal Principal, action Action, resource Resource) bool
}

PermissionChecker decides whether a principal may perform an action on a resource. Implementations should be stateless and fast — they are called on every request.

type Principal added in v0.38.0

type Principal struct {
	UserID    string `json:"user_id"`
	Email     string `json:"email"`
	Name      string `json:"name"`
	AvatarURL string `json:"avatar_url"`
	Role      string `json:"role"` // user role: admin or user
}

Principal is the request-scoped identity injected into context by the auth middleware.

func (Principal) IsAdmin added in v0.38.0

func (p Principal) IsAdmin() bool

IsAdmin returns true if the principal holds the admin role.

type RateLimiter

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

RateLimiter provides per-IP and per-username rate limiting for login attempts.

func NewRateLimiter

func NewRateLimiter() *RateLimiter

NewRateLimiter creates a new RateLimiter.

func (*RateLimiter) CheckIP

func (rl *RateLimiter) CheckIP(ip string) error

CheckIP verifies the IP has not exceeded the request limit. Does not increment the counter — call RecordIPAttempt after a failed attempt.

func (*RateLimiter) CheckRegistration added in v0.40.0

func (rl *RateLimiter) CheckRegistration(email string) error

CheckRegistration verifies the email is not in a registration cooldown period.

func (*RateLimiter) CheckUsername

func (rl *RateLimiter) CheckUsername(username string) error

CheckUsername verifies the username is not in a cooldown period.

func (*RateLimiter) RecordIPAttempt

func (rl *RateLimiter) RecordIPAttempt(ip string)

RecordIPAttempt records a failed attempt for rate limiting by IP.

func (*RateLimiter) RecordLoginFailure

func (rl *RateLimiter) RecordLoginFailure(username string)

RecordLoginFailure records a failed login attempt for a username.

func (*RateLimiter) RecordLoginSuccess

func (rl *RateLimiter) RecordLoginSuccess(username string)

RecordLoginSuccess resets the failure counter for a username.

func (*RateLimiter) RecordRegistrationFailure added in v0.40.0

func (rl *RateLimiter) RecordRegistrationFailure(email string)

RecordRegistrationFailure records a failed registration attempt for an email.

func (*RateLimiter) RecordRegistrationSuccess added in v0.40.0

func (rl *RateLimiter) RecordRegistrationSuccess(email string)

RecordRegistrationSuccess resets the registration failure counter for an email.

type Resource

type Resource struct {
	Type    ResourceType   `json:"type"`
	ID      string         `json:"id"`
	OwnerID string         `json:"owner_id"`
	Attrs   map[string]any `json:"attrs,omitempty"`
}

Resource represents the target of an authorization request.

type ResourceType

type ResourceType string

ResourceType is a string alias for resource types.

type RoleBasedChecker added in v0.38.0

type RoleBasedChecker struct{}

RoleBasedChecker is the single-tenant implementation: admin can do anything, regular users can only act on their own resources.

func (*RoleBasedChecker) Can added in v0.38.0

func (c *RoleBasedChecker) Can(ctx context.Context, p Principal, action Action, resource Resource) bool

Can returns true when the principal is allowed to perform action on resource.

type Session

type Session struct {
	ID        string    `json:"id"`
	UserID    string    `json:"user_id"`
	TokenHash string    `json:"-"`
	ExpiresAt time.Time `json:"expires_at"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Session represents an HTTP session. TokenHash is the SHA-256 hash of the raw token stored in the session cookie. Old sessions (auth_sessions table) leave TokenHash empty; new sessions (auth_session table) always have it set.

type SessionManager added in v0.38.0

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

SessionManager creates and validates login sessions backed by SessionStore. Raw tokens are stored in cookies; only SHA-256 hashes are stored in the DB.

func NewSessionManager added in v0.38.0

func NewSessionManager(store SessionStore, vaultKey string) (*SessionManager, error)

NewSessionManager creates a SessionManager. vaultKey is the raw STELLA_VAULT_KEY value; a per-purpose key is derived via HKDF-SHA256.

func (*SessionManager) ClearCookie added in v0.38.0

func (m *SessionManager) ClearCookie(w http.ResponseWriter)

ClearCookie removes the session cookie from the response.

func (*SessionManager) Create added in v0.38.0

func (m *SessionManager) Create(ctx context.Context, userID string) (rawToken string, session Session, err error)

Create generates a new session for userID. Returns the raw token to set as a cookie value; only the SHA-256 hash is stored in the DB.

func (*SessionManager) Extend added in v0.38.0

func (m *SessionManager) Extend(ctx context.Context, sessionID string) error

Extend updates the session expiry to now + SessionDuration.

func (*SessionManager) GetToken added in v0.38.0

func (m *SessionManager) GetToken(r *http.Request) (string, error)

GetToken extracts the raw token from the request cookie.

func (*SessionManager) Revoke added in v0.38.0

func (m *SessionManager) Revoke(ctx context.Context, sessionID string) error

Revoke deletes the session from the DB.

func (*SessionManager) SetCookie added in v0.38.0

func (m *SessionManager) SetCookie(w http.ResponseWriter, rawToken string, secure bool)

SetCookie writes the session token cookie to the response.

func (*SessionManager) Validate added in v0.38.0

func (m *SessionManager) Validate(ctx context.Context, rawToken string) (Session, error)

Validate looks up the session by the SHA-256 hash of rawToken and returns it. Returns an error if the session is not found or has expired.

func (*SessionManager) WithStore added in v0.38.0

func (m *SessionManager) WithStore(store SessionStore) *SessionManager

WithStore returns a shallow copy of the SessionManager backed by a different SessionStore. Used to run session creation inside a DB transaction.

type SessionStore added in v0.38.0

type SessionStore interface {
	CreateSession(ctx context.Context, s Session) (Session, error)
	GetSessionByTokenHash(ctx context.Context, tokenHash string) (Session, error)
	DeleteSession(ctx context.Context, id string) error
	DeleteExpiredSessions(ctx context.Context) error
	DeleteUserSessions(ctx context.Context, userID string) error
	UpdateSessionExpiry(ctx context.Context, id string, expiresAt time.Time) error
	GetSession(ctx context.Context, id string) (Session, error)
	ListSessionsByUser(ctx context.Context, userID string) ([]Session, error)
}

SessionStore provides CRUD for auth_session (token-hash-based sessions).

type Subject

type Subject struct {
	UserID   string         `json:"user_id"`
	Roles    []string       `json:"roles"`
	AgentIDs []string       `json:"agent_ids"`
	Attrs    map[string]any `json:"attrs,omitempty"`
}

Subject represents the entity requesting access.

func (Subject) Authority added in v0.60.0

func (s Subject) Authority() (authz.Authority, error)

Migration adapter: auth.Subject -> authz.Authority.

This adapter lives in the auth package (the trusted producer of a session-authenticated subject) rather than in authz, so the pure authz core does not import auth. It is single-direction: there is no reverse authz.Authority -> Subject path.

A Subject is a cookie/OIDC-session user, so it maps to a UserActor. Its role strings are validated against the known catalog and collapse to a single admin bool; an unknown role fails closed rather than being silently dropped, and no role (the default) means an ordinary user. The Subject's AgentIDs are assigned-agent policy attributes, not identity — they are resolved at the enforcement point, so this identity-only adapter deliberately drops them.

func (Subject) ChannelAuthority added in v0.60.0

func (s Subject) ChannelAuthority(channelID string) (authz.Authority, error)

ChannelAuthority mints a UserActor for a dedicated channel turn. channelID is read from the persisted channel configuration by the channel adapter; it is never request payload identity. The exact binding is consumed only by the Agent PEP's dedicated-channel decision.

type Transactioner added in v0.38.0

type Transactioner interface {
	BeginAuthTx(ctx context.Context) (stores AuthStores, commit func() error, rollback func(), err error)
}

Transactioner is an optional interface that store implementations may satisfy to run ProcessOIDCLogin atomically. OIDCStore implements this.

type User added in v0.38.0

type User struct {
	ID               string    `json:"id"`
	Email            string    `json:"email"`
	Name             string    `json:"name"`
	AvatarURL        string    `json:"avatar_url"`
	Role             string    `json:"role"`
	DefaultAgentID   string    `json:"default_agent_id,omitempty"`
	NotifyIdentityID *string   `json:"notify_identity_id,omitempty"`
	IsActive         bool      `json:"is_active"`
	AgePublicKey     string    `json:"-"`
	AgePrivateKey    string    `json:"-"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
}

User is the OIDC-based user record stored in auth_user.

type UserStore added in v0.38.0

type UserStore interface {
	CreateUser(ctx context.Context, u User) (User, error)
	GetUser(ctx context.Context, id string) (User, error)
	GetUserByEmail(ctx context.Context, email string) (User, error)
	ListUsers(ctx context.Context) ([]User, error)
	ListUsersPaged(ctx context.Context, limit, offset int64) ([]User, error)
	UpdateUser(ctx context.Context, u User) error
	DeleteUser(ctx context.Context, id string) error
	CountUsers(ctx context.Context) (int64, error)
	UpdateUserAgeKeys(ctx context.Context, userID, publicKey, privateKey string) error
	UpdateUserDefaultAgent(ctx context.Context, userID, agentID string) error
	UpdateUserNotifyIdentity(ctx context.Context, userID string, identityID *string) error
	UpdateUserRole(ctx context.Context, userID string, role string) error
	UpdateUserActive(ctx context.Context, userID string, isActive bool) error
}

UserStore provides CRUD for auth_user.

type UserToken

type UserToken struct {
	ID            string     `json:"id"`
	UserID        string     `json:"user_id"`
	Name          string     `json:"name"`
	TokenHash     string     `json:"-"`
	TokenPrefix   string     `json:"token_prefix"`
	AutoGenerated bool       `json:"auto_generated"`
	LastUsedAt    *time.Time `json:"last_used_at,omitempty"`
	ExpiresAt     *time.Time `json:"expires_at,omitempty"`
	RotatedAt     *time.Time `json:"rotated_at,omitempty"`
	RevokedAt     *time.Time `json:"revoked_at,omitempty"`
	CreatedAt     time.Time  `json:"created_at"`
	UpdatedAt     time.Time  `json:"updated_at"`
}

UserToken represents an API token owned by a user.

Directories

Path Synopsis
Package account is the application boundary for user-account management: the admin and self use cases over users, their login/channel identities, sessions, password credential, and agent assignments.
Package account is the application boundary for user-account management: the admin and self use cases over users, their login/channel identities, sessions, password credential, and agent assignments.

Jump to

Keyboard shortcuts

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