mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-03 23:55:44 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			ea24aaa314
			...
			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
 | 
					name: Nightly Release
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: "0 0 * * *"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  create-release:
 | 
					  create-release:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,7 @@
 | 
				
			|||||||
    "unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
 | 
					    "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.",
 | 
					    "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>.",
 | 
					    "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",
 | 
					    "unauthorizedButton": "Try again",
 | 
				
			||||||
    "untrustedRedirectTitle": "Untrusted redirect",
 | 
					    "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?",
 | 
					    "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>.",
 | 
					    "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.",
 | 
					    "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>.",
 | 
					    "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",
 | 
					    "unauthorizedButton": "Try again",
 | 
				
			||||||
    "untrustedRedirectTitle": "Untrusted redirect",
 | 
					    "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?",
 | 
					    "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 username = searchParams.get("username");
 | 
				
			||||||
  const resource = searchParams.get("resource");
 | 
					  const resource = searchParams.get("resource");
 | 
				
			||||||
  const groupErr = searchParams.get("groupErr");
 | 
					  const groupErr = searchParams.get("groupErr");
 | 
				
			||||||
 | 
					  const ip = searchParams.get("ip");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!username) {
 | 
					  if (!username && !ip) {
 | 
				
			||||||
    return <Navigate to="/" />;
 | 
					    return <Navigate to="/" />;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,6 +42,10 @@ export const UnauthorizedPage = () => {
 | 
				
			|||||||
    i18nKey = "unauthorizedGroupsSubtitle";
 | 
					    i18nKey = "unauthorizedGroupsSubtitle";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (ip) {
 | 
				
			||||||
 | 
					    i18nKey = "unauthorizedIpSubtitle";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Card className="min-w-xs sm:min-w-sm">
 | 
					    <Card className="min-w-xs sm:min-w-sm">
 | 
				
			||||||
      <CardHeader>
 | 
					      <CardHeader>
 | 
				
			||||||
@@ -55,6 +60,7 @@ export const UnauthorizedPage = () => {
 | 
				
			|||||||
            values={{
 | 
					            values={{
 | 
				
			||||||
              username,
 | 
					              username,
 | 
				
			||||||
              resource,
 | 
					              resource,
 | 
				
			||||||
 | 
					              ip,
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </CardDescription>
 | 
					        </CardDescription>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -351,3 +351,44 @@ func (auth *Auth) GetBasicAuth(c *gin.Context) *types.User {
 | 
				
			|||||||
		Password: password,
 | 
							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
 | 
							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
 | 
						// Check if auth is enabled
 | 
				
			||||||
	authEnabled, err := h.Auth.AuthEnabled(c, labels)
 | 
						authEnabled, err := h.Auth.AuthEnabled(c, labels)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ type UnauthorizedQuery struct {
 | 
				
			|||||||
	Username string `url:"username"`
 | 
						Username string `url:"username"`
 | 
				
			||||||
	Resource string `url:"resource"`
 | 
						Resource string `url:"resource"`
 | 
				
			||||||
	GroupErr bool   `url:"groupErr"`
 | 
						GroupErr bool   `url:"groupErr"`
 | 
				
			||||||
 | 
						IP       string `url:"ip"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Proxy is the uri parameters for the proxy endpoint
 | 
					// Proxy is the uri parameters for the proxy endpoint
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -105,6 +105,12 @@ type BasicLabels struct {
 | 
				
			|||||||
	Password string
 | 
						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
 | 
					// Labels is a struct that contains the labels for a tinyauth protected container
 | 
				
			||||||
type Labels struct {
 | 
					type Labels struct {
 | 
				
			||||||
	Users   string
 | 
						Users   string
 | 
				
			||||||
@@ -113,4 +119,5 @@ type Labels struct {
 | 
				
			|||||||
	Domain  string
 | 
						Domain  string
 | 
				
			||||||
	Basic   BasicLabels
 | 
						Basic   BasicLabels
 | 
				
			||||||
	OAuth   OAuthLabels
 | 
						OAuth   OAuthLabels
 | 
				
			||||||
 | 
						IP      IPLabels
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package utils
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
@@ -202,7 +203,7 @@ func GetLabels(labels map[string]string) (types.Labels, error) {
 | 
				
			|||||||
	var labelsParsed types.Labels
 | 
						var labelsParsed types.Labels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Decode the labels into the labels struct
 | 
						// 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
 | 
						// Check if there was an error
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -368,3 +369,39 @@ func GetBasicAuth(username string, password string) string {
 | 
				
			|||||||
	// Encode the auth string to base64
 | 
						// Encode the auth string to base64
 | 
				
			||||||
	return base64.StdEncoding.EncodeToString([]byte(auth))
 | 
						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