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 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
··· 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