Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions sources/dev/authentication-go/internal/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,18 @@ func (l *RateLimiter) Middleware() gin.HandlerFunc {

// --- CORS ---

// CORS mirrors the tower-http CorsLayer: echo allowed origins (or "*"), allow
// any method/header, and short-circuit preflight requests.
// corsAllowedHeaders is the explicit allow-list echoed on preflight responses.
// It must name every non-safelisted request header clients send. Authorization
// is listed explicitly on purpose: per the Fetch spec the "*" wildcard does NOT
// cover Authorization, so a wildcard would silently break cross-origin Bearer
// (and Basic, for /oauth) requests. X-Client-Id is the app identity header.
const corsAllowedHeaders = "Authorization, Content-Type, X-Client-Id"

// corsAllowedMethods is the explicit method allow-list for preflight responses.
const corsAllowedMethods = "GET, POST, PATCH, DELETE, OPTIONS"

// CORS mirrors the tower-http CorsLayer: echo allowed origins (or "*") and the
// explicit method/header allow-lists, then short-circuit preflight requests.
func CORS(allowedOrigins string) gin.HandlerFunc {
wildcard := strings.TrimSpace(allowedOrigins) == "*"
set := map[string]bool{}
Expand All @@ -388,8 +398,8 @@ func CORS(allowedOrigins string) gin.HandlerFunc {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Vary", "Origin")
}
c.Header("Access-Control-Allow-Methods", "*")
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Allow-Methods", corsAllowedMethods)
c.Header("Access-Control-Allow-Headers", corsAllowedHeaders)
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,11 @@ func TestRegisterLoginRefreshLogout(t *testing.T) {
}, ta.clientHeaders())
mustStatus(t, reuse, http.StatusUnauthorized)

// Logout authenticates via X-Client-Id (like refresh) and revokes by the
// refresh_token in the body, so it does not require a Bearer access token.
logout := ta.do(http.MethodPost, "/api/auth/logout", map[string]any{
"refresh_token": refreshResp.RefreshToken,
}, ta.bearer(refreshResp.AccessToken))
}, ta.clientHeaders())
mustStatus(t, logout, http.StatusOK)
}

Expand Down
6 changes: 4 additions & 2 deletions sources/dev/authentication-go/internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,17 @@ func NewRouter(repo repository.Repository, jwt *auth.JWTManager, cfg *config.Con
oauth.POST("/introspect", h.Introspect)
}

// Auth endpoints (X-Client-Id, except logout which is Bearer).
// Auth endpoints (X-Client-Id). Logout revokes by refresh_token in the body,
// so it uses ClientApp() like refresh instead of requiring a Bearer token;
// this keeps logout idempotent even when the access token has expired.
authGroup := r.Group("/api/auth")
authGroup.Use(authLimiter.Middleware())
{
authGroup.POST("/register", am.ClientApp(), h.Register)
authGroup.POST("/login", am.ClientApp(), h.Login)
authGroup.POST("/provider/:provider_id/login", am.ClientApp(), h.ProviderLogin)
authGroup.POST("/refresh", am.ClientApp(), h.Refresh)
authGroup.POST("/logout", am.AuthenticatedUser(), h.Logout)
authGroup.POST("/logout", am.ClientApp(), h.Logout)
}

// User endpoints (Bearer).
Expand Down
Loading