From 69a8abc0c3ceb2ca617452ac88101cfb293b1374 Mon Sep 17 00:00:00 2001 From: lin040204 <14823478+lin040204@user.noreply.gitee.com> Date: Tue, 23 Sep 2025 15:36:00 +0800 Subject: [PATCH 1/3] feat(spx-backend):add authorize --- spx-backend/cmd/spx-backend/get_themes.yap | 3 +++ spx-backend/cmd/spx-backend/post_ai_interaction_turn.yap | 9 +++------ .../cmd/spx-backend/post_game_assets_complete.yap | 3 +++ spx-backend/cmd/spx-backend/post_image.yap | 3 +++ spx-backend/cmd/spx-backend/post_image_svg.yap | 3 +++ spx-backend/cmd/spx-backend/post_images_feedback.yap | 3 +++ .../cmd/spx-backend/post_images_instant_recommend.yap | 3 +++ spx-backend/cmd/spx-backend/post_images_recommend.yap | 4 +++- .../cmd/spx-backend/post_projects_context_generate.yap | 3 +++ spx-backend/cmd/spx-backend/post_util_fileurls.yap | 3 +++ 10 files changed, 30 insertions(+), 7 deletions(-) diff --git a/spx-backend/cmd/spx-backend/get_themes.yap b/spx-backend/cmd/spx-backend/get_themes.yap index cced888e7..21a7584e0 100644 --- a/spx-backend/cmd/spx-backend/get_themes.yap +++ b/spx-backend/cmd/spx-backend/get_themes.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} result, err := ctrl.GetThemes(ctx.Context()) if err != nil { diff --git a/spx-backend/cmd/spx-backend/post_ai_interaction_turn.yap b/spx-backend/cmd/spx-backend/post_ai_interaction_turn.yap index 1a4733378..a97ad816f 100644 --- a/spx-backend/cmd/spx-backend/post_ai_interaction_turn.yap +++ b/spx-backend/cmd/spx-backend/post_ai_interaction_turn.yap @@ -8,12 +8,9 @@ import ( ) ctx := &Context - -// FIXME: Uncomment the following line to ensure the user is authenticated once -// https://github.com/goplus/builder/issues/1673 is fixed. -// if _, ok := ensureAuthenticatedUser(ctx); !ok { -// return -// } +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.AIInteractionTurnParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_game_assets_complete.yap b/spx-backend/cmd/spx-backend/post_game_assets_complete.yap index ab96b03f5..f2e2360e0 100644 --- a/spx-backend/cmd/spx-backend/post_game_assets_complete.yap +++ b/spx-backend/cmd/spx-backend/post_game_assets_complete.yap @@ -3,6 +3,9 @@ // POST /game-assets/complete ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} // 解析JSON请求体 var req struct { diff --git a/spx-backend/cmd/spx-backend/post_image.yap b/spx-backend/cmd/spx-backend/post_image.yap index f771d71b1..7ab54a393 100644 --- a/spx-backend/cmd/spx-backend/post_image.yap +++ b/spx-backend/cmd/spx-backend/post_image.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.GenerateImageParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_image_svg.yap b/spx-backend/cmd/spx-backend/post_image_svg.yap index b49866333..cf86750ec 100644 --- a/spx-backend/cmd/spx-backend/post_image_svg.yap +++ b/spx-backend/cmd/spx-backend/post_image_svg.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.GenerateSVGParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_images_feedback.yap b/spx-backend/cmd/spx-backend/post_images_feedback.yap index accd5549f..17c65431c 100644 --- a/spx-backend/cmd/spx-backend/post_images_feedback.yap +++ b/spx-backend/cmd/spx-backend/post_images_feedback.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.ImageFeedbackParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_images_instant_recommend.yap b/spx-backend/cmd/spx-backend/post_images_instant_recommend.yap index a1452c6d6..907f075ec 100644 --- a/spx-backend/cmd/spx-backend/post_images_instant_recommend.yap +++ b/spx-backend/cmd/spx-backend/post_images_instant_recommend.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.InstantRecommendParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_images_recommend.yap b/spx-backend/cmd/spx-backend/post_images_recommend.yap index 4f0890a06..5487d6649 100644 --- a/spx-backend/cmd/spx-backend/post_images_recommend.yap +++ b/spx-backend/cmd/spx-backend/post_images_recommend.yap @@ -8,7 +8,9 @@ import ( ) ctx := &Context - +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.ImageRecommendParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_projects_context_generate.yap b/spx-backend/cmd/spx-backend/post_projects_context_generate.yap index 1ad111f8b..2d8120ff6 100644 --- a/spx-backend/cmd/spx-backend/post_projects_context_generate.yap +++ b/spx-backend/cmd/spx-backend/post_projects_context_generate.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.ProjectContextParams{} if !parseJSON(ctx, params) { diff --git a/spx-backend/cmd/spx-backend/post_util_fileurls.yap b/spx-backend/cmd/spx-backend/post_util_fileurls.yap index 6c0d555b0..f0de29462 100644 --- a/spx-backend/cmd/spx-backend/post_util_fileurls.yap +++ b/spx-backend/cmd/spx-backend/post_util_fileurls.yap @@ -8,6 +8,9 @@ import ( ) ctx := &Context +if _, ok := ensureAuthenticatedUser(ctx); !ok { + return +} params := &controller.MakeFileURLsParams{} if !parseJSON(ctx, params) { From 95c142fd9c458aae02be7f9e2d750ccd67ebfd1f Mon Sep 17 00:00:00 2001 From: lin040204 <14823478+lin040204@user.noreply.gitee.com> Date: Tue, 23 Sep 2025 15:47:44 +0800 Subject: [PATCH 2/3] feat(spx-backend):add authorize test file --- .../cmd/spx-backend/auth_final_test.go | 202 ++++++++++++++++++ spx-backend/go.mod | 7 +- spx-backend/go.sum | 6 - 3 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 spx-backend/cmd/spx-backend/auth_final_test.go diff --git a/spx-backend/cmd/spx-backend/auth_final_test.go b/spx-backend/cmd/spx-backend/auth_final_test.go new file mode 100644 index 000000000..ce2c7d838 --- /dev/null +++ b/spx-backend/cmd/spx-backend/auth_final_test.go @@ -0,0 +1,202 @@ +package main + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/goplus/builder/spx-backend/internal/authn" + "github.com/goplus/builder/spx-backend/internal/model" + "github.com/goplus/yap" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func createTestUser() *model.User { + return &model.User{ + Model: model.Model{ + ID: 1, + }, + Username: "testuser", + } +} + +func TestEnsureAuthenticatedUserFunction(t *testing.T) { + t.Run("WithAuthenticatedUser", func(t *testing.T) { + user := createTestUser() + ctx := authn.NewContextWithUser(context.Background(), user) + recorder := httptest.NewRecorder() + + // Create yap context similar to how it's done in the .yap files + yapCtx := &yap.Context{} + yapCtx.Request = httptest.NewRequest("GET", "/test", nil).WithContext(ctx) + yapCtx.ResponseWriter = recorder + + mUser, ok := ensureAuthenticatedUser(yapCtx) + + assert.True(t, ok, "Should return true for authenticated user") + assert.Equal(t, user, mUser, "Should return the same user") + // When user is authenticated, no response should be written by ensureAuthenticatedUser + // But yap might set a default 200 status, so we check it didn't write an error + assert.NotEqual(t, http.StatusUnauthorized, recorder.Code, "Should not write unauthorized response") + }) + + t.Run("WithoutAuthenticatedUser", func(t *testing.T) { + ctx := context.Background() + recorder := httptest.NewRecorder() + + // Create yap context without user + yapCtx := &yap.Context{} + yapCtx.Request = httptest.NewRequest("GET", "/test", nil).WithContext(ctx) + yapCtx.ResponseWriter = recorder + + mUser, ok := ensureAuthenticatedUser(yapCtx) + + assert.False(t, ok, "Should return false for unauthenticated user") + assert.Nil(t, mUser, "Should return nil user") + + // Check that unauthorized response was written + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + assert.Equal(t, "Bearer", recorder.Header().Get("WWW-Authenticate")) + + // Check error response body + var errorResp errorPayload + err := json.Unmarshal(recorder.Body.Bytes(), &errorResp) + require.NoError(t, err) + assert.Equal(t, errorUnauthorized, errorResp.Code) + assert.Equal(t, "Unauthorized", errorResp.Msg) + }) +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + name string + code errorCode + expectedHTTP int + expectedMsg string + expectWWWAuth bool + }{ + { + name: "Unauthorized", + code: errorUnauthorized, + expectedHTTP: 401, + expectedMsg: "Unauthorized", + expectWWWAuth: true, + }, + { + name: "Forbidden", + code: errorForbidden, + expectedHTTP: 403, + expectedMsg: "Forbidden", + expectWWWAuth: false, + }, + { + name: "NotFound", + code: errorNotFound, + expectedHTTP: 404, + expectedMsg: "Not found", + expectWWWAuth: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + yapCtx := &yap.Context{} + yapCtx.ResponseWriter = recorder + + replyWithCode(yapCtx, tt.code) + + assert.Equal(t, tt.expectedHTTP, recorder.Code) + + if tt.expectWWWAuth { + assert.Equal(t, "Bearer", recorder.Header().Get("WWW-Authenticate")) + } else { + assert.Empty(t, recorder.Header().Get("WWW-Authenticate")) + } + + var errorResp errorPayload + err := json.Unmarshal(recorder.Body.Bytes(), &errorResp) + require.NoError(t, err) + assert.Equal(t, tt.code, errorResp.Code) + assert.Equal(t, tt.expectedMsg, errorResp.Msg) + }) + } +} + +// Test that demonstrates the authentication flow as used in the .yap files +func TestYapFileAuthFlow(t *testing.T) { + t.Run("SimulatePostImageYapWithAuth", func(t *testing.T) { + user := createTestUser() + ctx := authn.NewContextWithUser(context.Background(), user) + + req := httptest.NewRequest("POST", "/image", nil) + req = req.WithContext(ctx) + recorder := httptest.NewRecorder() + + // Simulate the exact flow from post_image.yap + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + yapCtx := &yap.Context{} + yapCtx.Request = r + yapCtx.ResponseWriter = w + + // This is the exact check from our modified .yap files + if _, ok := ensureAuthenticatedUser(yapCtx); !ok { + return + } + + // If we get here, authentication succeeded + // Simulate successful image generation response + response := map[string]interface{}{ + "id": "test-image-123", + "svg_url": "https://example.com/image.svg", + "status": "success", + } + + yapCtx.JSON(200, response) + }) + + handler.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusOK, recorder.Code) + + var response map[string]interface{} + err := json.Unmarshal(recorder.Body.Bytes(), &response) + require.NoError(t, err) + assert.Equal(t, "test-image-123", response["id"]) + assert.Equal(t, "success", response["status"]) + }) + + t.Run("SimulatePostImageYapWithoutAuth", func(t *testing.T) { + // No user in context - simulates unauthenticated request + req := httptest.NewRequest("POST", "/image", nil) + recorder := httptest.NewRecorder() + + // Simulate the exact flow from post_image.yap + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + yapCtx := &yap.Context{} + yapCtx.Request = r + yapCtx.ResponseWriter = w + + // This should fail and return early + if _, ok := ensureAuthenticatedUser(yapCtx); !ok { + return + } + + // This should NOT be reached + t.Fatal("Handler should not reach this point without authentication") + }) + + handler.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + assert.Equal(t, "Bearer", recorder.Header().Get("WWW-Authenticate")) + + var errorResp errorPayload + err := json.Unmarshal(recorder.Body.Bytes(), &errorResp) + require.NoError(t, err) + assert.Equal(t, errorUnauthorized, errorResp.Code) + }) +} \ No newline at end of file diff --git a/spx-backend/go.mod b/spx-backend/go.mod index c2e1284d7..485d992b0 100644 --- a/spx-backend/go.mod +++ b/spx-backend/go.mod @@ -20,11 +20,7 @@ require ( gorm.io/gorm v1.30.1 ) -require ( - github.com/google/uuid v1.6.0 - github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c - github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef -) +require github.com/google/uuid v1.6.0 require ( filippo.io/edwards25519 v1.1.0 // indirect @@ -53,7 +49,6 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.10.0 // indirect diff --git a/spx-backend/go.sum b/spx-backend/go.sum index e88b95bff..b7aedb37f 100644 --- a/spx-backend/go.sum +++ b/spx-backend/go.sum @@ -198,10 +198,6 @@ github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= -github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= -github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -241,8 +237,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= From d496f2b7b8d1016c98edcf08a8f644b263dfb827 Mon Sep 17 00:00:00 2001 From: thewindwillstop <162791608+thewindwillstop@users.noreply.github.com> Date: Tue, 23 Sep 2025 15:57:22 +0800 Subject: [PATCH 3/3] Update spx-backend/cmd/spx-backend/auth_final_test.go Co-authored-by: niupilot[bot] <230321281+niupilot[bot]@users.noreply.github.com> --- spx-backend/cmd/spx-backend/auth_final_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spx-backend/cmd/spx-backend/auth_final_test.go b/spx-backend/cmd/spx-backend/auth_final_test.go index ce2c7d838..4ffbf5183 100644 --- a/spx-backend/cmd/spx-backend/auth_final_test.go +++ b/spx-backend/cmd/spx-backend/auth_final_test.go @@ -199,4 +199,5 @@ func TestYapFileAuthFlow(t *testing.T) { require.NoError(t, err) assert.Equal(t, errorUnauthorized, errorResp.Code) }) -} \ No newline at end of file + +}