security

package
v0.1.41 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package security provides security-related functionality for the opampcommander application.

Index

Constants

View Source
const (
	// TokenTypeAccess marks a JWT issued as an access token.
	TokenTypeAccess = "access"
	// TokenTypeRefresh marks a JWT issued as a refresh token, accepted only by the refresh endpoint.
	TokenTypeRefresh = "refresh"
)

Token type values stored in the OPAMPClaims.TokenType claim.

View Source
const (
	// StateLength defines the length of the state string to be generated for OAuth2 authentication.
	StateLength = 16 // Length of the state string to be generated for OAuth2 authentication.
)

Variables

View Source
var (
	// ErrInvalidState is returned when the state parameter is invalid.
	ErrInvalidState = errors.New("invalid state parameter")
	// ErrStateExpired is returned when the state parameter has expired.
	ErrStateExpired = errors.New("state parameter has expired")
	// ErrInvalidToken is returned when the provided token is invalid.
	ErrInvalidToken = errors.New("invalid token")
	// ErrInvalidEmail is returned when the email in the token claims is invalid.
	ErrInvalidEmail = errors.New("invalid email in token claims")
	// ErrInvalidTokenClaims is returned when the token claims are invalid.
	ErrInvalidTokenClaims = errors.New("invalid token claims")
	// ErrTokenExpired is returned when the token has expired.
	ErrTokenExpired = errors.New("token has expired")
	// ErrInvalidUsernameOrPassword is returned when the provided username or password is invalid.
	ErrInvalidUsernameOrPassword = errors.New("invalid username or password")
	// ErrBasicAuthDisabled is returned when basic-auth password operations are attempted but no
	// pepper is configured, which disables DB-backed basic-auth user creation and login.
	ErrBasicAuthDisabled = errors.New("basic auth is disabled: no password pepper configured")
	// ErrNoPrimaryEmailFound is returned when no primary email is found in the user's emails.
	ErrNoPrimaryEmailFound = errors.New("no primary verified email found")
	// ErrOAuth2ClientCreationFailed is returned when the OAuth2 client creation fails.
	ErrOAuth2ClientCreationFailed = errors.New("failed to create OAuth2 client")
)
View Source
var (
	// ErrNilContext is returned when the context is nil.
	ErrNilContext = errors.New("nil context")
	// ErrInvalidContext is returned when the context is not a valid Gin context.
	ErrInvalidContext = errors.New("invalid context")
	// ErrInvalidUserInContext is returned when the user in the context is not valid.
	ErrInvalidUserInContext = errors.New("invalid user in context")
)

Functions

func GetUserID

func GetUserID(ctx *gin.Context, userUsecase userport.UserUsecase) (uuid.UUID, error)

GetUserID is a helper that resolves the authenticated user's UUID.

func NewAuthJWTMiddleware

func NewAuthJWTMiddleware(
	service *Service,
) gin.HandlerFunc

NewAuthJWTMiddleware creates a new Gin middleware for JWT authentication.

func NewAuthorizationMiddleware

func NewAuthorizationMiddleware(
	rbacUsecase userport.RBACUsecase,
	userUsecase userport.UserUsecase,
	adminEmail string,
	logger *slog.Logger,
) gin.HandlerFunc

