From 7f7c0362921d900108153c669331cd3fb3ba8691 Mon Sep 17 00:00:00 2001 From: Haelnorr Date: Wed, 21 Jan 2026 19:20:52 +1100 Subject: [PATCH] updated docs --- EZConf.md | 16 ++- Home.md | 6 + cookies.md | 313 +++++++++++++++++++++++++++++++++++++++++++++++++++++ env.md | 245 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 575 insertions(+), 5 deletions(-) create mode 100644 cookies.md create mode 100644 env.md diff --git a/EZConf.md b/EZConf.md index 2361941..6d534f0 100644 --- a/EZConf.md +++ b/EZConf.md @@ -1,4 +1,4 @@ -# EZConf - v0.1.0 +# EZConf - v0.1.1 A unified configuration management system for loading and managing environment-based configurations across multiple Go packages. @@ -59,8 +59,13 @@ func main() { hwsauth.NewEZConfIntegration(), ) - // Load everything - if err := loader.Load(); err != nil { + // Parse the envvars from the config structs + if err := loader.ParseEnvVars(); err != nil { + log.Fatal(err) + } + + // Load the configs + if err := loader.LoadConfigs(); err != nil { log.Fatal(err) } @@ -262,6 +267,8 @@ for name, cfg := range allConfigs { ``` ### Printing Environment Variables +Printing the environment variables requires `loader.ParseEnvVars` to have been called. +If passing `true` to show the values, `loader.LoadConfigs` must also be called ```go // Print variable names and descriptions (no values) @@ -275,6 +282,7 @@ if err := loader.PrintEnvVarsStdout(false); err != nil { // DATABASE_URL # Database connection string (required) // Print with current values +// Requires loader.LoadConfigs to be called if err := loader.PrintEnvVarsStdout(true); err != nil { log.Fatal(err) } @@ -285,8 +293,6 @@ if err := loader.PrintEnvVarsStdout(true); err != nil { // DATABASE_URL=postgres://localhost/db # Database connection string (required) ``` -**Note:** `PrintEnvVarsStdout()` and `PrintEnvVars()` will return an error if called before `Load()`. Make sure to call `Load()` first. - ### Writing to Custom Writer ```go diff --git a/Home.md b/Home.md index 9cb1f7e..3e27f83 100644 --- a/Home.md +++ b/Home.md @@ -22,6 +22,12 @@ Unified configuration management system for loading and managing environment-bas ### [TMDB](TMDB.md) Go client library for The Movie Database (TMDB) API. Features automatic rate limiting, retry logic with exponential backoff, movie search, detailed movie information, cast/crew data, and convenient image URL helpers. +### [env](env.md) +Environment variable utilities for Go applications with type safety and default values. Supports all basic Go types including strings, integers (all sizes), unsigned integers (all sizes), booleans, and time.Duration values with comprehensive boolean parsing. + +### [cookies](cookies.md) +HTTP cookie utilities for Go web applications with security best practices. Provides secure cookie setting, proper deletion, pagefrom tracking for post-login redirects, and host validation for referer-based redirects. + ## Installation ```bash diff --git a/cookies.md b/cookies.md new file mode 100644 index 0000000..2e932be --- /dev/null +++ b/cookies.md @@ -0,0 +1,313 @@ +# cookies v1.0.0 + +HTTP cookie utilities for Go web applications with security best practices. + +## Installation + +```bash +go get git.haelnorr.com/h/golib/cookies +``` + +## Key Concepts and Features + +The cookies package provides secure utilities for handling HTTP cookies in Go web applications. All cookies are set with the HttpOnly flag by default to prevent XSS attacks. + +### Core Functions + +- **SetCookie**: Set secure cookies with configurable path and max-age +- **DeleteCookie**: Properly delete cookies by setting expiration in the past +- **SetPageFrom**: Track the referring page for post-login redirects +- **CheckPageFrom**: Retrieve and delete pagefrom tracking cookie + +## Quick Start + +```go +package main + +import ( + "net/http" + "git.haelnorr.com/h/golib/cookies" +) + +func loginHandler(w http.ResponseWriter, r *http.Request) { + // Set pagefrom cookie before redirecting to login + cookies.SetPageFrom(w, r, "example.com") + http.Redirect(w, r, "/login", http.StatusFound) +} + +func postLoginHandler(w http.ResponseWriter, r *http.Request) { + // Set session cookie after successful login + cookies.SetCookie(w, "session", "/", "user-session-id", 3600) + + // Redirect back to the original page + redirectTo := cookies.CheckPageFrom(w, r) + http.Redirect(w, r, redirectTo, http.StatusFound) +} + +func logoutHandler(w http.ResponseWriter, r *http.Request) { + // Delete session cookie + cookies.DeleteCookie(w, "session", "/") + http.Redirect(w, r, "/login", http.StatusFound) +} +``` + +## Configuration + +### Cookie Security + +All cookies set by this package include: +- **HttpOnly**: Prevents JavaScript access (XSS protection) +- **Configurable Path**: Restricts cookie to specific paths +- **Configurable Max-Age**: Controls cookie lifetime + +### Environment Variables + +The package doesn't require specific environment variables, but commonly used ones include: + +```go +// ENV COOKIE_DOMAIN: Cookie domain (optional) +// ENV COOKIE_SECURE: Use HTTPS-only cookies (default: true in production) +// ENV TRUSTED_HOST: Host for pagefrom validation (recommended) +``` + +## Detailed Usage + +### Setting Cookies + +```go +// Basic session cookie (1 hour) +cookies.SetCookie(w, "session", "/", "abc123", 3600) + +// Persistent cookie (30 days) +cookies.SetCookie(w, "preferences", "/", "dark-mode", 2592000) + +// Temporary cookie (browser session) +cookies.SetCookie(w, "temp", "/", "data", 0) + +// Short-lived cookie (5 minutes) +cookies.SetCookie(w, "csrf", "/", "token", 300) +``` + +### Deleting Cookies + +```go +// Delete session cookie +cookies.DeleteCookie(w, "session", "/") + +// Delete cookie with specific path +cookies.DeleteCookie(w, "preferences", "/user") + +// Delete admin cookie +cookies.DeleteCookie(w, "admin_token", "/admin") +``` + +### Pagefrom Tracking + +The pagefrom functionality helps maintain user experience by remembering where users were before authentication: + +```go +// Middleware to track pagefrom before auth +func authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if needsAuth(r) && !isAuthenticated(r) { + // Track where user was going + cookies.SetPageFrom(w, r, "example.com") + http.Redirect(w, r, "/login", http.StatusFound) + return + } + next.ServeHTTP(w, r) + }) +} + +// After successful login +func loginSuccessHandler(w http.ResponseWriter, r *http.Request) { + // Authenticate user... + + // Get original destination + redirectTo := cookies.CheckPageFrom(w, r) + + // Default to dashboard if no pagefrom + if redirectTo == "/" { + redirectTo = "/dashboard" + } + + http.Redirect(w, r, redirectTo, http.StatusFound) +} +``` + +### Host Validation + +Pagefrom tracking includes host validation to prevent open redirects: + +```go +// Only allows redirects from trusted host +cookies.SetPageFrom(w, r, "example.com") + +// This will be rejected and redirect to "/" +// Referer: http://evil.com/steal-data + +// This will be accepted +// Referer: http://example.com/dashboard +``` + +## Integration + +### With HWS (Web Server) + +```go +import "git.haelnorr.com/h/golib/hws" +import "git.haelnorr.com/h/golib/cookies" + +func main() { + server := hws.NewServer() + + // Add auth middleware + server.Use(authMiddleware) + + // Login routes + server.Get("/login", loginHandler) + server.Post("/login", postLoginHandler) + server.Get("/logout", logoutHandler) + + server.Start() +} + +func authMiddleware(ctx *hws.Context) { + if !isAuthenticated(ctx.Request) { + cookies.SetPageFrom(ctx.ResponseWriter, ctx.Request, "example.com") + ctx.Redirect("/login", http.StatusFound) + return + } + ctx.Next() +} +``` + +### With HWSAuth (Authentication) + +```go +import "git.haelnorr.com/h/golib/hwsauth" +import "git.haelnorr.com/h/golib/cookies" + +func loginHandler(ctx *hws.Context) { + // Validate credentials... + + if valid { + // Create JWT token + token, err := hwsauth.CreateToken(userID) + if err != nil { + ctx.Error("Failed to create token", http.StatusInternalServerError) + return + } + + // Set cookie with token + cookies.SetCookie(ctx.ResponseWriter, "auth", "/", token, 3600) + + // Redirect to original destination + redirectTo := cookies.CheckPageFrom(ctx.ResponseWriter, ctx.Request) + ctx.Redirect(redirectTo, http.StatusFound) + } +} +``` + +## Best Practices + +1. **Use HttpOnly**: All cookies are HttpOnly by default for security +2. **Set appropriate paths**: Restrict cookies to necessary paths +3. **Use reasonable max-age**: Don't keep cookies longer than needed +4. **Validate pagefrom**: Always use trusted host validation +5. **Delete properly**: Use DeleteCookie instead of setting empty values + +```go +// Good: Specific path and reasonable max-age +cookies.SetCookie(w, "session", "/", sessionID, 3600) + +// Good: Proper deletion +cookies.DeleteCookie(w, "session", "/") + +// Avoid: Too broad or too persistent +cookies.SetCookie(w, "data", "/", "value", 31536000) // 1 year +``` + +### Security Considerations + +1. **HTTPS in Production**: Always use HTTPS for cookies with sensitive data +2. **SameSite**: Consider SameSite attribute for CSRF protection +3. **Domain Restriction**: Set domain to restrict cookies to your domain +4. **Path Restriction**: Use specific paths when possible + +```go +// Production example with additional security +func setSecureCookie(w http.ResponseWriter, name, value string) { + cookie := &http.Cookie{ + Name: name, + Value: value, + Path: "/", + Domain: "example.com", + MaxAge: 3600, + HttpOnly: true, + Secure: true, // HTTPS only + SameSite: http.SameSiteStrictMode, + } + http.SetCookie(w, cookie) +} +``` + +## Troubleshooting + +### Common Issues + +1. **Cookie not setting**: Check that response hasn't been written yet +2. **Cookie not deleting**: Ensure path matches original cookie path +3. **Pagefrom not working**: Verify trusted host matches referer host +4. **Redirect loops**: Check authentication logic carefully + +### Debug Tips + +```go +// Debug cookie headers +func debugCookies(w http.ResponseWriter, r *http.Request) { + fmt.Printf("Request cookies: %v\n", r.Cookies()) + + // Check response headers after setting cookies + headers := w.Header() + fmt.Printf("Set-Cookie headers: %v\n", headers["Set-Cookie"]) +} +``` + +### Testing Pagefrom + +```go +// Test pagefrom flow manually +func testPagefromFlow() { + // 1. Set pagefrom + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/protected", nil) + r.Header.Set("Referer", "http://example.com/dashboard") + + cookies.SetPageFrom(w, r, "example.com") + + // 2. Check pagefrom + w2 := httptest.NewRecorder() + r2 := httptest.NewRequest("GET", "/login", nil) + + // Add the cookie from step 1 + for _, cookie := range w.Result().Cookies() { + r2.AddCookie(cookie) + } + + redirectTo := cookies.CheckPageFrom(w2, r2) + fmt.Printf("Redirect to: %s\n", redirectTo) // Should be "/dashboard" +} +``` + +## See Also + +- [HWS](HWS.md) - Web server framework for building applications +- [HWSAuth](HWSAuth.md) - JWT authentication system +- [env](env.md) - Environment variable configuration + +## Links + +- [GoDoc API](https://pkg.go.dev/git.haelnorr.com/h/golib/cookies) +- [Source Code](https://git.haelnorr.com/h/golib/src/branch/main/cookies) +- [Issue Tracker](https://git.haelnorr.com/h/golib/issues) \ No newline at end of file diff --git a/env.md b/env.md new file mode 100644 index 0000000..2dcd472 --- /dev/null +++ b/env.md @@ -0,0 +1,245 @@ +# env v1.0.0 + +Environment variable utilities for Go applications with type safety and default values. + +## Installation + +```bash +go get git.haelnorr.com/h/golib/env +``` + +## Key Concepts and Features + +The env package provides type-safe functions for reading environment variables with automatic fallback to default values. All functions handle missing environment variables gracefully by returning the provided default value. + +### Supported Types + +- **String**: Basic string values +- **Integers**: int, int8, int16, int32, int64 +- **Unsigned Integers**: uint, uint8, uint16, uint32, uint64 +- **Boolean**: Flexible boolean parsing with multiple truthy/falsy values +- **Duration**: time.Duration values in nanoseconds + +## Quick Start + +```go +package main + +import ( + "fmt" + "time" + "git.haelnorr.com/h/golib/env" +) + +func main() { + // String values + host := env.String("HOST", "localhost") + + // Integer values (all sizes supported) + port := env.Int("PORT", 8080) + timeout := env.Int64("TIMEOUT_SECONDS", 30) + + // Unsigned integer values + maxConnections := env.UInt("MAX_CONNECTIONS", 100) + + // Boolean values (supports many formats) + debug := env.Bool("DEBUG", false) + + // Duration values + requestTimeout := env.Duration("REQUEST_TIMEOUT", 30*time.Second) + + fmt.Printf("Server: %s:%d\n", host, port) + fmt.Printf("Debug: %v\n", debug) + fmt.Printf("Timeout: %v\n", requestTimeout) +} +``` + +## Configuration + +### Environment Variables + +All functions follow the pattern: `FunctionName("ENV_VAR_NAME", defaultValue)` + +#### String Values +```go +// ENV HOST: Server hostname (default: localhost) +host := env.String("HOST", "localhost") + +// ENV DATABASE_URL: Database connection string (required) +dbURL := env.String("DATABASE_URL", "") +``` + +#### Integer Values +```go +// ENV PORT: Server port (default: 8080) +port := env.Int("PORT", 8080) + +// ENV MAX_CONNECTIONS: Maximum database connections (default: 10) +maxConn := env.Int16("MAX_CONNECTIONS", 10) +``` + +#### Boolean Values +```go +// ENV DEBUG: Enable debug mode (default: false) +debug := env.Bool("DEBUG", false) + +// ENV PRODUCTION: Production mode flag (default: false) +prod := env.Bool("PRODUCTION", false) +``` + +#### Duration Values +```go +// ENV TIMEOUT: Request timeout in seconds (default: 30) +timeout := env.Duration("TIMEOUT", 30*time.Second) +``` + +### Boolean Parsing + +The boolean parser supports many common truthy and falsy values: + +**Truthy values**: true, t, yes, y, on, 1, enable, enabled, active, affirmative +**Falsy values**: false, f, no, n, off, 0, disable, disabled, inactive, negative + +All values are case-insensitive and whitespace is trimmed. + +## Detailed Usage + +### String Functions + +```go +// Basic string with default +apiKey := env.String("API_KEY", "default-key") + +// Empty string as default +optional := env.String("OPTIONAL", "") +``` + +### Integer Functions + +All integer types are supported with consistent behavior: + +```go +// Different integer sizes +port := env.Int("PORT", 8080) +smallNum := env.Int8("SMALL_NUM", 42) +mediumNum := env.Int16("MEDIUM_NUM", 1000) +largeNum := env.Int32("LARGE_NUM", 100000) +veryLarge := env.Int64("VERY_LARGE", 1000000000) +``` + +### Unsigned Integer Functions + +```go +// Unsigned integers (reject negative values) +maxUsers := env.UInt("MAX_USERS", 1000) +byteValue := env.UInt8("BYTE_VALUE", 255) +shortValue := env.UInt16("SHORT_VALUE", 65535) +normalValue := env.UInt32("NORMAL_VALUE", 4294967295) +bigValue := env.UInt64("BIG_VALUE", 18446744073709551615) +``` + +### Boolean Functions + +```go +// Boolean with various input formats +enabled := env.Bool("ENABLED", false) +verbose := env.Bool("VERBOSE", true) +maintenance := env.Bool("MAINTENANCE", false) +``` + +### Duration Functions + +```go +// Duration in nanoseconds (from integer env var) +timeout := env.Duration("TIMEOUT", 30*time.Second) +heartbeat := env.Duration("HEARTBEAT", 5*time.Second) +``` + +## Integration + +The env package integrates well with other golib packages: + +### With HWS (Web Server) +```go +import "git.haelnorr.com/h/golib/env" +import "git.haelnorr.com/h/golib/hws" + +func main() { + port := env.Int("PORT", 8080) + debug := env.Bool("DEBUG", false) + + server := hws.NewServer() + server.SetPort(port) + server.SetDebug(debug) + server.Start() +} +``` + +### With Database Packages +```go +import "git.haelnorr.com/h/golib/env" + +func getDBConfig() (host string, port int, timeout time.Duration) { + host = env.String("DB_HOST", "localhost") + port = env.Int("DB_PORT", 5432) + timeout = env.Duration("DB_TIMEOUT", 30*time.Second) + return +} +``` + +## Best Practices + +1. **Use meaningful defaults**: Provide sensible defaults that work in development +2. **Document required variables**: Use comments to indicate which variables are required +3. **Prefer specific types**: Use the most appropriate integer size for your use case +4. **Handle validation**: For complex validation, parse the env var then validate separately +5. **Group related config**: Consider using a struct to group related configuration + +```go +type ServerConfig struct { + Host string + Port int + Debug bool + Timeout time.Duration +} + +func LoadServerConfig() *ServerConfig { + return &ServerConfig{ + Host: env.String("HOST", "localhost"), + Port: env.Int("PORT", 8080), + Debug: env.Bool("DEBUG", false), + Timeout: env.Duration("TIMEOUT", 30*time.Second), + } +} +``` + +## Troubleshooting + +### Common Issues + +1. **Parsing errors**: All functions return the default value on parsing errors +2. **Type mismatches**: Ensure environment variables contain valid values for the target type +3. **Missing variables**: Always provide a sensible default value + +### Debug Tips + +```go +// Debug environment variable loading +func debugEnvVars() { + fmt.Printf("HOST: %s\n", env.String("HOST", "not-set")) + fmt.Printf("PORT: %d\n", env.Int("PORT", 0)) + fmt.Printf("DEBUG: %v\n", env.Bool("DEBUG", false)) +} +``` + +## See Also + +- [HWS](HWS.md) - Web server framework that uses env for configuration +- [EZConf](EZConf.md) - Configuration management system +- [HWSAuth](HWSAuth.md) - Authentication package with env-based configuration + +## Links + +- [GoDoc API](https://pkg.go.dev/git.haelnorr.com/h/golib/env) +- [Source Code](https://git.haelnorr.com/h/golib/src/branch/main/env) +- [Issue Tracker](https://git.haelnorr.com/h/golib/issues) \ No newline at end of file