Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview: refactor knot endpoints into separate router

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
f67923c2 f4ec8829

+830 -343
+5 -4
appview/db/registration.go
··· 10 10 ) 11 11 12 12 type Registration struct { 13 + Id int64 13 14 Domain string 14 15 ByDid string 15 16 Created *time.Time ··· 37 36 var registrations []Registration 38 37 39 38 rows, err := e.Query(` 40 - select domain, did, created, registered from registrations 39 + select id, domain, did, created, registered from registrations 41 40 where did = ? 42 41 `, did) 43 42 if err != nil { ··· 48 47 var createdAt *string 49 48 var registeredAt *string 50 49 var registration Registration 51 - err = rows.Scan(&registration.Domain, &registration.ByDid, &createdAt, &registeredAt) 50 + err = rows.Scan(&registration.Id, &registration.Domain, &registration.ByDid, &createdAt, &registeredAt) 52 51 53 52 if err != nil { 54 53 log.Println(err) ··· 76 75 var registration Registration 77 76 78 77 err := e.QueryRow(` 79 - select domain, did, created, registered from registrations 78 + select id, domain, did, created, registered from registrations 80 79 where domain = ? 81 - `, domain).Scan(&registration.Domain, &registration.ByDid, &createdAt, &registeredAt) 80 + `, domain).Scan(&registration.Id, &registration.Domain, &registration.ByDid, &createdAt, &registeredAt) 82 81 83 82 if err != nil { 84 83 if err == sql.ErrNoRows {
+482
appview/knots/knots.go
··· 1 + package knots 2 + 3 + import ( 4 + "context" 5 + "crypto/hmac" 6 + "crypto/sha256" 7 + "encoding/hex" 8 + "fmt" 9 + "log/slog" 10 + "net/http" 11 + "strings" 12 + "time" 13 + 14 + "github.com/go-chi/chi/v5" 15 + "tangled.sh/tangled.sh/core/api/tangled" 16 + "tangled.sh/tangled.sh/core/appview" 17 + "tangled.sh/tangled.sh/core/appview/config" 18 + "tangled.sh/tangled.sh/core/appview/db" 19 + "tangled.sh/tangled.sh/core/appview/idresolver" 20 + "tangled.sh/tangled.sh/core/appview/middleware" 21 + "tangled.sh/tangled.sh/core/appview/oauth" 22 + "tangled.sh/tangled.sh/core/appview/pages" 23 + "tangled.sh/tangled.sh/core/eventconsumer" 24 + "tangled.sh/tangled.sh/core/knotclient" 25 + "tangled.sh/tangled.sh/core/rbac" 26 + 27 + comatproto "github.com/bluesky-social/indigo/api/atproto" 28 + lexutil "github.com/bluesky-social/indigo/lex/util" 29 + ) 30 + 31 + type Knots struct { 32 + Db *db.DB 33 + OAuth *oauth.OAuth 34 + Pages *pages.Pages 35 + Config *config.Config 36 + Enforcer *rbac.Enforcer 37 + IdResolver *idresolver.Resolver 38 + Logger *slog.Logger 39 + Knotstream *eventconsumer.Consumer 40 + } 41 + 42 + func (k *Knots) Router(mw *middleware.Middleware) http.Handler { 43 + r := chi.NewRouter() 44 + 45 + r.Use(middleware.AuthMiddleware(k.OAuth)) 46 + 47 + r.Get("/", k.index) 48 + r.Post("/key", k.generateKey) 49 + 50 + r.Route("/{domain}", func(r chi.Router) { 51 + r.Post("/init", k.init) 52 + r.Get("/", k.dashboard) 53 + r.Route("/member", func(r chi.Router) { 54 + r.Use(mw.KnotOwner()) 55 + r.Get("/", k.members) 56 + r.Put("/", k.addMember) 57 + r.Delete("/", k.removeMember) 58 + }) 59 + }) 60 + 61 + return r 62 + } 63 + 64 + // get knots registered by this user 65 + func (k *Knots) index(w http.ResponseWriter, r *http.Request) { 66 + l := k.Logger.With("handler", "index") 67 + 68 + user := k.OAuth.GetUser(r) 69 + registrations, err := db.RegistrationsByDid(k.Db, user.Did) 70 + if err != nil { 71 + l.Error("failed to get registrations by did", "err", err) 72 + } 73 + 74 + k.Pages.Knots(w, pages.KnotsParams{ 75 + LoggedInUser: user, 76 + Registrations: registrations, 77 + }) 78 + } 79 + 80 + // requires auth 81 + func (k *Knots) generateKey(w http.ResponseWriter, r *http.Request) { 82 + l := k.Logger.With("handler", "generateKey") 83 + 84 + user := k.OAuth.GetUser(r) 85 + did := user.Did 86 + l = l.With("did", did) 87 + 88 + // check if domain is valid url, and strip extra bits down to just host 89 + domain := r.FormValue("domain") 90 + if domain == "" { 91 + l.Error("empty domain") 92 + http.Error(w, "Invalid form", http.StatusBadRequest) 93 + return 94 + } 95 + l = l.With("domain", domain) 96 + 97 + noticeId := "registration-error" 98 + fail := func() { 99 + k.Pages.Notice(w, noticeId, "Failed to generate registration key.") 100 + } 101 + 102 + key, err := db.GenerateRegistrationKey(k.Db, domain, did) 103 + if err != nil { 104 + l.Error("failed to generate registration key", "err", err) 105 + fail() 106 + return 107 + } 108 + 109 + allRegs, err := db.RegistrationsByDid(k.Db, did) 110 + if err != nil { 111 + l.Error("failed to generate registration key", "err", err) 112 + fail() 113 + return 114 + } 115 + 116 + k.Pages.KnotListingFull(w, pages.KnotListingFullParams{ 117 + Registrations: allRegs, 118 + }) 119 + k.Pages.KnotSecret(w, pages.KnotSecretParams{ 120 + Secret: key, 121 + }) 122 + } 123 + 124 + // create a signed request and check if a node responds to that 125 + func (k *Knots) init(w http.ResponseWriter, r *http.Request) { 126 + l := k.Logger.With("handler", "init") 127 + user := k.OAuth.GetUser(r) 128 + 129 + noticeId := "operation-error" 130 + defaultErr := "Failed to initialize knot. Try again later." 131 + fail := func() { 132 + k.Pages.Notice(w, noticeId, defaultErr) 133 + } 134 + 135 + domain := chi.URLParam(r, "domain") 136 + if domain == "" { 137 + http.Error(w, "malformed url", http.StatusBadRequest) 138 + return 139 + } 140 + l = l.With("domain", domain) 141 + 142 + l.Info("checking domain") 143 + 144 + secret, err := db.GetRegistrationKey(k.Db, domain) 145 + if err != nil { 146 + l.Error("failed to get registration key for domain", "err", err) 147 + fail() 148 + return 149 + } 150 + 151 + client, err := knotclient.NewSignedClient(domain, secret, k.Config.Core.Dev) 152 + if err != nil { 153 + l.Error("failed to create knotclient", "err", err) 154 + fail() 155 + return 156 + } 157 + 158 + resp, err := client.Init(user.Did) 159 + if err != nil { 160 + k.Pages.Notice(w, noticeId, fmt.Sprintf("Failed to make request: %s", err.Error())) 161 + l.Error("failed to make init request", "err", err) 162 + return 163 + } 164 + 165 + if resp.StatusCode == http.StatusConflict { 166 + k.Pages.Notice(w, noticeId, "This knot is already registered") 167 + l.Error("knot already registered", "statuscode", resp.StatusCode) 168 + return 169 + } 170 + 171 + if resp.StatusCode != http.StatusNoContent { 172 + k.Pages.Notice(w, noticeId, fmt.Sprintf("Received status %d from knot, expected %d", resp.StatusCode, http.StatusNoContent)) 173 + l.Error("incorrect statuscode returned", "statuscode", resp.StatusCode, "expected", http.StatusNoContent) 174 + return 175 + } 176 + 177 + // verify response mac 178 + signature := resp.Header.Get("X-Signature") 179 + signatureBytes, err := hex.DecodeString(signature) 180 + if err != nil { 181 + return 182 + } 183 + 184 + expectedMac := hmac.New(sha256.New, []byte(secret)) 185 + expectedMac.Write([]byte("ok")) 186 + 187 + if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 188 + k.Pages.Notice(w, noticeId, "Response signature mismatch, consider regenerating the secret and retrying.") 189 + l.Error("signature mismatch", "bytes", signatureBytes) 190 + return 191 + } 192 + 193 + tx, err := k.Db.BeginTx(r.Context(), nil) 194 + if err != nil { 195 + l.Error("failed to start tx", "err", err) 196 + fail() 197 + return 198 + } 199 + defer func() { 200 + tx.Rollback() 201 + err = k.Enforcer.E.LoadPolicy() 202 + if err != nil { 203 + l.Error("rollback failed", "err", err) 204 + } 205 + }() 206 + 207 + // mark as registered 208 + err = db.Register(tx, domain) 209 + if err != nil { 210 + l.Error("failed to register domain", "err", err) 211 + fail() 212 + return 213 + } 214 + 215 + // set permissions for this did as owner 216 + reg, err := db.RegistrationByDomain(tx, domain) 217 + if err != nil { 218 + l.Error("failed get registration by domain", "err", err) 219 + fail() 220 + return 221 + } 222 + 223 + // add basic acls for this domain 224 + err = k.Enforcer.AddKnot(domain) 225 + if err != nil { 226 + l.Error("failed to add knot to enforcer", "err", err) 227 + fail() 228 + return 229 + } 230 + 231 + // add this did as owner of this domain 232 + err = k.Enforcer.AddKnotOwner(domain, reg.ByDid) 233 + if err != nil { 234 + l.Error("failed to add knot owner to enforcer", "err", err) 235 + fail() 236 + return 237 + } 238 + 239 + err = tx.Commit() 240 + if err != nil { 241 + l.Error("failed to commit changes", "err", err) 242 + fail() 243 + return 244 + } 245 + 246 + err = k.Enforcer.E.SavePolicy() 247 + if err != nil { 248 + l.Error("failed to update ACLs", "err", err) 249 + fail() 250 + return 251 + } 252 + 253 + // add this knot to knotstream 254 + go k.Knotstream.AddSource( 255 + context.Background(), 256 + eventconsumer.NewKnotSource(domain), 257 + ) 258 + 259 + k.Pages.KnotListing(w, pages.KnotListingParams{ 260 + Registration: *reg, 261 + }) 262 + } 263 + 264 + func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) { 265 + l := k.Logger.With("handler", "dashboard") 266 + fail := func() { 267 + w.WriteHeader(http.StatusInternalServerError) 268 + } 269 + 270 + domain := chi.URLParam(r, "domain") 271 + if domain == "" { 272 + http.Error(w, "malformed url", http.StatusBadRequest) 273 + return 274 + } 275 + l = l.With("domain", domain) 276 + 277 + user := k.OAuth.GetUser(r) 278 + l = l.With("did", user.Did) 279 + 280 + // dashboard is only available to owners 281 + ok, err := k.Enforcer.IsKnotOwner(user.Did, domain) 282 + if err != nil { 283 + l.Error("failed to query enforcer", "err", err) 284 + fail() 285 + } 286 + if !ok { 287 + http.Error(w, "only owners can view dashboards", http.StatusUnauthorized) 288 + return 289 + } 290 + 291 + reg, err := db.RegistrationByDomain(k.Db, domain) 292 + if err != nil { 293 + l.Error("failed to get registration by domain", "err", err) 294 + fail() 295 + return 296 + } 297 + 298 + var members []string 299 + if reg.Registered != nil { 300 + members, err = k.Enforcer.GetUserByRole("server:member", domain) 301 + if err != nil { 302 + l.Error("failed to get members list", "err", err) 303 + fail() 304 + return 305 + } 306 + } 307 + 308 + repos, err := db.GetRepos( 309 + k.Db, 310 + db.FilterEq("knot", domain), 311 + db.FilterIn("did", members), 312 + ) 313 + if err != nil { 314 + l.Error("failed to get repos list", "err", err) 315 + fail() 316 + return 317 + } 318 + // convert to map 319 + repoByMember := make(map[string][]db.Repo) 320 + for _, r := range repos { 321 + repoByMember[r.Did] = append(repoByMember[r.Did], r) 322 + } 323 + 324 + var didsToResolve []string 325 + for _, m := range members { 326 + didsToResolve = append(didsToResolve, m) 327 + } 328 + didsToResolve = append(didsToResolve, reg.ByDid) 329 + resolvedIds := k.IdResolver.ResolveIdents(r.Context(), didsToResolve) 330 + didHandleMap := make(map[string]string) 331 + for _, identity := range resolvedIds { 332 + if !identity.Handle.IsInvalidHandle() { 333 + didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 334 + } else { 335 + didHandleMap[identity.DID.String()] = identity.DID.String() 336 + } 337 + } 338 + 339 + k.Pages.Knot(w, pages.KnotParams{ 340 + LoggedInUser: user, 341 + DidHandleMap: didHandleMap, 342 + Registration: reg, 343 + Members: members, 344 + Repos: repoByMember, 345 + IsOwner: true, 346 + }) 347 + } 348 + 349 + // list members of domain, requires auth and requires owner status 350 + func (k *Knots) members(w http.ResponseWriter, r *http.Request) { 351 + l := k.Logger.With("handler", "members") 352 + 353 + domain := chi.URLParam(r, "domain") 354 + if domain == "" { 355 + http.Error(w, "malformed url", http.StatusBadRequest) 356 + return 357 + } 358 + l = l.With("domain", domain) 359 + 360 + // list all members for this domain 361 + memberDids, err := k.Enforcer.GetUserByRole("server:member", domain) 362 + if err != nil { 363 + w.Write([]byte("failed to fetch member list")) 364 + return 365 + } 366 + 367 + w.Write([]byte(strings.Join(memberDids, "\n"))) 368 + return 369 + } 370 + 371 + // add member to domain, requires auth and requires invite access 372 + func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) { 373 + l := k.Logger.With("handler", "members") 374 + 375 + domain := chi.URLParam(r, "domain") 376 + if domain == "" { 377 + http.Error(w, "malformed url", http.StatusBadRequest) 378 + return 379 + } 380 + l = l.With("domain", domain) 381 + 382 + reg, err := db.RegistrationByDomain(k.Db, domain) 383 + if err != nil { 384 + l.Error("failed to get registration by domain", "err", err) 385 + http.Error(w, "malformed url", http.StatusBadRequest) 386 + return 387 + } 388 + 389 + noticeId := fmt.Sprintf("add-member-error-%d", reg.Id) 390 + l = l.With("notice-id", noticeId) 391 + defaultErr := "Failed to add member. Try again later." 392 + fail := func() { 393 + k.Pages.Notice(w, noticeId, defaultErr) 394 + } 395 + 396 + subjectIdentifier := r.FormValue("subject") 397 + if subjectIdentifier == "" { 398 + http.Error(w, "malformed form", http.StatusBadRequest) 399 + return 400 + } 401 + l = l.With("subjectIdentifier", subjectIdentifier) 402 + 403 + subjectIdentity, err := k.IdResolver.ResolveIdent(r.Context(), subjectIdentifier) 404 + if err != nil { 405 + l.Error("failed to resolve identity", "err", err) 406 + k.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.") 407 + return 408 + } 409 + l = l.With("subjectDid", subjectIdentity.DID) 410 + 411 + l.Info("adding member to knot") 412 + 413 + // announce this relation into the firehose, store into owners' pds 414 + client, err := k.OAuth.AuthorizedClient(r) 415 + if err != nil { 416 + l.Error("failed to create client", "err", err) 417 + fail() 418 + return 419 + } 420 + 421 + currentUser := k.OAuth.GetUser(r) 422 + createdAt := time.Now().Format(time.RFC3339) 423 + resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 424 + Collection: tangled.KnotMemberNSID, 425 + Repo: currentUser.Did, 426 + Rkey: appview.TID(), 427 + Record: &lexutil.LexiconTypeDecoder{ 428 + Val: &tangled.KnotMember{ 429 + Subject: subjectIdentity.DID.String(), 430 + Domain: domain, 431 + CreatedAt: createdAt, 432 + }}, 433 + }) 434 + // invalid record 435 + if err != nil { 436 + l.Error("failed to write to PDS", "err", err) 437 + fail() 438 + return 439 + } 440 + l = l.With("at-uri", resp.Uri) 441 + l.Info("wrote record to PDS") 442 + 443 + secret, err := db.GetRegistrationKey(k.Db, domain) 444 + if err != nil { 445 + l.Error("failed to get registration key", "err", err) 446 + fail() 447 + return 448 + } 449 + 450 + ksClient, err := knotclient.NewSignedClient(domain, secret, k.Config.Core.Dev) 451 + if err != nil { 452 + l.Error("failed to create client", "err", err) 453 + fail() 454 + return 455 + } 456 + 457 + ksResp, err := ksClient.AddMember(subjectIdentity.DID.String()) 458 + if err != nil { 459 + l.Error("failed to reach knotserver", "err", err) 460 + k.Pages.Notice(w, noticeId, "Failed to reach to knotserver.") 461 + return 462 + } 463 + 464 + if ksResp.StatusCode != http.StatusNoContent { 465 + l.Error("status mismatch", "got", ksResp.StatusCode, "expected", http.StatusNoContent) 466 + k.Pages.Notice(w, noticeId, fmt.Sprintf("Unexpected status code from knotserver %d, expected %d", ksResp.StatusCode, http.StatusNoContent)) 467 + return 468 + } 469 + 470 + err = k.Enforcer.AddKnotMember(domain, subjectIdentity.DID.String()) 471 + if err != nil { 472 + l.Error("failed to add member to enforcer", "err", err) 473 + fail() 474 + return 475 + } 476 + 477 + // success 478 + k.Pages.HxRedirect(w, fmt.Sprintf("/knots/%s", domain)) 479 + } 480 + 481 + func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) { 482 + }
+19 -18
appview/state/router.go
··· 7 7 "github.com/go-chi/chi/v5" 8 8 "github.com/gorilla/sessions" 9 9 "tangled.sh/tangled.sh/core/appview/issues" 10 + "tangled.sh/tangled.sh/core/appview/knots" 10 11 "tangled.sh/tangled.sh/core/appview/middleware" 11 12 oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler" 12 13 "tangled.sh/tangled.sh/core/appview/pipelines" ··· 102 101 103 102 r.Get("/", s.Timeline) 104 103 105 - r.Route("/knots", func(r chi.Router) { 106 - r.Use(middleware.AuthMiddleware(s.oauth)) 107 - r.Get("/", s.Knots) 108 - r.Post("/key", s.RegistrationKey) 109 - 110 - r.Route("/{domain}", func(r chi.Router) { 111 - r.Post("/init", s.InitKnotServer) 112 - r.Get("/", s.KnotServerInfo) 113 - r.Route("/member", func(r chi.Router) { 114 - r.Use(mw.KnotOwner()) 115 - r.Get("/", s.ListMembers) 116 - r.Put("/", s.AddMember) 117 - r.Delete("/", s.RemoveMember) 118 - }) 119 - }) 120 - }) 121 - 122 104 r.Route("/repo", func(r chi.Router) { 123 105 r.Route("/new", func(r chi.Router) { 124 106 r.Use(middleware.AuthMiddleware(s.oauth)) ··· 135 151 }) 136 152 137 153 r.Mount("/settings", s.SettingsRouter()) 154 + r.Mount("/knots", s.KnotsRouter(mw)) 138 155 r.Mount("/spindles", s.SpindlesRouter()) 139 156 r.Mount("/", s.OAuthRouter()) 140 157 ··· 180 195 return spindles.Router() 181 196 } 182 197 198 + func (s *State) KnotsRouter(mw *middleware.Middleware) http.Handler { 199 + logger := log.New("knots") 200 + 201 + knots := &knots.Knots{ 202 + Db: s.db, 203 + OAuth: s.oauth, 204 + Pages: s.pages, 205 + Config: s.config, 206 + Enforcer: s.enforcer, 207 + IdResolver: s.idResolver, 208 + Knotstream: s.knotstream, 209 + Logger: logger, 210 + } 211 + 212 + return knots.Router(mw) 213 + } 214 + 183 215 func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler { 184 216 issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog) 185 217 return issues.Router(mw) 186 - 187 218 } 188 219 189 220 func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler {
+324 -321
appview/state/state.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "crypto/hmac" 6 - "crypto/sha256" 7 - "encoding/hex" 8 5 "fmt" 9 6 "log" 10 7 "log/slog" ··· 199 202 } 200 203 201 204 // requires auth 202 - func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) { 203 - switch r.Method { 204 - case http.MethodGet: 205 - // list open registrations under this did 206 - 207 - return 208 - case http.MethodPost: 209 - session, err := s.oauth.Stores().Get(r, oauth.SessionName) 210 - if err != nil || session.IsNew { 211 - log.Println("unauthorized attempt to generate registration key") 212 - http.Error(w, "Forbidden", http.StatusUnauthorized) 213 - return 214 - } 215 - 216 - did := session.Values[oauth.SessionDid].(string) 217 - 218 - // check if domain is valid url, and strip extra bits down to just host 219 - domain := r.FormValue("domain") 220 - if domain == "" { 221 - http.Error(w, "Invalid form", http.StatusBadRequest) 222 - return 223 - } 224 - 225 - key, err := db.GenerateRegistrationKey(s.db, domain, did) 226 - 227 - if err != nil { 228 - log.Println(err) 229 - http.Error(w, "unable to register this domain", http.StatusNotAcceptable) 230 - return 231 - } 232 - 233 - w.Write([]byte(key)) 234 - } 235 - } 205 + // func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) { 206 + // switch r.Method { 207 + // case http.MethodGet: 208 + // // list open registrations under this did 209 + // 210 + // return 211 + // case http.MethodPost: 212 + // session, err := s.oauth.Stores().Get(r, oauth.SessionName) 213 + // if err != nil || session.IsNew { 214 + // log.Println("unauthorized attempt to generate registration key") 215 + // http.Error(w, "Forbidden", http.StatusUnauthorized) 216 + // return 217 + // } 218 + // 219 + // did := session.Values[oauth.SessionDid].(string) 220 + // 221 + // // check if domain is valid url, and strip extra bits down to just host 222 + // domain := r.FormValue("domain") 223 + // if domain == "" { 224 + // http.Error(w, "Invalid form", http.StatusBadRequest) 225 + // return 226 + // } 227 + // 228 + // key, err := db.GenerateRegistrationKey(s.db, domain, did) 229 + // 230 + // if err != nil { 231 + // log.Println(err) 232 + // http.Error(w, "unable to register this domain", http.StatusNotAcceptable) 233 + // return 234 + // } 235 + // 236 + // w.Write([]byte(key)) 237 + // } 238 + // } 236 239 237 240 func (s *State) Keys(w http.ResponseWriter, r *http.Request) { 238 241 user := chi.URLParam(r, "user") ··· 267 270 } 268 271 269 272 // create a signed request and check if a node responds to that 270 - func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 271 - user := s.oauth.GetUser(r) 273 + // func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 274 + // user := s.oauth.GetUser(r) 275 + // 276 + // noticeId := "operation-error" 277 + // defaultErr := "Failed to register spindle. Try again later." 278 + // fail := func() { 279 + // s.pages.Notice(w, noticeId, defaultErr) 280 + // } 281 + // 282 + // domain := chi.URLParam(r, "domain") 283 + // if domain == "" { 284 + // http.Error(w, "malformed url", http.StatusBadRequest) 285 + // return 286 + // } 287 + // log.Println("checking ", domain) 288 + // 289 + // secret, err := db.GetRegistrationKey(s.db, domain) 290 + // if err != nil { 291 + // log.Printf("no key found for domain %s: %s\n", domain, err) 292 + // return 293 + // } 294 + // 295 + // client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev) 296 + // if err != nil { 297 + // log.Println("failed to create client to ", domain) 298 + // } 299 + // 300 + // resp, err := client.Init(user.Did) 301 + // if err != nil { 302 + // w.Write([]byte("no dice")) 303 + // log.Println("domain was unreachable after 5 seconds") 304 + // return 305 + // } 306 + // 307 + // if resp.StatusCode == http.StatusConflict { 308 + // log.Println("status conflict", resp.StatusCode) 309 + // w.Write([]byte("already registered, sorry!")) 310 + // return 311 + // } 312 + // 313 + // if resp.StatusCode != http.StatusNoContent { 314 + // log.Println("status nok", resp.StatusCode) 315 + // w.Write([]byte("no dice")) 316 + // return 317 + // } 318 + // 319 + // // verify response mac 320 + // signature := resp.Header.Get("X-Signature") 321 + // signatureBytes, err := hex.DecodeString(signature) 322 + // if err != nil { 323 + // return 324 + // } 325 + // 326 + // expectedMac := hmac.New(sha256.New, []byte(secret)) 327 + // expectedMac.Write([]byte("ok")) 328 + // 329 + // if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 330 + // log.Printf("response body signature mismatch: %x\n", signatureBytes) 331 + // return 332 + // } 333 + // 334 + // tx, err := s.db.BeginTx(r.Context(), nil) 335 + // if err != nil { 336 + // log.Println("failed to start tx", err) 337 + // http.Error(w, err.Error(), http.StatusInternalServerError) 338 + // return 339 + // } 340 + // defer func() { 341 + // tx.Rollback() 342 + // err = s.enforcer.E.LoadPolicy() 343 + // if err != nil { 344 + // log.Println("failed to rollback policies") 345 + // } 346 + // }() 347 + // 348 + // // mark as registered 349 + // err = db.Register(tx, domain) 350 + // if err != nil { 351 + // log.Println("failed to register domain", err) 352 + // http.Error(w, err.Error(), http.StatusInternalServerError) 353 + // return 354 + // } 355 + // 356 + // // set permissions for this did as owner 357 + // reg, err := db.RegistrationByDomain(tx, domain) 358 + // if err != nil { 359 + // log.Println("failed to register domain", err) 360 + // http.Error(w, err.Error(), http.StatusInternalServerError) 361 + // return 362 + // } 363 + // 364 + // // add basic acls for this domain 365 + // err = s.enforcer.AddKnot(domain) 366 + // if err != nil { 367 + // log.Println("failed to setup owner of domain", err) 368 + // http.Error(w, err.Error(), http.StatusInternalServerError) 369 + // return 370 + // } 371 + // 372 + // // add this did as owner of this domain 373 + // err = s.enforcer.AddKnotOwner(domain, reg.ByDid) 374 + // if err != nil { 375 + // log.Println("failed to setup owner of domain", err) 376 + // http.Error(w, err.Error(), http.StatusInternalServerError) 377 + // return 378 + // } 379 + // 380 + // err = tx.Commit() 381 + // if err != nil { 382 + // log.Println("failed to commit changes", err) 383 + // http.Error(w, err.Error(), http.StatusInternalServerError) 384 + // return 385 + // } 386 + // 387 + // err = s.enforcer.E.SavePolicy() 388 + // if err != nil { 389 + // log.Println("failed to update ACLs", err) 390 + // http.Error(w, err.Error(), http.StatusInternalServerError) 391 + // return 392 + // } 393 + // 394 + // // add this knot to knotstream 395 + // go s.knotstream.AddSource( 396 + // context.Background(), 397 + // eventconsumer.NewKnotSource(domain), 398 + // ) 399 + // 400 + // w.Write([]byte("check success")) 401 + // } 272 402 273 - domain := chi.URLParam(r, "domain") 274 - if domain == "" { 275 - http.Error(w, "malformed url", http.StatusBadRequest) 276 - return 277 - } 278 - log.Println("checking ", domain) 279 - 280 - secret, err := db.GetRegistrationKey(s.db, domain) 281 - if err != nil { 282 - log.Printf("no key found for domain %s: %s\n", domain, err) 283 - return 284 - } 285 - 286 - client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev) 287 - if err != nil { 288 - log.Println("failed to create client to ", domain) 289 - } 290 - 291 - resp, err := client.Init(user.Did) 292 - if err != nil { 293 - w.Write([]byte("no dice")) 294 - log.Println("domain was unreachable after 5 seconds") 295 - return 296 - } 297 - 298 - if resp.StatusCode == http.StatusConflict { 299 - log.Println("status conflict", resp.StatusCode) 300 - w.Write([]byte("already registered, sorry!")) 301 - return 302 - } 303 - 304 - if resp.StatusCode != http.StatusNoContent { 305 - log.Println("status nok", resp.StatusCode) 306 - w.Write([]byte("no dice")) 307 - return 308 - } 309 - 310 - // verify response mac 311 - signature := resp.Header.Get("X-Signature") 312 - signatureBytes, err := hex.DecodeString(signature) 313 - if err != nil { 314 - return 315 - } 316 - 317 - expectedMac := hmac.New(sha256.New, []byte(secret)) 318 - expectedMac.Write([]byte("ok")) 319 - 320 - if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 321 - log.Printf("response body signature mismatch: %x\n", signatureBytes) 322 - return 323 - } 324 - 325 - tx, err := s.db.BeginTx(r.Context(), nil) 326 - if err != nil { 327 - log.Println("failed to start tx", err) 328 - http.Error(w, err.Error(), http.StatusInternalServerError) 329 - return 330 - } 331 - defer func() { 332 - tx.Rollback() 333 - err = s.enforcer.E.LoadPolicy() 334 - if err != nil { 335 - log.Println("failed to rollback policies") 336 - } 337 - }() 338 - 339 - // mark as registered 340 - err = db.Register(tx, domain) 341 - if err != nil { 342 - log.Println("failed to register domain", err) 343 - http.Error(w, err.Error(), http.StatusInternalServerError) 344 - return 345 - } 346 - 347 - // set permissions for this did as owner 348 - reg, err := db.RegistrationByDomain(tx, domain) 349 - if err != nil { 350 - log.Println("failed to register domain", err) 351 - http.Error(w, err.Error(), http.StatusInternalServerError) 352 - return 353 - } 354 - 355 - // add basic acls for this domain 356 - err = s.enforcer.AddKnot(domain) 357 - if err != nil { 358 - log.Println("failed to setup owner of domain", err) 359 - http.Error(w, err.Error(), http.StatusInternalServerError) 360 - return 361 - } 362 - 363 - // add this did as owner of this domain 364 - err = s.enforcer.AddKnotOwner(domain, reg.ByDid) 365 - if err != nil { 366 - log.Println("failed to setup owner of domain", err) 367 - http.Error(w, err.Error(), http.StatusInternalServerError) 368 - return 369 - } 370 - 371 - err = tx.Commit() 372 - if err != nil { 373 - log.Println("failed to commit changes", err) 374 - http.Error(w, err.Error(), http.StatusInternalServerError) 375 - return 376 - } 377 - 378 - err = s.enforcer.E.SavePolicy() 379 - if err != nil { 380 - log.Println("failed to update ACLs", err) 381 - http.Error(w, err.Error(), http.StatusInternalServerError) 382 - return 383 - } 384 - 385 - // add this knot to knotstream 386 - go s.knotstream.AddSource( 387 - context.Background(), 388 - eventconsumer.NewKnotSource(domain), 389 - ) 390 - 391 - w.Write([]byte("check success")) 392 - } 393 - 394 - func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) { 395 - domain := chi.URLParam(r, "domain") 396 - if domain == "" { 397 - http.Error(w, "malformed url", http.StatusBadRequest) 398 - return 399 - } 400 - 401 - user := s.oauth.GetUser(r) 402 - reg, err := db.RegistrationByDomain(s.db, domain) 403 - if err != nil { 404 - w.Write([]byte("failed to pull up registration info")) 405 - return 406 - } 407 - 408 - var members []string 409 - if reg.Registered != nil { 410 - members, err = s.enforcer.GetUserByRole("server:member", domain) 411 - if err != nil { 412 - w.Write([]byte("failed to fetch member list")) 413 - return 414 - } 415 - } 416 - 417 - var didsToResolve []string 418 - for _, m := range members { 419 - didsToResolve = append(didsToResolve, m) 420 - } 421 - didsToResolve = append(didsToResolve, reg.ByDid) 422 - resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve) 423 - didHandleMap := make(map[string]string) 424 - for _, identity := range resolvedIds { 425 - if !identity.Handle.IsInvalidHandle() { 426 - didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 427 - } else { 428 - didHandleMap[identity.DID.String()] = identity.DID.String() 429 - } 430 - } 431 - 432 - ok, err := s.enforcer.IsKnotOwner(user.Did, domain) 433 - isOwner := err == nil && ok 434 - 435 - p := pages.KnotParams{ 436 - LoggedInUser: user, 437 - DidHandleMap: didHandleMap, 438 - Registration: reg, 439 - Members: members, 440 - IsOwner: isOwner, 441 - } 442 - 443 - s.pages.Knot(w, p) 444 - } 403 + // func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) { 404 + // domain := chi.URLParam(r, "domain") 405 + // if domain == "" { 406 + // http.Error(w, "malformed url", http.StatusBadRequest) 407 + // return 408 + // } 409 + // 410 + // user := s.oauth.GetUser(r) 411 + // reg, err := db.RegistrationByDomain(s.db, domain) 412 + // if err != nil { 413 + // w.Write([]byte("failed to pull up registration info")) 414 + // return 415 + // } 416 + // 417 + // var members []string 418 + // if reg.Registered != nil { 419 + // members, err = s.enforcer.GetUserByRole("server:member", domain) 420 + // if err != nil { 421 + // w.Write([]byte("failed to fetch member list")) 422 + // return 423 + // } 424 + // } 425 + // 426 + // var didsToResolve []string 427 + // for _, m := range members { 428 + // didsToResolve = append(didsToResolve, m) 429 + // } 430 + // didsToResolve = append(didsToResolve, reg.ByDid) 431 + // resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve) 432 + // didHandleMap := make(map[string]string) 433 + // for _, identity := range resolvedIds { 434 + // if !identity.Handle.IsInvalidHandle() { 435 + // didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 436 + // } else { 437 + // didHandleMap[identity.DID.String()] = identity.DID.String() 438 + // } 439 + // } 440 + // 441 + // ok, err := s.enforcer.IsKnotOwner(user.Did, domain) 442 + // isOwner := err == nil && ok 443 + // 444 + // p := pages.KnotParams{ 445 + // LoggedInUser: user, 446 + // DidHandleMap: didHandleMap, 447 + // Registration: reg, 448 + // Members: members, 449 + // IsOwner: isOwner, 450 + // } 451 + // 452 + // s.pages.Knot(w, p) 453 + // } 445 454 446 455 // get knots registered by this user 447 - func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 448 - // for now, this is just pubkeys 449 - user := s.oauth.GetUser(r) 450 - registrations, err := db.RegistrationsByDid(s.db, user.Did) 451 - if err != nil { 452 - log.Println(err) 453 - } 454 - 455 - s.pages.Knots(w, pages.KnotsParams{ 456 - LoggedInUser: user, 457 - Registrations: registrations, 458 - }) 459 - } 456 + // func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 457 + // // for now, this is just pubkeys 458 + // user := s.oauth.GetUser(r) 459 + // registrations, err := db.RegistrationsByDid(s.db, user.Did) 460 + // if err != nil { 461 + // log.Println(err) 462 + // } 463 + // 464 + // s.pages.Knots(w, pages.KnotsParams{ 465 + // LoggedInUser: user, 466 + // Registrations: registrations, 467 + // }) 468 + // } 460 469 461 470 // list members of domain, requires auth and requires owner status 462 - func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) { 463 - domain := chi.URLParam(r, "domain") 464 - if domain == "" { 465 - http.Error(w, "malformed url", http.StatusBadRequest) 466 - return 467 - } 468 - 469 - // list all members for this domain 470 - memberDids, err := s.enforcer.GetUserByRole("server:member", domain) 471 - if err != nil { 472 - w.Write([]byte("failed to fetch member list")) 473 - return 474 - } 475 - 476 - w.Write([]byte(strings.Join(memberDids, "\n"))) 477 - return 478 - } 471 + // func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) { 472 + // domain := chi.URLParam(r, "domain") 473 + // if domain == "" { 474 + // http.Error(w, "malformed url", http.StatusBadRequest) 475 + // return 476 + // } 477 + // 478 + // // list all members for this domain 479 + // memberDids, err := s.enforcer.GetUserByRole("server:member", domain) 480 + // if err != nil { 481 + // w.Write([]byte("failed to fetch member list")) 482 + // return 483 + // } 484 + // 485 + // w.Write([]byte(strings.Join(memberDids, "\n"))) 486 + // return 487 + // } 479 488 480 489 // add member to domain, requires auth and requires invite access 481 - func (s *State) AddMember(w http.ResponseWriter, r *http.Request) { 482 - domain := chi.URLParam(r, "domain") 483 - if domain == "" { 484 - http.Error(w, "malformed url", http.StatusBadRequest) 485 - return 486 - } 490 + // func (s *State) AddMember(w http.ResponseWriter, r *http.Request) { 491 + // domain := chi.URLParam(r, "domain") 492 + // if domain == "" { 493 + // http.Error(w, "malformed url", http.StatusBadRequest) 494 + // return 495 + // } 496 + // 497 + // subjectIdentifier := r.FormValue("subject") 498 + // if subjectIdentifier == "" { 499 + // http.Error(w, "malformed form", http.StatusBadRequest) 500 + // return 501 + // } 502 + // 503 + // subjectIdentity, err := s.idResolver.ResolveIdent(r.Context(), subjectIdentifier) 504 + // if err != nil { 505 + // w.Write([]byte("failed to resolve member did to a handle")) 506 + // return 507 + // } 508 + // log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain) 509 + // 510 + // // announce this relation into the firehose, store into owners' pds 511 + // client, err := s.oauth.AuthorizedClient(r) 512 + // if err != nil { 513 + // http.Error(w, "failed to authorize client", http.StatusInternalServerError) 514 + // return 515 + // } 516 + // currentUser := s.oauth.GetUser(r) 517 + // createdAt := time.Now().Format(time.RFC3339) 518 + // resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 519 + // Collection: tangled.KnotMemberNSID, 520 + // Repo: currentUser.Did, 521 + // Rkey: appview.TID(), 522 + // Record: &lexutil.LexiconTypeDecoder{ 523 + // Val: &tangled.KnotMember{ 524 + // Subject: subjectIdentity.DID.String(), 525 + // Domain: domain, 526 + // CreatedAt: createdAt, 527 + // }}, 528 + // }) 529 + // 530 + // // invalid record 531 + // if err != nil { 532 + // log.Printf("failed to create record: %s", err) 533 + // return 534 + // } 535 + // log.Println("created atproto record: ", resp.Uri) 536 + // 537 + // secret, err := db.GetRegistrationKey(s.db, domain) 538 + // if err != nil { 539 + // log.Printf("no key found for domain %s: %s\n", domain, err) 540 + // return 541 + // } 542 + // 543 + // ksClient, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev) 544 + // if err != nil { 545 + // log.Println("failed to create client to ", domain) 546 + // return 547 + // } 548 + // 549 + // ksResp, err := ksClient.AddMember(subjectIdentity.DID.String()) 550 + // if err != nil { 551 + // log.Printf("failed to make request to %s: %s", domain, err) 552 + // return 553 + // } 554 + // 555 + // if ksResp.StatusCode != http.StatusNoContent { 556 + // w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err))) 557 + // return 558 + // } 559 + // 560 + // err = s.enforcer.AddKnotMember(domain, subjectIdentity.DID.String()) 561 + // if err != nil { 562 + // w.Write([]byte(fmt.Sprint("failed to add member: ", err))) 563 + // return 564 + // } 565 + // 566 + // w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String()))) 567 + // } 487 568 488 - subjectIdentifier := r.FormValue("subject") 489 - if subjectIdentifier == "" { 490 - http.Error(w, "malformed form", http.StatusBadRequest) 491 - return 492 - } 493 - 494 - subjectIdentity, err := s.idResolver.ResolveIdent(r.Context(), subjectIdentifier) 495 - if err != nil { 496 - w.Write([]byte("failed to resolve member did to a handle")) 497 - return 498 - } 499 - log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain) 500 - 501 - // announce this relation into the firehose, store into owners' pds 502 - client, err := s.oauth.AuthorizedClient(r) 503 - if err != nil { 504 - http.Error(w, "failed to authorize client", http.StatusInternalServerError) 505 - return 506 - } 507 - currentUser := s.oauth.GetUser(r) 508 - createdAt := time.Now().Format(time.RFC3339) 509 - resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 510 - Collection: tangled.KnotMemberNSID, 511 - Repo: currentUser.Did, 512 - Rkey: appview.TID(), 513 - Record: &lexutil.LexiconTypeDecoder{ 514 - Val: &tangled.KnotMember{ 515 - Subject: subjectIdentity.DID.String(), 516 - Domain: domain, 517 - CreatedAt: createdAt, 518 - }}, 519 - }) 520 - 521 - // invalid record 522 - if err != nil { 523 - log.Printf("failed to create record: %s", err) 524 - return 525 - } 526 - log.Println("created atproto record: ", resp.Uri) 527 - 528 - secret, err := db.GetRegistrationKey(s.db, domain) 529 - if err != nil { 530 - log.Printf("no key found for domain %s: %s\n", domain, err) 531 - return 532 - } 533 - 534 - ksClient, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev) 535 - if err != nil { 536 - log.Println("failed to create client to ", domain) 537 - return 538 - } 539 - 540 - ksResp, err := ksClient.AddMember(subjectIdentity.DID.String()) 541 - if err != nil { 542 - log.Printf("failed to make request to %s: %s", domain, err) 543 - return 544 - } 545 - 546 - if ksResp.StatusCode != http.StatusNoContent { 547 - w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err))) 548 - return 549 - } 550 - 551 - err = s.enforcer.AddKnotMember(domain, subjectIdentity.DID.String()) 552 - if err != nil { 553 - w.Write([]byte(fmt.Sprint("failed to add member: ", err))) 554 - return 555 - } 556 - 557 - w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String()))) 558 - } 559 - 560 - func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) { 561 - } 569 + // func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) { 570 + // } 562 571 563 572 func validateRepoName(name string) error { 564 573 // check for path traversal attempts