config

package
v1.28.0 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

README

Configuration Package

A modern, senior-level configuration package using Viper with best practices.

Features

No Global State - Uses instance-based configuration instead of global variables
Environment Variable Support - Automatic environment variable binding with PW_ prefix
Type-Safe - Strongly typed configuration structs with validation
Multiple Sources - Supports config files, environment variables, and defaults
Auto-Creation - Automatically creates config file with secure defaults if missing
Validation - Built-in configuration validation
Testable - Fully testable without globals or side effects
Backwards Compatible - Supports legacy environment variable names

Usage

Basic Usage
import "github.com/passwall/passwall-server/internal/config"

// Load configuration with defaults
cfg, err := config.Load()
if err != nil {
    log.Fatal(err)
}

// Access configuration
fmt.Println(cfg.Server.Port)
fmt.Println(cfg.Database.Host)
Custom Config File
cfg, err := config.Load(config.LoaderOptions{
    ConfigFile: "/path/to/config.yml",
    EnvPrefix:  "PW",
})
Using Environment Variables

The package supports automatic environment variable binding:

# Server configuration
export PW_SERVER_PORT=8080
export PW_SERVER_ENV=production
export PW_SERVER_PASSPHRASE=your-secure-passphrase
export PW_SERVER_SECRET=your-jwt-secret

# Database configuration
export PW_DB_HOST=localhost
export PW_DB_PORT=5432
export PW_DB_NAME=passwall
export PW_DB_USERNAME=postgres
export PW_DB_PASSWORD=secure-password

# Email configuration
export PW_EMAIL_HOST=smtp.example.com
export PW_EMAIL_PORT=587
export PW_EMAIL_USERNAME=noreply@example.com
Backwards Compatible Variables

For backwards compatibility, these environment variables are also supported:

  • PORTserver.port
  • DOMAINserver.domain
  • POSTGRES_DBdatabase.name
  • POSTGRES_USERdatabase.username
  • POSTGRES_PASSWORDdatabase.password
  • POSTGRES_HOSTdatabase.host
  • POSTGRES_PORTdatabase.port

Configuration Structure

Server Configuration
server:
  env: prod # Environment: dev, prod
  host: 0.0.0.0 # Server host
  port: 3625 # Server port
  domain: https://vault.passwall.io # Public domain
  dir: /app/config # Config directory
  passphrase: <auto-generated-secure-key> # Encryption passphrase
  secret: <auto-generated-secure-key> # JWT secret
  timeout: 24 # Request timeout (seconds)
  generated_password_length: 16 # Default password length
  access_token_expire_duration: 30m # Access token expiry
  refresh_token_expire_duration: 15d # Refresh token expiry
  api_key: <auto-generated-secure-key> # API key
Database Configuration
database:
  name: passwall # Database name
  username: postgres # Database username
  password: password # Database password
  host: localhost # Database host
  port: 5432 # Database port
  log_mode: false # Enable SQL logging
  ssl_mode: disable # SSL mode: disable, require, verify-full, verify-ca
Email Configuration
email:
  host: smtp.passwall.io # SMTP host
  port: 25 # SMTP port
  username: hello@passwall.io # SMTP username
  password: password # SMTP password
  from_name: Passwall # From name
  from_email: hello@passwall.io # From email
  admin: hello@passwall.io # Admin email
  api_key: "" # Email service API key

Validation

The configuration is automatically validated on load. Required fields:

  • server.port - Must be set
  • server.passphrase - Must be a secure value (not placeholder)
  • server.secret - Must be a secure value (not placeholder)
  • database.host - Must be set
  • database.name - Must be set
  • database.username - Must be set

Security Features

Secure Key Generation

The package automatically generates cryptographically secure keys using crypto/rand:

  • 32-byte keys encoded as base64
  • Used for server.passphrase, server.secret, and server.api_key
  • Keys are unique per installation
Validation

