package webui import ( "crypto/rand" "fmt" "os" "path/filepath" ) const appSecretSize = 32 // LoadOrCreateAppSecret reads the app's CSRF-signing secret from path, generating a // fresh random one on first run if the file doesn't exist yet — mirrors // mailstore.LoadOrCreateMasterKey's identical generate-if-missing pattern for the // mailstore encryption key. Unlike that key, losing this one has no data-loss // consequence: every outstanding CSRF token just stops validating, so users get // logged-out-feeling form-submit errors until they reload a page for a fresh one. func LoadOrCreateAppSecret(path string) ([]byte, error) { if b, err := os.ReadFile(path); err == nil { if len(b) != appSecretSize { return nil, fmt.Errorf("app secret at %s is %d bytes, want %d", path, len(b), appSecretSize) } return b, nil } else if !os.IsNotExist(err) { return nil, err } if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err } secret := make([]byte, appSecretSize) if _, err := rand.Read(secret); err != nil { return nil, err } if err := os.WriteFile(path, secret, 0o600); err != nil { return nil, err } return secret, nil }