NewAuthorizationMiddleware creates a Gin middleware that enforces RBAC for both namespace-scoped (/api/v1/namespaces/:namespace/*) and global (/api/v1/users, /api/v1/roles, /api/v1/servers) resources. The adminEmail user bypasses all RBAC checks.

func SetUser

func SetUser(ctx *gin.Context, user *User)

SetUser injects a User into the Gin context. Intended for use in tests and middleware.

Types

type AdminSettings

type AdminSettings struct {
	// Username is the username for the admin user.
	Username string
	// Password is the password for the admin user.
	Password string
	// Email is the email address for the admin user.
	// This is used in jwt claims.
	Email string
}

AdminSettings holds the configuration settings for admin authentication. This is used for basic authentication of the admin user.

type AdminUsecase

type AdminUsecase interface {
	// BasicAuth authenticates the user using basic authentication with username and password.
	BasicAuth(ctx context.Context, username, password string) (LoginResult, error)
}

AdminUsecase defines the use case for admin authentication.

type BasicAuthSettings

type BasicAuthSettings struct {
	// Pepper is a server-side secret mixed into every basic-auth password hash.
	// It must be a long, random value set once and kept stable for the lifetime of the
	// stored credentials. When empty, basic-auth user creation and password login are disabled.
	Pepper string
}

BasicAuthSettings holds the configuration settings for DB-backed basic-auth users.

type Config

type Config struct {
	// JWTSettings holds the configuration settings for JSON Web Tokens (JWT).
	JWTSettings JWTSettings
	// AdminSettings holds the configuration settings for admin authentication.
	AdminSettings AdminSettings
	// BasicAuthSettings holds the configuration settings for DB-backed basic-auth users.
	BasicAuthSettings BasicAuthSettings
	// OAuthSettings holds the configuration settings for OAuth2 authentication.
	OAuthSettings *OAuthSettings
}

Config holds the authentication configuration consumed by the security Service. It is owned by the security package so that callers do not depend on the aggregate config package; the config package composes this into its settings.

type JWTSettings

type JWTSettings struct {
	SigningKey string
	Issuer     string
	Expiration time.Duration
	// RefreshExpiration is the lifetime of refresh tokens issued alongside access tokens.
	// Zero means refresh tokens are disabled.
	RefreshExpiration time.Duration
	Audience          []string
}

JWTSettings holds the configuration settings for JSON Web Tokens (JWT).

type LoginResult

type LoginResult struct {
	Token        string
	RefreshToken string
	ExpiresAt    time.Time
	Email        string
	Groups       []string // provider group/org memberships, added as labels to the user on login
}

LoginResult contains the result of a successful authentication.

type OAuth2Usecase

type OAuth2Usecase interface {
	// AuthCodeURL generates the OAuth2 authorization URL with a unique state parameter.
	// cliRedirect is an optional loopback redirect URI (e.g. http://127.0.0.1:PORT/callback)
	// to be encoded into the state JWT; on callback the server redirects to it instead of
	// returning JSON. Empty string preserves the legacy JSON behavior.
	AuthCodeURL(cliRedirect string) (string, error)
	// CLIRedirectFromState extracts the loopback redirect URI from a state JWT, if any.
	CLIRedirectFromState(state string) (string, error)
	// Exchange exchanges the OAuth2 authorization code for an access token.
	Exchange(ctx context.Context, state, code string) (LoginResult, error)
}

OAuth2Usecase defines the use case for OAuth2 authentication.

type OAuthSettings

type OAuthSettings struct {
	// ClientID is the OAuth2 client ID for GitHub authentication.
	ClientID string
	// Secret is the OAuth2 client secret for GitHub authentication.
	Secret string
	// CallbackURL is the URL to which GitHub will redirect after authentication.
	CallbackURL string
	// AllowedRedirectHosts is the list of additional hostnames that the
	// /api/v1/auth/github/authcode endpoint will accept as redirect targets,
	// in addition to the always-allowed loopback hosts (127.0.0.1, ::1,
	// localhost). Use this to allowlist the web UI deployment (e.g.
	// "opampcommander-alpha.minuk.dev"). Empty by default.
	AllowedRedirectHosts []string
	// JWTSettings holds the JWT configuration settings.
	// This is used for the state parameter in OAuth2 authentication.
	JWTSettings JWTSettings
}

OAuthSettings holds the configuration settings for GitHub OAuth2 authentication.

type OAuthStateClaims

type OAuthStateClaims struct {
	jwt.RegisteredClaims

	// CLIRedirect, when non-empty, signals the callback handler to redirect the browser
	// to this loopback URI (with token query params) instead of returning JSON.
	CLIRedirect string `json:"cliRedirect,omitempty"`
}

OAuthStateClaims defines the custom claims for the JWT token used for the state parameter in OAuth2 authentication.

type OPAMPClaims

type OPAMPClaims struct {
	jwt.RegisteredClaims

	Email string `json:"email"`
	// TokenType is "access" or "refresh". Empty value is treated as "access" for backward compatibility.
	TokenType string `json:"tokenType,omitempty"`
}

OPAMPClaims defines the custom claims for the JWT token used for opampcommander's authentication. It includes the user's email and standard JWT registered claims.

type PasswordHasher

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

PasswordHasher hashes and verifies basic-auth passwords.

Each password is first combined with a server-side pepper via HMAC-SHA256 and then hashed with bcrypt (which adds a per-user random salt). The pepper is held in a memguard Enclave so the secret is kept in locked, encrypted memory and only decrypted transiently during an operation. When no pepper is configured the hasher is disabled and every operation returns ErrBasicAuthDisabled.

func NewPasswordHasher

func NewPasswordHasher(cfg *Config) *PasswordHasher

NewPasswordHasher builds a PasswordHasher from the security config. When the configured pepper is empty the returned hasher is disabled: Hash and Verify return ErrBasicAuthDisabled. The plaintext pepper is sealed into a memguard Enclave and the caller-visible config string is the only remaining plaintext copy.

func (*PasswordHasher) Enabled

func (h *PasswordHasher) Enabled() bool

Enabled reports whether basic-auth password operations are available (a pepper is configured).

func (*PasswordHasher) Hash

func (h *PasswordHasher) Hash(password string) (string, error)

Hash returns a one-way hash of the password, peppered and salted, suitable for persistence. Returns ErrBasicAuthDisabled when no pepper is configured.

func (*PasswordHasher) Verify

func (h *PasswordHasher) Verify(password, hash string) error

Verify checks a plaintext password against a stored hash. It returns nil on a match, ErrBasicAuthDisabled when no pepper is configured, and a non-nil error otherwise (including a wrong password).

type Service

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

Service provides security-related functionality for the opampcommander application.

func New

func New(
	logger *slog.Logger,
	settings *Config,
	httpClient *http.Client,
	passwordHasher *PasswordHasher,
	userPort userport.UserPersistencePort,
) *Service

New creates a new instance of the Service struct with the provided logger and OAuth settings.

func (*Service) AllowedRedirectHosts

func (s *Service) AllowedRedirectHosts() []string

AllowedRedirectHosts returns the configured extra hosts that the authcode endpoint will accept as redirect targets (in addition to loopback hosts).

func (*Service) AuthCodeURL

func (s *Service) AuthCodeURL(cliRedirect string) (string, error)

AuthCodeURL generates the OAuth2 authorization URL with a unique state parameter. If cliRedirect is non-empty, it is encoded into the state JWT and used by the callback handler to redirect to a loopback URL instead of returning JSON.

func (*Service) BasicAuth

func (s *Service) BasicAuth(ctx context.Context, username, password string) (LoginResult, error)

BasicAuth authenticates the user using basic authentication with username and password. It first checks the config-provided admin credentials, then falls back to DB-backed basic-auth users (verified against their stored password hash with the configured pepper).

func (*Service) CLIRedirectFromState

func (s *Service) CLIRedirectFromState(state string) (string, error)

CLIRedirectFromState parses the state JWT and returns the embedded CLI loopback redirect, if any. Returns an empty string when the state has no CLI redirect.

func (*Service) DeviceAuth

func (s *Service) DeviceAuth(ctx context.Context) (*oauth2.DeviceAuthResponse, error)

DeviceAuth initiates the OAuth2 device authorization flow. It returns a device authorization response that contains the user code and verification URL.

func (*Service) Exchange

func (s *Service) Exchange(ctx context.Context, state, code string) (LoginResult, error)

Exchange exchanges the OAuth2 authorization code for an access token. It validates the state parameter to prevent CSRF attacks.

func (*Service) ExchangeDeviceAuth

func (s *Service) ExchangeDeviceAuth(
	ctx context.Context,
	deviceCode string,
	expiry time.Time,
) (LoginResult, error)

ExchangeDeviceAuth exchanges the device code for an access token. It retrieves the user's primary email from GitHub and creates a JWT token with the email as a claim. It returns the JWT token string or an error if the process fails.

func (*Service) Refresh

func (s *Service) Refresh(refreshToken string) (LoginResult, error)

Refresh validates a refresh token and issues a new access/refresh token pair for the same user. It rotates the refresh token (the old one remains valid until its own expiry — JWTs are stateless).

func (*Service) ValidateToken

func (s *Service) ValidateToken(tokenString string) (*OPAMPClaims, error)

ValidateToken validates the provided JWT token string and returns the claims if valid. It checks the token's validity, expiration, and rejects refresh tokens.

type UnsupportedTokenTypeError

type UnsupportedTokenTypeError struct {
	TokenType string
}

UnsupportedTokenTypeError is returned when the token type is not supported.

func (*UnsupportedTokenTypeError) Error

func (e *UnsupportedTokenTypeError) Error() string

type Usecase

type Usecase interface {
	// ValidateToken validates the provided JWT token string and returns the claims if valid.
	ValidateToken(tokenString string) (*OPAMPClaims, error)
	// Refresh exchanges a valid refresh token for a new access token (and rotated refresh token).
	Refresh(refreshToken string) (LoginResult, error)

	// AdminUsecase returns the use case for admin authentication.
	AdminUsecase
	// OAuth2Usecase returns the use case for OAuth2 authentication.
	OAuth2Usecase
}

Usecase defines the use case for the security package.

type User

type User struct {
	// Authenticated indicates if the user is authenticated
	Authenticated bool
	// Email is the primary email of the user
	// If the user is not authenticated, this will be nil
	Email *string
}

User represents a user in the system.

func GetUser

func GetUser(ctx context.Context) (*User, error)

GetUser retrieves the user from the Gin context.

func NewAnonymousUser

func NewAnonymousUser() *User

NewAnonymousUser creates a new anonymous user. Some operations needs an user (e.g., for audit logging) even if the user is not authenticated.

func (*User) String

func (user *User) String() string

Jump to

Keyboard shortcuts

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