Configuration is validated to ensure:

  • No placeholder values in production
  • Required fields are present
  • Values meet security requirements

Testing

The package includes comprehensive tests:

go test ./internal/config -v

Tests cover:

  • Default configuration loading
  • Environment variable override
  • Backwards compatible env vars
  • Configuration validation
  • Secure key generation
  • Config file auto-creation

Migration from Old Config

Old Way (Junior)
// Global state
cfg, err := config.Init("./config", "config")

// Accessing global viper
viper.GetString("server.port")
New Way (Senior)
// Instance-based, no globals
cfg, err := config.Load(config.LoaderOptions{
    ConfigFile: "./config/config.yml",
    EnvPrefix:  "PW",
})

// Type-safe access
cfg.Server.Port
cfg.Database.Host

Best Practices

  1. Use Environment Variables in Production - Don't commit secrets to config files
  2. Validate Configuration - The package validates on load, catch errors early
  3. Use Type-Safe Access - Access config via structs, not string keys
  4. Set Secure Values - Always override auto-generated keys in production
  5. Test Configuration - Write tests that validate your config loading

Environment Variable Precedence

Values are loaded in this order (later overrides earlier):

  1. Default values (in code)
  2. Config file values
  3. Environment variables

Example Config File

The package auto-generates a config.yml file with secure defaults:

database:
  host: localhost
  log_mode: false
  name: passwall
  password: password
  port: "5432"
  ssl_mode: disable
  username: postgres
email:
  admin: hello@passwall.io
  api_key: ""
  from_email: hello@passwall.io
  from_name: Passwall
  host: smtp.passwall.io
  password: password
  port: "25"
  username: hello@passwall.io
server:
  access_token_expire_duration: 30m
  api_key: <secure-random-key>
  dir: /app/config
  domain: https://vault.passwall.io
  env: prod
  generated_password_length: 16
  host: 0.0.0.0
  passphrase: <secure-random-key>
  port: "3625"
  refresh_token_expire_duration: 15d
  secret: <secure-random-key>
  timeout: 24

Mocking for Tests

The config package provides comprehensive mocking utilities that can be used anywhere in your project for testing.

Basic Mock Usage
import "github.com/passwall/passwall-server/internal/config"

func TestMyService(t *testing.T) {
    // Create a ready-to-use mock config
    cfg := config.NewMockConfig()

    // Use it in your service
    service := NewMyService(cfg)

    // All values are pre-set and validated
    assert.Equal(t, "test", cfg.Server.Env)
    assert.Equal(t, "passwall_test", cfg.Database.Name)
}
Using the Builder Pattern

For custom test scenarios, use the fluent builder:

func TestWithCustomConfig(t *testing.T) {
    cfg := config.NewMockBuilder().
        WithServerPort("8080").
        WithDatabaseName("my_test_db").
        WithPassphrase("my-test-passphrase").
        WithAccessTokenDuration("30m").
        Build()

    service := NewAuthService(cfg)
    // ... your tests
}
Available Builder Methods

Server Configuration:

  • WithServerPort(port string)
  • WithServerHost(host string)
  • WithServerEnv(env string)
  • WithPassphrase(passphrase string)
  • WithSecret(secret string)
  • WithAccessTokenDuration(duration string)
  • WithRefreshTokenDuration(duration string)
  • WithCustomServer(server ServerConfig)

Database Configuration:

  • WithDatabaseHost(host string)
  • WithDatabasePort(port string)
  • WithDatabaseName(name string)
  • WithDatabaseUser(username string)
  • WithDatabasePassword(password string)
  • WithDatabaseSSLMode(sslMode string)
  • WithCustomDatabase(database DatabaseConfig)

Email Configuration:

  • WithEmailHost(host string)
  • WithEmailPort(port string)
  • WithEmailFrom(email, name string)
  • WithCustomEmail(email EmailConfig)
