Add notification channels and a provider abstraction
Users get a notification_channels table (replacing users.ntfy_topic); pages fan
out to every enabled channel through a notify.Provider registry, with ntfy as
the first provider. Fallbacks are typed {type, address}. Schema edited in place:
existing databases must be recreated.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
This commit is contained in:
@@ -459,18 +459,38 @@ func TestTeams_OutsiderSeesNothing(t *testing.T) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Everybody signed in can list users to name them, but only an admin (or the
|
||||
// row's owner) sees an email or an ntfy topic, which is a publish secret.
|
||||
func TestListUsers_RedactsEmailAndTopicForOthers(t *testing.T) {
|
||||
// row's owner) sees an email, and only the owner sees a channel's address, which
|
||||
// can be a publish secret. An admin sees that a channel exists, not where it goes.
|
||||
func TestListUsers_RedactsEmailAndChannelsForOthers(t *testing.T) {
|
||||
s := newTS(t)
|
||||
red := newTeam(t, s, "red")
|
||||
_ = newTeam(t, s, "blue")
|
||||
s.exec(t, "UPDATE users SET ntfy_topic = 'secret-topic'")
|
||||
s.exec(t, `INSERT INTO notification_channels (user_id, type, address)
|
||||
SELECT id, 'ntfy', 'secret-topic' FROM users`)
|
||||
|
||||
channelAddresses := func(u map[string]any) []string {
|
||||
var out []string
|
||||
list, _ := u["channels"].([]any)
|
||||
for _, c := range list {
|
||||
addr, _ := c.(map[string]any)["address"].(string) // omitted when blank
|
||||
out = append(out, addr)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var asAdmin []map[string]any
|
||||
decode(t, s.req(t, http.MethodGet, "/api/users", nil), &asAdmin)
|
||||
for _, u := range asAdmin {
|
||||
if u["email"] == "" || u["ntfy_topic"] != "secret-topic" {
|
||||
t.Errorf("admin should see everything, got %v", u)
|
||||
if u["email"] == "" {
|
||||
t.Errorf("admin should see every email, got %v", u)
|
||||
}
|
||||
addrs := channelAddresses(u)
|
||||
if len(addrs) != 1 {
|
||||
t.Errorf("admin should see that every user has a channel, got %v", u)
|
||||
continue
|
||||
}
|
||||
if own := u["username"] == "admin"; own != (addrs[0] != "") {
|
||||
t.Errorf("admin should see only their own channel's address, got %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,8 +500,15 @@ func TestListUsers_RedactsEmailAndTopicForOthers(t *testing.T) {
|
||||
}
|
||||
for _, u := range asMember {
|
||||
own := u["username"] == "red-user"
|
||||
if own != (u["email"] != "") || own != (u["ntfy_topic"] != nil) {
|
||||
t.Errorf("only red-user's own row should keep email and topic, got %v", u)
|
||||
if own != (u["email"] != "") {
|
||||
t.Errorf("only red-user's own row should keep its email, got %v", u)
|
||||
}
|
||||
addrs := channelAddresses(u)
|
||||
if own && (len(addrs) != 1 || addrs[0] != "secret-topic") {
|
||||
t.Errorf("red-user should see their own channel and address, got %v", u)
|
||||
}
|
||||
if !own && len(addrs) != 0 {
|
||||
t.Errorf("a member must not see anybody else's channels, got %v", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user