mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-31 06:05:43 +00:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6596e4dea6 | ||
|   | a2e6231cc4 | ||
|   | 644b343a1b | ||
|   | 7817add9b4 | ||
|   | fcaa3779d5 | ||
|   | e2f97d1fbe | ||
|   | 4f4645f32b | ||
|   | d0c1aae1e7 | ||
|   | b8a134ed12 | 
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,3 +3,9 @@ internal/assets/dist | |||||||
|  |  | ||||||
| # binaries | # binaries | ||||||
| tinyauth | tinyauth | ||||||
|  |  | ||||||
|  | # test docker compose | ||||||
|  | docker-compose.test.yml | ||||||
|  |  | ||||||
|  | # users file | ||||||
|  | users.txt | ||||||
							
								
								
									
										34
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,38 +1,16 @@ | |||||||
| # Tinyauth - The easiest way to secure your traefik apps with a login screen | # Tinyauth - The simplest way to protect your apps with a login screen | ||||||
|  |  | ||||||
| Tinyauth is an extremely simple traefik forward auth proxy that adds a login screen to all of your apps that are using the traefik reverse proxy. Tinyauth is configurable through environment variables and it is only 20MB in size. | Tinyauth is an extremely simple traefik middleware that adds a login screen to all of your apps that are using the traefik reverse proxy. Tinyauth is configurable through environment variables and it is only 20MB in size. | ||||||
|  |  | ||||||
| ## Screenshots |  | ||||||
|  |  | ||||||
| Talk is cheap, here are some screenshots: |  | ||||||
|  |  | ||||||
| |                                         |                                           | |  | ||||||
| | --------------------------------------- | ----------------------------------------- | |  | ||||||
| |        |        | |  | ||||||
| |  |  | |  | ||||||
|  |  | ||||||
| ## Getting started | ## Getting started | ||||||
|  |  | ||||||
| Tinyauth is extremely easy to run since it's shipped as a docker container. I chose to bundle it with busybox so as you can easily debug the API (e.g. using curl) and have some simple linux tools. If you want to get started with an example just check the example docker compose file [here](./docker-compose.example.yml) | Tinyauth is extremely easy to run since it's shipped as a docker container. The guide on how to get started is available on the website [here](https://tinyauth.doesmycode.work/). | ||||||
|  |  | ||||||
| ## Environment variables |  | ||||||
|  |  | ||||||
| Tinyauth accepts the following environment variables: |  | ||||||
|  |  | ||||||
| | Name       | Description                                             | Default | Required | |  | ||||||
| | ---------- | ------------------------------------------------------- | ------- | -------- | |  | ||||||
| | `PORT`     | The port the API listens on.                            | 3000    | no       | |  | ||||||
| | `ADDRESS`  | The address the API binds on.                           | 0.0.0.0 | no       | |  | ||||||
| | `SECRET`   | A 32 character long string used for the sessions.       | -       | yes      | |  | ||||||
| | `ROOT_URL` | The base URL of your domain. (e.g. https://example.com) | -       | yes      | |  | ||||||
| | `APP_URL`  | The Tinyauth URL. (e.g. https://tinyauth.example.com)   | -       | yes      | |  | ||||||
| | `USERS`    | Comma seperated list of `user:bcrypt-password-hash`.    | -       | yes      | |  | ||||||
|  |  | ||||||
| ## FAQ | ## FAQ | ||||||
|  |  | ||||||
| ### Why? | ### Why? | ||||||
|  |  | ||||||
| Why make this project? Well we all know that more powerful alternatives like authentik and authelia exist but when I tried to use them I felt overwhelmed with all the configration options and environment variables I had to configure in order for them to work, so, I decided to make a small alternative in Go to both test my skills and cover my simple login screen needs. | Why make this project? Well, we all know that more powerful alternatives like authentik and authelia exist, but when I tried to use them, I felt overwhelmed with all the configration options and environment variables I had to configure in order for them to work. So, I decided to make a small alternative in Go to both test my skills and cover my simple login screen needs. | ||||||
|  |  | ||||||
| ### Is this secure? | ### Is this secure? | ||||||
|  |  | ||||||
| @@ -40,7 +18,7 @@ Probably, the sessions are managed with the gin sessions package so it should be | |||||||
|  |  | ||||||
| ### Do I need to login every time? | ### Do I need to login every time? | ||||||
|  |  | ||||||
| No, when you login tinyauth sets a `tinyauth` cookie in your browser that applies to all of the subdomains of the root URL you set. | No, when you login, tinyauth sets a `tinyauth` cookie in your browser that applies to all of the subdomains of your domain. | ||||||
|  |  | ||||||
| ## License | ## License | ||||||
|  |  | ||||||
| @@ -48,7 +26,7 @@ Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You ma | |||||||
|  |  | ||||||
| ## Contributing | ## Contributing | ||||||
|  |  | ||||||
| Any contributions to the codebase are welcome! I am not a cybersecurity person so my code may have some vulnerability, if you find something that could be used to exploit and bypass tinyauth please tell me as soon as possible so I can fix it. | Any contributions to the codebase are welcome! I am not a cybersecurity person so my code may have a security issue, if you find something that could be used to exploit and bypass tinyauth please let me know as soon as possible so I can fix it. | ||||||
|  |  | ||||||
| ## Acknowledgements | ## Acknowledgements | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								cmd/root.go
									
									
									
									
									
								
							| @@ -2,6 +2,7 @@ package cmd | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 	"tinyauth/internal/api" | 	"tinyauth/internal/api" | ||||||
| 	"tinyauth/internal/assets" | 	"tinyauth/internal/assets" | ||||||
| @@ -36,10 +37,30 @@ var rootCmd = &cobra.Command{ | |||||||
| 		validateErr := validator.Struct(config) | 		validateErr := validator.Struct(config) | ||||||
| 		HandleError(validateErr, "Invalid config") | 		HandleError(validateErr, "Invalid config") | ||||||
|  |  | ||||||
| 		// Create users list | 		// Parse users | ||||||
| 		log.Info().Msg("Creating users list") | 		log.Info().Msg("Parsing users") | ||||||
| 		userList, createErr := utils.CreateUsersList(config.Users) |  | ||||||
| 		HandleError(createErr, "Failed to create users list") | 		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 | 		// Start server | ||||||
| 		log.Info().Msg("Starting server") | 		log.Info().Msg("Starting server") | ||||||
| @@ -66,14 +87,14 @@ func init() { | |||||||
| 	rootCmd.Flags().IntP("port", "p", 3000, "Port to run the server on.") | 	rootCmd.Flags().IntP("port", "p", 3000, "Port to run the server on.") | ||||||
| 	rootCmd.Flags().String("address", "0.0.0.0", "Address to bind the server to.") | 	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("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("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", "", "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("port", "PORT") | ||||||
| 	viper.BindEnv("address", "ADDRESS") | 	viper.BindEnv("address", "ADDRESS") | ||||||
| 	viper.BindEnv("secret", "SECRET") | 	viper.BindEnv("secret", "SECRET") | ||||||
| 	viper.BindEnv("root-url", "ROOT_URL") |  | ||||||
| 	viper.BindEnv("app-url", "APP_URL") | 	viper.BindEnv("app-url", "APP_URL") | ||||||
| 	viper.BindEnv("users", "USERS") | 	viper.BindEnv("users", "USERS") | ||||||
|  | 	viper.BindEnv("users-file", "USERS_FILE") | ||||||
| 	viper.BindPFlags(rootCmd.Flags()) | 	viper.BindPFlags(rootCmd.Flags()) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								docker-compose.dev.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								docker-compose.dev.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | services: | ||||||
|  |   traefik: | ||||||
|  |     container_name: traefik | ||||||
|  |     image: traefik:v3.3 | ||||||
|  |     command: --api.insecure=true --providers.docker | ||||||
|  |     ports: | ||||||
|  |       - 80:80 | ||||||
|  |     volumes: | ||||||
|  |       - /var/run/docker.sock:/var/run/docker.sock | ||||||
|  |     labels: | ||||||
|  |       traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth | ||||||
|  |  | ||||||
|  |   nginx: | ||||||
|  |     container_name: nginx | ||||||
|  |     image: nginx:latest | ||||||
|  |     labels: | ||||||
|  |       traefik.enable: true | ||||||
|  |       traefik.http.routers.nginx.rule: Host(`nginx.dev.local`) | ||||||
|  |       traefik.http.services.nginx.loadbalancer.server.port: 80 | ||||||
|  |       traefik.http.routers.nginx.middlewares: tinyauth | ||||||
|  |  | ||||||
|  |   tinyauth: | ||||||
|  |     container_name: tinyauth | ||||||
|  |     build: | ||||||
|  |       context: . | ||||||
|  |       dockerfile: Dockerfile | ||||||
|  |     environment: | ||||||
|  |       - SECRET=some-random-32-chars-string | ||||||
|  |       - APP_URL=http://tinyauth.dev.local | ||||||
|  |       - USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password | ||||||
|  |     labels: | ||||||
|  |       traefik.enable: true | ||||||
|  |       traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`) | ||||||
|  |       traefik.http.services.tinyauth.loadbalancer.server.port: 3000 | ||||||
| @@ -15,7 +15,7 @@ services: | |||||||
|     image: nginx:latest |     image: nginx:latest | ||||||
|     labels: |     labels: | ||||||
|       traefik.enable: true |       traefik.enable: true | ||||||
|       traefik.http.routers.nginx.rule: Host(`nginx.dev.local`) |       traefik.http.routers.nginx.rule: Host(`nginx.example.com`) | ||||||
|       traefik.http.services.nginx.loadbalancer.server.port: 80 |       traefik.http.services.nginx.loadbalancer.server.port: 80 | ||||||
|       traefik.http.routers.nginx.middlewares: tinyauth |       traefik.http.routers.nginx.middlewares: tinyauth | ||||||
|  |  | ||||||
| @@ -24,10 +24,9 @@ services: | |||||||
|     image: ghcr.io/steveiliop56/tinyauth:latest |     image: ghcr.io/steveiliop56/tinyauth:latest | ||||||
|     environment: |     environment: | ||||||
|       - SECRET=some-random-32-chars-string |       - SECRET=some-random-32-chars-string | ||||||
|       - ROOT_URL=https://example.com |  | ||||||
|       - APP_URL=https://tinyauth.example.com |       - APP_URL=https://tinyauth.example.com | ||||||
|       - USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u |       - USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password | ||||||
|     labels: |     labels: | ||||||
|       traefik.enable: true |       traefik.enable: true | ||||||
|       traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`) |       traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`) | ||||||
|       traefik.http.services.tinyauth.loadbalancer.server.port: 3000 |       traefik.http.services.tinyauth.loadbalancer.server.port: 3000 | ||||||
|   | |||||||
| @@ -9,7 +9,9 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| 	"tinyauth/internal/assets" | 	"tinyauth/internal/assets" | ||||||
| 	"tinyauth/internal/auth" | 	"tinyauth/internal/auth" | ||||||
|  | 	"tinyauth/internal/hooks" | ||||||
| 	"tinyauth/internal/types" | 	"tinyauth/internal/types" | ||||||
|  | 	"tinyauth/internal/utils" | ||||||
|  |  | ||||||
| 	"github.com/gin-contrib/sessions" | 	"github.com/gin-contrib/sessions" | ||||||
| 	"github.com/gin-contrib/sessions/cookie" | 	"github.com/gin-contrib/sessions/cookie" | ||||||
| @@ -32,7 +34,12 @@ func Run(config types.Config, users types.UserList) { | |||||||
| 	fileServer := http.FileServer(http.FS(dist)) | 	fileServer := http.FileServer(http.FS(dist)) | ||||||
| 	store := cookie.NewStore([]byte(config.Secret)) | 	store := cookie.NewStore([]byte(config.Secret)) | ||||||
|  |  | ||||||
| 	domain := strings.Split(config.RootURL, "://")[1] | 	domain, domainErr := utils.GetRootURL(config.AppURL) | ||||||
|  |  | ||||||
|  | 	if domainErr != nil { | ||||||
|  | 		log.Fatal().Err(domainErr).Msg("Failed to get domain") | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
| 	 | 	 | ||||||
| 	store.Options(sessions.Options{ | 	store.Options(sessions.Options{ | ||||||
| 		Domain: fmt.Sprintf(".%s", domain), | 		Domain: fmt.Sprintf(".%s", domain), | ||||||
| @@ -52,21 +59,15 @@ func Run(config types.Config, users types.UserList) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	router.GET("/api/auth", func (c *gin.Context) { | 	router.GET("/api/auth", func (c *gin.Context) { | ||||||
| 		session := sessions.Default(c) | 		userContext := hooks.UseUserContext(c, users) | ||||||
| 		value := session.Get("tinyauth") |  | ||||||
|  |  | ||||||
| 		if value != nil { | 		if userContext.IsLoggedIn { | ||||||
| 			usernameString, ok := value.(string) |  | ||||||
| 			if ok { |  | ||||||
| 				if auth.FindUser(users, usernameString) != nil { |  | ||||||
| 			c.JSON(200, gin.H{ | 			c.JSON(200, gin.H{ | ||||||
| 				"status": 200, | 				"status": 200, | ||||||
| 						"message": "Authorized", | 				"message": "Authenticated", | ||||||
| 			}) | 			}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		uri := c.Request.Header.Get("X-Forwarded-Uri") | 		uri := c.Request.Header.Get("X-Forwarded-Uri") | ||||||
| 		proto := c.Request.Header.Get("X-Forwarded-Proto") | 		proto := c.Request.Header.Get("X-Forwarded-Proto") | ||||||
| @@ -139,29 +140,23 @@ func Run(config types.Config, users types.UserList) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	router.GET("/api/status", func (c *gin.Context) { | 	router.GET("/api/status", func (c *gin.Context) { | ||||||
| 		session := sessions.Default(c) | 		userContext := hooks.UseUserContext(c, users) | ||||||
| 		value := session.Get("tinyauth") |  | ||||||
|  |  | ||||||
| 		if value != nil { | 		if !userContext.IsLoggedIn { | ||||||
| 			usernameString, ok := value.(string) |  | ||||||
| 			if ok { |  | ||||||
| 				if auth.FindUser(users, usernameString) != nil { |  | ||||||
| 			c.JSON(200, gin.H{ | 			c.JSON(200, gin.H{ | ||||||
| 				"status": 200, | 				"status": 200, | ||||||
| 						"isLoggedIn": true, | 				"message": "Unauthenticated", | ||||||
| 						"username": usernameString, | 				"username": "", | ||||||
| 						"version": assets.Version, | 				"isLoggedIn": false, | ||||||
| 			}) | 			}) | ||||||
| 			return | 			return | ||||||
| 		}  | 		}  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		c.JSON(200, gin.H{ | 		c.JSON(200, gin.H{ | ||||||
| 			"status": 200, | 			"status": 200, | ||||||
| 			"isLoggedIn": false, | 			"message": "Authenticated", | ||||||
| 			"username": "", | 			"username": userContext.Username, | ||||||
| 			"version": assets.Version, | 			"isLoggedIn": true, | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| v0.1.0 | v0.2.0 | ||||||
							
								
								
									
										44
									
								
								internal/hooks/hooks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								internal/hooks/hooks.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | package hooks | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"tinyauth/internal/auth" | ||||||
|  | 	"tinyauth/internal/types" | ||||||
|  |  | ||||||
|  | 	"github.com/gin-contrib/sessions" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func UseUserContext(c *gin.Context, userList types.UserList) (types.UserContext) { | ||||||
|  | 	session := sessions.Default(c) | ||||||
|  | 	cookie := session.Get("tinyauth") | ||||||
|  |  | ||||||
|  | 	if cookie == nil { | ||||||
|  | 		return types.UserContext{ | ||||||
|  | 			Username: "", | ||||||
|  | 			IsLoggedIn: false, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	username, ok := cookie.(string) | ||||||
|  |  | ||||||
|  | 	if !ok { | ||||||
|  | 		return types.UserContext{ | ||||||
|  | 			Username: "", | ||||||
|  | 			IsLoggedIn: false, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	user := auth.FindUser(userList, username) | ||||||
|  |  | ||||||
|  | 	if user == nil { | ||||||
|  | 		return types.UserContext{ | ||||||
|  | 			Username: "", | ||||||
|  | 			IsLoggedIn: false, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return types.UserContext{ | ||||||
|  | 		Username: username, | ||||||
|  | 		IsLoggedIn: true, | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -22,7 +22,12 @@ type Config struct { | |||||||
| 	Port int `validate:"number" mapstructure:"port"` | 	Port int `validate:"number" mapstructure:"port"` | ||||||
| 	Address string `mapstructure:"address, ip4_addr"` | 	Address string `mapstructure:"address, ip4_addr"` | ||||||
| 	Secret string `validate:"required,len=32" mapstructure:"secret"` | 	Secret string `validate:"required,len=32" mapstructure:"secret"` | ||||||
| 	RootURL string `validate:"required,url" mapstructure:"root-url"` |  | ||||||
| 	AppURL string `validate:"required,url" mapstructure:"app-url"` | 	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 { | ||||||
|  | 	Username string | ||||||
|  | 	IsLoggedIn bool | ||||||
| } | } | ||||||
| @@ -2,16 +2,18 @@ package utils | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"tinyauth/internal/types" | 	"tinyauth/internal/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func CreateUsersList(users string) (types.UserList, error) { | func ParseUsers(users string) (types.UserList, error) { | ||||||
| 	var userList types.UserList | 	var userList types.UserList | ||||||
| 	userListString := strings.Split(users, ",") | 	userListString := strings.Split(users, ",") | ||||||
|  |  | ||||||
| 	if len(userListString) == 0 { | 	if len(userListString) == 0 { | ||||||
| 		return types.UserList{}, errors.New("no users found") | 		return types.UserList{}, errors.New("invalid user format") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, user := range userListString { | 	for _, user := range userListString { | ||||||
| @@ -27,3 +29,33 @@ func CreateUsersList(users string) (types.UserList, error) { | |||||||
|  |  | ||||||
| 	return userList, nil | 	return userList, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func GetRootURL(urlSrc string) (string, error) { | ||||||
|  | 	urlParsed, parseErr := url.Parse(urlSrc) | ||||||
|  |  | ||||||
|  | 	if parseErr != nil { | ||||||
|  | 		return "", parseErr | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	urlSplitted := strings.Split(urlParsed.Host, ".") | ||||||
|  |  | ||||||
|  | 	urlFinal := urlSplitted[len(urlSplitted)-2] + "." + urlSplitted[len(urlSplitted)-1] | ||||||
|  |  | ||||||
|  | 	return urlFinal, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | } | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ export const ContinuePage = () => { | |||||||
|   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 ? ( |         {redirectUri !== "null" ? ( | ||||||
|           <> |           <> | ||||||
|             <Text size="xl" fw={700}> |             <Text size="xl" fw={700}> | ||||||
|               Continue |               Continue | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| 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 { useMutation } from "@tanstack/react-query"; | import { useMutation } from "@tanstack/react-query"; | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| @@ -43,8 +43,8 @@ export const LogoutPage = () => { | |||||||
|           Logout |           Logout | ||||||
|         </Text> |         </Text> | ||||||
|         <Text> |         <Text> | ||||||
|           You are currently logged in as {username}, click the button below to |           You are currently logged in as <Code>{username}</Code>, click the | ||||||
|           log out. |           button below to log out. | ||||||
|         </Text> |         </Text> | ||||||
|         <Button |         <Button | ||||||
|           fullWidth |           fullWidth | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user