Practical Examples
Testing Different Environments
func TestEnvironmentBehavior(t *testing.T) {
    tests := []struct {
        name   string
        config *config.Config
    }{
        {
            name: "development",
            config: config.NewMockBuilder().
                WithServerEnv("dev").
                Build(),
        },
        {
            name: "production",
            config: config.NewMockBuilder().
                WithServerEnv("prod").
                WithDatabaseSSLMode("require").
                Build(),
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // Test with specific environment config
        })
    }
}
Testing Authentication Services
func TestAuthService(t *testing.T) {
    cfg := config.NewMockBuilder().
        WithSecret("test-jwt-secret").
        WithAccessTokenDuration("15m").
        WithRefreshTokenDuration("7d").
        Build()

    authService := NewAuthService(cfg)
    // Test token generation, validation, etc.
}
Testing Database Services
func TestDatabaseConnection(t *testing.T) {
    cfg := config.NewMockBuilder().
        WithDatabaseHost("localhost").
        WithDatabasePort("5432").
        WithDatabaseName("test_db").
        WithDatabaseUser("test_user").
        Build()

    db, err := ConnectDatabase(cfg)
    // Test database operations
}
Parallel Tests with Isolated Configs
func TestParallelServices(t *testing.T) {
    t.Run("service_a", func(t *testing.T) {
        t.Parallel()
        cfg := config.NewMockBuilder().
            WithServerPort("8081").
            Build()
        // Test service A with its own config
    })

    t.Run("service_b", func(t *testing.T) {
        t.Parallel()
        cfg := config.NewMockBuilder().
            WithServerPort("8082").
            Build()
        // Test service B with its own config
    })
}
Mock Config Defaults

The mock config comes with safe test defaults:

server:
  env: test
  host: localhost
  port: 3625
  passphrase: test-passphrase-for-encryption
  secret: test-secret-for-jwt-tokens
  timeout: 10
  access_token_expire_duration: 15m
  refresh_token_expire_duration: 1h

database:
  name: passwall_test
  username: test_user
  password: test_password
  host: localhost
  port: 5432
  ssl_mode: disable

email:
  host: smtp.test.local
  from_email: test@passwall.io
  from_name: Passwall Test
Using Mocks Across Packages

The mock can be imported and used from any package in your project:

// In internal/service/auth_test.go
import "github.com/passwall/passwall-server/internal/config"

func TestAuthService(t *testing.T) {
    cfg := config.NewMockConfig()
    service := NewAuthService(cfg.Server.Secret, cfg.Server.AccessTokenExpireDuration)
    // ... tests
}
// In internal/handler/http/login_test.go
import "github.com/passwall/passwall-server/internal/config"

func TestLoginHandler(t *testing.T) {
    cfg := config.NewMockBuilder().
        WithPassphrase("handler-test-passphrase").
        Build()
    handler := NewLoginHandler(cfg)
    // ... tests
}
// In internal/repository/gormrepo/user_test.go
import "github.com/passwall/passwall-server/internal/config"

func TestUserRepository(t *testing.T) {
    cfg := config.NewMockBuilder().
        WithDatabaseName("user_repo_test").
        Build()
    // Use cfg.Database for test database setup
}

Troubleshooting

Config file not found

The package automatically creates a config file if it doesn't exist. Ensure the directory is writable.

Validation errors

Check that required fields are set and secure values are not placeholders.

Environment variables not working

Ensure the PW_ prefix is used and keys use underscores (e.g., PW_SERVER_PORT).

Mock config validation failing in tests

The mock config is pre-validated and should always pass. If it fails, check that you haven't overridden required fields with empty values using the builder.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config added in v1.28.0

type Config struct {
	Server     ServerConfig     `mapstructure:"server"`
	Database   DatabaseConfig   `mapstructure:"database"`
	Email      EmailConfig      `mapstructure:"email"`
	Stripe     StripeConfig     `mapstructure:"stripe"`
	RevenueCat RevenueCatConfig `mapstructure:"revenuecat"`
}

