mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-08-17 16:07:18 +02:00
Initial open source release
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/phishingclub/phishingclub/config"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func NewAllowIPMiddleware(conf *config.Config, logger *zap.SugaredLogger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// If no IP restrictions are configured, allow all
|
||||
if len(conf.IPSecurity.AdminAllowed) == 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.RemoteIP()
|
||||
clientIP := c.ClientIP()
|
||||
allowed := false
|
||||
for _, allowedIP := range conf.IPSecurity.AdminAllowed {
|
||||
// check if the allowed entry is a CIDR
|
||||
if strings.Contains(allowedIP, "/") {
|
||||
_, ipNet, err := net.ParseCIDR(allowedIP)
|
||||
if err != nil {
|
||||
logger.Errorw("Invalid CIDR in allowed IPs",
|
||||
"cidr", allowedIP,
|
||||
"error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
ip := net.ParseIP(clientIP)
|
||||
if ipNet.Contains(ip) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// Direct IP comparison
|
||||
if clientIP == allowedIP {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
logger.Infow("blocked unauthorized IP access attempt",
|
||||
"ip", clientIP)
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// NewIPRateLimiterMiddleware creates a middleware that limits the number of requests per IP
|
||||
// limit is the number of requests per second
|
||||
// burst is the maximum burst size, the maximum number of requests that can be made in a burst without being limited
|
||||
func NewIPRateLimiterMiddleware(limit float64, burst int) gin.HandlerFunc {
|
||||
ipLimiter := NewKeyRateLimiter(rate.Limit(limit), burst, 10*time.Minute)
|
||||
return func(c *gin.Context) {
|
||||
limiter := ipLimiter.GetLimiter(c.ClientIP())
|
||||
if !limiter.Allow() {
|
||||
c.AbortWithStatus(http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
//const cleanupInterval = 1 * time.Minute
|
||||
//const entryExpiration = 10 * time.Minute
|
||||
|
||||
// KeyRateLimiter is a rate limiter for key such as username, email or IP
|
||||
type KeyRateLimiter struct {
|
||||
// ips is a map of key to rate limit
|
||||
key sync.Map
|
||||
// limiter is the rate limit, e.g. 1 request per seconds
|
||||
limiter rate.Limit
|
||||
// burst is the maximum burst size, the maximum number of requests that can be made in a burst without being limited
|
||||
burst int
|
||||
// cleanupInterval is the interval at which the expired keys are cleaned up
|
||||
cleanupInterval time.Duration
|
||||
}
|
||||
|
||||
// NewKeyRateLimiter creates a new key rate limiter
|
||||
// limiter is the rate limit, e.g. 1 request per seconds
|
||||
// burst is the maximum burst size, the maximum number of requests that can be made in a burst without being limited
|
||||
func NewKeyRateLimiter(
|
||||
limiter rate.Limit,
|
||||
burst int,
|
||||
cleanupInterval time.Duration,
|
||||
) *KeyRateLimiter {
|
||||
rl := &KeyRateLimiter{
|
||||
limiter: limiter,
|
||||
burst: burst,
|
||||
}
|
||||
go rl.cleanup()
|
||||
return rl
|
||||
}
|
||||
|
||||
// cleanup cleans up the expired keys, this is to avoid
|
||||
// memory leaking through the sync.Map when the key is not used anymore
|
||||
func (r *KeyRateLimiter) cleanup() {
|
||||
for range time.Tick(r.cleanupInterval) {
|
||||
now := time.Now()
|
||||
r.key.Range(func(key, value interface{}) bool {
|
||||
expirationTime := value.(time.Time)
|
||||
if now.After(expirationTime) {
|
||||
r.key.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetLimiter gets the limiter for an key or creates one if it does not exist
|
||||
func (r *KeyRateLimiter) GetLimiter(key string) *rate.Limiter {
|
||||
value, exists := r.key.Load(key)
|
||||
if exists {
|
||||
return value.(*rate.Limiter)
|
||||
}
|
||||
|
||||
limiter := rate.NewLimiter(r.limiter, r.burst)
|
||||
r.key.Store(key, limiter)
|
||||
return limiter
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/phishingclub/phishingclub/api"
|
||||
"github.com/phishingclub/phishingclub/controller"
|
||||
"github.com/phishingclub/phishingclub/data"
|
||||
"github.com/phishingclub/phishingclub/model"
|
||||
"github.com/phishingclub/phishingclub/service"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NewSessionHandler creates a middleware that authenticates the user
|
||||
// by checking it has a session, and if it does, it extends the session and puts
|
||||
// the user and the session in the gin context.
|
||||
// if the user does not have a session or must renew password, it returns an unauthorized response.
|
||||
// if the request contains a valid user API key, the entire session handling is skipped
|
||||
func NewSessionHandler(
|
||||
sessionService *service.Session,
|
||||
userService *service.User,
|
||||
responseHandler api.JSONResponseHandler,
|
||||
logger *zap.SugaredLogger,
|
||||
) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
isValidAPISession := handleAPISession(c, userService, logger)
|
||||
if isValidAPISession {
|
||||
return
|
||||
}
|
||||
s, err := sessionService.GetAndExtendSession(c)
|
||||
if err != nil {
|
||||
// errors are logged in service
|
||||
_ = err
|
||||
responseHandler.Unauthorized(c)
|
||||
return
|
||||
}
|
||||
user := s.User
|
||||
if user == nil {
|
||||
logger.Error("user not found in session")
|
||||
responseHandler.Unauthorized(c)
|
||||
return
|
||||
}
|
||||
controller.SetSessionInGinContext(c, s)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// handleAPISession handles if there is a API token in the request header
|
||||
// returns true if this was a valid API session request
|
||||
func handleAPISession(
|
||||
c *gin.Context,
|
||||
userService *service.User,
|
||||
logger *zap.SugaredLogger,
|
||||
) bool {
|
||||
if headerAPIKey := c.Request.Header.Get(data.APIHeaderKey); len(headerAPIKey) > 0 {
|
||||
// to check API apiUsers in constant time, we have to retrieve them all
|
||||
// hash them all and constant time check.
|
||||
apiUsers, err := userService.GetAllAPIKeysSHA256(c)
|
||||
if err != nil {
|
||||
logger.Error("failed to get all api key hashes")
|
||||
// responseHandler.BadRequest(c)
|
||||
return false
|
||||
}
|
||||
incomingHash := sha256.Sum256([]byte(headerAPIKey))
|
||||
found := false
|
||||
// Must check ALL keys in constant time
|
||||
var rApiUser *model.APIUser
|
||||
for _, apiUser := range apiUsers {
|
||||
if subtle.ConstantTimeCompare(incomingHash[:], apiUser.APIKeyHash[:]) == 1 {
|
||||
found = true
|
||||
rApiUser = apiUser
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logger.Debug("API key not found")
|
||||
// responseHandler.Unauthorized(c)
|
||||
return false
|
||||
}
|
||||
// get user
|
||||
systemService, err := model.NewSystemSession()
|
||||
if err != nil {
|
||||
logger.Error("failed to get system user")
|
||||
return false
|
||||
}
|
||||
user, err := userService.GetByID(c, systemService, rApiUser.ID)
|
||||
if err != nil {
|
||||
logger.Error("failed to get user from API token")
|
||||
return false
|
||||
}
|
||||
now := time.Now()
|
||||
t := now.Add(time.Duration(1 * time.Minute)).UTC()
|
||||
expiresAt := &t
|
||||
maxAgeAt := &t
|
||||
sid := uuid.MustParse(data.APISessionID)
|
||||
session := &model.Session{
|
||||
ID: &sid,
|
||||
ExpiresAt: expiresAt,
|
||||
MaxAgeAt: maxAgeAt,
|
||||
IP: c.ClientIP(),
|
||||
User: user,
|
||||
IsUserLoaded: true,
|
||||
IsAPITokenRequest: true,
|
||||
}
|
||||
controller.SetSessionInGinContext(c, session)
|
||||
c.Next()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user