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 pages.Timeline(w, pages.TimelineParams{ 91 User: user, 92 }) 93 return 94} 95 96// requires auth 97func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) { 98 switch r.Method { 99 case http.MethodGet: 100 // list open registrations under this did 101 102 return 103 case http.MethodPost: 104 session, err := s.auth.Store.Get(r, appview.SessionName) 105 if err != nil || session.IsNew { 106 log.Println("unauthorized attempt to generate registration key") 107 http.Error(w, "Forbidden", http.StatusUnauthorized) 108 return 109 } 110 111 did := session.Values[appview.SessionDid].(string) 112 113 // check if domain is valid url, and strip extra bits down to just host 114 domain := r.FormValue("domain") 115 if domain == "" { 116 http.Error(w, "Invalid form", http.StatusBadRequest) 117 return 118 } 119 120 key, err := s.db.GenerateRegistrationKey(domain, did) 121 122 if err != nil { 123 log.Println(err) 124 http.Error(w, "unable to register this domain", http.StatusNotAcceptable) 125 return 126 } 127 128 w.Write([]byte(key)) 129 } 130} 131 132func (s *State) Settings(w http.ResponseWriter, r *http.Request) { 133 // for now, this is just pubkeys 134 user := s.auth.GetUser(r) 135 pubKeys, err := s.db.GetPublicKeys(user.Did) 136 if err != nil { 137 log.Println(err) 138 } 139 140 pages.Settings(w, pages.SettingsParams{ 141 User: user, 142 PubKeys: pubKeys, 143 }) 144} 145 146func (s *State) Keys(w http.ResponseWriter, r *http.Request) { 147 switch r.Method { 148 case http.MethodGet: 149 w.Write([]byte("unimplemented")) 150 log.Println("unimplemented") 151 return 152 case http.MethodPut: 153 did := s.auth.GetDID(r) 154 key := r.FormValue("key") 155 name := r.FormValue("name") 156 client, _ := s.auth.AuthorizedClient(r) 157 158 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 159 if err != nil { 160 log.Printf("parsing public key: %s", err) 161 return 162 } 163 164 if err := s.db.AddPublicKey(did, name, key); err != nil { 165 log.Printf("adding public key: %s", err) 166 return 167 } 168 169 // store in pds too 170 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 171 Collection: tangled.PublicKeyNSID, 172 Repo: did, 173 Rkey: uuid.New().String(), 174 Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{ 175 Created: time.Now().String(), 176 Key: key, 177 Name: name, 178 }}, 179 }) 180 181 // invalid record 182 if err != nil { 183 log.Printf("failed to create record: %s", err) 184 return 185 } 186 187 log.Println("created atproto record: ", resp.Uri) 188 189 return 190 } 191} 192 193// create a signed request and check if a node responds to that 194func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 195 domain := chi.URLParam(r, "domain") 196 if domain == "" { 197 http.Error(w, "malformed url", http.StatusBadRequest) 198 return 199 } 200 201 log.Println("checking ", domain) 202 203 secret, err := s.db.GetRegistrationKey(domain) 204 if err != nil { 205 log.Printf("no key found for domain %s: %s\n", domain, err) 206 return 207 } 208 log.Println("has secret ", secret) 209 210 // make a request do the knotserver with an empty body and above signature 211 url := fmt.Sprintf("http://%s/health", domain) 212 213 pingRequest, err := buildPingRequest(url, secret) 214 if err != nil { 215 log.Println("failed to build ping request", err) 216 return 217 } 218 219 client := &http.Client{ 220 Timeout: 5 * time.Second, 221 } 222 resp, err := client.Do(pingRequest) 223 if err != nil { 224 w.Write([]byte("no dice")) 225 log.Println("domain was unreachable after 5 seconds") 226 return 227 } 228 229 if resp.StatusCode != http.StatusOK { 230 log.Println("status nok", resp.StatusCode) 231 w.Write([]byte("no dice")) 232 return 233 } 234 235 // verify response mac 236 signature := resp.Header.Get("X-Signature") 237 signatureBytes, err := hex.DecodeString(signature) 238 if err != nil { 239 return 240 } 241 242 expectedMac := hmac.New(sha256.New, []byte(secret)) 243 expectedMac.Write([]byte("ok")) 244 245 if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 246 log.Printf("response body signature mismatch: %x\n", signatureBytes) 247 return 248 } 249 250 // mark as registered 251 err = s.db.Register(domain) 252 if err != nil { 253 log.Println("failed to register domain", err) 254 http.Error(w, err.Error(), http.StatusInternalServerError) 255 return 256 } 257 258 // set permissions for this did as owner 259 reg, err := s.db.RegistrationByDomain(domain) 260 if err != nil { 261 log.Println("failed to register domain", err) 262 http.Error(w, err.Error(), http.StatusInternalServerError) 263 return 264 } 265 266 // add basic acls for this domain 267 err = s.enforcer.AddDomain(domain) 268 if err != nil { 269 log.Println("failed to setup owner of domain", err) 270 http.Error(w, err.Error(), http.StatusInternalServerError) 271 return 272 } 273 274 // add this did as owner of this domain 275 err = s.enforcer.AddOwner(domain, reg.ByDid) 276 if err != nil { 277 log.Println("failed to setup owner of domain", err) 278 http.Error(w, err.Error(), http.StatusInternalServerError) 279 return 280 } 281 282 w.Write([]byte("check success")) 283} 284 285// get knots registered by this user 286func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 287 // for now, this is just pubkeys 288 user := s.auth.GetUser(r) 289 registrations, err := s.db.RegistrationsByDid(user.Did) 290 if err != nil { 291 log.Println(err) 292 } 293 294 pages.Knots(w, pages.KnotsParams{ 295 User: user, 296 Registrations: registrations, 297 }) 298} 299 300func buildPingRequest(url, secret string) (*http.Request, error) { 301 pingRequest, err := http.NewRequest("GET", url, nil) 302 if err != nil { 303 return nil, err 304 } 305 306 timestamp := time.Now().Format(time.RFC3339) 307 mac := hmac.New(sha256.New, []byte(secret)) 308 message := pingRequest.Method + pingRequest.URL.Path + timestamp 309 mac.Write([]byte(message)) 310 signature := hex.EncodeToString(mac.Sum(nil)) 311 312 pingRequest.Header.Set("X-Signature", signature) 313 pingRequest.Header.Set("X-Timestamp", timestamp) 314 315 return pingRequest, nil 316} 317 318func (s *State) Router() http.Handler { 319 r := chi.NewRouter() 320 321 r.Get("/", s.Timeline) 322 323 r.Get("/login", s.Login) 324 r.Post("/login", s.Login) 325 326 r.Route("/knots", func(r chi.Router) { 327 r.Use(AuthMiddleware(s)) 328 r.Get("/", s.Knots) 329 r.Post("/init/{domain}", s.InitKnotServer) 330 r.Post("/key", s.RegistrationKey) 331 }) 332 333 r.Group(func(r chi.Router) { 334 r.Use(AuthMiddleware(s)) 335 r.Get("/settings", s.Settings) 336 r.Put("/settings/keys", s.Keys) 337 }) 338 339 return r 340}