Config holds all application configuration

func Load added in v1.28.0

func Load(opts ...LoaderOptions) (*Config, error)

Load loads configuration from file and environment variables

func NewMockConfig added in v1.28.0

func NewMockConfig() *Config

NewMockConfig creates a mock configuration for testing purposes This can be used in any test across the project without needing actual config files

Example (ServiceTest)

Example: Using mock config in a service test

// In your service tests, just import the config package and use the mock
cfg := NewMockConfig()

// Use the config in your service initialization
_ = cfg.Server.Port
_ = cfg.Database.Host
// ... your service code

func (*Config) Validate added in v1.28.0

func (c *Config) Validate() error

Validate validates the configuration

type DatabaseConfig added in v1.28.0

type DatabaseConfig struct {
	Name     string `mapstructure:"name"`
	Username string `mapstructure:"username"`
	Password string `mapstructure:"password"`
	Host     string `mapstructure:"host"`
	Port     string `mapstructure:"port"`
	LogMode  bool   `mapstructure:"log_mode"`
	SSLMode  string `mapstructure:"ssl_mode"`
}

DatabaseConfig contains database-related configuration

type EmailConfig added in v1.28.0

type EmailConfig struct {
	Host      string `mapstructure:"host"`
	Port      string `mapstructure:"port"`
	Username  string `mapstructure:"username"`
	Password  string `mapstructure:"password"`
	FromName  string `mapstructure:"from_name"`
	FromEmail string `mapstructure:"from_email"`
	Admin     string `mapstructure:"admin"`
	BCC       string `mapstructure:"bcc"` // BCC email for all outgoing emails
	// AWS SES specific fields
	AccessKey string `mapstructure:"access_key"` // AWS Access Key ID
	SecretKey string `mapstructure:"secret_key"` // AWS Secret Access Key
	Region    string `mapstructure:"region"`     // AWS Region (e.g., us-east-1)
	// Gmail API specific fields
	GmailClientID     string `mapstructure:"gmail_client_id"`     // Gmail OAuth2 Client ID
	GmailClientSecret string `mapstructure:"gmail_client_secret"` // Gmail OAuth2 Client Secret
	GmailRefreshToken string `mapstructure:"gmail_refresh_token"` // Gmail OAuth2 Refresh Token
}

EmailConfig contains email-related configuration

type LoaderOptions added in v1.28.0

type LoaderOptions struct {
	ConfigFile string
	EnvPrefix  string
}

LoaderOptions contains options for loading configuration

type MockConfigBuilder added in v1.28.0

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

MockConfigBuilder provides a fluent interface for building mock configs

func NewMockBuilder added in v1.28.0

func NewMockBuilder() *MockConfigBuilder

NewMockBuilder creates a new mock config builder

Example (CustomScenario)

Example: Using builder for specific test scenarios

// Create a config with specific values for your test
cfg := NewMockBuilder().
	WithServerPort("8080").
	WithDatabaseName("integration_test_db").
	WithPassphrase("my-test-passphrase").
	Build()

// Use in your test
_ = cfg
Example (Environments)

Example: Testing different environments

// Development config
devCfg := NewMockBuilder().
	WithServerEnv("dev").
	WithDatabaseHost("localhost").
	Build()

// Production config
prodCfg := NewMockBuilder().
	WithServerEnv("prod").
	WithDatabaseHost("prod-db.internal").
	WithDatabaseSSLMode("require").
	Build()

_ = devCfg
_ = prodCfg

func (*MockConfigBuilder) Build added in v1.28.0

func (b *MockConfigBuilder) Build() *Config

Build returns the built config

func (*MockConfigBuilder) WithAccessTokenDuration added in v1.28.0

func (b *MockConfigBuilder) WithAccessTokenDuration(duration string) *MockConfigBuilder

WithAccessTokenDuration sets access token expiration

func (*MockConfigBuilder) WithCustomDatabase added in v1.28.0

