this repo has no description
1package state 2 3import ( 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" 14 "github.com/icyphox/bild/appview/auth" 15 "github.com/icyphox/bild/appview/db" 16) 17 18type State struct { 19 Db *db.DB 20 Auth *auth.Auth 21} 22 23func Make() (*State, error) { 24 db, err := db.Make("appview.db") 25 if err != nil { 26 return nil, err 27 } 28 29 auth, err := auth.Make() 30 if err != nil { 31 return nil, err 32 } 33 34 return &State{db, auth}, nil 35} 36 37func (s *State) Login(w http.ResponseWriter, r *http.Request) { 38 ctx := r.Context() 39 40 switch r.Method { 41 case http.MethodGet: 42 log.Println("unimplemented") 43 return 44 case http.MethodPost: 45 username := r.FormValue("username") 46 appPassword := r.FormValue("password") 47 48 atSession, err := s.Auth.CreateInitialSession(ctx, username, appPassword) 49 if err != nil { 50 log.Printf("creating initial session: %s", err) 51 return 52 } 53 sessionish := auth.CreateSessionWrapper{atSession} 54 55 err = s.Auth.StoreSession(r, w, &sessionish) 56 if err != nil { 57 log.Printf("storing session: %s", err) 58 return 59 } 60 61 log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did) 62 http.Redirect(w, r, "/", http.StatusSeeOther) 63 return 64 } 65} 66 67// requires auth 68func (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 108func (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 157func (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 172}