this repo has no description
1package state 2 3import ( 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 "fmt" 8 "log" 9 "net/http" 10 "time" 11 12 comatproto "github.com/bluesky-social/indigo/api/atproto" 13 lexutil "github.com/bluesky-social/indigo/lex/util" 14 "github.com/gliderlabs/ssh" 15 "github.com/go-chi/chi/v5" 16 "github.com/google/uuid" 17 tangled "github.com/sotangled/tangled/api/tangled" 18 "github.com/sotangled/tangled/appview" 19 "github.com/sotangled/tangled/appview/auth" 20 "github.com/sotangled/tangled/appview/db" 21 "github.com/sotangled/tangled/appview/pages" 22) 23 24type State struct { 25 db *db.DB 26 auth *auth.Auth 27 enforcer *Enforcer 28} 29 30func Make() (*State, error) { 31 db, err := db.Make("appview.db") 32 if err != nil { 33 return nil, err 34 } 35 36 auth, err := auth.Make() 37 if err != nil { 38 return nil, err 39 } 40 41 enforcer, err := NewEnforcer() 42 if err != nil { 43 return nil, err 44 } 45 46 return &State{db, auth, enforcer}, nil 47} 48 49func (s *State) Login(w http.ResponseWriter, r *http.Request) { 50 ctx := r.Context() 51 52 switch r.Method { 53 case http.MethodGet: 54 pages.Login(w, pages.LoginParams{}) 55 return 56 case http.MethodPost: 57 handle := r.FormValue("handle") 58 appPassword := r.FormValue("app_password") 59 60 fmt.Println("handle", handle) 61 fmt.Println("app_password", appPassword) 62 63 resolved, err := auth.ResolveIdent(ctx, handle) 64 if err != nil { 65 log.Printf("resolving identity: %s", err) 66 return 67 } 68 69 atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword) 70 if err != nil { 71 log.Printf("creating initial session: %s", err) 72 return 73 } 74 sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession} 75 76 err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint()) 77 if err != nil { 78 log.Printf("storing session: %s", err) 79 return 80 } 81 82 log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did) 83 http.Redirect(w, r, "/", http.StatusSeeOther) 84 return 85 } 86} 87 88func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 89 user := s.auth.GetUser(r) 90 fmt.Printf("%+v\n", user) 91 pages.Timeline(w, pages.TimelineParams{ 92 User: user, 93 }) 94 return 95} 96 97// requires auth 98func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) { 99 switch r.Method { 100 case http.MethodGet: 101 // list open registrations under this did 102 103 return 104 case http.MethodPost: 105 session, err := s.auth.Store.Get(r, appview.SessionName) 106 if err != nil || session.IsNew { 107 log.Println("unauthorized attempt to generate registration key") 108 http.Error(w, "Forbidden", http.StatusUnauthorized) 109 return 110 } 111 112 did := session.Values[appview.SessionDid].(string) 113 114 // check if domain is valid url, and strip extra bits down to just host 115 domain := r.FormValue("domain") 116 if domain == "" { 117 http.Error(w, "Invalid form", http.StatusBadRequest) 118 return 119 } 120 121 key, err := s.db.GenerateRegistrationKey(domain, did) 122 123 if err != nil { 124 log.Println(err) 125 http.Error(w, "unable to register this domain", http.StatusNotAcceptable) 126 return 127 } 128 129 w.Write([]byte(key)) 130 } 131} 132 133func (s *State) Settings(w http.ResponseWriter, r *http.Request) { 134 // for now, this is just pubkeys 135 user := s.auth.GetUser(r) 136 pubKeys, err := s.db.GetPublicKeys(user.Did) 137 if err != nil { 138 log.Println(err) 139 } 140 141 pages.Settings(w, pages.SettingsParams{ 142 User: user, 143 PubKeys: pubKeys, 144 }) 145 return 146 147} 148 149func (s *State) Keys(w http.ResponseWriter, r *http.Request) { 150 switch r.Method { 151 case http.MethodGet: 152 w.Write([]byte("unimplemented")) 153 log.Println("unimplemented") 154 return 155 case http.MethodPut: 156 did := s.auth.GetDID(r) 157 key := r.FormValue("key") 158 name := r.FormValue("name") 159 client, _ := s.auth.AuthorizedClient(r) 160 161 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 162 if err != nil { 163 log.Printf("parsing public key: %s", err) 164 return 165 } 166 167 if err := s.db.AddPublicKey(did, name, key); err != nil { 168 log.Printf("adding public key: %s", err) 169 return 170 } 171 172 // store in pds too 173 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 174 Collection: tangled.PublicKeyNSID, 175 Repo: did, 176 Rkey: uuid.New().String(), 177 Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{ 178 Created: time.Now().String(), 179 Key: key, 180 Name: name, 181 }}, 182 }) 183 184 // invalid record 185 if err != nil { 186 log.Printf("failed to create record: %s", err) 187 return 188 } 189 190 log.Println("created atproto record: ", resp.Uri) 191 192 return 193 } 194} 195 196// create a signed request and check if a node responds to that 197// 198// we should also rate limit these checks to avoid ddosing knotservers 199func (s *State) Check(w http.ResponseWriter, r *http.Request) { 200 domain := r.FormValue("domain") 201 if domain == "" { 202 http.Error(w, "Invalid form", http.StatusBadRequest) 203 return 204 } 205 206 log.Println("checking ", domain) 207 208 secret, err := s.db.GetRegistrationKey(domain) 209 if err != nil { 210 log.Printf("no key found for domain %s: %s\n", domain, err) 211 return 212 } 213 log.Println("has secret ", secret) 214 215 // make a request do the knotserver with an empty body and above signature 216 url := fmt.Sprintf("http://%s/health", domain) 217 218 pingRequest, err := buildPingRequest(url, secret) 219 if err != nil { 220 log.Println("failed to build ping request", err) 221 return 222 } 223 224 client := &http.Client{ 225 Timeout: 5 * time.Second, 226 } 227 resp, err := client.Do(pingRequest) 228 if err != nil { 229 w.Write([]byte("no dice")) 230 log.Println("domain was unreachable after 5 seconds") 231 return 232 } 233 234 if resp.StatusCode != http.StatusOK { 235 log.Println("status nok", resp.StatusCode) 236 w.Write([]byte("no dice")) 237 return 238 } 239 240 // verify response mac 241 signature := resp.Header.Get("X-Signature") 242 signatureBytes, err := hex.DecodeString(signature) 243 if err != nil { 244 return 245 } 246 247 expectedMac := hmac.New(sha256.New, []byte(secret)) 248 expectedMac.Write([]byte("ok")) 249 250 if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 251 log.Printf("response body signature mismatch: %x\n", signatureBytes) 252 return 253 } 254 255 // mark as registered 256 err = s.db.Register(domain) 257 if err != nil { 258 log.Println("failed to register domain", err) 259 http.Error(w, err.Error(), http.StatusInternalServerError) 260 return 261 } 262 263 // set permissions for this did as owner 264 _, did, err := s.db.RegistrationStatus(domain) 265 if err != nil { 266 log.Println("failed to register domain", err) 267 http.Error(w, err.Error(), http.StatusInternalServerError) 268 return 269 } 270 271 // add basic acls for this domain 272 err = s.enforcer.AddDomain(domain) 273 if err != nil { 274 log.Println("failed to setup owner of domain", err) 275 http.Error(w, err.Error(), http.StatusInternalServerError) 276 return 277 } 278 279 // add this did as owner of this domain 280 err = s.enforcer.AddOwner(domain, did) 281 if err != nil { 282 log.Println("failed to setup owner of domain", err) 283 http.Error(w, err.Error(), http.StatusInternalServerError) 284 return 285 } 286 287 w.Write([]byte("check success")) 288 289 return 290} 291 292func buildPingRequest(url, secret string) (*http.Request, error) { 293 pingRequest, err := http.NewRequest("GET", url, nil) 294 if err != nil { 295 return nil, err 296 } 297 298 timestamp := time.Now().Format(time.RFC3339) 299 mac := hmac.New(sha256.New, []byte(secret)) 300 message := pingRequest.Method + pingRequest.URL.Path + timestamp 301 mac.Write([]byte(message)) 302 signature := hex.EncodeToString(mac.Sum(nil)) 303 304 pingRequest.Header.Set("X-Signature", signature) 305 pingRequest.Header.Set("X-Timestamp", timestamp) 306 307 return pingRequest, nil 308} 309 310func (s *State) Router() http.Handler { 311 r := chi.NewRouter() 312 313 r.Get("/", s.Timeline) 314 315 r.Get("/login", s.Login) 316 r.Post("/login", s.Login) 317 318 r.Route("/node", func(r chi.Router) { 319 r.Post("/check", s.Check) 320 321 r.Group(func(r chi.Router) { 322 r.Use(AuthMiddleware(s)) 323 r.Post("/key", s.RegistrationKey) 324 }) 325 }) 326 327 r.Group(func(r chi.Router) { 328 r.Use(AuthMiddleware(s)) 329 r.Get("/settings", s.Settings) 330 r.Put("/settings/keys", s.Keys) 331 }) 332 333 return r 334}