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 http.Redirect(w, r, "/login", http.StatusSeeOther) 67 return 68 } 69 70 atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword) 71 if err != nil { 72 log.Printf("creating initial session: %s", err) 73 return 74 } 75 sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession} 76 77 err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint()) 78 if err != nil { 79 log.Printf("storing session: %s", err) 80 return 81 } 82 83 log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did) 84 http.Redirect(w, r, "/", http.StatusSeeOther) 85 return 86 } 87} 88 89func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 90 user := s.auth.GetUser(r) 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} 146 147func (s *State) Keys(w http.ResponseWriter, r *http.Request) { 148 switch r.Method { 149 case http.MethodGet: 150 w.Write([]byte("unimplemented")) 151 log.Println("unimplemented") 152 return 153 case http.MethodPut: 154 did := s.auth.GetDID(r) 155 key := r.FormValue("key") 156 name := r.FormValue("name") 157 client, _ := s.auth.AuthorizedClient(r) 158 159 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 160 if err != nil { 161 log.Printf("parsing public key: %s", err) 162 return 163 } 164 165 if err := s.db.AddPublicKey(did, name, key); err != nil { 166 log.Printf("adding public key: %s", err) 167 return 168 } 169 170 // store in pds too 171 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 172 Collection: tangled.PublicKeyNSID, 173 Repo: did, 174 Rkey: uuid.New().String(), 175 Record: &lexutil.LexiconTypeDecoder{Val: &tangled.PublicKey{ 176 Created: time.Now().String(), 177 Key: key, 178 Name: name, 179 }}, 180 }) 181 182 // invalid record 183 if err != nil { 184 log.Printf("failed to create record: %s", err) 185 return 186 } 187 188 log.Println("created atproto record: ", resp.Uri) 189 190 return 191 } 192} 193 194// create a signed request and check if a node responds to that 195func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 196 domain := chi.URLParam(r, "domain") 197 if domain == "" { 198 http.Error(w, "malformed url", http.StatusBadRequest) 199 return 200 } 201 202 log.Println("checking ", domain) 203 204 secret, err := s.db.GetRegistrationKey(domain) 205 if err != nil { 206 log.Printf("no key found for domain %s: %s\n", domain, err) 207 return 208 } 209 log.Println("has secret ", secret) 210 211 // make a request do the knotserver with an empty body and above signature 212 url := fmt.Sprintf("http://%s/health", domain) 213 214 pingRequest, err := buildPingRequest(url, secret) 215 if err != nil { 216 log.Println("failed to build ping request", err) 217 return 218 } 219 220 client := &http.Client{ 221 Timeout: 5 * time.Second, 222 } 223 resp, err := client.Do(pingRequest) 224 if err != nil { 225 w.Write([]byte("no dice")) 226 log.Println("domain was unreachable after 5 seconds") 227 return 228 } 229 230 if resp.StatusCode != http.StatusOK { 231 log.Println("status nok", resp.StatusCode) 232 w.Write([]byte("no dice")) 233 return 234 } 235 236 // verify response mac 237 signature := resp.Header.Get("X-Signature") 238 signatureBytes, err := hex.DecodeString(signature) 239 if err != nil { 240 return 241 } 242 243 expectedMac := hmac.New(sha256.New, []byte(secret)) 244 expectedMac.Write([]byte("ok")) 245 246 if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 247 log.Printf("response body signature mismatch: %x\n", signatureBytes) 248 return 249 } 250 251 // mark as registered 252 err = s.db.Register(domain) 253 if err != nil { 254 log.Println("failed to register domain", err) 255 http.Error(w, err.Error(), http.StatusInternalServerError) 256 return 257 } 258 259 // set permissions for this did as owner 260 reg, err := s.db.RegistrationByDomain(domain) 261 if err != nil { 262 log.Println("failed to register domain", err) 263 http.Error(w, err.Error(), http.StatusInternalServerError) 264 return 265 } 266 267 // add basic acls for this domain 268 err = s.enforcer.AddDomain(domain) 269 if err != nil { 270 log.Println("failed to setup owner of domain", err) 271 http.Error(w, err.Error(), http.StatusInternalServerError) 272 return 273 } 274 275 // add this did as owner of this domain 276 err = s.enforcer.AddOwner(domain, reg.ByDid) 277 if err != nil { 278 log.Println("failed to setup owner of domain", err) 279 http.Error(w, err.Error(), http.StatusInternalServerError) 280 return 281 } 282 283 w.Write([]byte("check success")) 284} 285 286// get knots registered by this user 287func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 288 // for now, this is just pubkeys 289 user := s.auth.GetUser(r) 290 registrations, err := s.db.RegistrationsByDid(user.Did) 291 if err != nil { 292 log.Println(err) 293 } 294 295 pages.Knots(w, pages.KnotsParams{ 296 User: user, 297 Registrations: registrations, 298 }) 299} 300 301func buildPingRequest(url, secret string) (*http.Request, error) { 302 pingRequest, err := http.NewRequest("GET", url, nil) 303 if err != nil { 304 return nil, err 305 } 306 307 timestamp := time.Now().Format(time.RFC3339) 308 mac := hmac.New(sha256.New, []byte(secret)) 309 message := pingRequest.Method + pingRequest.URL.Path + timestamp 310 mac.Write([]byte(message)) 311 signature := hex.EncodeToString(mac.Sum(nil)) 312 313 pingRequest.Header.Set("X-Signature", signature) 314 pingRequest.Header.Set("X-Timestamp", timestamp) 315 316 return pingRequest, nil 317} 318 319func (s *State) Router() http.Handler { 320 r := chi.NewRouter() 321 322 r.Get("/", s.Timeline) 323 324 r.Get("/login", s.Login) 325 r.Post("/login", s.Login) 326 327 r.Route("/knots", func(r chi.Router) { 328 r.Use(AuthMiddleware(s)) 329 r.Get("/", s.Knots) 330 r.Post("/init/{domain}", s.InitKnotServer) 331 r.Post("/key", s.RegistrationKey) 332 }) 333 334 r.Group(func(r chi.Router) { 335 r.Use(AuthMiddleware(s)) 336 r.Get("/settings", s.Settings) 337 r.Put("/settings/keys", s.Keys) 338 }) 339 340 return r 341}