fix: fix authentification middlewares

This commit is contained in:
Jonas Kaninda
2024-11-24 22:13:26 +01:00
parent 6258b07c82
commit 3df8dce59b
7 changed files with 228 additions and 216 deletions

View File

@@ -53,6 +53,12 @@ func isPathBlocked(requestPath, blockedPath string) bool {
}
return false
}
func isProtectedPath(urlPath string, paths []string) bool {
for _, path := range paths {
return isPathBlocked(urlPath, util.ParseURLPath(path))
}
return false
}
// NewRateLimiter creates a new requests limiter with the specified refill requests and token capacity
func NewRateLimiter(maxTokens int, refillRate time.Duration) *TokenRateLimiter {

View File

@@ -29,73 +29,75 @@ import (
// authorization based on the result of backend's response and continue the request when the client is authorized
func (jwtAuth JwtAuth) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, header := range jwtAuth.RequiredHeaders {
if r.Header.Get(header) == "" {
logger.Error("Proxy error, missing %s header", header)
w.Header().Set("Content-Type", "application/json")
// check allowed origin
if allowedOrigin(jwtAuth.Origins, r.Header.Get("Origin")) {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
if isProtectedPath(r.URL.Path, jwtAuth.Paths) {
for _, header := range jwtAuth.RequiredHeaders {
if r.Header.Get(header) == "" {
logger.Error("Proxy error, missing %s header", header)
w.Header().Set("Content-Type", "application/json")
// check allowed origin
if allowedOrigin(jwtAuth.Origins, r.Header.Get("Origin")) {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
}
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
}
authURL, err := url.Parse(jwtAuth.AuthURL)
if err != nil {
logger.Error("Error parsing auth URL: %v", err)
RespondWithError(w, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
// Create a new request for /authentication
authReq, err := http.NewRequest("GET", authURL.String(), nil)
if err != nil {
logger.Error("Proxy error creating authentication request: %v", err)
RespondWithError(w, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
logger.Trace("JWT Auth response headers: %v", authReq.Header)
// Copy headers from the original request to the new request
for name, values := range r.Header {
for _, value := range values {
authReq.Header.Set(name, value)
}
}
// Copy cookies from the original request to the new request
for _, cookie := range r.Cookies() {
authReq.AddCookie(cookie)
}
// Perform the request to the auth service
client := &http.Client{}
authResp, err := client.Do(authReq)
if err != nil || authResp.StatusCode != http.StatusOK {
logger.Debug("%s %s %s %s", r.Method, getRealIP(r), r.URL, r.UserAgent())
logger.Debug("Proxy authentication error")
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
}
authURL, err := url.Parse(jwtAuth.AuthURL)
if err != nil {
logger.Error("Error parsing auth URL: %v", err)
RespondWithError(w, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
// Create a new request for /authentication
authReq, err := http.NewRequest("GET", authURL.String(), nil)
if err != nil {
logger.Error("Proxy error creating authentication request: %v", err)
RespondWithError(w, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}
logger.Trace("JWT Auth response headers: %v", authReq.Header)
// Copy headers from the original request to the new request
for name, values := range r.Header {
for _, value := range values {
authReq.Header.Set(name, value)
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
logger.Error("Error closing body: %v", err)
}
}(authResp.Body)
// Inject specific header tp the current request's header
// Add header to the next request from AuthRequest header, depending on your requirements
if jwtAuth.Headers != nil {
for k, v := range jwtAuth.Headers {
r.Header.Set(v, authResp.Header.Get(k))
}
}
}
// Copy cookies from the original request to the new request
for _, cookie := range r.Cookies() {
authReq.AddCookie(cookie)
}
// Perform the request to the auth service
client := &http.Client{}
authResp, err := client.Do(authReq)
if err != nil || authResp.StatusCode != http.StatusOK {
logger.Debug("%s %s %s %s", r.Method, getRealIP(r), r.URL, r.UserAgent())
logger.Debug("Proxy authentication error")
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
logger.Error("Error closing body: %v", err)
}
}(authResp.Body)
// Inject specific header tp the current request's header
// Add header to the next request from AuthRequest header, depending on your requirements
if jwtAuth.Headers != nil {
for k, v := range jwtAuth.Headers {
r.Header.Set(v, authResp.Header.Get(k))
query := r.URL.Query()
// Add query parameters to the next request from AuthRequest header, depending on your requirements
if jwtAuth.Params != nil {
for k, v := range jwtAuth.Params {
query.Set(v, authResp.Header.Get(k))
}
}
r.URL.RawQuery = query.Encode()
}
query := r.URL.Query()
// Add query parameters to the next request from AuthRequest header, depending on your requirements
if jwtAuth.Params != nil {
for k, v := range jwtAuth.Params {
query.Set(v, authResp.Header.Get(k))
}
}
r.URL.RawQuery = query.Encode()
next.ServeHTTP(w, r)
})
@@ -105,36 +107,37 @@ func (jwtAuth JwtAuth) AuthMiddleware(next http.Handler) http.Handler {
func (basicAuth AuthBasic) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Trace("Basic-Auth request headers: %v", r.Header)
// Get the Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
logger.Debug("Proxy error, missing Authorization header")
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
// Check if the Authorization header contains "Basic" scheme
if !strings.HasPrefix(authHeader, "Basic ") {
logger.Error("Proxy error, missing Basic Authorization header")
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
if isProtectedPath(r.URL.Path, basicAuth.Paths) {
// Get the Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
logger.Debug("Proxy error, missing Authorization header")
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
// Check if the Authorization header contains "Basic" scheme
if !strings.HasPrefix(authHeader, "Basic ") {
logger.Error("Proxy error, missing Basic Authorization header")
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
return
}
// Decode the base64 encoded username:password string
payload, err := base64.StdEncoding.DecodeString(authHeader[len("Basic "):])
if err != nil {
logger.Debug("Proxy error, missing Basic Authorization header")
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
// Split the payload into username and password
pair := strings.SplitN(string(payload), ":", 2)
if len(pair) != 2 || pair[0] != basicAuth.Username || pair[1] != basicAuth.Password {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
// Decode the base64 encoded username:password string
payload, err := base64.StdEncoding.DecodeString(authHeader[len("Basic "):])
if err != nil {
logger.Debug("Proxy error, missing Basic Authorization header")
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
// Split the payload into username and password
pair := strings.SplitN(string(payload), ":", 2)
if len(pair) != 2 || pair[0] != basicAuth.Username || pair[1] != basicAuth.Password {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
RespondWithError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
return
}
// Continue to the next handler if the authentication is successful

View File

@@ -26,27 +26,29 @@ import (
func (oauth Oauth) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
oauthConf := oauth2Config(oauth)
// Check if the user is authenticated
token, err := r.Cookie("goma.oauth")
if err != nil {
// If no token, redirect to OAuth provider
url := oauthConf.AuthCodeURL(oauth.State)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
return
}
ok, err := validateJWT(token.Value, oauth)
if err != nil {
// If no token, redirect to OAuth provider
url := oauthConf.AuthCodeURL(oauth.State)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
return
}
if !ok {
// If no token, redirect to OAuth provider
url := oauthConf.AuthCodeURL(oauth.State)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
return
if isProtectedPath(r.URL.Path, oauth.Paths) {
oauthConf := oauth2Config(oauth)
// Check if the user is authenticated
token, err := r.Cookie("goma.oauth")
if err != nil {
// If no token, redirect to OAuth provider
url := oauthConf.AuthCodeURL(oauth.State)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
return
}
ok, err := validateJWT(token.Value, oauth)
if err != nil {
// If no token, redirect to OAuth provider
url := oauthConf.AuthCodeURL(oauth.State)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
return
}
if !ok {
// If no token, redirect to OAuth provider
url := oauthConf.AuthCodeURL(oauth.State)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
return
}
}
// Token exists, proceed with request
next.ServeHTTP(w, r)

View File

@@ -79,6 +79,8 @@ type ProxyResponseError struct {
// JwtAuth stores JWT configuration
type JwtAuth struct {
RoutePath string
Paths []string
AuthURL string
RequiredHeaders []string
Headers map[string]string
@@ -101,6 +103,7 @@ type AccessListMiddleware struct {
// AuthBasic contains Basic auth configuration
type AuthBasic struct {
Paths []string
Username string
Password string
Headers map[string]string
@@ -120,6 +123,8 @@ type responseRecorder struct {
body *bytes.Buffer
}
type Oauth struct {
// Route protected path
Paths []string
// ClientID is the application's ID.
ClientID string
// ClientSecret is the application's secret.