+65
-75
api/tangled/cbor_gen.go
+65
-75
api/tangled/cbor_gen.go
···
216
217
return nil
218
}
219
-
func (t *KnotPolicy) MarshalCBOR(w io.Writer) error {
220
if t == nil {
221
_, err := w.Write(cbg.CborNull)
222
return err
223
}
224
225
cw := cbg.NewCborWriter(w)
226
227
-
if _, err := cw.Write([]byte{165}); err != nil {
228
return err
229
}
230
···
240
return err
241
}
242
243
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.policy"))); err != nil {
244
-
return err
245
-
}
246
-
if _, err := cw.WriteString(string("sh.tangled.knot.policy")); err != nil {
247
return err
248
}
249
-
250
-
// t.Action (string) (string)
251
-
if len("action") > 1000000 {
252
-
return xerrors.Errorf("Value in field \"action\" was too long")
253
-
}
254
-
255
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("action"))); err != nil {
256
-
return err
257
-
}
258
-
if _, err := cw.WriteString(string("action")); err != nil {
259
-
return err
260
-
}
261
-
262
-
if len(t.Action) > 1000000 {
263
-
return xerrors.Errorf("Value in field t.Action was too long")
264
-
}
265
-
266
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Action))); err != nil {
267
-
return err
268
-
}
269
-
if _, err := cw.WriteString(string(t.Action)); err != nil {
270
return err
271
}
272
···
293
return err
294
}
295
296
-
// t.Object (string) (string)
297
-
if len("object") > 1000000 {
298
-
return xerrors.Errorf("Value in field \"object\" was too long")
299
}
300
301
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("object"))); err != nil {
302
return err
303
}
304
-
if _, err := cw.WriteString(string("object")); err != nil {
305
return err
306
}
307
308
-
if len(t.Object) > 1000000 {
309
-
return xerrors.Errorf("Value in field t.Object was too long")
310
}
311
312
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Object))); err != nil {
313
return err
314
}
315
-
if _, err := cw.WriteString(string(t.Object)); err != nil {
316
return err
317
}
318
319
-
// t.Subject (string) (string)
320
-
if len("subject") > 1000000 {
321
-
return xerrors.Errorf("Value in field \"subject\" was too long")
322
-
}
323
324
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
325
-
return err
326
-
}
327
-
if _, err := cw.WriteString(string("subject")); err != nil {
328
-
return err
329
-
}
330
331
-
if len(t.Subject) > 1000000 {
332
-
return xerrors.Errorf("Value in field t.Subject was too long")
333
-
}
334
335
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
336
-
return err
337
-
}
338
-
if _, err := cw.WriteString(string(t.Subject)); err != nil {
339
-
return err
340
}
341
return nil
342
}
343
344
-
func (t *KnotPolicy) UnmarshalCBOR(r io.Reader) (err error) {
345
-
*t = KnotPolicy{}
346
347
cr := cbg.NewCborReader(r)
348
···
361
}
362
363
if extra > cbg.MaxLength {
364
-
return fmt.Errorf("KnotPolicy: map struct too large (%d)", extra)
365
}
366
367
n := extra
···
393
394
t.LexiconTypeID = string(sval)
395
}
396
-
// t.Action (string) (string)
397
-
case "action":
398
-
399
-
{
400
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
401
-
if err != nil {
402
-
return err
403
-
}
404
-
405
-
t.Action = string(sval)
406
-
}
407
// t.Domain (string) (string)
408
case "domain":
409
···
415
416
t.Domain = string(sval)
417
}
418
-
// t.Object (string) (string)
419
-
case "object":
420
421
{
422
sval, err := cbg.ReadStringWithMax(cr, 1000000)
···
424
return err
425
}
426
427
-
t.Object = string(sval)
428
}
429
-
// t.Subject (string) (string)
430
-
case "subject":
431
432
{
433
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
434
if err != nil {
435
return err
436
}
437
438
-
t.Subject = string(sval)
439
}
440
441
default:
···
216
217
return nil
218
}
219
+
func (t *KnotMember) MarshalCBOR(w io.Writer) error {
220
if t == nil {
221
_, err := w.Write(cbg.CborNull)
222
return err
223
}
224
225
cw := cbg.NewCborWriter(w)
226
+
fieldCount := 4
227
228
+
if t.AddedAt == nil {
229
+
fieldCount--
230
+
}
231
+
232
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
233
return err
234
}
235
···
245
return err
246
}
247
248
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.member"))); err != nil {
249
return err
250
}
251
+
if _, err := cw.WriteString(string("sh.tangled.knot.member")); err != nil {
252
return err
253
}
254
···
275
return err
276
}
277
278
+
// t.Member (string) (string)
279
+
if len("member") > 1000000 {
280
+
return xerrors.Errorf("Value in field \"member\" was too long")
281
}
282
283
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("member"))); err != nil {
284
return err
285
}
286
+
if _, err := cw.WriteString(string("member")); err != nil {
287
return err
288
}
289
290
+
if len(t.Member) > 1000000 {
291
+
return xerrors.Errorf("Value in field t.Member was too long")
292
}
293
294
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Member))); err != nil {
295
return err
296
}
297
+
if _, err := cw.WriteString(string(t.Member)); err != nil {
298
return err
299
}
300
301
+
// t.AddedAt (string) (string)
302
+
if t.AddedAt != nil {
303
304
+
if len("addedAt") > 1000000 {
305
+
return xerrors.Errorf("Value in field \"addedAt\" was too long")
306
+
}
307
308
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("addedAt"))); err != nil {
309
+
return err
310
+
}
311
+
if _, err := cw.WriteString(string("addedAt")); err != nil {
312
+
return err
313
+
}
314
315
+
if t.AddedAt == nil {
316
+
if _, err := cw.Write(cbg.CborNull); err != nil {
317
+
return err
318
+
}
319
+
} else {
320
+
if len(*t.AddedAt) > 1000000 {
321
+
return xerrors.Errorf("Value in field t.AddedAt was too long")
322
+
}
323
+
324
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.AddedAt))); err != nil {
325
+
return err
326
+
}
327
+
if _, err := cw.WriteString(string(*t.AddedAt)); err != nil {
328
+
return err
329
+
}
330
+
}
331
}
332
return nil
333
}
334
335
+
func (t *KnotMember) UnmarshalCBOR(r io.Reader) (err error) {
336
+
*t = KnotMember{}
337
338
cr := cbg.NewCborReader(r)
339
···
352
}
353
354
if extra > cbg.MaxLength {
355
+
return fmt.Errorf("KnotMember: map struct too large (%d)", extra)
356
}
357
358
n := extra
···
384
385
t.LexiconTypeID = string(sval)
386
}
387
// t.Domain (string) (string)
388
case "domain":
389
···
395
396
t.Domain = string(sval)
397
}
398
+
// t.Member (string) (string)
399
+
case "member":
400
401
{
402
sval, err := cbg.ReadStringWithMax(cr, 1000000)
···
404
return err
405
}
406
407
+
t.Member = string(sval)
408
}
409
+
// t.AddedAt (string) (string)
410
+
case "addedAt":
411
412
{
413
+
b, err := cr.ReadByte()
414
if err != nil {
415
return err
416
}
417
+
if b != cbg.CborNull[0] {
418
+
if err := cr.UnreadByte(); err != nil {
419
+
return err
420
+
}
421
422
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
423
+
if err != nil {
424
+
return err
425
+
}
426
+
427
+
t.AddedAt = (*string)(&sval)
428
+
}
429
}
430
431
default:
+25
api/tangled/knotmember.go
+25
api/tangled/knotmember.go
···
···
1
+
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
+
3
+
package tangled
4
+
5
+
// schema: sh.tangled.knot.member
6
+
7
+
import (
8
+
"github.com/bluesky-social/indigo/lex/util"
9
+
)
10
+
11
+
const (
12
+
KnotMemberNSID = "sh.tangled.knot.member"
13
+
)
14
+
15
+
func init() {
16
+
util.RegisterType("sh.tangled.knot.member", &KnotMember{})
17
+
} //
18
+
// RECORDTYPE: KnotMember
19
+
type KnotMember struct {
20
+
LexiconTypeID string `json:"$type,const=sh.tangled.knot.member" cborgen:"$type,const=sh.tangled.knot.member"`
21
+
AddedAt *string `json:"addedAt,omitempty" cborgen:"addedAt,omitempty"`
22
+
// domain: domain that this member now belongs to
23
+
Domain string `json:"domain" cborgen:"domain"`
24
+
Member string `json:"member" cborgen:"member"`
25
+
}
-29
api/tangled/knotpolicy.go
-29
api/tangled/knotpolicy.go
···
1
-
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
-
3
-
package tangled
4
-
5
-
// schema: sh.tangled.knot.policy
6
-
7
-
import (
8
-
"github.com/bluesky-social/indigo/lex/util"
9
-
)
10
-
11
-
const (
12
-
KnotPolicyNSID = "sh.tangled.knot.policy"
13
-
)
14
-
15
-
func init() {
16
-
util.RegisterType("sh.tangled.knot.policy", &KnotPolicy{})
17
-
} //
18
-
// RECORDTYPE: KnotPolicy
19
-
type KnotPolicy struct {
20
-
LexiconTypeID string `json:"$type,const=sh.tangled.knot.policy" cborgen:"$type,const=sh.tangled.knot.policy"`
21
-
// action: action associated with the key
22
-
Action string `json:"action" cborgen:"action"`
23
-
// domain: domain of the key
24
-
Domain string `json:"domain" cborgen:"domain"`
25
-
// object: object associated with the key
26
-
Object string `json:"object" cborgen:"object"`
27
-
// subject: subject of the key
28
-
Subject string `json:"subject" cborgen:"subject"`
29
-
}
···
+1
appview/consts.go
+1
appview/consts.go
+3
-4
appview/state/rbac.go
rbac/rbac.go
+3
-4
appview/state/rbac.go
rbac/rbac.go
···
1
-
package state
2
3
import (
4
"database/sql"
···
37
return matched
38
}
39
40
-
func NewEnforcer() (*Enforcer, error) {
41
m, err := model.NewModelFromString(Model)
42
if err != nil {
43
return nil, err
44
}
45
46
-
// TODO: conf this
47
-
db, err := sql.Open("sqlite3", "appview.db")
48
if err != nil {
49
return nil, err
50
}
···
1
+
package rbac
2
3
import (
4
"database/sql"
···
37
return matched
38
}
39
40
+
func NewEnforcer(path string) (*Enforcer, error) {
41
m, err := model.NewModelFromString(Model)
42
if err != nil {
43
return nil, err
44
}
45
46
+
db, err := sql.Open("sqlite3", path)
47
if err != nil {
48
return nil, err
49
}
+35
-11
appview/state/state.go
+35
-11
appview/state/state.go
···
22
"github.com/sotangled/tangled/appview/auth"
23
"github.com/sotangled/tangled/appview/db"
24
"github.com/sotangled/tangled/appview/pages"
25
)
26
27
type State struct {
28
db *db.DB
29
auth *auth.Auth
30
-
enforcer *Enforcer
31
}
32
33
func Make() (*State, error) {
34
-
db, err := db.Make("appview.db")
35
if err != nil {
36
return nil, err
37
}
···
41
return nil, err
42
}
43
44
-
enforcer, err := NewEnforcer()
45
if err != nil {
46
return nil, err
47
}
···
175
Collection: tangled.PublicKeyNSID,
176
Repo: did,
177
Rkey: uuid.New().String(),
178
-
Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{
179
-
Created: time.Now().String(),
180
-
Key: key,
181
-
Name: name,
182
-
}},
183
})
184
-
185
// invalid record
186
if err != nil {
187
log.Printf("failed to create record: %s", err)
···
382
w.Write([]byte("failed to resolve member did to a handle"))
383
return
384
}
385
-
386
log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain)
387
388
err = s.enforcer.AddMember(domain, memberIdent.DID.String())
389
if err != nil {
390
w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
···
481
r.Post("/key", s.RegistrationKey)
482
483
r.Route("/{domain}", func(r chi.Router) {
484
-
r.Get("/", s.KnotServerInfo)
485
r.Post("/init", s.InitKnotServer)
486
r.Route("/member", func(r chi.Router) {
487
r.Use(RoleMiddleware(s, "server:owner"))
488
r.Get("/", s.ListMembers)
···
22
"github.com/sotangled/tangled/appview/auth"
23
"github.com/sotangled/tangled/appview/db"
24
"github.com/sotangled/tangled/appview/pages"
25
+
"github.com/sotangled/tangled/rbac"
26
)
27
28
type State struct {
29
db *db.DB
30
auth *auth.Auth
31
+
enforcer *rbac.Enforcer
32
}
33
34
func Make() (*State, error) {
35
+
36
+
db, err := db.Make(appview.SqliteDbPath)
37
if err != nil {
38
return nil, err
39
}
···
43
return nil, err
44
}
45
46
+
enforcer, err := rbac.NewEnforcer(appview.SqliteDbPath)
47
if err != nil {
48
return nil, err
49
}
···
177
Collection: tangled.PublicKeyNSID,
178
Repo: did,
179
Rkey: uuid.New().String(),
180
+
Record: &lexutil.LexiconTypeDecoder{
181
+
Val: &tangled.PublicKey{
182
+
Created: time.Now().Format(time.RFC3339),
183
+
Key: key,
184
+
Name: name,
185
+
}},
186
})
187
// invalid record
188
if err != nil {
189
log.Printf("failed to create record: %s", err)
···
384
w.Write([]byte("failed to resolve member did to a handle"))
385
return
386
}
387
log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain)
388
389
+
// announce this relation into the firehose, store into owners' pds
390
+
client, _ := s.auth.AuthorizedClient(r)
391
+
currentUser := s.auth.GetUser(r)
392
+
addedAt := time.Now().Format(time.RFC3339)
393
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
394
+
Collection: tangled.KnotMemberNSID,
395
+
Repo: currentUser.Did,
396
+
Rkey: uuid.New().String(),
397
+
Record: &lexutil.LexiconTypeDecoder{
398
+
Val: &tangled.KnotMember{
399
+
Member: memberIdent.DID.String(),
400
+
Domain: domain,
401
+
AddedAt: &addedAt,
402
+
}},
403
+
})
404
+
// invalid record
405
+
if err != nil {
406
+
log.Printf("failed to create record: %s", err)
407
+
return
408
+
}
409
+
410
+
log.Println("created atproto record: ", resp.Uri)
411
+
412
err = s.enforcer.AddMember(domain, memberIdent.DID.String())
413
if err != nil {
414
w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
···
505
r.Post("/key", s.RegistrationKey)
506
507
r.Route("/{domain}", func(r chi.Router) {
508
r.Post("/init", s.InitKnotServer)
509
+
r.Get("/", s.KnotServerInfo)
510
r.Route("/member", func(r chi.Router) {
511
r.Use(RoleMiddleware(s, "server:owner"))
512
r.Get("/", s.ListMembers)
+1
-1
cmd/gen.go
+1
-1
cmd/gen.go
+7
-1
cmd/knotserver/main.go
+7
-1
cmd/knotserver/main.go
···
11
"github.com/sotangled/tangled/knotserver"
12
"github.com/sotangled/tangled/knotserver/config"
13
"github.com/sotangled/tangled/knotserver/db"
14
)
15
16
func main() {
···
34
log.Fatalf("failed to setup db: %s", err)
35
}
36
37
-
mux, err := knotserver.Setup(ctx, c, db)
38
if err != nil {
39
log.Fatal(err)
40
}
···
11
"github.com/sotangled/tangled/knotserver"
12
"github.com/sotangled/tangled/knotserver/config"
13
"github.com/sotangled/tangled/knotserver/db"
14
+
"github.com/sotangled/tangled/rbac"
15
)
16
17
func main() {
···
35
log.Fatalf("failed to setup db: %s", err)
36
}
37
38
+
e, err := rbac.NewEnforcer(c.Server.DBPath)
39
+
if err != nil {
40
+
log.Fatalf("failed to setup rbac enforcer: %s", err)
41
+
}
42
+
43
+
mux, err := knotserver.Setup(ctx, c, db, e)
44
if err != nil {
45
log.Fatal(err)
46
}
+26
-4
knotserver/handler.go
+26
-4
knotserver/handler.go
···
12
"github.com/sotangled/tangled/knotserver/config"
13
"github.com/sotangled/tangled/knotserver/db"
14
"github.com/sotangled/tangled/knotserver/jsclient"
15
)
16
17
type Handle struct {
18
c *config.Config
19
db *db.DB
20
js *jsclient.JetstreamClient
21
22
// init is a channel that is closed when the knot has been initailized
23
// i.e. when the first user (knot owner) has been added.
···
25
knotInitialized bool
26
}
27
28
-
func Setup(ctx context.Context, c *config.Config, db *db.DB) (http.Handler, error) {
29
r := chi.NewRouter()
30
31
h := Handle{
32
c: c,
33
db: db,
34
init: make(chan struct{}),
35
}
36
37
-
err := h.StartJetstream(ctx)
38
if err != nil {
39
return nil, fmt.Errorf("failed to start jetstream: %w", err)
40
}
···
94
}
95
96
func (h *Handle) StartJetstream(ctx context.Context) error {
97
-
colections := []string{tangled.PublicKeyNSID}
98
dids := []string{}
99
100
-
h.js = jsclient.NewJetstreamClient(colections, dids)
101
messages, err := h.js.ReadJetstream(ctx)
102
if err != nil {
103
return fmt.Errorf("failed to read from jetstream: %w", err)
···
126
log.Printf("failed to add public key: %v", err)
127
} else {
128
log.Printf("added public key from firehose: %s", data["did"])
129
}
130
default:
131
}
···
12
"github.com/sotangled/tangled/knotserver/config"
13
"github.com/sotangled/tangled/knotserver/db"
14
"github.com/sotangled/tangled/knotserver/jsclient"
15
+
"github.com/sotangled/tangled/rbac"
16
+
)
17
+
18
+
const (
19
+
ThisServer = "thisserver" // resource identifier for rbac enforcement
20
)
21
22
type Handle struct {
23
c *config.Config
24
db *db.DB
25
js *jsclient.JetstreamClient
26
+
e *rbac.Enforcer
27
28
// init is a channel that is closed when the knot has been initailized
29
// i.e. when the first user (knot owner) has been added.
···
31
knotInitialized bool
32
}
33
34
+
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer) (http.Handler, error) {
35
r := chi.NewRouter()
36
37
h := Handle{
38
c: c,
39
db: db,
40
+
e: e,
41
init: make(chan struct{}),
42
}
43
44
+
err := e.AddDomain(ThisServer)
45
+
if err != nil {
46
+
return nil, fmt.Errorf("failed to setup enforcer: %w", err)
47
+
}
48
+
49
+
err = h.StartJetstream(ctx)
50
if err != nil {
51
return nil, fmt.Errorf("failed to start jetstream: %w", err)
52
}
···
106
}
107
108
func (h *Handle) StartJetstream(ctx context.Context) error {
109
+
collections := []string{tangled.PublicKeyNSID, tangled.KnotMemberNSID}
110
dids := []string{}
111
112
+
h.js = jsclient.NewJetstreamClient(collections, dids)
113
messages, err := h.js.ReadJetstream(ctx)
114
if err != nil {
115
return fmt.Errorf("failed to read from jetstream: %w", err)
···
138
log.Printf("failed to add public key: %v", err)
139
} else {
140
log.Printf("added public key from firehose: %s", data["did"])
141
+
}
142
+
case tangled.KnotMemberNSID:
143
+
did := data["did"].(string)
144
+
record := commit["record"].(map[string]interface{})
145
+
ok, err := h.e.E.Enforce(did, ThisServer, ThisServer, "server:invite")
146
+
if err != nil || !ok {
147
+
log.Printf("failed to add member from did %s", did)
148
+
} else {
149
+
log.Printf("adding member")
150
+
h.e.AddMember(ThisServer, record["member"].(string))
151
}
152
default:
153
}
+8
knotserver/jsclient/jetstream.go
+8
knotserver/jsclient/jetstream.go
···
55
j.triggerReconnect()
56
}
57
58
+
// Adds one did to the did list
59
+
func (j *JetstreamClient) AddDid(did string) {
60
+
j.mu.Lock()
61
+
j.dids = append(j.dids, did)
62
+
j.mu.Unlock()
63
+
j.triggerReconnect()
64
+
}
65
+
66
func (j *JetstreamClient) triggerReconnect() {
67
select {
68
case j.reconnectCh <- struct{}{}:
+1
knotserver/routes.go
+1
knotserver/routes.go
+33
lexicons/member.json
+33
lexicons/member.json
···
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.tangled.knot.member",
4
+
"needsCbor": true,
5
+
"needsType": true,
6
+
"defs": {
7
+
"main": {
8
+
"type": "record",
9
+
"key": "tid",
10
+
"record": {
11
+
"type": "object",
12
+
"required": [
13
+
"member",
14
+
"domain"
15
+
],
16
+
"properties": {
17
+
"member": {
18
+
"type": "string",
19
+
"format": "did"
20
+
},
21
+
"domain": {
22
+
"type": "string",
23
+
"description": "domain that this member now belongs to"
24
+
},
25
+
"addedAt": {
26
+
"type": "string",
27
+
"format": "datetime"
28
+
}
29
+
}
30
+
}
31
+
}
32
+
}
33
+
}
-34
lexicons/policy.json
-34
lexicons/policy.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.tangled.knot.policy",
4
-
"needsCbor": true,
5
-
"needsType": true,
6
-
"defs": {
7
-
"main": {
8
-
"type": "record",
9
-
"key": "tid",
10
-
"record": {
11
-
"type": "object",
12
-
"required": ["subject", "domain", "object", "action"],
13
-
"properties": {
14
-
"subject": {
15
-
"type": "string",
16
-
"description": "subject of the key"
17
-
},
18
-
"domain": {
19
-
"type": "string",
20
-
"description": "domain of the key"
21
-
},
22
-
"object": {
23
-
"type": "string",
24
-
"description": "object associated with the key"
25
-
},
26
-
"action": {
27
-
"type": "string",
28
-
"description": "action associated with the key"
29
-
}
30
-
}
31
-
}
32
-
}
33
-
}
34
-
}
···