tinyrelaycommit

Commit 591f26270893

browse files at this commit | history

1diff --git a/internal/daemon/connections_public_test.go b/internal/daemon/connections_public_test.go
2new file mode 100644
3index 0000000..ae4908b
4--- /dev/null
5+++ b/internal/daemon/connections_public_test.go
6@@ -0,0 +1,42 @@
7+package daemon
8+
9+import (
10+ "context"
11+ "encoding/json"
12+ "strings"
13+ "testing"
14+)
15+
16+func TestVisibleConnectionsFollowViewerRole(t *testing.T) {
17+ ctx := context.Background()
18+ _, tenant := testTenant(t)
19+ rows := `[{"template":"notes","visibility":"public"},{"template":"bookmarks","visibility":"members"},{"template":"relay-page","visibility":"owner"},{"template":"nope","visibility":"public"}]`
20+ if _, err := tenant.Execute(ctx, tenant.Policy().Owner, "setconnections", []json.RawMessage{json.RawMessage(rows)}); err != nil {
21+ t.Fatal(err)
22+ }
23+ names := func(actor string) string {
24+ value, err := tenant.Execute(ctx, actor, "connections", nil)
25+ if err != nil {
26+ t.Fatal(err)
27+ }
28+ raw, _ := json.Marshal(value)
29+ var list []map[string]any
30+ _ = json.Unmarshal(raw, &list)
31+ out := []string{}
32+ for _, item := range list {
33+ out = append(out, item["template"].(string))
34+ }
35+ return strings.Join(out, ",")
36+ }
37+ if got := names(""); got != "notes" {
38+ t.Fatalf("guest sees %q", got)
39+ }
40+ if got := names(tenant.Policy().Owner); got != "notes,bookmarks,relay-page" {
41+ t.Fatalf("owner sees %q", got)
42+ }
43+ value, _ := tenant.Execute(ctx, "", "connections", nil)
44+ raw, _ := json.Marshal(value)
45+ if !strings.Contains(string(raw), "jumble.social") || !strings.Contains(string(raw), `"links"`) {
46+ t.Fatalf("connection is missing its template links: %s", raw)
47+ }
48+}
49diff --git a/internal/daemon/management.go b/internal/daemon/management.go
50index 1c3ccd0..69d7e6e 100644
51--- a/internal/daemon/management.go
52+++ b/internal/daemon/management.go
53@@ -110,7 +110,7 @@ func managementAdapterMethod(method string) bool {
54 // are intentionally separate from the NIP-86 management methods so a public
55 // page cannot accidentally gain access to privileged search or export APIs.
56 func PublicMethods() []string {
57- return []string{"queryevents", "eventdetail", "feed", "listview", "claiminvite"}
58+ return []string{"queryevents", "eventdetail", "feed", "listview", "claiminvite", "connections"}
59 }
60
61 func publicManagementMethod(method string) bool {
62@@ -151,6 +151,9 @@ func roleName(role string) string {
63 }
64
65 func (t *Tenant) executePublicManagement(ctx context.Context, actor, method string, params []json.RawMessage) (any, bool, error) {
66+ if method == "connections" {
67+ return t.visibleConnections(ctx, actor)
68+ }
69 if method == "claiminvite" {
70 if strings.TrimSpace(actor) == "" {
71 return nil, true, errors.New("auth-required: claiminvite")
72@@ -487,6 +490,57 @@ func featureEnabled(p policy.Policy, name string) bool {
73 }
74 }
75
76+// visibleConnections lists configured connections the viewer may see, each
77+// merged with its template so pages can render titles, links and QR codes.
78+// Visibility "public" shows to everyone, "members" or "auth" to members,
79+// moderators and the owner, and "owner" to the owner alone. Connections
80+// whose feature is off are left out.
81+func (t *Tenant) visibleConnections(ctx context.Context, actor string) (any, bool, error) {
82+ rows, _, err := t.connections(ctx)
83+ if err != nil {
84+ return nil, true, err
85+ }
86+ role := ""
87+ if strings.TrimSpace(actor) != "" {
88+ if role, err = t.community.Role(ctx, actor); err != nil {
89+ return nil, true, err
90+ }
91+ }
92+ catalog := map[string]templates.Connection{}
93+ for _, c := range templates.Connections() {
94+ catalog[c.Name] = c
95+ }
96+ p := t.Policy()
97+ result := make([]map[string]any, 0)
98+ for _, row := range rows.([]map[string]any) {
99+ name, _ := row["template"].(string)
100+ if name == "" {
101+ name, _ = row["name"].(string)
102+ }
103+ c, ok := catalog[name]
104+ if !ok || (c.Feature != "" && !featureEnabled(p, c.Feature)) {
105+ continue
106+ }
107+ visibility, _ := row["visibility"].(string)
108+ if visibility == "" {
109+ visibility = c.Visibility
110+ }
111+ switch visibility {
112+ case "public":
113+ case "members", "member", "auth":
114+ if role != "member" && role != "moderator" && role != "owner" {
115+ continue
116+ }
117+ default:
118+ if role != "owner" {
119+ continue
120+ }
121+ }
122+ result = append(result, map[string]any{"template": c.Name, "title": c.Title, "about": c.About, "app": c.App, "where": c.Where, "icon": c.Icon, "feature": c.Feature, "visibility": visibility, "qr": c.QR, "inputs": c.Inputs, "links": c.Links})
123+ }
124+ return result, true, nil
125+}
126+
127 func (t *Tenant) connections(ctx context.Context) (any, bool, error) {
128 rows, err := t.store.DB().QueryContext(ctx, `SELECT value FROM config_connections ORDER BY position`)
129 if err != nil {

About

ownerc01503e8035f
visibilitypublic
head591f26270893

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs