diff --git a/cmd/root.go b/cmd/root.go index 5fab0ed..0c04d6f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,17 +2,47 @@ package cmd import ( "os" + "time" "tinyauth/internal/api" + "tinyauth/internal/types" + "tinyauth/internal/utils" + "github.com/go-playground/validator/v10" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var rootCmd = &cobra.Command{ Use: "tinyauth", - Short: "A dead simple login page for your apps.", + Short: "An extremely simple traefik forward auth proxy.", Long: `Tinyauth is an extremely simple traefik forward-auth login screen that makes securing your apps easy.`, Run: func(cmd *cobra.Command, args []string) { - api.Run() + // Logger + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger() + log.Info().Msg("Starting tinyauth") + + // Get config + log.Info().Msg("Parsing config") + var config types.Config + parseErr := viper.Unmarshal(&config) + HandleError(parseErr, "Failed to parse config") + + // Validate config + log.Info().Msg("Validating config") + validator := validator.New() + 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") + + // Start server + log.Info().Msg("Starting server") + api.Run(config, userList) }, } @@ -22,3 +52,27 @@ func Execute() { os.Exit(1) } } + +func HandleError(err error, msg string) { + if err != nil { + log.Fatal().Err(err).Msg(msg) + os.Exit(1) + } +} + +func init() { + viper.AutomaticEnv() + rootCmd.Flags().IntP("port", "p", 8080, "Port to run the server on.") + rootCmd.Flags().String("address", "0.0.0.0", "Address to bind the server to.") + rootCmd.Flags().String("secret", "", "Secret to use for the cookie.") + rootCmd.Flags().String("root-url", "", "Root URL of traefik.") + rootCmd.Flags().String("app-url", "", "The tinyauth URL.") + rootCmd.Flags().String("users", "", "Comma separated list of users in the format username:bcrypt-hashed-password.") + viper.BindEnv("port", "PORT") + viper.BindEnv("address", "ADDRESS") + viper.BindEnv("secret", "SECRET") + viper.BindEnv("root-url", "ROOT_URL") + viper.BindEnv("app-url", "APP_URL") + viper.BindEnv("users", "USERS") + viper.BindPFlags(rootCmd.Flags()) +} diff --git a/docker-compose.yml b/docker-compose.yml index df88f97..592938e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,6 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock labels: - traefik.http.middlewares.basic-auth.basicauth.users: user:$$2y$$05$$HgkRucUeFKgZ7ZsPkIJY6uAX2Nh8ZAeIlJ5Rpq.05yYBPsITTnnLu traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth nginx: @@ -30,6 +29,13 @@ services: dockerfile: Dockerfile ports: - 3000:3000 + environment: + - PORT=3000 + - ADDRESS=0.0.0.0 + - SECRET=ghDaPuDFjvlBuF93zcacFrDiHFHTZhUh + - ROOT_URL=http://dev.local + - APP_URL=http://tinyauth.dev.local + - USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u labels: traefik.enable: true traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`) diff --git a/go.mod b/go.mod index 4d14baa..939177d 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/bytedance/sonic v1.12.7 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/cloudwego/base64x v0.1.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sessions v1.0.2 // indirect github.com/gin-contrib/sse v1.0.0 // indirect @@ -22,22 +23,38 @@ require ( github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/rs/zerolog v1.33.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.13.0 // indirect golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 56548b1..43c0390 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,13 @@ github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFos github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA= @@ -28,6 +31,7 @@ github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -40,6 +44,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -50,8 +56,17 @@ github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -59,13 +74,29 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -78,17 +109,27 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= @@ -99,6 +140,8 @@ google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1B google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/api/api.go b/internal/api/api.go index 995e2a0..8020798 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -6,22 +6,36 @@ import ( "net/http" "os" "strings" + "time" "tinyauth/internal/assets" + "tinyauth/internal/auth" "tinyauth/internal/types" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/google/go-querystring/query" + "github.com/rs/zerolog/log" ) -func Run() { - router := gin.Default() - dist, _ := fs.Sub(assets.Assets, "dist") +func Run(config types.Config, users types.UserList) { + gin.SetMode(gin.ReleaseMode) + router := gin.New() + router.Use(zerolog()) + dist, distErr := fs.Sub(assets.Assets, "dist") + + if distErr != nil { + log.Fatal().Err(distErr).Msg("Failed to get UI assets") + os.Exit(1) + } + fileServer := http.FileServer(http.FS(dist)) - store := cookie.NewStore([]byte("secret")) + store := cookie.NewStore([]byte(config.Secret)) + + domain := strings.Split(config.RootURL, "://")[1] + store.Options(sessions.Options{ - Domain: ".dev.local", + Domain: fmt.Sprintf(".%s", domain), Path: "/", }) router.Use(sessions.Sessions("tinyauth", store)) @@ -40,22 +54,36 @@ func Run() { router.GET("/api/auth", func (c *gin.Context) { session := sessions.Default(c) value := session.Get("tinyauth") - - if value == nil || value != "true" { - uri := c.Request.Header.Get("X-Forwarded-Uri") - proto := c.Request.Header.Get("X-Forwarded-Proto") - host := c.Request.Header.Get("X-Forwarded-Host") - queries := types.LoginQuery{ - RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri), + + if value != nil { + usernameString, ok := value.(string) + if ok { + if auth.FindUser(users, usernameString) != nil { + c.JSON(200, gin.H{ + "status": 200, + "message": "Authorized", + }) + return + } } - values, _ := query.Values(queries) - c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("http://tinyauth.dev.local?%s", values.Encode())) } - c.JSON(200, gin.H{ - "status": 200, - "message": "Authorized", + uri := c.Request.Header.Get("X-Forwarded-Uri") + proto := c.Request.Header.Get("X-Forwarded-Proto") + host := c.Request.Header.Get("X-Forwarded-Host") + queries, queryErr := query.Values(types.LoginQuery{ + RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri), }) + + if queryErr != nil { + c.JSON(501, gin.H{ + "status": 501, + "message": "Internal Server Error", + }) + return + } + + c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/?%s", config.AppURL, queries.Encode())) }) router.POST("/api/login", func (c *gin.Context) { @@ -71,7 +99,17 @@ func Run() { return } - if login.Email != "user@example.com" || login.Password != "password" { + user := auth.FindUser(users, login.Username) + + if user == nil { + c.JSON(401, gin.H{ + "status": 401, + "message": "Unauthorized", + }) + return + } + + if !auth.CheckPassword(*user, login.Password) { c.JSON(401, gin.H{ "status": 401, "message": "Unauthorized", @@ -80,7 +118,7 @@ func Run() { } session := sessions.Default(c) - session.Set("tinyauth", "true") + session.Set("tinyauth", user.Username) session.Save() c.JSON(200, gin.H{ @@ -104,19 +142,50 @@ func Run() { session := sessions.Default(c) value := session.Get("tinyauth") - if value == nil || value != "true" { - c.JSON(200, gin.H{ - "status": 200, - "isLoggedIn": false, - }) - return + if value != nil { + usernameString, ok := value.(string) + if ok { + if auth.FindUser(users, usernameString) != nil { + c.JSON(200, gin.H{ + "status": 200, + "isLoggedIn": true, + "username": usernameString, + }) + return + } + } } c.JSON(200, gin.H{ "status": 200, - "isLoggedIn": true, + "isLoggedIn": false, + "username": "", }) }) - router.Run(":3000") + router.Run(fmt.Sprintf("%s:%d", config.Address, config.Port)) +} + +func zerolog() gin.HandlerFunc { + return func(c *gin.Context) { + tStart := time.Now() + + c.Next() + + code := c.Writer.Status() + address := c.Request.RemoteAddr + method := c.Request.Method + path := c.Request.URL.Path + + latency := time.Since(tStart).String() + + switch { + case code >= 200 && code < 300: + log.Info().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request") + case code >= 300 && code < 400: + log.Warn().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request") + case code >= 400: + log.Error().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request") + } + } } \ No newline at end of file diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 0000000..24f168b --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,21 @@ +package auth + +import ( + "tinyauth/internal/types" + + "golang.org/x/crypto/bcrypt" +) + +func FindUser(userList types.UserList, username string) (*types.User) { + for _, user := range userList.Users { + if user.Username == username { + return &user + } + } + return nil +} + +func CheckPassword(user types.User, password string) bool { + hashedPasswordErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) + return hashedPasswordErr == nil +} \ No newline at end of file diff --git a/internal/types/types.go b/internal/types/types.go index 542e45f..871a791 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -5,6 +5,24 @@ type LoginQuery struct { } type LoginRequest struct { - Email string `json:"email"` + Username string `json:"username"` Password string `json:"password"` +} + +type User struct { + Username string + Password string +} + +type UserList struct { + Users []User +} + +type Config struct { + Port int `validate:"number" mapstructure:"port"` + Address string `mapstructure:"address, ip4_addr"` + Secret string `validate:"required,len=32" mapstructure:"secret"` + RootURL string `validate:"required,url" mapstructure:"root-url"` + AppURL string `validate:"required,url" mapstructure:"app-url"` + Users string `validate:"required" mapstructure:"users"` } \ No newline at end of file diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..ecba7c8 --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,29 @@ +package utils + +import ( + "errors" + "strings" + "tinyauth/internal/types" +) + +func CreateUsersList(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") + } + + for _, user := range userListString { + userSplit := strings.Split(user, ":") + if len(userSplit) != 2 { + return types.UserList{}, errors.New("invalid user format") + } + userList.Users = append(userList.Users, types.User{ + Username: userSplit[0], + Password: userSplit[1], + }) + } + + return userList, nil +} \ No newline at end of file diff --git a/site/src/pages/login-page.tsx b/site/src/pages/login-page.tsx index 17e6e17..d332f32 100644 --- a/site/src/pages/login-page.tsx +++ b/site/src/pages/login-page.tsx @@ -20,7 +20,7 @@ export const LoginPage = () => { } const schema = z.object({ - email: z.string().email({ message: "Invalid email" }), + username: z.string(), password: z.string(), }); @@ -29,7 +29,7 @@ export const LoginPage = () => { const form = useForm({ mode: "uncontrolled", initialValues: { - email: "", + username: "", password: "", }, validate: zodResolver(schema), @@ -42,7 +42,7 @@ export const LoginPage = () => { onError: () => { notifications.show({ title: "Failed to login", - message: "Check your email and password", + message: "Check your username and password", color: "red", }); }, @@ -68,12 +68,12 @@ export const LoginPage = () => {
{ - const { isLoggedIn } = useUserContext(); + const { isLoggedIn, username } = useUserContext(); if (!isLoggedIn) { return ; @@ -43,7 +43,8 @@ export const LogoutPage = () => { Logout - You are already logged in, click the button below to log out. + You are currently logged in as {username}, click the button below to + log out.