diff --git a/cmd/start.go b/cmd/start.go index ea85a98..d565066 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -3,6 +3,8 @@ package cmd import ( "context" "log/slog" + "os" + "strings" "git.coopcloud.tech/wiki-cafe/member-console/internal/logging" "git.coopcloud.tech/wiki-cafe/member-console/internal/server" @@ -22,17 +24,52 @@ var startCmd = &cobra.Command{ // Create base context for the application ctx := context.Background() - // Retrieve the configuration values from Viper - port := viper.GetString("port") - env := viper.GetString("env") - csrfSecret := viper.GetString("csrf-secret") - // Set up structured logging + env := viper.GetString("env") logger := logging.SetupLogger(env) // Store logger in context ctx = logging.WithContext(ctx, logger) + // Validate and load configurations from files if specified + configPairs := []struct { + value string + file string + configKey string + }{ + {viper.GetString("client-secret"), viper.GetString("client-secret-file"), "client-secret"}, + {viper.GetString("session-secret"), viper.GetString("session-secret-file"), "session-secret"}, + {viper.GetString("csrf-secret"), viper.GetString("csrf-secret-file"), "csrf-secret"}, + } + + // Check for conflicts between direct values and file paths + for _, pair := range configPairs { + // Check if both a direct value and a file path are provided + if pair.value != "" && pair.file != "" { + logger.Error("configuration error", + slog.String("config", pair.configKey), + slog.String("error", "both direct value and file path provided; use only one")) + return + } + + // If a file path is provided, load the value from the file + if pair.file != "" { + value, err := loadFromFile(pair.file) + if err != nil { + logger.Error("failed to load configuration from file", + slog.String("config", pair.configKey), + slog.String("file", pair.file), + slog.Any("error", err)) + return + } + viper.Set(pair.configKey, value) + } + } + + // Retrieve the configuration values from Viper + port := viper.GetString("port") + csrfSecret := viper.GetString("csrf-secret") + // Create server config serverConfig := server.Config{ Port: port, @@ -52,13 +89,17 @@ func init() { // Register flags with Cobra startCmd.Flags().StringP("port", "p", "", "Port to listen on") startCmd.Flags().String("client-id", "", "OIDC Client ID") - startCmd.Flags().String("client-secret", "", "OIDC Client Secret") startCmd.Flags().String("issuer-url", "", "Identity Provider Issuer URL") startCmd.Flags().String("hostname", "", "Address at which the server is exposed") - startCmd.Flags().String("session-secret", "", "Session encryption secret") - startCmd.Flags().String("csrf-secret", "", "Secret key for CSRF protection (must be exactly 32 bytes)") startCmd.Flags().String("env", "", "Environment (development/production)") + startCmd.Flags().String("client-secret", "", "OIDC Client Secret") + startCmd.Flags().String("client-secret-file", "", "Path to file containing OIDC Client Secret") + startCmd.Flags().String("session-secret", "", "Secret key for session management (must be exactly 32 bytes)") + startCmd.Flags().String("session-secret-file", "", "Path to file containing session secret key") + startCmd.Flags().String("csrf-secret", "", "Secret key for CSRF protection (must be exactly 32 bytes)") + startCmd.Flags().String("csrf-secret-file", "", "Path to file containing CSRF secret key") + // Bind all flags to Viper viper.BindPFlags(startCmd.Flags()) @@ -69,3 +110,12 @@ func init() { // Add the command to the root command rootCmd.AddCommand(startCmd) } + +// loadFromFile reads a file and returns its contents as a trimmed string +func loadFromFile(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + return strings.TrimSpace(string(data)), nil +}