mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-04 08:05:42 +00:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2385599c80 | ||
| 
						 | 
					6f184856f1 | ||
| 
						 | 
					e2e3b3bdc6 | ||
| 
						 | 
					3efcb26db1 | 
							
								
								
									
										23
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								cmd/root.go
									
									
									
									
									
								
							@@ -1,9 +1,12 @@
 | 
				
			|||||||
package cmd
 | 
					package cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
	cmd "tinyauth/cmd/user"
 | 
						cmd "tinyauth/cmd/user"
 | 
				
			||||||
	"tinyauth/internal/api"
 | 
						"tinyauth/internal/api"
 | 
				
			||||||
 | 
						"tinyauth/internal/assets"
 | 
				
			||||||
	"tinyauth/internal/auth"
 | 
						"tinyauth/internal/auth"
 | 
				
			||||||
	"tinyauth/internal/hooks"
 | 
						"tinyauth/internal/hooks"
 | 
				
			||||||
	"tinyauth/internal/providers"
 | 
						"tinyauth/internal/providers"
 | 
				
			||||||
@@ -22,29 +25,28 @@ var rootCmd = &cobra.Command{
 | 
				
			|||||||
	Short: "The simplest way to protect your apps with a login screen.",
 | 
						Short: "The simplest way to protect your apps with a login screen.",
 | 
				
			||||||
	Long:  `Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.`,
 | 
						Long:  `Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.`,
 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
							// Logger
 | 
				
			||||||
 | 
							log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger().Level(zerolog.FatalLevel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Get config
 | 
							// Get config
 | 
				
			||||||
		log.Info().Msg("Parsing config")
 | 
					 | 
				
			||||||
		var config types.Config
 | 
							var config types.Config
 | 
				
			||||||
		parseErr := viper.Unmarshal(&config)
 | 
							parseErr := viper.Unmarshal(&config)
 | 
				
			||||||
		HandleError(parseErr, "Failed to parse config")
 | 
							HandleError(parseErr, "Failed to parse config")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Secrets
 | 
							// Secrets
 | 
				
			||||||
		log.Info().Msg("Parsing secrets")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		config.Secret = utils.GetSecret(config.Secret, config.SecretFile)
 | 
							config.Secret = utils.GetSecret(config.Secret, config.SecretFile)
 | 
				
			||||||
		config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile)
 | 
							config.GithubClientSecret = utils.GetSecret(config.GithubClientSecret, config.GithubClientSecretFile)
 | 
				
			||||||
		config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile)
 | 
							config.GoogleClientSecret = utils.GetSecret(config.GoogleClientSecret, config.GoogleClientSecretFile)
 | 
				
			||||||
		config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile)
 | 
							config.GenericClientSecret = utils.GetSecret(config.GenericClientSecret, config.GenericClientSecretFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Validate config
 | 
							// Validate config
 | 
				
			||||||
		log.Info().Msg("Validating config")
 | 
					 | 
				
			||||||
		validator := validator.New()
 | 
							validator := validator.New()
 | 
				
			||||||
		validateErr := validator.Struct(config)
 | 
							validateErr := validator.Struct(config)
 | 
				
			||||||
		HandleError(validateErr, "Invalid config")
 | 
							HandleError(validateErr, "Failed to validate config")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Set log level
 | 
							// Logger
 | 
				
			||||||
		log.Info().Int8("log_level", config.LogLevel).Msg("Setting log level")
 | 
							log.Logger = log.Level(zerolog.Level(config.LogLevel))
 | 
				
			||||||
		log.Logger = log.Logger.Level(zerolog.Level(config.LogLevel))
 | 
							log.Info().Str("version", assets.Version).Msg("Starting tinyauth")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Users
 | 
							// Users
 | 
				
			||||||
		log.Info().Msg("Parsing users")
 | 
							log.Info().Msg("Parsing users")
 | 
				
			||||||
@@ -56,7 +58,7 @@ var rootCmd = &cobra.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Create oauth whitelist
 | 
							// Create oauth whitelist
 | 
				
			||||||
		oauthWhitelist := strings.Split(config.OAuthWhitelist, ",")
 | 
							oauthWhitelist := strings.Split(config.OAuthWhitelist, ",")
 | 
				
			||||||
		log.Debug().Strs("oauth_whitelist", oauthWhitelist).Msg("Parsed OAuth whitelist")
 | 
							log.Debug().Msg("Parsed OAuth whitelist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create OAuth config
 | 
							// Create OAuth config
 | 
				
			||||||
		oauthConfig := types.OAuthConfig{
 | 
							oauthConfig := types.OAuthConfig{
 | 
				
			||||||
@@ -72,7 +74,8 @@ var rootCmd = &cobra.Command{
 | 
				
			|||||||
			GenericUserURL:      config.GenericUserURL,
 | 
								GenericUserURL:      config.GenericUserURL,
 | 
				
			||||||
			AppURL:              config.AppURL,
 | 
								AppURL:              config.AppURL,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Debug().Interface("oauth_config", oauthConfig).Msg("Parsed OAuth config")
 | 
					
 | 
				
			||||||
 | 
							log.Debug().Msg("Parsed OAuth config")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create auth service
 | 
							// Create auth service
 | 
				
			||||||
		auth := auth.NewAuth(users, oauthWhitelist)
 | 
							auth := auth.NewAuth(users, oauthWhitelist)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/charmbracelet/huh"
 | 
						"github.com/charmbracelet/huh"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
	"golang.org/x/crypto/bcrypt"
 | 
						"golang.org/x/crypto/bcrypt"
 | 
				
			||||||
@@ -21,6 +22,8 @@ var CreateCmd = &cobra.Command{
 | 
				
			|||||||
	Short: "Create a user",
 | 
						Short: "Create a user",
 | 
				
			||||||
	Long:  `Create a user either interactively or by passing flags.`,
 | 
						Long:  `Create a user either interactively or by passing flags.`,
 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
							log.Logger = log.Level(zerolog.InfoLevel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if interactive {
 | 
							if interactive {
 | 
				
			||||||
			form := huh.NewForm(
 | 
								form := huh.NewForm(
 | 
				
			||||||
				huh.NewGroup(
 | 
									huh.NewGroup(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,17 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func UserCmd() *cobra.Command {
 | 
					func UserCmd() *cobra.Command {
 | 
				
			||||||
 | 
						// Create the user command
 | 
				
			||||||
	userCmd := &cobra.Command{
 | 
						userCmd := &cobra.Command{
 | 
				
			||||||
		Use:  "user",
 | 
							Use:   "user",
 | 
				
			||||||
		Short: "User utilities",
 | 
							Short: "User utilities",
 | 
				
			||||||
		Long: `Utilities for creating and verifying tinyauth compatible users.`,
 | 
							Long:  `Utilities for creating and verifying tinyauth compatible users.`,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add subcommands
 | 
				
			||||||
	userCmd.AddCommand(create.CreateCmd)
 | 
						userCmd.AddCommand(create.CreateCmd)
 | 
				
			||||||
	userCmd.AddCommand(verify.VerifyCmd)
 | 
						userCmd.AddCommand(verify.VerifyCmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Return the user command
 | 
				
			||||||
	return userCmd
 | 
						return userCmd
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -5,6 +5,7 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/charmbracelet/huh"
 | 
						"github.com/charmbracelet/huh"
 | 
				
			||||||
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
	"golang.org/x/crypto/bcrypt"
 | 
						"golang.org/x/crypto/bcrypt"
 | 
				
			||||||
@@ -21,6 +22,8 @@ var VerifyCmd = &cobra.Command{
 | 
				
			|||||||
	Short: "Verify a user is set up correctly",
 | 
						Short: "Verify a user is set up correctly",
 | 
				
			||||||
	Long:  `Verify a user is set up correctly meaning that it has a correct username and password.`,
 | 
						Long:  `Verify a user is set up correctly meaning that it has a correct username and password.`,
 | 
				
			||||||
	Run: func(cmd *cobra.Command, args []string) {
 | 
						Run: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
							log.Logger = log.Level(zerolog.InfoLevel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if interactive {
 | 
							if interactive {
 | 
				
			||||||
			form := huh.NewForm(
 | 
								form := huh.NewForm(
 | 
				
			||||||
				huh.NewGroup(
 | 
									huh.NewGroup(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,7 +114,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
 | 
								RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Interface("queries", queries).Msg("Redirecting to login")
 | 
							log.Debug().Interface("redirect_uri", fmt.Sprintf("%s://%s%s", proto, host, uri)).Msg("Redirecting to login")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if queryErr != nil {
 | 
							if queryErr != nil {
 | 
				
			||||||
			log.Error().Err(queryErr).Msg("Failed to build query")
 | 
								log.Error().Err(queryErr).Msg("Failed to build query")
 | 
				
			||||||
@@ -142,7 +142,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Interface("login", login).Msg("Got login request")
 | 
							log.Debug().Msg("Got login request")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		user := api.Auth.GetUser(login.Username)
 | 
							user := api.Auth.GetUser(login.Username)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -250,7 +250,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Interface("request", request).Msg("Got OAuth request")
 | 
							log.Debug().Msg("Got OAuth request")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		provider := api.Providers.GetProvider(request.Provider)
 | 
							provider := api.Providers.GetProvider(request.Provider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -266,7 +266,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		authURL := provider.GetAuthURL()
 | 
							authURL := provider.GetAuthURL()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Str("authURL", authURL).Msg("Got auth URL")
 | 
							log.Debug().Msg("Got auth URL")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		redirectURI := c.Query("redirect_uri")
 | 
							redirectURI := c.Query("redirect_uri")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -291,7 +291,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Interface("providerName", providerName).Msg("Got provider name")
 | 
							log.Debug().Interface("provider", providerName.Provider).Msg("Got provider name")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		code := c.Query("code")
 | 
							code := c.Query("code")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -301,7 +301,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Str("code", code).Msg("Got code")
 | 
							log.Debug().Msg("Got code")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		provider := api.Providers.GetProvider(providerName.Provider)
 | 
							provider := api.Providers.GetProvider(providerName.Provider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -312,9 +312,9 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		token, tokenErr := provider.ExchangeToken(code)
 | 
							_, tokenErr := provider.ExchangeToken(code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Str("token", token).Msg("Got token")
 | 
							log.Debug().Msg("Got token")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if handleApiError(c, "Failed to exchange token", tokenErr) {
 | 
							if handleApiError(c, "Failed to exchange token", tokenErr) {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@@ -363,7 +363,7 @@ func (api *API) SetupRoutes() {
 | 
				
			|||||||
			RedirectURI: redirectURI,
 | 
								RedirectURI: redirectURI,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.Debug().Interface("redirectQuery", redirectQuery).Msg("Got redirect query")
 | 
							log.Debug().Msg("Got redirect query")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if handleApiError(c, "Failed to build query", redirectQueryErr) {
 | 
							if handleApiError(c, "Failed to build query", redirectQueryErr) {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
v2.0.1
 | 
					v2.0.2
 | 
				
			||||||
@@ -50,7 +50,7 @@ func (auth *Auth) EmailWhitelisted(emailSrc string) bool {
 | 
				
			|||||||
func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie) {
 | 
					func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie) {
 | 
				
			||||||
	log.Debug().Msg("Creating session cookie")
 | 
						log.Debug().Msg("Creating session cookie")
 | 
				
			||||||
	sessions := sessions.Default(c)
 | 
						sessions := sessions.Default(c)
 | 
				
			||||||
	log.Debug().Interface("data", data).Msg("Setting session cookie")
 | 
						log.Debug().Msg("Setting session cookie")
 | 
				
			||||||
	sessions.Set("username", data.Username)
 | 
						sessions.Set("username", data.Username)
 | 
				
			||||||
	sessions.Set("provider", data.Provider)
 | 
						sessions.Set("provider", data.Provider)
 | 
				
			||||||
	sessions.Save()
 | 
						sessions.Save()
 | 
				
			||||||
@@ -70,13 +70,10 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error)
 | 
				
			|||||||
	cookieUsername := sessions.Get("username")
 | 
						cookieUsername := sessions.Get("username")
 | 
				
			||||||
	cookieProvider := sessions.Get("provider")
 | 
						cookieProvider := sessions.Get("provider")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Interface("cookieUsername", cookieUsername).Msg("Got username")
 | 
					 | 
				
			||||||
	log.Debug().Interface("cookieProvider", cookieProvider).Msg("Got provider")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	username, usernameOk := cookieUsername.(string)
 | 
						username, usernameOk := cookieUsername.(string)
 | 
				
			||||||
	provider, providerOk := cookieProvider.(string)
 | 
						provider, providerOk := cookieProvider.(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Str("username", username).Bool("usernameOk", usernameOk).Str("provider", provider).Bool("providerOk", providerOk).Msg("Parsed cookie")
 | 
						log.Debug().Str("username", username).Str("provider", provider).Msg("Parsed cookie")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !usernameOk || !providerOk {
 | 
						if !usernameOk || !providerOk {
 | 
				
			||||||
		log.Warn().Msg("Session cookie invalid")
 | 
							log.Warn().Msg("Session cookie invalid")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,8 +34,6 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Interface("cookie", cookie).Msg("Got session cookie")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if cookie.Provider == "username" {
 | 
						if cookie.Provider == "username" {
 | 
				
			||||||
		log.Debug().Msg("Provider is username")
 | 
							log.Debug().Msg("Provider is username")
 | 
				
			||||||
		if hooks.Auth.GetUser(cookie.Username) != nil {
 | 
							if hooks.Auth.GetUser(cookie.Username) != nil {
 | 
				
			||||||
@@ -55,7 +53,7 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
 | 
				
			|||||||
	if provider != nil {
 | 
						if provider != nil {
 | 
				
			||||||
		log.Debug().Msg("Provider exists")
 | 
							log.Debug().Msg("Provider exists")
 | 
				
			||||||
		if !hooks.Auth.EmailWhitelisted(cookie.Username) {
 | 
							if !hooks.Auth.EmailWhitelisted(cookie.Username) {
 | 
				
			||||||
			log.Error().Msgf("Email %s not whitelisted", cookie.Username)
 | 
								log.Error().Str("email", cookie.Username).Msg("Email is not whitelisted")
 | 
				
			||||||
			hooks.Auth.DeleteSessionCookie(c)
 | 
								hooks.Auth.DeleteSessionCookie(c)
 | 
				
			||||||
			return types.UserContext{
 | 
								return types.UserContext{
 | 
				
			||||||
				Username:   "",
 | 
									Username:   "",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ func GetGenericEmail(client *http.Client, url string) (string, error) {
 | 
				
			|||||||
		return "", jsonErr
 | 
							return "", jsonErr
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Interface("user", user).Msg("Parsed user from generic provider")
 | 
						log.Debug().Msg("Parsed user from generic provider")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return user.Email, nil
 | 
						return user.Email, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ func GetGithubEmail(client *http.Client) (string, error) {
 | 
				
			|||||||
		return "", jsonErr
 | 
							return "", jsonErr
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Interface("emails", emails).Msg("Parsed emails from github")
 | 
						log.Debug().Msg("Parsed emails from github")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, email := range emails {
 | 
						for _, email := range emails {
 | 
				
			||||||
		if email.Primary {
 | 
							if email.Primary {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,7 +41,7 @@ func GetGoogleEmail(client *http.Client) (string, error) {
 | 
				
			|||||||
		return "", jsonErr
 | 
							return "", jsonErr
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Interface("user", user).Msg("Parsed user from google")
 | 
						log.Debug().Msg("Parsed user from google")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return user.Email, nil
 | 
						return user.Email, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,9 +19,9 @@ type User struct {
 | 
				
			|||||||
type Users []User
 | 
					type Users []User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
	Port                    int    `mapstructure:"port"`
 | 
						Port                    int    `mapstructure:"port" validate:"required"`
 | 
				
			||||||
	Address                 string `validate:"ip4_addr" mapstructure:"address"`
 | 
						Address                 string `validate:"required,ip4_addr" mapstructure:"address"`
 | 
				
			||||||
	Secret                  string `validate:"len=32" mapstructure:"secret"`
 | 
						Secret                  string `validate:"required,len=32" mapstructure:"secret"`
 | 
				
			||||||
	SecretFile              string `mapstructure:"secret-file"`
 | 
						SecretFile              string `mapstructure:"secret-file"`
 | 
				
			||||||
	AppURL                  string `validate:"required,url" mapstructure:"app-url"`
 | 
						AppURL                  string `validate:"required,url" mapstructure:"app-url"`
 | 
				
			||||||
	Users                   string `mapstructure:"users"`
 | 
						Users                   string `mapstructure:"users"`
 | 
				
			||||||
@@ -43,7 +43,7 @@ type Config struct {
 | 
				
			|||||||
	DisableContinue         bool   `mapstructure:"disable-continue"`
 | 
						DisableContinue         bool   `mapstructure:"disable-continue"`
 | 
				
			||||||
	OAuthWhitelist          string `mapstructure:"oauth-whitelist"`
 | 
						OAuthWhitelist          string `mapstructure:"oauth-whitelist"`
 | 
				
			||||||
	CookieExpiry            int    `mapstructure:"cookie-expiry"`
 | 
						CookieExpiry            int    `mapstructure:"cookie-expiry"`
 | 
				
			||||||
	LogLevel                int8   `mapstructure:"log-level"`
 | 
						LogLevel                int8   `mapstructure:"log-level" validate:"min=-1,max=5"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserContext struct {
 | 
					type UserContext struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,15 +15,12 @@ func ParseUsers(users string) (types.Users, error) {
 | 
				
			|||||||
	var usersParsed types.Users
 | 
						var usersParsed types.Users
 | 
				
			||||||
	userList := strings.Split(users, ",")
 | 
						userList := strings.Split(users, ",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Strs("users", userList).Msg("Splitted users")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(userList) == 0 {
 | 
						if len(userList) == 0 {
 | 
				
			||||||
		return types.Users{}, errors.New("invalid user format")
 | 
							return types.Users{}, errors.New("invalid user format")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, user := range userList {
 | 
						for _, user := range userList {
 | 
				
			||||||
		userSplit := strings.Split(user, ":")
 | 
							userSplit := strings.Split(user, ":")
 | 
				
			||||||
		log.Debug().Strs("user", userSplit).Msg("Splitting user")
 | 
					 | 
				
			||||||
		if len(userSplit) != 2 {
 | 
							if len(userSplit) != 2 {
 | 
				
			||||||
			return types.Users{}, errors.New("invalid user format")
 | 
								return types.Users{}, errors.New("invalid user format")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -33,7 +30,7 @@ func ParseUsers(users string) (types.Users, error) {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Interface("users", usersParsed).Msg("Parsed users")
 | 
						log.Debug().Msg("Parsed users")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return usersParsed, nil
 | 
						return usersParsed, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -45,7 +42,7 @@ func GetRootURL(urlSrc string) (string, error) {
 | 
				
			|||||||
		return "", parseErr
 | 
							return "", parseErr
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	urlSplitted := strings.Split(urlParsed.Host, ".")
 | 
						urlSplitted := strings.Split(urlParsed.Hostname(), ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	urlFinal := strings.Join(urlSplitted[1:], ".")
 | 
						urlFinal := strings.Join(urlSplitted[1:], ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,15 +80,13 @@ func ParseFileToLine(content string) string {
 | 
				
			|||||||
	return strings.Join(users, ",")
 | 
						return strings.Join(users, ",")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetSecret(env string, file string) string {
 | 
					func GetSecret(conf string, file string) string {
 | 
				
			||||||
	if env == "" && file == "" {
 | 
						if conf == "" && file == "" {
 | 
				
			||||||
		log.Debug().Msg("No secret provided")
 | 
					 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if env != "" {
 | 
						if conf != "" {
 | 
				
			||||||
		log.Debug().Str("secret", env).Msg("Using secret from env")
 | 
							return conf
 | 
				
			||||||
		return env
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	contents, err := ReadFile(file)
 | 
						contents, err := ReadFile(file)
 | 
				
			||||||
@@ -100,28 +95,26 @@ func GetSecret(env string, file string) string {
 | 
				
			|||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Debug().Str("secret", contents).Msg("Using secret from file")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return contents
 | 
						return contents
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetUsers(env string, file string) (types.Users, error) {
 | 
					func GetUsers(conf string, file string) (types.Users, error) {
 | 
				
			||||||
	var users string
 | 
						var users string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if env == "" && file == "" {
 | 
						if conf == "" && file == "" {
 | 
				
			||||||
		return types.Users{}, errors.New("no users provided")
 | 
							return types.Users{}, errors.New("no users provided")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if env != "" {
 | 
						if conf != "" {
 | 
				
			||||||
		log.Debug().Str("users", env).Msg("Using users from env")
 | 
							log.Debug().Msg("Using users from config")
 | 
				
			||||||
		users += env
 | 
							users += conf
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if file != "" {
 | 
						if file != "" {
 | 
				
			||||||
		fileContents, fileErr := ReadFile(file)
 | 
							fileContents, fileErr := ReadFile(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if fileErr == nil {
 | 
							if fileErr == nil {
 | 
				
			||||||
			log.Debug().Str("users", ParseFileToLine(fileContents)).Msg("Using users from file")
 | 
								log.Debug().Msg("Using users from file")
 | 
				
			||||||
			if users != "" {
 | 
								if users != "" {
 | 
				
			||||||
				users += ","
 | 
									users += ","
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								main.go
									
									
									
									
									
								
							@@ -4,7 +4,6 @@ import (
 | 
				
			|||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
	"tinyauth/cmd"
 | 
						"tinyauth/cmd"
 | 
				
			||||||
	"tinyauth/internal/assets"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/rs/zerolog"
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
	"github.com/rs/zerolog/log"
 | 
						"github.com/rs/zerolog/log"
 | 
				
			||||||
@@ -12,8 +11,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	// Logger
 | 
						// Logger
 | 
				
			||||||
	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger()
 | 
						log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Logger().Level(zerolog.FatalLevel)
 | 
				
			||||||
	log.Info().Str("version", assets.Version).Msg("Starting tinyauth")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Run cmd
 | 
						// Run cmd
 | 
				
			||||||
	cmd.Execute()
 | 
						cmd.Execute()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								site/bun.lockb
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								site/bun.lockb
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,8 +1,9 @@
 | 
				
			|||||||
import { Button, Paper, Text } from "@mantine/core";
 | 
					import { Button, Code, Paper, Text } from "@mantine/core";
 | 
				
			||||||
import { notifications } from "@mantine/notifications";
 | 
					import { notifications } from "@mantine/notifications";
 | 
				
			||||||
import { Navigate } from "react-router";
 | 
					import { Navigate } from "react-router";
 | 
				
			||||||
import { useUserContext } from "../context/user-context";
 | 
					import { useUserContext } from "../context/user-context";
 | 
				
			||||||
import { Layout } from "../components/layouts/layout";
 | 
					import { Layout } from "../components/layouts/layout";
 | 
				
			||||||
 | 
					import { ReactNode } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ContinuePage = () => {
 | 
					export const ContinuePage = () => {
 | 
				
			||||||
  const queryString = window.location.search;
 | 
					  const queryString = window.location.search;
 | 
				
			||||||
@@ -12,11 +13,11 @@ export const ContinuePage = () => {
 | 
				
			|||||||
  const { isLoggedIn, disableContinue } = useUserContext();
 | 
					  const { isLoggedIn, disableContinue } = useUserContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!isLoggedIn) {
 | 
					  if (!isLoggedIn) {
 | 
				
			||||||
    return <Navigate to="/login" />;
 | 
					    return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (disableContinue && redirectUri !== "null") {
 | 
					  if (redirectUri === "null") {
 | 
				
			||||||
    window.location.replace(redirectUri!);
 | 
					    return <Navigate to="/" />;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const redirect = () => {
 | 
					  const redirect = () => {
 | 
				
			||||||
@@ -26,31 +27,62 @@ export const ContinuePage = () => {
 | 
				
			|||||||
      color: "blue",
 | 
					      color: "blue",
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
      window.location.replace(redirectUri!);
 | 
					      window.location.href = redirectUri!;
 | 
				
			||||||
    }, 500);
 | 
					    }, 500);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const urlParsed = URL.parse(redirectUri!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    window.location.protocol === "https:" &&
 | 
				
			||||||
 | 
					    urlParsed!.protocol === "http:"
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <ContinuePageLayout>
 | 
				
			||||||
 | 
					        <Text size="xl" fw={700}>
 | 
				
			||||||
 | 
					          Insecure Redirect
 | 
				
			||||||
 | 
					        </Text>
 | 
				
			||||||
 | 
					        <Text>
 | 
				
			||||||
 | 
					          Your are logged in but trying to redirect from <Code>https</Code> to{" "}
 | 
				
			||||||
 | 
					          <Code>http</Code>, please click the button to redirect.
 | 
				
			||||||
 | 
					        </Text>
 | 
				
			||||||
 | 
					        <Button fullWidth mt="xl" onClick={redirect}>
 | 
				
			||||||
 | 
					          Continue
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </ContinuePageLayout>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (disableContinue) {
 | 
				
			||||||
 | 
					    window.location.href = redirectUri!;
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <ContinuePageLayout>
 | 
				
			||||||
 | 
					        <Text size="xl" fw={700}>
 | 
				
			||||||
 | 
					          Redirecting
 | 
				
			||||||
 | 
					        </Text>
 | 
				
			||||||
 | 
					        <Text>You should be redirected to your app soon.</Text>
 | 
				
			||||||
 | 
					      </ContinuePageLayout>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ContinuePageLayout>
 | 
				
			||||||
 | 
					      <Text size="xl" fw={700}>
 | 
				
			||||||
 | 
					        Continue
 | 
				
			||||||
 | 
					      </Text>
 | 
				
			||||||
 | 
					      <Text>Click the button to continue to your app.</Text>
 | 
				
			||||||
 | 
					      <Button fullWidth mt="xl" onClick={redirect}>
 | 
				
			||||||
 | 
					        Continue
 | 
				
			||||||
 | 
					      </Button>
 | 
				
			||||||
 | 
					    </ContinuePageLayout>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ContinuePageLayout = ({ children }: { children: ReactNode }) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Layout>
 | 
					    <Layout>
 | 
				
			||||||
      <Paper shadow="md" p={30} mt={30} radius="md" withBorder>
 | 
					      <Paper shadow="md" p={30} mt={30} radius="md" withBorder>
 | 
				
			||||||
        {redirectUri !== "null" ? (
 | 
					        {children}
 | 
				
			||||||
          <>
 | 
					 | 
				
			||||||
            <Text size="xl" fw={700}>
 | 
					 | 
				
			||||||
              Continue
 | 
					 | 
				
			||||||
            </Text>
 | 
					 | 
				
			||||||
            <Text>Click the button to continue to your app.</Text>
 | 
					 | 
				
			||||||
            <Button fullWidth mt="xl" onClick={redirect}>
 | 
					 | 
				
			||||||
              Continue
 | 
					 | 
				
			||||||
            </Button>
 | 
					 | 
				
			||||||
          </>
 | 
					 | 
				
			||||||
        ) : (
 | 
					 | 
				
			||||||
          <>
 | 
					 | 
				
			||||||
            <Text size="xl" fw={700}>
 | 
					 | 
				
			||||||
              Logged in
 | 
					 | 
				
			||||||
            </Text>
 | 
					 | 
				
			||||||
            <Text>You are now signed in and can use your apps.</Text>
 | 
					 | 
				
			||||||
          </>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </Paper>
 | 
					      </Paper>
 | 
				
			||||||
    </Layout>
 | 
					    </Layout>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,8 +65,12 @@ export const LoginPage = () => {
 | 
				
			|||||||
        color: "green",
 | 
					        color: "green",
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      setTimeout(() => {
 | 
					      setTimeout(() => {
 | 
				
			||||||
        window.location.replace(`/continue?redirect_uri=${redirectUri}`);
 | 
					        if (redirectUri === "null") {
 | 
				
			||||||
      });
 | 
					          window.location.replace("/");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          window.location.replace(`/continue?redirect_uri=${redirectUri}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }, 500);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,7 +88,14 @@ export const LoginPage = () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    onSuccess: (data) => {
 | 
					    onSuccess: (data) => {
 | 
				
			||||||
      window.location.replace(data.data.url);
 | 
					      notifications.show({
 | 
				
			||||||
 | 
					        title: "Redirecting",
 | 
				
			||||||
 | 
					        message: "Redirecting to your OAuth provider",
 | 
				
			||||||
 | 
					        color: "blue",
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      setTimeout(() => {
 | 
				
			||||||
 | 
					        window.location.href = data.data.url;
 | 
				
			||||||
 | 
					      }, 500);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,7 +32,7 @@ export const LogoutPage = () => {
 | 
				
			|||||||
        color: "green",
 | 
					        color: "green",
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      setTimeout(() => {
 | 
					      setTimeout(() => {
 | 
				
			||||||
        window.location.reload();
 | 
					        window.location.replace("/login");
 | 
				
			||||||
      }, 500);
 | 
					      }, 500);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user