mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
feat: allow or block an ip/range of ips using labels
This commit is contained in:
@@ -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,9 +17,10 @@ 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) {
|
||||
return <Navigate to="/" />;
|
||||
if (!username && !ip) {
|
||||
return <Navigate to="/login" />;
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -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