+15
-31
appview/db/db.go
+15
-31
appview/db/db.go
···
1
1
package db
2
2
3
3
import (
4
-
"context"
5
4
"database/sql"
6
-
"encoding/hex"
7
5
"fmt"
8
6
"log"
9
7
10
8
"github.com/google/uuid"
11
9
_ "github.com/mattn/go-sqlite3"
12
-
"golang.org/x/crypto/bcrypt"
13
10
)
14
11
15
12
type DB struct {
···
88
85
}
89
86
90
87
secret := uuid.New().String()
91
-
hashedSecret, err := bcrypt.GenerateFromPassword([]byte(secret), 3)
92
88
93
89
if err != nil {
94
90
return "", err
···
98
94
insert into registrations (domain, did, secret)
99
95
values (?, ?, ?)
100
96
on conflict(domain) do update set did = excluded.did, secret = excluded.secret
101
-
`, domain, did, fmt.Sprintf("%x", hashedSecret))
97
+
`, domain, did, secret)
102
98
103
99
if err != nil {
104
100
return "", err
···
107
103
return secret, nil
108
104
}
109
105
110
-
func (d *DB) Register(domain, secret string) error {
111
-
ctx := context.TODO()
106
+
func (d *DB) GetRegistrationKey(domain string) (string, error) {
107
+
res := d.db.QueryRow(`select secret from registrations where domain = ?`, domain)
112
108
113
-
tx, err := d.db.BeginTx(ctx, nil)
109
+
var secret string
110
+
err := res.Scan(&secret)
114
111
if err != nil {
115
-
return err
112
+
return "", nil
116
113
}
117
114
118
-
res := tx.QueryRow(`select secret from registrations where domain = ?`, domain)
115
+
return secret, nil
116
+
}
119
117
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(`
118
+
func (d *DB) Register(domain string) error {
119
+
_, err := d.db.Exec(`
137
120
update registrations
138
121
set registered = strftime('%s', 'now')
139
122
where domain = ?;
140
123
`, domain)
141
-
if err != nil {
142
-
return err
143
-
}
144
124
145
-
err = tx.Commit()
146
125
if err != nil {
147
126
return err
148
127
}
149
128
150
129
return nil
151
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
1
package state
2
2
3
3
import (
4
-
"encoding/json"
4
+
"crypto/hmac"
5
+
"crypto/sha256"
6
+
"encoding/hex"
5
7
"log"
6
8
"net/http"
9
+
"net/url"
10
+
"time"
7
11
8
12
"github.com/go-chi/chi/v5"
9
13
"github.com/icyphox/bild/appview"
···
61
65
}
62
66
63
67
// 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)
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))
69
101
return
70
102
}
103
+
}
71
104
72
-
did := session.Values[appview.SESSION_DID].(string)
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) {
73
109
domain := r.FormValue("domain")
74
110
if domain == "" {
75
111
http.Error(w, "Invalid form", http.StatusBadRequest)
76
112
return
77
113
}
78
114
79
-
key, err := s.Db.GenerateRegistrationKey(domain, did)
80
-
115
+
secret, err := s.Db.GetRegistrationKey(domain)
81
116
if err != nil {
82
-
log.Println(err)
83
-
http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
117
+
log.Printf("no key found for domain %s: %s\n", domain, err)
84
118
return
85
119
}
86
120
87
-
w.Write([]byte(key))
88
-
return
89
-
}
121
+
hmac := hmac.New(sha256.New, []byte(secret))
122
+
signature := hex.EncodeToString(hmac.Sum(nil))
90
123
91
-
type RegisterRequest struct {
92
-
Domain string `json:"domain"`
93
-
Secret string `json:"secret"`
94
-
}
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)
95
133
96
-
func (s *State) Register(w http.ResponseWriter, r *http.Request) {
97
-
switch r.Method {
98
-
case http.MethodGet:
99
-
log.Println("unimplemented")
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")
100
141
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
-
}
142
+
}
107
143
108
-
domain := req.Domain
109
-
secret := req.Secret
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"))
110
150
111
-
err := s.Db.Register(domain, secret)
112
-
if err != nil {
113
-
log.Println("failed to register domain", err)
114
-
return
115
-
}
151
+
// mark as registered
152
+
s.Db.Register(domain)
116
153
117
-
log.Printf("Registered domain: %s with secret: %s", domain, secret)
118
-
}
154
+
return
119
155
}
120
156
121
157
func (s *State) Router() http.Handler {
122
158
r := chi.NewRouter()
123
159
124
160
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)
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
+
})
129
169
})
130
170
131
171
return r