+65
-75
api/tangled/cbor_gen.go
+65
-75
api/tangled/cbor_gen.go
···
216
216
217
217
return nil
218
218
}
219
-
func (t *KnotPolicy) MarshalCBOR(w io.Writer) error {
219
+
func (t *KnotMember) MarshalCBOR(w io.Writer) error {
220
220
if t == nil {
221
221
_, err := w.Write(cbg.CborNull)
222
222
return err
223
223
}
224
224
225
225
cw := cbg.NewCborWriter(w)
226
+
fieldCount := 4
226
227
227
-
if _, err := cw.Write([]byte{165}); err != nil {
228
+
if t.AddedAt == nil {
229
+
fieldCount--
230
+
}
231
+
232
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
228
233
return err
229
234
}
230
235
···
240
245
return err
241
246
}
242
247
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 {
248
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot.member"))); err != nil {
247
249
return err
248
250
}
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 {
251
+
if _, err := cw.WriteString(string("sh.tangled.knot.member")); err != nil {
270
252
return err
271
253
}
272
254
···
293
275
return err
294
276
}
295
277
296
-
// t.Object (string) (string)
297
-
if len("object") > 1000000 {
298
-
return xerrors.Errorf("Value in field \"object\" was too long")
278
+
// t.Member (string) (string)
279
+
if len("member") > 1000000 {
280
+
return xerrors.Errorf("Value in field \"member\" was too long")
299
281
}
300
282
301
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("object"))); err != nil {
283
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("member"))); err != nil {
302
284
return err
303
285
}
304
-
if _, err := cw.WriteString(string("object")); err != nil {
286
+
if _, err := cw.WriteString(string("member")); err != nil {
305
287
return err
306
288
}
307
289
308
-
if len(t.Object) > 1000000 {
309
-
return xerrors.Errorf("Value in field t.Object was too long")
290
+
if len(t.Member) > 1000000 {
291
+
return xerrors.Errorf("Value in field t.Member was too long")
310
292
}
311
293
312
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Object))); err != nil {
294
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Member))); err != nil {
313
295
return err
314
296
}
315
-
if _, err := cw.WriteString(string(t.Object)); err != nil {
297
+
if _, err := cw.WriteString(string(t.Member)); err != nil {
316
298
return err
317
299
}
318
300
319
-
// t.Subject (string) (string)
320
-
if len("subject") > 1000000 {
321
-
return xerrors.Errorf("Value in field \"subject\" was too long")
322
-
}
301
+
// t.AddedAt (string) (string)
302
+
if t.AddedAt != nil {
323
303
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
-
}
304
+
if len("addedAt") > 1000000 {
305
+
return xerrors.Errorf("Value in field \"addedAt\" was too long")
306
+
}
330
307
331
-
if len(t.Subject) > 1000000 {
332
-
return xerrors.Errorf("Value in field t.Subject was too long")
333
-
}
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
+
}
334
314
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
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
+
}
340
331
}
341
332
return nil
342
333
}
343
334
344
-
func (t *KnotPolicy) UnmarshalCBOR(r io.Reader) (err error) {
345
-
*t = KnotPolicy{}
335
+
func (t *KnotMember) UnmarshalCBOR(r io.Reader) (err error) {
336
+
*t = KnotMember{}
346
337
347
338
cr := cbg.NewCborReader(r)
348
339
···
361
352
}
362
353
363
354
if extra > cbg.MaxLength {
364
-
return fmt.Errorf("KnotPolicy: map struct too large (%d)", extra)
355
+
return fmt.Errorf("KnotMember: map struct too large (%d)", extra)
365
356
}
366
357
367
358
n := extra
···
393
384
394
385
t.LexiconTypeID = string(sval)
395
386
}
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
387
// t.Domain (string) (string)
408
388
case "domain":
409
389
···
415
395
416
396
t.Domain = string(sval)
417
397
}
418
-
// t.Object (string) (string)
419
-
case "object":
398
+
// t.Member (string) (string)
399
+
case "member":
420
400
421
401
{
422
402
sval, err := cbg.ReadStringWithMax(cr, 1000000)
···
424
404
return err
425
405
}
426
406
427
-
t.Object = string(sval)
407
+
t.Member = string(sval)
428
408
}
429
-
// t.Subject (string) (string)
430
-
case "subject":
409
+
// t.AddedAt (string) (string)
410
+
case "addedAt":
431
411
432
412
{
433
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
413
+
b, err := cr.ReadByte()
434
414
if err != nil {
435
415
return err
436
416
}
417
+
if b != cbg.CborNull[0] {
418
+
if err := cr.UnreadByte(); err != nil {
419
+
return err
420
+
}
437
421
438
-
t.Subject = string(sval)
422
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
423
+
if err != nil {
424
+
return err
425
+
}
426
+
427
+
t.AddedAt = (*string)(&sval)
428
+
}
439
429
}
440
430
441
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
1
+
package rbac
2
2
3
3
import (
4
4
"database/sql"
···
37
37
return matched
38
38
}
39
39
40
-
func NewEnforcer() (*Enforcer, error) {
40
+
func NewEnforcer(path string) (*Enforcer, error) {
41
41
m, err := model.NewModelFromString(Model)
42
42
if err != nil {
43
43
return nil, err
44
44
}
45
45
46
-
// TODO: conf this
47
-
db, err := sql.Open("sqlite3", "appview.db")
46
+
db, err := sql.Open("sqlite3", path)
48
47
if err != nil {
49
48
return nil, err
50
49
}
+35
-11
appview/state/state.go
+35
-11
appview/state/state.go
···
22
22
"github.com/sotangled/tangled/appview/auth"
23
23
"github.com/sotangled/tangled/appview/db"
24
24
"github.com/sotangled/tangled/appview/pages"
25
+
"github.com/sotangled/tangled/rbac"
25
26
)
26
27
27
28
type State struct {
28
29
db *db.DB
29
30
auth *auth.Auth
30
-
enforcer *Enforcer
31
+
enforcer *rbac.Enforcer
31
32
}
32
33
33
34
func Make() (*State, error) {
34
-
db, err := db.Make("appview.db")
35
+
36
+
db, err := db.Make(appview.SqliteDbPath)
35
37
if err != nil {
36
38
return nil, err
37
39
}
···
41
43
return nil, err
42
44
}
43
45
44
-
enforcer, err := NewEnforcer()
46
+
enforcer, err := rbac.NewEnforcer(appview.SqliteDbPath)
45
47
if err != nil {
46
48
return nil, err
47
49
}
···
175
177
Collection: tangled.PublicKeyNSID,
176
178
Repo: did,
177
179
Rkey: uuid.New().String(),
178
-
Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{
179
-
Created: time.Now().String(),
180
-
Key: key,
181
-
Name: name,
182
-
}},
180
+
Record: &lexutil.LexiconTypeDecoder{
181
+
Val: &tangled.PublicKey{
182
+
Created: time.Now().Format(time.RFC3339),
183
+
Key: key,
184
+
Name: name,
185
+
}},
183
186
})
184
-
185
187
// invalid record
186
188
if err != nil {
187
189
log.Printf("failed to create record: %s", err)
···
382
384
w.Write([]byte("failed to resolve member did to a handle"))
383
385
return
384
386
}
385
-
386
387
log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain)
387
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
+
388
412
err = s.enforcer.AddMember(domain, memberIdent.DID.String())
389
413
if err != nil {
390
414
w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
···
481
505
r.Post("/key", s.RegistrationKey)
482
506
483
507
r.Route("/{domain}", func(r chi.Router) {
484
-
r.Get("/", s.KnotServerInfo)
485
508
r.Post("/init", s.InitKnotServer)
509
+
r.Get("/", s.KnotServerInfo)
486
510
r.Route("/member", func(r chi.Router) {
487
511
r.Use(RoleMiddleware(s, "server:owner"))
488
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
11
"github.com/sotangled/tangled/knotserver"
12
12
"github.com/sotangled/tangled/knotserver/config"
13
13
"github.com/sotangled/tangled/knotserver/db"
14
+
"github.com/sotangled/tangled/rbac"
14
15
)
15
16
16
17
func main() {
···
34
35
log.Fatalf("failed to setup db: %s", err)
35
36
}
36
37
37
-
mux, err := knotserver.Setup(ctx, c, db)
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)
38
44
if err != nil {
39
45
log.Fatal(err)
40
46
}
+26
-4
knotserver/handler.go
+26
-4
knotserver/handler.go
···
12
12
"github.com/sotangled/tangled/knotserver/config"
13
13
"github.com/sotangled/tangled/knotserver/db"
14
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
15
20
)
16
21
17
22
type Handle struct {
18
23
c *config.Config
19
24
db *db.DB
20
25
js *jsclient.JetstreamClient
26
+
e *rbac.Enforcer
21
27
22
28
// init is a channel that is closed when the knot has been initailized
23
29
// i.e. when the first user (knot owner) has been added.
···
25
31
knotInitialized bool
26
32
}
27
33
28
-
func Setup(ctx context.Context, c *config.Config, db *db.DB) (http.Handler, error) {
34
+
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer) (http.Handler, error) {
29
35
r := chi.NewRouter()
30
36
31
37
h := Handle{
32
38
c: c,
33
39
db: db,
40
+
e: e,
34
41
init: make(chan struct{}),
35
42
}
36
43
37
-
err := h.StartJetstream(ctx)
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)
38
50
if err != nil {
39
51
return nil, fmt.Errorf("failed to start jetstream: %w", err)
40
52
}
···
94
106
}
95
107
96
108
func (h *Handle) StartJetstream(ctx context.Context) error {
97
-
colections := []string{tangled.PublicKeyNSID}
109
+
collections := []string{tangled.PublicKeyNSID, tangled.KnotMemberNSID}
98
110
dids := []string{}
99
111
100
-
h.js = jsclient.NewJetstreamClient(colections, dids)
112
+
h.js = jsclient.NewJetstreamClient(collections, dids)
101
113
messages, err := h.js.ReadJetstream(ctx)
102
114
if err != nil {
103
115
return fmt.Errorf("failed to read from jetstream: %w", err)
···
126
138
log.Printf("failed to add public key: %v", err)
127
139
} else {
128
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))
129
151
}
130
152
default:
131
153
}
+8
knotserver/jsclient/jetstream.go
+8
knotserver/jsclient/jetstream.go
···
55
55
j.triggerReconnect()
56
56
}
57
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
+
58
66
func (j *JetstreamClient) triggerReconnect() {
59
67
select {
60
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
-
}