mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-04 08:05:42 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			060e20e578
			...
			feat/ip-al
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5dda1d22e7 | ||
| 
						 | 
					1770eb3e8e | ||
| 
						 | 
					fae5e7919a | 
							
								
								
									
										2
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,8 @@
 | 
			
		||||
name: Nightly Release
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: "0 0 * * *"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  create-release:
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,7 @@
 | 
			
		||||
    "unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
 | 
			
		||||
    "unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedButton": "Try again",
 | 
			
		||||
    "untrustedRedirectTitle": "Untrusted redirect",
 | 
			
		||||
    "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,7 @@
 | 
			
		||||
    "unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedLoginSubtitle": "The user with username <code>{{username}}</code> is not authorized to login.",
 | 
			
		||||
    "unauthorizedGroupsSubtitle": "The user with username <code>{{username}}</code> is not in the groups required by the resource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
 | 
			
		||||
    "unauthorizedButton": "Try again",
 | 
			
		||||
    "untrustedRedirectTitle": "Untrusted redirect",
 | 
			
		||||
    "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{domain}}</code>). Are you sure you want to continue?",
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,9 @@ export const UnauthorizedPage = () => {
 | 
			
		||||
  const username = searchParams.get("username");
 | 
			
		||||
  const resource = searchParams.get("resource");
 | 
			
		||||
  const groupErr = searchParams.get("groupErr");
 | 
			
		||||
  const ip = searchParams.get("ip");
 | 
			
		||||
 | 
			
		||||
  if (!username) {
 | 
			
		||||
  if (!username && !ip) {
 | 
			
		||||
    return <Navigate to="/" />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +42,10 @@ export const UnauthorizedPage = () => {
 | 
			
		||||
    i18nKey = "unauthorizedGroupsSubtitle";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (ip) {
 | 
			
		||||
    i18nKey = "unauthorizedIpSubtitle";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Card className="min-w-xs sm:min-w-sm">
 | 
			
		||||
      <CardHeader>
 | 
			
		||||
@@ -55,6 +60,7 @@ export const UnauthorizedPage = () => {
 | 
			
		||||
            values={{
 | 
			
		||||
              username,
 | 
			
		||||
              resource,
 | 
			
		||||
              ip,
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </CardDescription>
 | 
			
		||||
 
 | 
			
		||||
@@ -351,3 +351,44 @@ func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
 | 
			
		||||
		Password: password,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (auth *Auth) CheckIP(c *gin.Context, labels types.Labels) bool {
 | 
			
		||||
	// Get the IP address from the request
 | 
			
		||||
	ip := c.ClientIP()
 | 
			
		||||
 | 
			
		||||
	// Check if the IP is in block list
 | 
			
		||||
	for _, blocked := range labels.IP.Block {
 | 
			
		||||
		res, err := utils.FilterIP(blocked, ip)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if res {
 | 
			
		||||
			log.Warn().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// For every IP in the allow list, check if the IP matches
 | 
			
		||||
	for _, allowed := range labels.IP.Allow {
 | 
			
		||||
		res, err := utils.FilterIP(allowed, ip)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if res {
 | 
			
		||||
			log.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If not in allowed range and allowed range is not empty, deny access
 | 
			
		||||
	if len(labels.IP.Allow) > 0 {
 | 
			
		||||
		log.Warn().Str("ip", ip).Msg("IP not in allow list, denying access")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,38 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the IP is allowed/blocked
 | 
			
		||||
	ip := c.ClientIP()
 | 
			
		||||
	if !h.Auth.CheckIP(c, labels) {
 | 
			
		||||
		log.Warn().Str("ip", ip).Msg("IP not allowed")
 | 
			
		||||
 | 
			
		||||
		if proxy.Proxy == "nginx" || !isBrowser {
 | 
			
		||||
			c.JSON(403, gin.H{
 | 
			
		||||
				"status":  403,
 | 
			
		||||
				"message": "Forbidden",
 | 
			
		||||
			})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		values := types.UnauthorizedQuery{
 | 
			
		||||
			Resource: strings.Split(host, ".")[0],
 | 
			
		||||
			IP:       ip,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Build query
 | 
			
		||||
		queries, err := query.Values(values)
 | 
			
		||||
 | 
			
		||||
		// Handle error
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error().Err(err).Msg("Failed to build queries")
 | 
			
		||||
			c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if auth is enabled
 | 
			
		||||
	authEnabled, err := h.Auth.AuthEnabled(c, labels)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ type UnauthorizedQuery struct {
 | 
			
		||||
	Username string `url:"username"`
 | 
			
		||||
	Resource string `url:"resource"`
 | 
			
		||||
	GroupErr bool   `url:"groupErr"`
 | 
			
		||||
	IP       string `url:"ip"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Proxy is the uri parameters for the proxy endpoint
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,12 @@ type BasicLabels struct {
 | 
			
		||||
	Password string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IP labels for a tinyauth protected container
 | 
			
		||||
type IPLabels struct {
 | 
			
		||||
	Allow []string
 | 
			
		||||
	Block []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Labels is a struct that contains the labels for a tinyauth protected container
 | 
			
		||||
type Labels struct {
 | 
			
		||||
	Users   string
 | 
			
		||||
@@ -113,4 +119,5 @@ type Labels struct {
 | 
			
		||||
	Domain  string
 | 
			
		||||
	Basic   BasicLabels
 | 
			
		||||
	OAuth   OAuthLabels
 | 
			
		||||
	IP      IPLabels
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package utils
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
@@ -202,7 +203,7 @@ func GetLabels(labels map[string]string) (types.Labels, error) {
 | 
			
		||||
	var labelsParsed types.Labels
 | 
			
		||||
 | 
			
		||||
	// Decode the labels into the labels struct
 | 
			
		||||
	err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.domain", "tinyauth.basic", "tinyauth.oauth")
 | 
			
		||||
	err := parser.Decode(labels, &labelsParsed, "tinyauth", "tinyauth.users", "tinyauth.allowed", "tinyauth.headers", "tinyauth.domain", "tinyauth.basic", "tinyauth.oauth", "tinyauth.ip")
 | 
			
		||||
 | 
			
		||||
	// Check if there was an error
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -368,3 +369,39 @@ func GetBasicAuth(username string, password string) string {
 | 
			
		||||
	// Encode the auth string to base64
 | 
			
		||||
	return base64.StdEncoding.EncodeToString([]byte(auth))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if an IP is contained in a CIDR range/matches a single IP
 | 
			
		||||
func FilterIP(filter string, ip string) (bool, error) {
 | 
			
		||||
	// Convert the check IP to an IP instance
 | 
			
		||||
	ipAddr := net.ParseIP(ip)
 | 
			
		||||
 | 
			
		||||
	// Check if the filter is a CIDR range
 | 
			
		||||
	if strings.Contains(filter, "/") {
 | 
			
		||||
		// Parse the CIDR range
 | 
			
		||||
		_, cidr, err := net.ParseCIDR(filter)
 | 
			
		||||
 | 
			
		||||
		// Check if there was an error
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check if the IP is in the CIDR range
 | 
			
		||||
		return cidr.Contains(ipAddr), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Parse the filter as a single IP
 | 
			
		||||
	ipFilter := net.ParseIP(filter)
 | 
			
		||||
 | 
			
		||||
	// Check if the IP is valid
 | 
			
		||||
	if ipFilter == nil {
 | 
			
		||||
		return false, errors.New("invalid IP address in filter")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the IP matches the filter
 | 
			
		||||
	if ipFilter.Equal(ipAddr) {
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the filter is not a CIDR range or a single IP, return false
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user