* refactor(auth): replace dashboard sessions with stateless tokens * feat(auth): harden session issuance and distributed enforcement * fix(proxy): preserve trusted proxy compatibility defaults * refactor: address dashboard auth review feedback * refactor: remove classic frontend and flatten web app
77 lines
2.1 KiB
Go
77 lines
2.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// SessionCookieOriginGuard protects cookie-authenticated refresh/logout
|
|
// endpoints when secure cookie mode is enabled. In insecure local development
|
|
// mode it preserves the legacy behavior and intentionally performs no Origin
|
|
// validation. It never adds CORS response headers and must not be installed on
|
|
// relay routes.
|
|
func SessionCookieOriginGuard() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if !common.SessionCookieSecure {
|
|
c.Next()
|
|
return
|
|
}
|
|
origin, ok := requestBrowserOrigin(c.Request)
|
|
if !ok || !isAllowedSessionOrigin(c.Request, origin) {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"success": false,
|
|
"code": "AUTH_ORIGIN_FORBIDDEN",
|
|
"message": "request origin is not allowed",
|
|
})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func requestBrowserOrigin(request *http.Request) (string, bool) {
|
|
originValues := request.Header.Values("Origin")
|
|
if len(originValues) > 1 {
|
|
return "", false
|
|
}
|
|
if len(originValues) == 1 {
|
|
if strings.Contains(originValues[0], ",") {
|
|
return "", false
|
|
}
|
|
origin, err := common.NormalizeOrigin(originValues[0])
|
|
return origin, err == nil
|
|
}
|
|
refererValues := request.Header.Values("Referer")
|
|
if len(refererValues) != 1 {
|
|
return "", false
|
|
}
|
|
referer, err := url.Parse(strings.TrimSpace(refererValues[0]))
|
|
if err != nil || referer.Scheme == "" || referer.Host == "" || referer.User != nil {
|
|
return "", false
|
|
}
|
|
origin, err := common.NormalizeOrigin(referer.Scheme + "://" + referer.Host)
|
|
return origin, err == nil
|
|
}
|
|
|
|
func isAllowedSessionOrigin(request *http.Request, origin string) bool {
|
|
requestScheme := "http"
|
|
if request.TLS != nil {
|
|
requestScheme = "https"
|
|
}
|
|
requestOrigin, err := common.NormalizeOrigin(requestScheme + "://" + request.Host)
|
|
if err == nil && subtle.ConstantTimeCompare([]byte(origin), []byte(requestOrigin)) == 1 {
|
|
return true
|
|
}
|
|
for _, trustedOrigin := range common.SessionCookieTrustedURLs {
|
|
if subtle.ConstantTimeCompare([]byte(origin), []byte(trustedOrigin)) == 1 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|