mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-31 14:15:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			210 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package handlers
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 	"tinyauth/internal/auth"
 | |
| 	"tinyauth/internal/hooks"
 | |
| 	"tinyauth/internal/types"
 | |
| 
 | |
| 	"github.com/gin-gonic/gin"
 | |
| 	"github.com/google/go-querystring/query"
 | |
| 	"github.com/rs/zerolog/log"
 | |
| )
 | |
| 
 | |
| func NewHandlers(config types.APIConfig, auth *auth.Auth, hooks *hooks.Hooks) *Handlers {
 | |
| 	return &Handlers{
 | |
| 		Config: config,
 | |
| 		Auth:   auth,
 | |
| 		Hooks:  hooks,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type Handlers struct {
 | |
| 	Config types.APIConfig
 | |
| 	Auth   *auth.Auth
 | |
| 	Hooks  *hooks.Hooks
 | |
| }
 | |
| 
 | |
| // @Summary Health Check
 | |
| // @Description Simple health check
 | |
| // @Tags health
 | |
| // @Produce  json
 | |
| // @Success 200 {object} types.HealthCheckResponse
 | |
| // @Router /healthcheck [get]
 | |
| func (h *Handlers) HealthCheck(c *gin.Context) {
 | |
| 	c.JSON(200, gin.H{
 | |
| 		"status":  200,
 | |
| 		"message": "OK",
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // @Summary Logout
 | |
| // @Description Log the user out by invalidating the session cookie
 | |
| // @Tags auth
 | |
| // @Produce  json
 | |
| // @Success 200 {object} types.LogoutResponse
 | |
| // @Router /auth/logout [get]
 | |
| func (h *Handlers) Logout(c *gin.Context) {
 | |
| 	log.Debug().Msg("Logging out")
 | |
| 
 | |
| 	h.Auth.DeleteSessionCookie(c)
 | |
| 
 | |
| 	log.Debug().Msg("Cleaning up redirect cookie")
 | |
| 
 | |
| 	c.SetCookie("tinyauth_redirect_uri", "", -1, "/", h.Config.Domain, h.Config.CookieSecure, true)
 | |
| 
 | |
| 	c.JSON(200, gin.H{
 | |
| 		"status":  200,
 | |
| 		"message": "Logged out",
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // @Summary Auth Check (Traefik)
 | |
| // @Description Check the authentication status of the user and redirect to the login page if not authenticated
 | |
| // @Tags authn
 | |
| // @Produce  json
 | |
| // @Success 302
 | |
| // @Router /api/auth/traefik [get]
 | |
| func (h *Handlers) CheckAuth(c *gin.Context) {
 | |
| 	var proxy types.Proxy
 | |
| 
 | |
| 	err := c.BindUri(&proxy)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		log.Error().Err(err).Msg("Failed to bind URI")
 | |
| 		c.JSON(400, gin.H{
 | |
| 			"status":  400,
 | |
| 			"message": "Bad Request",
 | |
| 		})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
 | |
| 
 | |
| 	if isBrowser {
 | |
| 		log.Debug().Msg("Request is most likely coming from a browser")
 | |
| 	} else {
 | |
| 		log.Debug().Msg("Request is most likely not coming from a browser")
 | |
| 	}
 | |
| 
 | |
| 	log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
 | |
| 
 | |
| 	authEnabled, err := h.Auth.AuthEnabled(c)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		log.Error().Err(err).Msg("Failed to check if auth is enabled")
 | |
| 
 | |
| 		if proxy.Proxy == "nginx" || !isBrowser {
 | |
| 			c.JSON(500, gin.H{
 | |
| 				"status":  500,
 | |
| 				"message": "Internal Server Error",
 | |
| 			})
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !authEnabled {
 | |
| 		c.JSON(200, gin.H{
 | |
| 			"status":  200,
 | |
| 			"message": "Authenticated",
 | |
| 		})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	userContext := h.Hooks.UseUserContext(c)
 | |
| 
 | |
| 	uri := c.Request.Header.Get("X-Forwarded-Uri")
 | |
| 	proto := c.Request.Header.Get("X-Forwarded-Proto")
 | |
| 	host := c.Request.Header.Get("X-Forwarded-Host")
 | |
| 
 | |
| 	if userContext.IsLoggedIn {
 | |
| 		log.Debug().Msg("Authenticated")
 | |
| 
 | |
| 		appAllowed, err := h.Auth.ResourceAllowed(c, userContext)
 | |
| 
 | |
| 		if err != nil {
 | |
| 			log.Error().Err(err).Msg("Failed to check if app is allowed")
 | |
| 
 | |
| 			if proxy.Proxy == "nginx" || !isBrowser {
 | |
| 				c.JSON(500, gin.H{
 | |
| 					"status":  500,
 | |
| 					"message": "Internal Server Error",
 | |
| 				})
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		log.Debug().Bool("appAllowed", appAllowed).Msg("Checking if app is allowed")
 | |
| 
 | |
| 		if !appAllowed {
 | |
| 			log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed")
 | |
| 
 | |
| 			c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
 | |
| 
 | |
| 			if proxy.Proxy == "nginx" || !isBrowser {
 | |
| 				c.JSON(401, gin.H{
 | |
| 					"status":  401,
 | |
| 					"message": "Unauthorized",
 | |
| 				})
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			queries, err := query.Values(types.UnauthorizedQuery{
 | |
| 				Username: userContext.Username,
 | |
| 				Resource: strings.Split(host, ".")[0],
 | |
| 			})
 | |
| 
 | |
| 			if err != nil {
 | |
| 				log.Error().Err(err).Msg("Failed to build query")
 | |
| 				c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		c.Header("Remote-User", userContext.Username)
 | |
| 
 | |
| 		c.JSON(200, gin.H{
 | |
| 			"status":  200,
 | |
| 			"message": "Authenticated",
 | |
| 		})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	log.Debug().Msg("Unauthorized")
 | |
| 
 | |
| 	c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
 | |
| 
 | |
| 	if proxy.Proxy == "nginx" || !isBrowser {
 | |
| 		c.JSON(401, gin.H{
 | |
| 			"status":  401,
 | |
| 			"message": "Unauthorized",
 | |
| 		})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	queries, err := query.Values(types.LoginQuery{
 | |
| 		RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
 | |
| 	})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		log.Error().Err(err).Msg("Failed to build query")
 | |
| 		c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	log.Debug().Interface("redirect_uri", fmt.Sprintf("%s://%s%s", proto, host, uri)).Msg("Redirecting to login")
 | |
| 
 | |
| 	c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/?%s", h.Config.AppURL, queries.Encode()))
 | |
| }
 | 
