+90
-35
appview/db/db.go
+90
-35
appview/db/db.go
···
4
"database/sql"
5
"fmt"
6
"log"
7
8
"github.com/google/uuid"
9
-
10
_ "github.com/mattn/go-sqlite3"
11
)
12
···
25
domain text not null unique,
26
did text not null,
27
secret text not null,
28
-
created integer default (strftime('%s', 'now')),
29
-
registered integer);
30
create table if not exists public_keys (
31
id integer primary key autoincrement,
32
did text not null,
···
42
return &DB{db: db}, nil
43
}
44
45
-
type RegStatus uint32
46
47
const (
48
-
Registered RegStatus = iota
49
-
Unregistered
50
Pending
51
)
52
53
// returns registered status, did of owner, error
54
-
func (d *DB) RegistrationStatus(domain string) (RegStatus, string, error) {
55
-
var registeredBy string
56
-
var registratedAt *uint64
57
err := d.db.QueryRow(`
58
-
select did, registered from registrations
59
where domain = ?
60
-
`, domain).Scan(®isteredBy, ®istratedAt)
61
if err != nil {
62
if err == sql.ErrNoRows {
63
-
return Unregistered, "", nil
64
} else {
65
-
return Unregistered, "", err
66
}
67
}
68
69
-
if registratedAt != nil {
70
-
return Registered, registeredBy, nil
71
-
} else {
72
-
return Pending, registeredBy, nil
73
-
}
74
}
75
76
func (d *DB) GenerateRegistrationKey(domain, did string) (string, error) {
77
// sanity check: does this domain already have a registration?
78
-
status, owner, err := d.RegistrationStatus(domain)
79
if err != nil {
80
return "", err
81
}
82
-
switch status {
83
-
case Registered:
84
-
// already registered by `owner`
85
-
return "", fmt.Errorf("%s already registered by %s", domain, owner)
86
-
case Pending:
87
-
log.Printf("%s registered by %s, status pending", domain, owner)
88
-
// TODO: provide a warning here, and allow the current user to overwrite
89
-
// the registration, this prevents users from registering domains that they
90
-
// do not own
91
-
default:
92
-
// ok, we can register this domain
93
}
94
95
secret := uuid.New().String()
···
134
135
return nil
136
}
137
-
138
-
// type Registration struct {
139
-
// status RegStatus
140
-
// }
141
-
// func (d *DB) RegistrationsForDid(did string) ()
···
4
"database/sql"
5
"fmt"
6
"log"
7
+
"time"
8
9
"github.com/google/uuid"
10
_ "github.com/mattn/go-sqlite3"
11
)
12
···
25
domain text not null unique,
26
did text not null,
27
secret text not null,
28
+
created timestamp default current_timestamp,
29
+
registered timestamp);
30
create table if not exists public_keys (
31
id integer primary key autoincrement,
32
did text not null,
···
42
return &DB{db: db}, nil
43
}
44
45
+
type Registration struct {
46
+
Domain string
47
+
ByDid string
48
+
Created *time.Time
49
+
Registered *time.Time
50
+
}
51
+
52
+
func (r *Registration) Status() Status {
53
+
if r.Registered != nil {
54
+
return Registered
55
+
} else {
56
+
return Pending
57
+
}
58
+
}
59
+
60
+
type Status uint32
61
62
const (
63
+
Registered Status = iota
64
Pending
65
)
66
67
// returns registered status, did of owner, error
68
+
func (d *DB) RegistrationsByDid(did string) ([]Registration, error) {
69
+
var registrations []Registration
70
+
71
+
rows, err := d.db.Query(`
72
+
select domain, did, created, registered from registrations
73
+
where did = ?
74
+
`, did)
75
+
if err != nil {
76
+
return nil, err
77
+
}
78
+
79
+
for rows.Next() {
80
+
var createdAt *int64
81
+
var registeredAt *int64
82
+
var registration Registration
83
+
err = rows.Scan(®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt)
84
+
85
+
if err != nil {
86
+
log.Println(err)
87
+
} else {
88
+
createdAtTime := time.Unix(*createdAt, 0)
89
+
90
+
var registeredAtTime *time.Time
91
+
if registeredAt != nil {
92
+
x := time.Unix(*registeredAt, 0)
93
+
registeredAtTime = &x
94
+
}
95
+
96
+
registration.Created = &createdAtTime
97
+
registration.Registered = registeredAtTime
98
+
registrations = append(registrations, registration)
99
+
}
100
+
}
101
+
102
+
return registrations, nil
103
+
}
104
+
105
+
// returns registered status, did of owner, error
106
+
func (d *DB) RegistrationByDomain(domain string) (*Registration, error) {
107
+
var createdAt *int64
108
+
var registeredAt *int64
109
+
var registration Registration
110
err := d.db.QueryRow(`
111
+
select domain, did, created, registered from registrations
112
where domain = ?
113
+
`, domain).Scan(®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt)
114
+
115
+
createdAtTime := time.Unix(*createdAt, 0)
116
+
var registeredAtTime *time.Time
117
+
if registeredAt != nil {
118
+
x := time.Unix(*registeredAt, 0)
119
+
registeredAtTime = &x
120
+
}
121
+
122
+
registration.Created = &createdAtTime
123
+
registration.Registered = registeredAtTime
124
+
125
if err != nil {
126
if err == sql.ErrNoRows {
127
+
return nil, nil
128
} else {
129
+
return nil, err
130
}
131
}
132
133
+
return ®istration, nil
134
}
135
136
func (d *DB) GenerateRegistrationKey(domain, did string) (string, error) {
137
// sanity check: does this domain already have a registration?
138
+
reg, err := d.RegistrationByDomain(domain)
139
if err != nil {
140
return "", err
141
}
142
+
143
+
// registration is open
144
+
if reg != nil {
145
+
switch reg.Status() {
146
+
case Registered:
147
+
// already registered by `owner`
148
+
return "", fmt.Errorf("%s already registered by %s", domain, reg.ByDid)
149
+
case Pending:
150
+
// TODO: be loud about this
151
+
log.Printf("%s registered by %s, status pending", domain, reg.ByDid)
152
+
}
153
}
154
155
secret := uuid.New().String()
···
194
195
return nil
196
}
+34
appview/pages/knots.html
+34
appview/pages/knots.html
···
···
1
+
{{define "title"}}knots{{end}}
2
+
3
+
{{define "content"}}
4
+
<a href="/">back to timeline</a>
5
+
<h1>knots</h1>
6
+
7
+
<h2>register</h2>
8
+
put in a domain, and use the key while booting up your knotserver
9
+
<form hx-post="/knots/key">
10
+
<label for="domain">domain:</label>
11
+
<input type="text" id="domain" name="domain" required>
12
+
13
+
<button type="domain">generate key</button>
14
+
</form>
15
+
16
+
<h3>existing registrations</h3>
17
+
<ul id="registrations">
18
+
{{range .Registrations}}
19
+
<li>
20
+
<code>domain: {{.Domain}}</code><br>
21
+
<code>opened by: {{.ByDid}}</code><br>
22
+
<code>on: {{.Created}}</code><br>
23
+
{{if .Registered}}
24
+
<code>registered on: {{.Registered}}</code>
25
+
{{else}}
26
+
<code>pending registration</code>
27
+
<button hx-post="/knots/init/{{.Domain}}">initialize</button>
28
+
{{end}}
29
+
</li>
30
+
{{else}}
31
+
<p>no registrations yet</p>
32
+
{{end}}
33
+
</ul>
34
+
{{end}}
+9
appview/pages/pages.go
+9
appview/pages/pages.go
···
57
func Settings(w io.Writer, p SettingsParams) error {
58
return parse("settings.html").Execute(w, p)
59
}
60
+
61
+
type KnotsParams struct {
62
+
User *auth.User
63
+
Registrations []db.Registration
64
+
}
65
+
66
+
func Knots(w io.Writer, p KnotsParams) error {
67
+
return parse("knots.html").Execute(w, p)
68
+
}
+1
appview/pages/timeline.html
+1
appview/pages/timeline.html
+24
-18
appview/state/state.go
+24
-18
appview/state/state.go
···
87
88
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
89
user := s.auth.GetUser(r)
90
-
fmt.Printf("%+v\n", user)
91
pages.Timeline(w, pages.TimelineParams{
92
User: user,
93
})
···
142
User: user,
143
PubKeys: pubKeys,
144
})
145
-
return
146
-
147
}
148
149
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
···
194
}
195
196
// create a signed request and check if a node responds to that
197
-
//
198
-
// we should also rate limit these checks to avoid ddosing knotservers
199
-
func (s *State) Check(w http.ResponseWriter, r *http.Request) {
200
-
domain := r.FormValue("domain")
201
if domain == "" {
202
-
http.Error(w, "Invalid form", http.StatusBadRequest)
203
return
204
}
205
···
261
}
262
263
// set permissions for this did as owner
264
-
_, did, err := s.db.RegistrationStatus(domain)
265
if err != nil {
266
log.Println("failed to register domain", err)
267
http.Error(w, err.Error(), http.StatusInternalServerError)
···
277
}
278
279
// add this did as owner of this domain
280
-
err = s.enforcer.AddOwner(domain, did)
281
if err != nil {
282
log.Println("failed to setup owner of domain", err)
283
http.Error(w, err.Error(), http.StatusInternalServerError)
···
285
}
286
287
w.Write([]byte("check success"))
288
289
-
return
290
}
291
292
func buildPingRequest(url, secret string) (*http.Request, error) {
···
315
r.Get("/login", s.Login)
316
r.Post("/login", s.Login)
317
318
-
r.Route("/node", func(r chi.Router) {
319
-
r.Post("/check", s.Check)
320
-
321
-
r.Group(func(r chi.Router) {
322
-
r.Use(AuthMiddleware(s))
323
-
r.Post("/key", s.RegistrationKey)
324
-
})
325
})
326
327
r.Group(func(r chi.Router) {
···
87
88
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
89
user := s.auth.GetUser(r)
90
pages.Timeline(w, pages.TimelineParams{
91
User: user,
92
})
···
141
User: user,
142
PubKeys: pubKeys,
143
})
144
}
145
146
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
···
191
}
192
193
// create a signed request and check if a node responds to that
194
+
func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
195
+
domain := chi.URLParam(r, "domain")
196
if domain == "" {
197
+
http.Error(w, "malformed url", http.StatusBadRequest)
198
return
199
}
200
···
256
}
257
258
// set permissions for this did as owner
259
+
reg, err := s.db.RegistrationByDomain(domain)
260
if err != nil {
261
log.Println("failed to register domain", err)
262
http.Error(w, err.Error(), http.StatusInternalServerError)
···
272
}
273
274
// add this did as owner of this domain
275
+
err = s.enforcer.AddOwner(domain, reg.ByDid)
276
if err != nil {
277
log.Println("failed to setup owner of domain", err)
278
http.Error(w, err.Error(), http.StatusInternalServerError)
···
280
}
281
282
w.Write([]byte("check success"))
283
+
}
284
285
+
// get knots registered by this user
286
+
func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
287
+
// for now, this is just pubkeys
288
+
user := s.auth.GetUser(r)
289
+
registrations, err := s.db.RegistrationsByDid(user.Did)
290
+
if err != nil {
291
+
log.Println(err)
292
+
}
293
+
294
+
pages.Knots(w, pages.KnotsParams{
295
+
User: user,
296
+
Registrations: registrations,
297
+
})
298
}
299
300
func buildPingRequest(url, secret string) (*http.Request, error) {
···
323
r.Get("/login", s.Login)
324
r.Post("/login", s.Login)
325
326
+
r.Route("/knots", func(r chi.Router) {
327
+
r.Use(AuthMiddleware(s))
328
+
r.Get("/", s.Knots)
329
+
r.Post("/init/{domain}", s.InitKnotServer)
330
+
r.Post("/key", s.RegistrationKey)
331
})
332
333
r.Group(func(r chi.Router) {