tangled
alpha
login
or
join now
tjh.dev
/
core
forked from
tangled.org/core
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
add /knots/{domain} pages
oppi.li
1 year ago
ae9c2cca
d1bdb4c4
+203
-13
6 changed files
expand all
collapse all
unified
split
appview
db
db.go
pages
knot.html
knots.html
pages.go
state
middleware.go
state.go
+1
-5
appview/db/db.go
···
189
189
where domain = ?;
190
190
`, domain)
191
191
192
192
-
if err != nil {
193
193
-
return err
194
194
-
}
195
195
-
196
196
-
return nil
192
192
+
return err
197
193
}
+37
appview/pages/knot.html
···
1
1
+
{{define "title"}}knot{{end}}
2
2
+
3
3
+
{{define "content"}}
4
4
+
<a href="/">back to timeline</a>
5
5
+
<a href="/knots">back to all knots</a>
6
6
+
<h1>{{.Registration.Domain}}</h1>
7
7
+
<p>
8
8
+
<code>
9
9
+
opened by: {{.Registration.ByDid}}
10
10
+
{{ if $.IsOwner }}
11
11
+
(you)
12
12
+
{{ end }}
13
13
+
</code><br>
14
14
+
<code>on: {{.Registration.Created}}</code><br>
15
15
+
{{ if .Registration.Registered }}
16
16
+
<code>registered on: {{.Registration.Registered}}</code>
17
17
+
{{ else }}
18
18
+
<code>pending registration</code>
19
19
+
<button hx-post="/knots/{{.Domain}}/init">initialize</button>
20
20
+
{{ end }}
21
21
+
</p>
22
22
+
23
23
+
{{ if .Registration.Registered }}
24
24
+
<h3> members </h3>
25
25
+
{{ range $.Members }}
26
26
+
<ol>
27
27
+
<li>{{.}}</li>
28
28
+
</ol>
29
29
+
{{ else }}
30
30
+
<p>no members</p>
31
31
+
{{ end }}
32
32
+
{{ end }}
33
33
+
34
34
+
{{ if $.IsOwner }}
35
35
+
<a href="/knots/{{.Registration.Domain}}/member">add member</a>
36
36
+
{{ end }}
37
37
+
{{end}}
+20
-7
appview/pages/knots.html
···
13
13
<button type="domain">generate key</button>
14
14
</form>
15
15
16
16
-
<h3>existing registrations</h3>
17
17
-
<ul id="registrations">
16
16
+
<h3>my knots</h3>
17
17
+
<ul id="my-knots">
18
18
+
{{range .Registrations}}
19
19
+
{{ if .Registered }}
20
20
+
<li>
21
21
+
<p>domain: <a href="/knots/{{.Domain}}">{{.Domain}}</a></p><br>
22
22
+
<code>opened by: {{.ByDid}}</code><br>
23
23
+
<code>on: {{.Created}}</code><br>
24
24
+
<code>registered on: {{.Registered}}</code>
25
25
+
</li>
26
26
+
{{ end }}
27
27
+
{{else}}
28
28
+
<p>you don't have any knots yet</p>
29
29
+
{{end}}
30
30
+
</ul>
31
31
+
<h3>pending registrations</h3>
32
32
+
<ul id="pending-registrations">
18
33
{{range .Registrations}}
34
34
+
{{ if not .Registered }}
19
35
<li>
20
36
<code>domain: {{.Domain}}</code><br>
21
37
<code>opened by: {{.ByDid}}</code><br>
22
38
<code>on: {{.Created}}</code><br>
23
23
-
{{if .Registered}}
24
24
-
<code>registered on: {{.Registered}}</code>
25
25
-
{{else}}
26
39
<code>pending registration</code>
27
27
-
<button hx-post="/knots/init/{{.Domain}}">initialize</button>
28
28
-
{{end}}
40
40
+
<button hx-post="/knots/{{.Domain}}/init">initialize</button>
29
41
</li>
42
42
+
{{ end }}
30
43
{{else}}
31
44
<p>no registrations yet</p>
32
45
{{end}}
+11
appview/pages/pages.go
···
66
66
func Knots(w io.Writer, p KnotsParams) error {
67
67
return parse("knots.html").Execute(w, p)
68
68
}
69
69
+
70
70
+
type KnotParams struct {
71
71
+
User *auth.User
72
72
+
Registration *db.Registration
73
73
+
Members []string
74
74
+
IsOwner bool
75
75
+
}
76
76
+
77
77
+
func Knot(w io.Writer, p KnotParams) error {
78
78
+
return parse("knot.html").Execute(w, p)
79
79
+
}
+31
appview/state/middleware.go
···
7
7
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
9
"github.com/bluesky-social/indigo/xrpc"
10
10
+
"github.com/go-chi/chi/v5"
10
11
"github.com/sotangled/tangled/appview"
11
12
"github.com/sotangled/tangled/appview/auth"
12
13
)
···
68
69
})
69
70
}
70
71
}
72
72
+
73
73
+
func RoleMiddleware(s *State, group string) Middleware {
74
74
+
return func(next http.Handler) http.Handler {
75
75
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76
76
+
// requires auth also
77
77
+
actor := s.auth.GetUser(r)
78
78
+
if actor == nil {
79
79
+
// we need a logged in user
80
80
+
log.Printf("not logged in, redirecting")
81
81
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
82
82
+
return
83
83
+
}
84
84
+
domain := chi.URLParam(r, "domain")
85
85
+
if domain == "" {
86
86
+
http.Error(w, "malformed url", http.StatusBadRequest)
87
87
+
return
88
88
+
}
89
89
+
90
90
+
ok, err := s.enforcer.E.HasGroupingPolicy(actor.Did, group, domain)
91
91
+
if err != nil || !ok {
92
92
+
// we need a logged in user
93
93
+
log.Printf("%s does not have perms of a %s in domain %s", actor.Did, group, domain)
94
94
+
http.Error(w, "Forbiden", http.StatusUnauthorized)
95
95
+
return
96
96
+
}
97
97
+
98
98
+
next.ServeHTTP(w, r)
99
99
+
})
100
100
+
}
101
101
+
}
+103
-1
appview/state/state.go
···
7
7
"fmt"
8
8
"log"
9
9
"net/http"
10
10
+
"strings"
10
11
"time"
11
12
12
13
comatproto "github.com/bluesky-social/indigo/api/atproto"
···
283
284
w.Write([]byte("check success"))
284
285
}
285
286
287
287
+
func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) {
288
288
+
domain := chi.URLParam(r, "domain")
289
289
+
if domain == "" {
290
290
+
http.Error(w, "malformed url", http.StatusBadRequest)
291
291
+
return
292
292
+
}
293
293
+
294
294
+
user := s.auth.GetUser(r)
295
295
+
reg, err := s.db.RegistrationByDomain(domain)
296
296
+
if err != nil {
297
297
+
w.Write([]byte("failed to pull up registration info"))
298
298
+
return
299
299
+
}
300
300
+
301
301
+
var members []string
302
302
+
if reg.Registered != nil {
303
303
+
members, err = s.enforcer.E.GetUsersForRole("server:member", domain)
304
304
+
if err != nil {
305
305
+
w.Write([]byte("failed to fetch member list"))
306
306
+
return
307
307
+
}
308
308
+
}
309
309
+
310
310
+
ok, err := s.enforcer.E.HasGroupingPolicy(user.Did, "server:owner", domain)
311
311
+
isOwner := err == nil && ok
312
312
+
313
313
+
p := pages.KnotParams{
314
314
+
User: user,
315
315
+
Registration: reg,
316
316
+
Members: members,
317
317
+
IsOwner: isOwner,
318
318
+
}
319
319
+
320
320
+
pages.Knot(w, p)
321
321
+
}
322
322
+
286
323
// get knots registered by this user
287
324
func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
288
325
// for now, this is just pubkeys
···
298
335
})
299
336
}
300
337
338
338
+
// list members of domain, requires auth and requires owner status
339
339
+
func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) {
340
340
+
domain := chi.URLParam(r, "domain")
341
341
+
if domain == "" {
342
342
+
http.Error(w, "malformed url", http.StatusBadRequest)
343
343
+
return
344
344
+
}
345
345
+
346
346
+
// list all members for this domain
347
347
+
memberDids, err := s.enforcer.E.GetUsersForRole("server:member", domain)
348
348
+
if err != nil {
349
349
+
w.Write([]byte("failed to fetch member list"))
350
350
+
return
351
351
+
}
352
352
+
353
353
+
w.Write([]byte(strings.Join(memberDids, "\n")))
354
354
+
return
355
355
+
}
356
356
+
357
357
+
// add member to domain, requires auth and requires invite access
358
358
+
func (s *State) AddMember(w http.ResponseWriter, r *http.Request) {
359
359
+
domain := chi.URLParam(r, "domain")
360
360
+
if domain == "" {
361
361
+
http.Error(w, "malformed url", http.StatusBadRequest)
362
362
+
return
363
363
+
}
364
364
+
365
365
+
memberDid := r.FormValue("member")
366
366
+
if memberDid == "" {
367
367
+
http.Error(w, "malformed form", http.StatusBadRequest)
368
368
+
return
369
369
+
}
370
370
+
371
371
+
// TODO: validate member did?
372
372
+
memberIdent, err := auth.ResolveIdent(r.Context(), memberDid)
373
373
+
if err != nil {
374
374
+
w.Write([]byte("failed to resolve member did to a handle"))
375
375
+
return
376
376
+
}
377
377
+
378
378
+
log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain)
379
379
+
380
380
+
err = s.enforcer.AddMember(domain, memberDid)
381
381
+
if err != nil {
382
382
+
w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
383
383
+
return
384
384
+
}
385
385
+
386
386
+
w.Write([]byte(fmt.Sprint("added member: ", memberIdent.Handle.String())))
387
387
+
}
388
388
+
389
389
+
// list members of domain, requires auth and requires owner status
390
390
+
func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) {
391
391
+
}
392
392
+
301
393
func buildPingRequest(url, secret string) (*http.Request, error) {
302
394
pingRequest, err := http.NewRequest("GET", url, nil)
303
395
if err != nil {
···
327
419
r.Route("/knots", func(r chi.Router) {
328
420
r.Use(AuthMiddleware(s))
329
421
r.Get("/", s.Knots)
330
330
-
r.Post("/init/{domain}", s.InitKnotServer)
331
422
r.Post("/key", s.RegistrationKey)
423
423
+
424
424
+
r.Route("/{domain}", func(r chi.Router) {
425
425
+
r.Get("/", s.KnotServerInfo)
426
426
+
r.Post("/init", s.InitKnotServer)
427
427
+
r.Route("/member", func(r chi.Router) {
428
428
+
r.Use(RoleMiddleware(s, "server:owner"))
429
429
+
r.Get("/", s.ListMembers)
430
430
+
r.Put("/", s.AddMember)
431
431
+
r.Delete("/", s.RemoveMember)
432
432
+
})
433
433
+
})
332
434
})
333
435
334
436
r.Group(func(r chi.Router) {