From 03e7a14aa39c490e30c817e7501149e9bbb0029e Mon Sep 17 00:00:00 2001 From: xuans Date: Fri, 25 Sep 2026 02:12:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AA=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=A4=B1=E6=95=88=E5=B9=B6=E4=BF=9D=E7=95=99=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E6=9D=83=E9=99=90=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../httpapi/auth_optional_email_test.go | 81 +++++++++++++++++++ backend/internal/httpapi/auth_retention.go | 3 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 backend/internal/httpapi/auth_optional_email_test.go diff --git a/backend/internal/httpapi/auth_optional_email_test.go b/backend/internal/httpapi/auth_optional_email_test.go new file mode 100644 index 0000000..a0ddd07 --- /dev/null +++ b/backend/internal/httpapi/auth_optional_email_test.go @@ -0,0 +1,81 @@ +package httpapi + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "git.pcux.cn/xuans/Scert/backend/internal/database" + "git.pcux.cn/xuans/Scert/backend/internal/repository" +) + +func TestAuthSessionsSupportOptionalEmail(t *testing.T) { + for _, role := range []string{repository.UserRoleUser, repository.UserRoleAdmin} { + t.Run(role, func(t *testing.T) { + directory := t.TempDir() + cfg := readyTestConfig(directory) + cfg.Database.DSN = filepath.Join(directory, "optional-email.db") + db, err := database.Open(t.Context(), cfg.Database) + if err != nil { + t.Fatal(err) + } + defer db.Close() + if err := database.Migrate(t.Context(), db.DB, "sqlite"); err != nil { + t.Fatal(err) + } + users := repository.NewSQLUserRepository(db.DB, "sqlite") + user := repository.User{ + ID: "optional-email", Username: "optional-email", + PasswordHash: httpUserPasswordHash(t, "Operator123"), + Role: role, Status: repository.UserStatusActive, + } + if err := users.CreateUser(t.Context(), user); err != nil { + t.Fatal(err) + } + server := New(Dependencies{Config: cfg, Repository: &stubDomainRepository{}, Users: users}) + login := loginAuthTest(t, server, user.Username, "Operator123", false) + cookies := login.Result().Cookies() + if len(cookies) == 0 { + t.Fatal("missing session cookie") + } + loginAuthTest(t, server, user.Username, "Operator123", false) + response := authSessionRequest(server, cookies[0]) + if response.Code != http.StatusOK { + t.Fatalf("optional-email session: status=%d body=%s", response.Code, response.Body.String()) + } + var payload struct { + Authenticated bool `json:"authenticated"` + Email string `json:"email"` + Role string `json:"role"` + IsAdmin bool `json:"is_admin"` + } + if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil { + t.Fatal(err) + } + if !payload.Authenticated || payload.Email != "" || payload.Role != role || payload.IsAdmin != (role == repository.UserRoleAdmin) { + t.Fatalf("unexpected session identity: %+v", payload) + } + request := httptest.NewRequest(http.MethodGet, "/api/v1/domains", nil) + request.AddCookie(cookies[0]) + access := httptest.NewRecorder() + server.Handler().ServeHTTP(access, request) + expectedStatus := http.StatusForbidden + if role == repository.UserRoleAdmin { + expectedStatus = http.StatusOK + } + if access.Code != expectedStatus { + t.Fatalf("role boundary: status=%d want=%d", access.Code, expectedStatus) + } + user.Status = repository.UserStatusDisabled + if err := users.UpdateUser(t.Context(), user); err != nil { + t.Fatal(err) + } + disabled := authSessionRequest(server, cookies[0]) + if disabled.Code != http.StatusUnauthorized { + t.Fatalf("disabled session: status=%d", disabled.Code) + } + }) + } +} diff --git a/backend/internal/httpapi/auth_retention.go b/backend/internal/httpapi/auth_retention.go index 1db4c21..3b4d500 100644 --- a/backend/internal/httpapi/auth_retention.go +++ b/backend/internal/httpapi/auth_retention.go @@ -11,7 +11,8 @@ const authStateCapacity = 4096 const authUserSessionCapacity = 16 func (session authSession) complete() bool { - return strings.TrimSpace(session.UserID) != "" && strings.TrimSpace(session.Username) != "" && strings.TrimSpace(session.Email) != "" && + return strings.TrimSpace(session.UserID) != "" && strings.TrimSpace(session.Username) != "" && + (session.Role != repository.UserRoleSuperAdmin || strings.TrimSpace(session.Email) != "") && strings.TrimSpace(session.PasswordHash) != "" && (session.Role == repository.UserRoleUser || session.Role == repository.UserRoleAdmin || session.Role == repository.UserRoleSuperAdmin) }