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:
ryan
2026-08-29 11:39:13 +08:00
parent 53ae3007d0
commit e0f2309520
17 changed files with 411 additions and 32 deletions
+17 -2
View File
@@ -79,7 +79,9 @@ func Login(c *gin.Context) {
sess := sessions.Default(c)
sess.Set(contracts.AuthUserIDKey, user.ID)
sess.Set(contracts.AuthUserNameKey, user.Username)
_ = sess.Save()
if err := sess.Save(); err != nil {
logger.ErrorF(c.Request.Context(), "save session failed on login: %v", err)
}
c.JSON(http.StatusOK, response.OK(user))
}
@@ -107,14 +109,27 @@ func Register(c *gin.Context) {
return
}
sess := sessions.Default(c)
sess.Set(contracts.AuthUserIDKey, newUser.ID)
sess.Set(contracts.AuthUserNameKey, newUser.Username)
if err := sess.Save(); err != nil {
logger.ErrorF(c.Request.Context(), "save session failed on register: %v", err)
}
c.JSON(http.StatusOK, response.OK(newUser))
}
// Logout logs out the current session.
func Logout(c *gin.Context) {
sess := sessions.Default(c)
sess.Options(sessions.Options{
Path: "/",
MaxAge: -1,
})
sess.Clear()
_ = sess.Save()
if err := sess.Save(); err != nil {
logger.ErrorF(c.Request.Context(), "clear session failed on logout: %v", err)
}
c.JSON(http.StatusOK, response.OKNil())
}
@@ -8,10 +8,16 @@ import (
"Wavelet/core/contracts"
"Wavelet/pkg/idgen"
"Wavelet/plugins/domain/user"
"bytes"
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -111,3 +117,44 @@ func TestUserPluginUnit(t *testing.T) {
assert.GreaterOrEqual(t, total, int64(1))
assert.NotEmpty(t, list)
}
func TestUserLoginHTTPHandler(t *testing.T) {
ctx := core.NewContext(context.Background())
ctx.Config().SetSource(core.NewMapSource(nil))
require.NoError(t, ctx.Config().Resolve())
testDB := setupTestDB(t)
dbPlugin := database.New(database.WithDB(testDB))
require.NoError(t, dbPlugin.Apply(ctx))
p := user.New()
require.NoError(t, p.Apply(ctx))
userSvc, err := core.Inject[contracts.UserService](ctx)
require.NoError(t, err)
_, err = userSvc.CreateUser(context.Background(), contracts.CreateUserRequest{
Username: "admin",
Password: "Password123!",
Email: "admin@example.com",
})
require.NoError(t, err)
r := gin.New()
cookieStore := cookie.NewStore([]byte("test-session-secret"))
r.Use(sessions.Sessions("wavelet_session", cookieStore))
r.POST("/api/v1/user/login", user.Login)
reqBody := `{"username":"admin","password":"Password123!"}`
req, _ := http.NewRequest(http.MethodPost, "/api/v1/user/login", bytes.NewBufferString(reqBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), `"username":"admin"`)
setCookie := w.Header().Get("Set-Cookie")
assert.NotEmpty(t, setCookie)
assert.Contains(t, setCookie, "wavelet_session=")
}