this repo has no description

implement node endpoints

Akshay e8692579 2d3fbaa7

Changed files
+96 -72
appview
db
state
+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
··· 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