+15
-31
appview/db/db.go
+15
-31
appview/db/db.go
···
1
package db
2
3
import (
4
-
"context"
5
"database/sql"
6
-
"encoding/hex"
7
"fmt"
8
"log"
9
10
"github.com/google/uuid"
11
_ "github.com/mattn/go-sqlite3"
12
-
"golang.org/x/crypto/bcrypt"
13
)
14
15
type DB struct {
···
88
}
89
90
secret := uuid.New().String()
91
-
hashedSecret, err := bcrypt.GenerateFromPassword([]byte(secret), 3)
92
93
if err != nil {
94
return "", err
···
98
insert into registrations (domain, did, secret)
99
values (?, ?, ?)
100
on conflict(domain) do update set did = excluded.did, secret = excluded.secret
101
-
`, domain, did, fmt.Sprintf("%x", hashedSecret))
102
103
if err != nil {
104
return "", err
···
107
return secret, nil
108
}
109
110
-
func (d *DB) Register(domain, secret string) error {
111
-
ctx := context.TODO()
112
113
-
tx, err := d.db.BeginTx(ctx, nil)
114
if err != nil {
115
-
return err
116
}
117
118
-
res := tx.QueryRow(`select secret from registrations where domain = ?`, domain)
119
120
-
var hexSecret string
121
-
err = res.Scan(&hexSecret)
122
-
if err != nil {
123
-
return err
124
-
}
125
-
126
-
decoded, err := hex.DecodeString(hexSecret)
127
-
if err != nil {
128
-
return err
129
-
}
130
-
131
-
err = bcrypt.CompareHashAndPassword(decoded, []byte(secret))
132
-
if err != nil {
133
-
return err
134
-
}
135
-
136
-
_, err = tx.Exec(`
137
update registrations
138
set registered = strftime('%s', 'now')
139
where domain = ?;
140
`, domain)
141
-
if err != nil {
142
-
return err
143
-
}
144
145
-
err = tx.Commit()
146
if err != nil {
147
return err
148
}
149
150
return nil
151
}
···
1
package db
2
3
import (
4
"database/sql"
5
"fmt"
6
"log"
7
8
"github.com/google/uuid"
9
_ "github.com/mattn/go-sqlite3"
10
)
11
12
type DB struct {
···
85
}
86
87
secret := uuid.New().String()
88
89
if err != nil {
90
return "", err
···
94
insert into registrations (domain, did, secret)
95
values (?, ?, ?)
96
on conflict(domain) do update set did = excluded.did, secret = excluded.secret
97
+
`, domain, did, secret)
98
99
if err != nil {
100
return "", err
···
103
return secret, nil
104
}
105
106
+
func (d *DB) GetRegistrationKey(domain string) (string, error) {
107
+
res := d.db.QueryRow(`select secret from registrations where domain = ?`, domain)
108
109
+
var secret string
110
+
err := res.Scan(&secret)
111
if err != nil {
112
+
return "", nil
113
}
114
115
+
return secret, nil
116
+
}
117
118
+
func (d *DB) Register(domain string) error {
119
+
_, err := d.db.Exec(`
120
update registrations
121
set registered = strftime('%s', 'now')
122
where domain = ?;
123
`, domain)
124
125
if err != nil {
126
return err
127
}
128
129
return nil
130
}
131
+
132
+
// type Registration struct {
133
+
// status RegStatus
134
+
// }
135
+
// func (d *DB) RegistrationsForDid(did string) ()
+81
-41
appview/state/state.go
+81
-41
appview/state/state.go
···
1
package state
2
3
import (
4
-
"encoding/json"
5
"log"
6
"net/http"
7
8
"github.com/go-chi/chi/v5"
9
"github.com/icyphox/bild/appview"
···
61
}
62
63
// requires auth
64
-
func (s *State) GenerateRegistrationKey(w http.ResponseWriter, r *http.Request) {
65
-
session, err := s.Auth.Store.Get(r, appview.SESSION_NAME)
66
-
if err != nil || session.IsNew {
67
-
log.Println("unauthorized attempt to generate registration key")
68
-
http.Error(w, "Forbidden", http.StatusUnauthorized)
69
return
70
}
71
72
-
did := session.Values[appview.SESSION_DID].(string)
73
domain := r.FormValue("domain")
74
if domain == "" {
75
http.Error(w, "Invalid form", http.StatusBadRequest)
76
return
77
}
78
79
-
key, err := s.Db.GenerateRegistrationKey(domain, did)
80
-
81
if err != nil {
82
-
log.Println(err)
83
-
http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
84
return
85
}
86
87
-
w.Write([]byte(key))
88
-
return
89
-
}
90
91
-
type RegisterRequest struct {
92
-
Domain string `json:"domain"`
93
-
Secret string `json:"secret"`
94
-
}
95
96
-
func (s *State) Register(w http.ResponseWriter, r *http.Request) {
97
-
switch r.Method {
98
-
case http.MethodGet:
99
-
log.Println("unimplemented")
100
return
101
-
case http.MethodPost:
102
-
var req RegisterRequest
103
-
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
104
-
http.Error(w, "invalid request body", http.StatusBadRequest)
105
-
return
106
-
}
107
108
-
domain := req.Domain
109
-
secret := req.Secret
110
111
-
err := s.Db.Register(domain, secret)
112
-
if err != nil {
113
-
log.Println("failed to register domain", err)
114
-
return
115
-
}
116
117
-
log.Printf("Registered domain: %s with secret: %s", domain, secret)
118
-
}
119
}
120
121
func (s *State) Router() http.Handler {
122
r := chi.NewRouter()
123
124
r.Post("/login", s.Login)
125
-
r.Post("/node/register", s.Register)
126
-
r.Group(func(r chi.Router) {
127
-
r.Use(AuthMiddleware(s))
128
-
r.Post("/node/generate-key", s.GenerateRegistrationKey)
129
})
130
131
return r
···
1
package state
2
3
import (
4
+
"crypto/hmac"
5
+
"crypto/sha256"
6
+
"encoding/hex"
7
"log"
8
"net/http"
9
+
"net/url"
10
+
"time"
11
12
"github.com/go-chi/chi/v5"
13
"github.com/icyphox/bild/appview"
···
65
}
66
67
// requires auth
68
+
func (s *State) Key(w http.ResponseWriter, r *http.Request) {
69
+
switch r.Method {
70
+
case http.MethodGet:
71
+
// list open registrations under this did
72
+
73
+
return
74
+
case http.MethodPost:
75
+
session, err := s.Auth.Store.Get(r, appview.SESSION_NAME)
76
+
if err != nil || session.IsNew {
77
+
log.Println("unauthorized attempt to generate registration key")
78
+
http.Error(w, "Forbidden", http.StatusUnauthorized)
79
+
return
80
+
}
81
+
82
+
did := session.Values[appview.SESSION_DID].(string)
83
+
84
+
// check if domain is valid url, and strip extra bits down to just host
85
+
domain := r.FormValue("domain")
86
+
url, err := url.Parse(domain)
87
+
if domain == "" || err != nil {
88
+
http.Error(w, "Invalid form", http.StatusBadRequest)
89
+
return
90
+
}
91
+
92
+
key, err := s.Db.GenerateRegistrationKey(url.Host, did)
93
+
94
+
if err != nil {
95
+
log.Println(err)
96
+
http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
97
+
return
98
+
}
99
+
100
+
w.Write([]byte(key))
101
return
102
}
103
+
}
104
105
+
// create a signed request and check if a node responds to that
106
+
//
107
+
// we should also rate limit these checks to avoid ddosing knotservers
108
+
func (s *State) Check(w http.ResponseWriter, r *http.Request) {
109
domain := r.FormValue("domain")
110
if domain == "" {
111
http.Error(w, "Invalid form", http.StatusBadRequest)
112
return
113
}
114
115
+
secret, err := s.Db.GetRegistrationKey(domain)
116
if err != nil {
117
+
log.Printf("no key found for domain %s: %s\n", domain, err)
118
return
119
}
120
121
+
hmac := hmac.New(sha256.New, []byte(secret))
122
+
signature := hex.EncodeToString(hmac.Sum(nil))
123
124
+
// make a request do the knotserver with an empty body and above signature
125
+
url, _ := url.Parse(domain)
126
+
url = url.JoinPath("check")
127
+
pingRequest, err := http.NewRequest("GET", url.String(), nil)
128
+
if err != nil {
129
+
log.Println("failed to create ping request for ", url.String())
130
+
return
131
+
}
132
+
pingRequest.Header.Set("X-Signature", signature)
133
134
+
client := &http.Client{
135
+
Timeout: 5 * time.Second,
136
+
}
137
+
resp, err := client.Do(pingRequest)
138
+
if err != nil {
139
+
w.Write([]byte("no dice"))
140
+
log.Println("domain was unreachable after 5 seconds")
141
return
142
+
}
143
144
+
if resp.StatusCode != http.StatusOK {
145
+
log.Println("status nok")
146
+
w.Write([]byte("no dice"))
147
+
return
148
+
}
149
+
w.Write([]byte("check success"))
150
151
+
// mark as registered
152
+
s.Db.Register(domain)
153
154
+
return
155
}
156
157
func (s *State) Router() http.Handler {
158
r := chi.NewRouter()
159
160
r.Post("/login", s.Login)
161
+
162
+
r.Route("/node", func(r chi.Router) {
163
+
r.Post("/check", s.Check)
164
+
165
+
r.Group(func(r chi.Router) {
166
+
r.Use(AuthMiddleware(s))
167
+
r.Post("/key", s.Key)
168
+
})
169
})
170
171
return r