func (b *MockConfigBuilder) WithCustomDatabase(database DatabaseConfig) *MockConfigBuilder

WithCustomDatabase sets a custom database config

func (*MockConfigBuilder) WithCustomEmail added in v1.28.0

func (b *MockConfigBuilder) WithCustomEmail(email EmailConfig) *MockConfigBuilder

WithCustomEmail sets a custom email config

func (*MockConfigBuilder) WithCustomServer added in v1.28.0

func (b *MockConfigBuilder) WithCustomServer(server ServerConfig) *MockConfigBuilder

WithCustomServer sets a custom server config

func (*MockConfigBuilder) WithDatabaseHost added in v1.28.0

func (b *MockConfigBuilder) WithDatabaseHost(host string) *MockConfigBuilder

WithDatabaseHost sets the database host

func (*MockConfigBuilder) WithDatabaseName added in v1.28.0

func (b *MockConfigBuilder) WithDatabaseName(name string) *MockConfigBuilder

WithDatabaseName sets the database name

func (*MockConfigBuilder) WithDatabasePassword added in v1.28.0

func (b *MockConfigBuilder) WithDatabasePassword(password string) *MockConfigBuilder

WithDatabasePassword sets the database password

func (*MockConfigBuilder) WithDatabasePort added in v1.28.0

func (b *MockConfigBuilder) WithDatabasePort(port string) *MockConfigBuilder

WithDatabasePort sets the database port

func (*MockConfigBuilder) WithDatabaseSSLMode added in v1.28.0

func (b *MockConfigBuilder) WithDatabaseSSLMode(sslMode string) *MockConfigBuilder

WithDatabaseSSLMode sets the database SSL mode

func (*MockConfigBuilder) WithDatabaseUser added in v1.28.0

func (b *MockConfigBuilder) WithDatabaseUser(username string) *MockConfigBuilder

WithDatabaseUser sets the database username

func (*MockConfigBuilder) WithEmailFrom added in v1.28.0

func (b *MockConfigBuilder) WithEmailFrom(email, name string) *MockConfigBuilder

WithEmailFrom sets the email from address

func (*MockConfigBuilder) WithEmailHost added in v1.28.0

func (b *MockConfigBuilder) WithEmailHost(host string) *MockConfigBuilder

WithEmailHost sets the email host

func (*MockConfigBuilder) WithEmailPort added in v1.28.0

func (b *MockConfigBuilder) WithEmailPort(port string) *MockConfigBuilder

WithEmailPort sets the email port

func (*MockConfigBuilder) WithPassphrase added in v1.28.0

func (b *MockConfigBuilder) WithPassphrase(passphrase string) *MockConfigBuilder

WithPassphrase sets the server passphrase

func (*MockConfigBuilder) WithRefreshTokenDuration added in v1.28.0

func (b *MockConfigBuilder) WithRefreshTokenDuration(duration string) *MockConfigBuilder

WithRefreshTokenDuration sets refresh token expiration

func (*MockConfigBuilder) WithSecret added in v1.28.0

func (b *MockConfigBuilder) WithSecret(secret string) *MockConfigBuilder

WithSecret sets the JWT secret

func (*MockConfigBuilder) WithServerEnv added in v1.28.0

func (b *MockConfigBuilder) WithServerEnv(env string) *MockConfigBuilder

WithServerEnv sets the server environment

func (*MockConfigBuilder) WithServerHost added in v1.28.0

func (b *MockConfigBuilder) WithServerHost(host string) *MockConfigBuilder

WithServerHost sets the server host

func (*MockConfigBuilder) WithServerPort added in v1.28.0

func (b *MockConfigBuilder) WithServerPort(port string) *MockConfigBuilder

WithServerPort sets the server port

type PlanConfig added in v1.28.0

