Refactoring for Gin, NoSurf and SCS continues.

This commit is contained in:
2025-10-24 13:08:53 +01:00
parent 7276903733
commit fb07c4a5eb
61 changed files with 546 additions and 524 deletions

View File

@@ -1,26 +0,0 @@
package bootstrap
import (
"fmt"
"net/http"
"github.com/gorilla/csrf"
)
var CSRFMiddleware func(http.Handler) http.Handler
func InitCSRFProtection(csrfKey []byte, isProduction bool) error {
if len(csrfKey) != 32 {
return fmt.Errorf("csrf key must be 32 bytes, got %d", len(csrfKey))
}
CSRFMiddleware = csrf.Protect(
csrfKey,
csrf.Secure(isProduction),
csrf.SameSite(csrf.SameSiteStrictMode),
csrf.Path("/"),
csrf.HttpOnly(true),
)
return nil
}

View File

@@ -5,12 +5,12 @@ import (
"time"
internal "synlotto-website/internal/licensecheck"
"synlotto-website/internal/models"
"synlotto-website/internal/platform/config"
)
var globalChecker *internal.LicenseChecker
func InitLicenseChecker(config *models.Config) error {
func InitLicenseChecker(config *config.Config) error {
checker := &internal.LicenseChecker{
LicenseAPIURL: config.License.APIURL,
APIKey: config.License.APIKey,

View File

@@ -5,11 +5,11 @@ import (
"fmt"
"os"
"synlotto-website/internal/models"
"synlotto-website/internal/platform/config"
)
type AppState struct {
Config *models.Config
Config *config.Config
}
func LoadAppState(configPath string) (*AppState, error) {
@@ -19,7 +19,7 @@ func LoadAppState(configPath string) (*AppState, error) {
}
defer file.Close()
var config models.Config
var config config.Config
if err := json.NewDecoder(file).Decode(&config); err != nil {
return nil, fmt.Errorf("decode config: %w", err)
}

View File

@@ -1,115 +0,0 @@
package bootstrap
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/gob"
"fmt"
"net/http"
"os"
"time"
sessionHandlers "synlotto-website/internal/handlers/session"
sessionHelpers "synlotto-website/internal/helpers/session"
"synlotto-website/internal/logging"
"synlotto-website/internal/models"
"github.com/gorilla/sessions"
)
var (
sessionStore *sessions.CookieStore
Name string
authKey []byte
encryptKey []byte
)
func InitSession(cfg *models.Config) error {
gob.Register(time.Time{})
authPath := cfg.Session.AuthKeyPath
encPath := cfg.Session.EncryptionKeyPath
if _, err := os.Stat(authPath); os.IsNotExist(err) {
logging.Info("⚠️ Auth key not found, creating: %s", authPath)
key, err := generateRandomBytes(32)
if err != nil {
return err
}
encoded := sessionHelpers.EncodeKey(key)
err = os.WriteFile(authPath, []byte(encoded), 0600)
if err != nil {
return err
}
}
if _, err := os.Stat(encPath); os.IsNotExist(err) {
logging.Info("⚠️ Encryption key not found, creating: %s", encPath)
key, err := generateRandomBytes(32)
if err != nil {
return err
}
encoded := sessionHelpers.EncodeKey(key)
err = os.WriteFile(encPath, []byte(encoded), 0600)
if err != nil {
return err
}
}
return loadSessionKeys(
authPath,
encPath,
cfg.Session.Name,
cfg.HttpServer.ProductionMode,
)
}
func generateRandomBytes(length int) ([]byte, error) {
b := make([]byte, length)
_, err := rand.Read(b)
if err != nil {
logging.Error("failed to generate random bytes: %w", err)
return nil, err
}
return b, nil
}
func loadSessionKeys(authPath, encryptionPath, name string, isProduction bool) error {
var err error
rawAuth, err := os.ReadFile(authPath)
if err != nil {
return fmt.Errorf("error reading auth key: %w", err)
}
authKey, err = base64.StdEncoding.DecodeString(string(bytes.TrimSpace(rawAuth)))
if err != nil {
return fmt.Errorf("error decoding auth key: %w", err)
}
rawEnc, err := os.ReadFile(encryptionPath)
if err != nil {
return fmt.Errorf("error reading encryption key: %w", err)
}
encryptKey, err = base64.StdEncoding.DecodeString(string(bytes.TrimSpace(rawEnc)))
if err != nil {
return fmt.Errorf("error decoding encryption key: %w", err)
}
if len(authKey) != 32 || len(encryptKey) != 32 {
return fmt.Errorf("auth and encryption keys must be 32 bytes each (got auth=%d, enc=%d)", len(authKey), len(encryptKey))
}
sessionHandlers.SessionStore = sessions.NewCookieStore(authKey, encryptKey)
sessionHandlers.SessionStore.Options = &sessions.Options{
Path: "/",
MaxAge: 86400,
HttpOnly: true,
Secure: isProduction,
SameSite: http.SameSiteLaxMode,
}
sessionHandlers.Name = name
return nil
}

View File

@@ -3,20 +3,20 @@ package config
import (
"sync"
"synlotto-website/internal/models"
"synlotto-website/internal/platform/config"
)
var (
appConfig *models.Config
appConfig *config.Config
once sync.Once
)
func Init(config *models.Config) {
func Init(config *config.Config) {
once.Do(func() {
appConfig = config
})
}
func Get() *models.Config {
func Get() *config.Config {
return appConfig
}

View File

@@ -0,0 +1,21 @@
package config
import (
"encoding/json"
"os"
)
func Load(path string) (Config, error) {
var cfg Config
data, err := os.ReadFile(path)
if err != nil {
return cfg, err
}
if err := json.Unmarshal(data, &cfg); err != nil {
return cfg, err
}
return cfg, nil
}

View File

@@ -2,7 +2,7 @@ package config
type Config struct {
CSRF struct {
CSRFKey string `json:"csrfKey"`
CookieName string `json:"cookieName"`
} `json:"csrf"`
Database struct {
@@ -28,9 +28,8 @@ type Config struct {
} `json:"license"`
Session struct {
AuthKeyPath string `json:"authKeyPath"`
EncryptionKeyPath string `json:"encryptionKeyPath"`
Name string `json:"name"`
Name string `json:"name"`
Lifetime string `json:"lifetime"`
} `json:"session"`
Site struct {

View File

@@ -0,0 +1,21 @@
package csrf
import (
"net/http"
"synlotto-website/internal/platform/config"
"github.com/justinas/nosurf"
)
func Wrap(h http.Handler, cfg config.Config) http.Handler {
cs := nosurf.New(h)
cs.SetBaseCookie(http.Cookie{
Name: cfg.CSRF.CookieName,
Path: "/",
HttpOnly: true,
Secure: cfg.HttpServer.ProductionMode,
SameSite: http.SameSiteLaxMode,
})
return cs
}

View File

@@ -0,0 +1,25 @@
package session
import (
"net/http"
"time"
"synlotto-website/internal/platform/config"
"github.com/alexedwards/scs/v2"
)
func New(cfg config.Config) *scs.SessionManager {
lifetime := 12 * time.Hour
if d, err := time.ParseDuration(cfg.Session.Lifetime); err == nil && d > 0 {
lifetime = d
}
s := scs.New()
s.Lifetime = lifetime
s.Cookie.Name = cfg.Session.Name
s.Cookie.HttpOnly = true
s.Cookie.SameSite = http.SameSiteLaxMode
s.Cookie.Secure = cfg.HttpServer.ProductionMode
return s
}