mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
Red's review. The aggregate route reads columns directly, so it gated only hidden fields: grouping an auth collection by email, or counting its unique emails, answered addresses the records keep private, which the list masks and the filter resolver will not compare. core.EmailGated is now the one rule both ask: an auth collection's email, which a caller who is not a superuser reads only where emailVisibility is set. The aggregate route refuses it as it refuses a hidden field. TestAPrivateEmailIsNotAFigure is red's proof inverted: the group and the count are refused, and fail without the change. A record's history showed the changes of a field the collection had since removed. An org admin who removes a field means its values to be gone from view, so a caller who is not a superuser no longer reads them; _history keeps the row for a superuser. The composite uniqueness check is a read before the write, which PostgreSQL can race; that is now said where it is done and in LLM.md, with the fix (a unique expression index from the dialect).
82 lines
2.5 KiB
Go
82 lines
2.5 KiB
Go
package apis_test
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tests"
|
|
"github.com/hanzoai/base/tools/types"
|
|
)
|
|
|
|
// targets is an auth collection a member may list whose records keep their
|
|
// emails private (emailVisibility off).
|
|
func targets(t testing.TB, app *tests.TestApp) {
|
|
t.Helper()
|
|
|
|
c := core.NewAuthCollection("targets")
|
|
c.ListRule = types.Pointer("")
|
|
if err := app.Save(c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, email := range []string{"[email protected]", "[email protected]", "[email protected]"} {
|
|
r := core.NewRecord(c)
|
|
r.Set("email", email)
|
|
r.Set("emailVisibility", false)
|
|
r.Set("password", "secret123456")
|
|
if err := app.Save(r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var privateEmails = []string{"[email protected]", "[email protected]", "[email protected]"}
|
|
|
|
// An address a record keeps private reaches nobody but a superuser by any
|
|
// route: not in a list, not through a filter, and not as a figure or a group.
|
|
func TestAPrivateEmailIsNotAFigure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "the list masks it",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/collections/targets/records",
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{`"totalItems":3`},
|
|
NotExpectedContent: privateEmails,
|
|
},
|
|
{
|
|
Name: "a filter finds none of it",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/collections/targets/records?filter=" + url.QueryEscape(`email != ""`),
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{`"totalItems":0`},
|
|
},
|
|
{
|
|
Name: "a group by it is refused",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/collections/targets/aggregate?" + url.Values{"ops": {"count"}, "group": {"email"}}.Encode(),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`not filterable`},
|
|
NotExpectedContent: privateEmails,
|
|
},
|
|
{
|
|
Name: "a count of it is refused",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/collections/targets/aggregate?" + url.Values{"ops": {"countUnique(email)"}}.Encode(),
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`not filterable`},
|
|
NotExpectedContent: []string{`"countUnique(email)":3`},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
s.Headers = map[string]string{"Authorization": aggregateMemberToken}
|
|
s.BeforeTestFunc = func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) { targets(t, app) }
|
|
s.Test(t)
|
|
}
|
|
}
|