feat(auth): host-based org resolution + session/host match guard
This commit is contained in:
@@ -55,6 +55,12 @@ func Middleware() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
c.Set(ctxSessionKey, sess)
|
||||
|
||||
if hostOrg, ok := OrgFromHost(c); ok && hostOrg.OrgID != sess.OrgID {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "org host mismatch"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mrhid6/vantage/server/internal/models"
|
||||
"github.com/mrhid6/vantage/server/internal/services"
|
||||
)
|
||||
|
||||
type cachedOrg struct {
|
||||
org *models.Org
|
||||
at time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
orgCacheMu sync.Mutex
|
||||
orgCache = map[string]cachedOrg{}
|
||||
)
|
||||
|
||||
const orgCacheTTL = 60 * time.Second
|
||||
|
||||
// hostSlug extracts the leftmost DNS label if the host is a subdomain of the
|
||||
// app root. Returns "" for the apex or an unknown host shape.
|
||||
func hostSlug(host string) string {
|
||||
host = strings.ToLower(host)
|
||||
if i := strings.IndexByte(host, ':'); i >= 0 {
|
||||
host = host[:i]
|
||||
}
|
||||
// Expect <slug>.vantage.<...>; apex is vantage.<...>
|
||||
parts := strings.Split(host, ".")
|
||||
if len(parts) < 3 {
|
||||
return ""
|
||||
}
|
||||
if parts[1] != "vantage" {
|
||||
return ""
|
||||
}
|
||||
if parts[0] == "vantage" || parts[0] == "www" {
|
||||
return ""
|
||||
}
|
||||
return parts[0]
|
||||
}
|
||||
|
||||
func OrgFromHost(c *gin.Context) (*models.Org, bool) {
|
||||
slug := hostSlug(c.Request.Host)
|
||||
if slug == "" {
|
||||
return nil, false
|
||||
}
|
||||
orgCacheMu.Lock()
|
||||
if e, ok := orgCache[slug]; ok && time.Since(e.at) < orgCacheTTL {
|
||||
orgCacheMu.Unlock()
|
||||
return e.org, e.org != nil
|
||||
}
|
||||
orgCacheMu.Unlock()
|
||||
|
||||
org, err := services.GetOrgBySlug(slug)
|
||||
if err != nil {
|
||||
org = nil
|
||||
}
|
||||
orgCacheMu.Lock()
|
||||
orgCache[slug] = cachedOrg{org: org, at: time.Now()}
|
||||
orgCacheMu.Unlock()
|
||||
return org, org != nil
|
||||
}
|
||||
Reference in New Issue
Block a user