type PlanConfig struct {
	Code           string       `mapstructure:"code"`            // Plan code (e.g., "personal-monthly")
	Name           string       `mapstructure:"name"`            // Display name (e.g., "Premium")
	BillingCycle   string       `mapstructure:"billing_cycle"`   // "monthly" or "yearly"
	PriceCents     int          `mapstructure:"price_cents"`     // Price in cents
	Currency       string       `mapstructure:"currency"`        // Currency code (e.g., "USD")
	TrialDays      int          `mapstructure:"trial_days"`      // Trial period in days
	MaxUsers       *int         `mapstructure:"max_users"`       // Max users (nil = unlimited)
	MaxCollections *int         `mapstructure:"max_collections"` // Max collections (nil = unlimited)
	MaxItems       *int         `mapstructure:"max_items"`       // Max items (nil = unlimited)
	StripePriceID  string       `mapstructure:"stripe_price_id"` // Stripe Price ID
	Features       PlanFeatures `mapstructure:"features"`        // Feature flags
}

PlanConfig defines a subscription plan

type PlanFeatures added in v1.28.0

type PlanFeatures struct {
	Sharing         bool `mapstructure:"sharing"`          // Item sharing enabled
	Teams           bool `mapstructure:"teams"`            // Team management enabled
	Audit           bool `mapstructure:"audit"`            // Audit logs enabled
	SSO             bool `mapstructure:"sso"`              // Single Sign-On enabled
	APIAccess       bool `mapstructure:"api_access"`       // API access enabled
	PrioritySupport bool `mapstructure:"priority_support"` // Priority support enabled
}

PlanFeatures defines feature availability for a plan

type RevenueCatConfig added in v1.28.0

type RevenueCatConfig struct {
	WebhookSecret string                    `mapstructure:"webhook_secret"` // Webhook authorization header value
	Products      []RevenueCatProductConfig `mapstructure:"products"`       // Product ID to plan mappings
}

RevenueCatConfig contains RevenueCat in-app purchase configuration

type RevenueCatProductConfig added in v1.28.0

type RevenueCatProductConfig struct {
	ProductID    string `mapstructure:"product_id"`    // RevenueCat product ID (e.g., "passwall_pro_monthly")
	PlanCode     string `mapstructure:"plan_code"`     // Internal plan code (e.g., "pro-monthly")
	BillingCycle string `mapstructure:"billing_cycle"` // "monthly" or "yearly"
}

RevenueCatProductConfig maps a RevenueCat product ID to an internal plan

type ServerConfig added in v1.28.0

type ServerConfig struct {
	Env                        string  `mapstructure:"env"`
	Host                       string  `mapstructure:"host"`
	Port                       string  `mapstructure:"port"`
	Domain                     string  `mapstructure:"domain"`
	Passphrase                 string  `mapstructure:"passphrase"`
	Secret                     string  `mapstructure:"secret"`
	Timeout                    int     `mapstructure:"timeout"`
	GeneratedPasswordLength    int     `mapstructure:"generated_password_length"`
	AccessTokenExpireDuration  string  `mapstructure:"access_token_expire_duration"`
	RefreshTokenExpireDuration string  `mapstructure:"refresh_token_expire_duration"`
	FrontendURL                string  `mapstructure:"frontend_url"`
	RecaptchaSecretKey         string  `mapstructure:"recaptcha_secret_key"`
	RecaptchaThreshold         float64 `mapstructure:"recaptcha_threshold"`
}

ServerConfig contains server-related configuration

type StripeConfig added in v1.28.0

type StripeConfig struct {
	SecretKey      string `mapstructure:"secret_key"`      // Stripe Secret Key (sk_test_... or sk_live_...)
	WebhookSecret  string `mapstructure:"webhook_secret"`  // Webhook signing secret
	PublishableKey string `mapstructure:"publishable_key"` // Publishable key for frontend

	// Plan definitions
	Plans []PlanConfig `mapstructure:"plans"`
}

StripeConfig contains Stripe payment configuration

Jump to

Keyboard shortcuts

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