From fcaa3779d5ce2a24beb8b251bd8887a468e904da Mon Sep 17 00:00:00 2001 From: Stavros Date: Mon, 20 Jan 2025 18:39:22 +0200 Subject: [PATCH] feat: allow users config from file --- .gitignore | 5 ++++- cmd/root.go | 31 +++++++++++++++++++++++++++---- internal/types/types.go | 3 ++- internal/utils/utils.go | 23 ++++++++++++++++++++--- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index a8d0048..61edd90 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ internal/assets/dist tinyauth # dev docker compose -docker-compose.dev.yml \ No newline at end of file +docker-compose.dev.yml + +# users file +users.txt \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 916f55b..06a6232 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "os" + "strings" "time" "tinyauth/internal/api" "tinyauth/internal/assets" @@ -36,10 +37,30 @@ var rootCmd = &cobra.Command{ validateErr := validator.Struct(config) HandleError(validateErr, "Invalid config") - // Create users list - log.Info().Msg("Creating users list") - userList, createErr := utils.CreateUsersList(config.Users) - HandleError(createErr, "Failed to create users list") + // Parse users + log.Info().Msg("Parsing users") + + if config.UsersFile == "" && config.Users == "" { + log.Fatal().Msg("No users provided") + os.Exit(1) + } + + users := config.Users + + if config.UsersFile != "" { + log.Info().Msg("Reading users from file") + usersFromFile, readErr := utils.GetUsersFromFile(config.UsersFile) + HandleError(readErr, "Failed to read users from file") + usersFromFileParsed := strings.Join(strings.Split(usersFromFile, "\n"), ",") + if users != "" { + users = users + "," + usersFromFileParsed + } else { + users = usersFromFileParsed + } + } + + userList, createErr := utils.ParseUsers(users) + HandleError(createErr, "Failed to parse users") // Start server log.Info().Msg("Starting server") @@ -68,10 +89,12 @@ func init() { rootCmd.Flags().String("secret", "", "Secret to use for the cookie.") rootCmd.Flags().String("app-url", "", "The tinyauth URL.") rootCmd.Flags().String("users", "", "Comma separated list of users in the format username:bcrypt-hashed-password.") + rootCmd.Flags().String("users-file", "", "Path to a file containing users in the format username:bcrypt-hashed-password.") viper.BindEnv("port", "PORT") viper.BindEnv("address", "ADDRESS") viper.BindEnv("secret", "SECRET") viper.BindEnv("app-url", "APP_URL") viper.BindEnv("users", "USERS") + viper.BindEnv("users-file", "USERS_FILE") viper.BindPFlags(rootCmd.Flags()) } diff --git a/internal/types/types.go b/internal/types/types.go index cdefc75..053cd81 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -23,7 +23,8 @@ type Config struct { Address string `mapstructure:"address, ip4_addr"` Secret string `validate:"required,len=32" mapstructure:"secret"` AppURL string `validate:"required,url" mapstructure:"app-url"` - Users string `validate:"required" mapstructure:"users"` + Users string `mapstructure:"users"` + UsersFile string `mapstructure:"users-file"` } type UserContext struct { diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 9fc024b..2f710a9 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -3,16 +3,17 @@ package utils import ( "errors" "net/url" + "os" "strings" "tinyauth/internal/types" ) -func CreateUsersList(users string) (types.UserList, error) { +func ParseUsers(users string) (types.UserList, error) { var userList types.UserList userListString := strings.Split(users, ",") if len(userListString) == 0 { - return types.UserList{}, errors.New("no users found") + return types.UserList{}, errors.New("invalid user format") } for _, user := range userListString { @@ -41,4 +42,20 @@ func GetRootURL(urlSrc string) (string, error) { urlFinal := urlSplitted[len(urlSplitted)-2] + "." + urlSplitted[len(urlSplitted)-1] return urlFinal, nil -} \ No newline at end of file +} + +func GetUsersFromFile(usersFile string) (string, error) { + _, statErr := os.Stat(usersFile) + + if statErr != nil { + return "", statErr + } + + data, readErr := os.ReadFile(usersFile) + + if readErr != nil { + return "", readErr + } + + return string(data), nil +}