mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 16:46:37 +08:00
feat(router): add whitelist mechanism for http driver and auth plugin
- implement route whitelist registration and wildcard matching in RouterExtension - add cookie store session fallback when Redis is disabled in driver_http - actively register public auth endpoints to whitelist in auth plugin - update user handlers to persist session and clear cookie on logout - document router whitelist mechanism in AGENTS.md and new-api skill
This commit is contained in:
@@ -5,6 +5,7 @@ package auth
|
||||
|
||||
import (
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/core/extpoints"
|
||||
"Wavelet/pkg/ginutil"
|
||||
"Wavelet/pkg/response"
|
||||
"Wavelet/pkg/trace"
|
||||
@@ -12,10 +13,35 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
whitelistMu sync.RWMutex
|
||||
whitelist []string
|
||||
)
|
||||
|
||||
// RegisterWhitelist registers route patterns that bypass mandatory authentication.
|
||||
func RegisterWhitelist(patterns ...string) {
|
||||
whitelistMu.Lock()
|
||||
defer whitelistMu.Unlock()
|
||||
whitelist = append(whitelist, patterns...)
|
||||
}
|
||||
|
||||
// IsWhitelisted checks if the specified path matches the auth whitelist.
|
||||
func IsWhitelisted(path string) bool {
|
||||
whitelistMu.RLock()
|
||||
defer whitelistMu.RUnlock()
|
||||
for _, pattern := range whitelist {
|
||||
if extpoints.MatchPathPattern(pattern, path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hashToken(token string) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(token))
|
||||
@@ -112,6 +138,11 @@ func GetUserFromRequest(c *gin.Context) (*contracts.UserDTO, error) {
|
||||
// LoginRequired 返回登录鉴权中间件,校验 Access Token 或 Session
|
||||
func LoginRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if IsWhitelisted(c.Request.URL.Path) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
_, span := trace.Start(c.Request.Context(), "LoginRequired")
|
||||
defer span.End()
|
||||
|
||||
|
||||
@@ -122,6 +122,24 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
core.Provide[contracts.AuthService](ctx, p.authSvc)
|
||||
core.Provide[contracts.AuthRegistry](ctx, p.authRegistry)
|
||||
|
||||
// 2.1 Register Public / Auth Whitelist Endpoints
|
||||
publicEndpoints := []string{
|
||||
"/api/v1/oauth/sources",
|
||||
"/api/v1/oauth/login",
|
||||
"/api/v1/oauth/*/authorize",
|
||||
"/api/v1/oauth/:source/authorize",
|
||||
"/api/v1/oauth/callback",
|
||||
"/api/v1/user/login",
|
||||
"/api/v1/user/register",
|
||||
"/api/v1/user/send-email-code",
|
||||
"/api/v1/cap/challenge",
|
||||
"/api/v1/cap/redeem",
|
||||
"/healthz",
|
||||
"/metrics",
|
||||
}
|
||||
RegisterWhitelist(publicEndpoints...)
|
||||
ctx.Router().RegisterWhitelist(publicEndpoints...)
|
||||
|
||||
// 3. Register HTTP Routes
|
||||
oauthGroup := ctx.Router().Group("/api/v1/oauth")
|
||||
{
|
||||
|
||||
@@ -365,3 +365,37 @@ func TestLoginStateContextParityWithLegacyImplementation(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthWhitelistMiddleware(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctx := core.NewContext(context.Background())
|
||||
p := auth.New()
|
||||
require.NoError(t, p.Apply(ctx))
|
||||
|
||||
svc, err := core.Inject[contracts.AuthService](ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
mw, ok := svc.RequireAuthMiddleware().(gin.HandlerFunc)
|
||||
require.True(t, ok)
|
||||
|
||||
engine := newSessionEngine()
|
||||
engine.Use(mw)
|
||||
engine.POST("/api/v1/user/login", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response.OK("login-ok"))
|
||||
})
|
||||
engine.GET("/api/v1/secret-profile", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response.OK("profile-ok"))
|
||||
})
|
||||
|
||||
// 1. Whitelisted route /api/v1/user/login passes through without auth
|
||||
w1 := httptest.NewRecorder()
|
||||
req1, _ := http.NewRequest(http.MethodPost, "/api/v1/user/login", nil)
|
||||
engine.ServeHTTP(w1, req1)
|
||||
assert.Equal(t, http.StatusOK, w1.Code)
|
||||
|
||||
// 2. Non-whitelisted route /api/v1/secret-profile gets 401 Unauthorized
|
||||
w2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest(http.MethodGet, "/api/v1/secret-profile", nil)
|
||||
engine.ServeHTTP(w2, req2)
|
||||
assert.Equal(t, http.StatusUnauthorized, w2.Code)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user