100 lines
4.0 KiB
Go
100 lines
4.0 KiB
Go
package identity
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"yovision.local/sense/internal/platform"
|
|
)
|
|
|
|
func TestHTTPLoginCookieAndProductBoundary(t *testing.T) {
|
|
store := NewMemoryStore()
|
|
cfg := Config{SigningKey: []byte("0123456789abcdef0123456789abcdef"), BootstrapToken: "bootstrap-test", SessionTTL: time.Hour}
|
|
service, err := NewService(store, cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
app := platform.NewApp(platform.Config{DatabaseMode: platform.DatabaseModeMemory}, nil, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
NewModule(service, cfg).Register(app)
|
|
app.Handle("GET /same-origin-frame", RequireSameOriginFrame(PermissionMediaRead, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})))
|
|
|
|
bootstrap := httptest.NewRequest(http.MethodPost, "/api/v1/identity/bootstrap", bytes.NewBufferString(`{"username":"admin","display_name":"管理员","password":"StrongPass2026"}`))
|
|
bootstrap.Header.Set("X-Sense-Bootstrap-Token", "bootstrap-test")
|
|
bootstrapResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(bootstrapResult, bootstrap)
|
|
if bootstrapResult.Code != http.StatusCreated {
|
|
t.Fatalf("bootstrap status = %d, body = %s", bootstrapResult.Code, bootstrapResult.Body.String())
|
|
}
|
|
|
|
login := httptest.NewRequest(http.MethodPost, "/api/v1/identity/login", bytes.NewBufferString(`{"username":"admin","password":"StrongPass2026"}`))
|
|
loginResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(loginResult, login)
|
|
if loginResult.Code != http.StatusOK {
|
|
t.Fatalf("login status = %d, body = %s", loginResult.Code, loginResult.Body.String())
|
|
}
|
|
cookies := loginResult.Result().Cookies()
|
|
if len(cookies) != 1 || cookies[0].Name != sessionCookieName || !cookies[0].HttpOnly || cookies[0].SameSite != http.SameSiteStrictMode {
|
|
t.Fatalf("unexpected session cookies: %#v", cookies)
|
|
}
|
|
|
|
me := httptest.NewRequest(http.MethodGet, "/api/v1/identity/me", nil)
|
|
me.AddCookie(cookies[0])
|
|
me.Header.Set("X-Product", "bell")
|
|
foreignResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(foreignResult, me)
|
|
if foreignResult.Code != http.StatusUnauthorized {
|
|
t.Fatalf("Bell product header status = %d", foreignResult.Code)
|
|
}
|
|
|
|
me.Header.Set("X-Product", "sense")
|
|
senseResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(senseResult, me)
|
|
if senseResult.Code != http.StatusOK {
|
|
t.Fatalf("Sense product header status = %d", senseResult.Code)
|
|
}
|
|
|
|
meWithoutProduct := httptest.NewRequest(http.MethodGet, "/api/v1/identity/me", nil)
|
|
meWithoutProduct.AddCookie(cookies[0])
|
|
missingProductResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(missingProductResult, meWithoutProduct)
|
|
if missingProductResult.Code != http.StatusUnauthorized {
|
|
t.Fatalf("missing product header status = %d", missingProductResult.Code)
|
|
}
|
|
|
|
frame := httptest.NewRequest(http.MethodGet, "/same-origin-frame", nil)
|
|
frame.AddCookie(cookies[0])
|
|
frame.Header.Set("Sec-Fetch-Site", "same-origin")
|
|
frame.Header.Set("Sec-Fetch-Mode", "navigate")
|
|
frame.Header.Set("Sec-Fetch-Dest", "iframe")
|
|
frameResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(frameResult, frame)
|
|
if frameResult.Code != http.StatusNoContent {
|
|
t.Fatalf("same-origin frame status = %d, body = %s", frameResult.Code, frameResult.Body.String())
|
|
}
|
|
|
|
frame.Header.Set("Sec-Fetch-Site", "cross-site")
|
|
crossSiteResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(crossSiteResult, frame)
|
|
if crossSiteResult.Code != http.StatusUnauthorized {
|
|
t.Fatalf("cross-site frame status = %d", crossSiteResult.Code)
|
|
}
|
|
|
|
missingCookie := httptest.NewRequest(http.MethodGet, "/same-origin-frame", nil)
|
|
missingCookie.Header.Set("Sec-Fetch-Site", "same-origin")
|
|
missingCookie.Header.Set("Sec-Fetch-Mode", "navigate")
|
|
missingCookie.Header.Set("Sec-Fetch-Dest", "iframe")
|
|
missingCookieResult := httptest.NewRecorder()
|
|
app.Handler().ServeHTTP(missingCookieResult, missingCookie)
|
|
if missingCookieResult.Code != http.StatusUnauthorized {
|
|
t.Fatalf("missing-cookie frame status = %d", missingCookieResult.Code)
|
|
}
|
|
}
|
|
|