appview: refactor knot endpoints into separate router #306

merged
opened by oppi.li targeting master from push-nwslswprzvmx
Changed files
+832 -345
appview
+5 -4
appview/db/registration.go
···
)
type Registration struct {
+
Id int64
Domain string
ByDid string
Created *time.Time
···
var registrations []Registration
rows, err := e.Query(`
-
select domain, did, created, registered from registrations
+
select id, domain, did, created, registered from registrations
where did = ?
`, did)
if err != nil {
···
var createdAt *string
var registeredAt *string
var registration Registration
-
err = rows.Scan(&registration.Domain, &registration.ByDid, &createdAt, &registeredAt)
+
err = rows.Scan(&registration.Id, &registration.Domain, &registration.ByDid, &createdAt, &registeredAt)
if err != nil {
log.Println(err)
···
var registration Registration
err := e.QueryRow(`
-
select domain, did, created, registered from registrations
+
select id, domain, did, created, registered from registrations
where domain = ?
-
`, domain).Scan(&registration.Domain, &registration.ByDid, &createdAt, &registeredAt)
+
`, domain).Scan(&registration.Id, &registration.Domain, &registration.ByDid, &createdAt, &registeredAt)
if err != nil {
if err == sql.ErrNoRows {
+482
appview/knots/knots.go
···
+
package knots
+
+
import (
+
"context"
+
"crypto/hmac"
+
"crypto/sha256"
+
"encoding/hex"
+
"fmt"
+
"log/slog"
+
"net/http"
+
"strings"
+
"time"
+
+
"github.com/go-chi/chi/v5"
+
"tangled.sh/tangled.sh/core/api/tangled"
+
"tangled.sh/tangled.sh/core/appview"
+
"tangled.sh/tangled.sh/core/appview/config"
+
"tangled.sh/tangled.sh/core/appview/db"
+
"tangled.sh/tangled.sh/core/appview/idresolver"
+
"tangled.sh/tangled.sh/core/appview/middleware"
+
"tangled.sh/tangled.sh/core/appview/oauth"
+
"tangled.sh/tangled.sh/core/appview/pages"
+
"tangled.sh/tangled.sh/core/eventconsumer"
+
"tangled.sh/tangled.sh/core/knotclient"
+
"tangled.sh/tangled.sh/core/rbac"
+
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
+
lexutil "github.com/bluesky-social/indigo/lex/util"
+
)
+
+
type Knots struct {
+
Db *db.DB
+
OAuth *oauth.OAuth
+
Pages *pages.Pages
+
Config *config.Config
+
Enforcer *rbac.Enforcer
+
IdResolver *idresolver.Resolver
+
Logger *slog.Logger
+
Knotstream *eventconsumer.Consumer
+
}
+
+
func (k *Knots) Router(mw *middleware.Middleware) http.Handler {
+
r := chi.NewRouter()
+
+
r.Use(middleware.AuthMiddleware(k.OAuth))
+
+
r.Get("/", k.index)
+
r.Post("/key", k.generateKey)
+
+
r.Route("/{domain}", func(r chi.Router) {
+
r.Post("/init", k.init)
+
r.Get("/", k.dashboard)
+
r.Route("/member", func(r chi.Router) {
+
r.Use(mw.KnotOwner())
+
r.Get("/", k.members)
+
r.Put("/", k.addMember)
+
r.Delete("/", k.removeMember)
+
})
+
})
+
+
return r
+
}
+
+
// get knots registered by this user
+
func (k *Knots) index(w http.ResponseWriter, r *http.Request) {
+
l := k.Logger.With("handler", "index")
+
+
user := k.OAuth.GetUser(r)
+
registrations, err := db.RegistrationsByDid(k.Db, user.Did)
+
if err != nil {
+
l.Error("failed to get registrations by did", "err", err)
+
}
+
+
k.Pages.Knots(w, pages.KnotsParams{
+
LoggedInUser: user,
+
Registrations: registrations,
+
})
+
}
+
+
// requires auth
+
func (k *Knots) generateKey(w http.ResponseWriter, r *http.Request) {
+
l := k.Logger.With("handler", "generateKey")
+
+
user := k.OAuth.GetUser(r)
+
did := user.Did
+
l = l.With("did", did)
+
+
// check if domain is valid url, and strip extra bits down to just host
+
domain := r.FormValue("domain")
+
if domain == "" {
+
l.Error("empty domain")
+
http.Error(w, "Invalid form", http.StatusBadRequest)
+
return
+
}
+
l = l.With("domain", domain)
+
+
noticeId := "registration-error"
+
fail := func() {
+
k.Pages.Notice(w, noticeId, "Failed to generate registration key.")
+
}
+
+
key, err := db.GenerateRegistrationKey(k.Db, domain, did)
+
if err != nil {
+
l.Error("failed to generate registration key", "err", err)
+
fail()
+
return
+
}
+
+
allRegs, err := db.RegistrationsByDid(k.Db, did)
+
if err != nil {
+
l.Error("failed to generate registration key", "err", err)
+
fail()
+
return
+
}
+
+
k.Pages.KnotListingFull(w, pages.KnotListingFullParams{
+
Registrations: allRegs,
+
})
+
k.Pages.KnotSecret(w, pages.KnotSecretParams{
+
Secret: key,
+
})
+
}
+
+
// create a signed request and check if a node responds to that
+
func (k *Knots) init(w http.ResponseWriter, r *http.Request) {
+
l := k.Logger.With("handler", "init")
+
user := k.OAuth.GetUser(r)
+
+
noticeId := "operation-error"
+
defaultErr := "Failed to initialize knot. Try again later."
+
fail := func() {
+
k.Pages.Notice(w, noticeId, defaultErr)
+
}
+
+
domain := chi.URLParam(r, "domain")
+
if domain == "" {
+
http.Error(w, "malformed url", http.StatusBadRequest)
+
return
+
}
+
l = l.With("domain", domain)
+
+
l.Info("checking domain")
+
+
secret, err := db.GetRegistrationKey(k.Db, domain)
+
if err != nil {
+
l.Error("failed to get registration key for domain", "err", err)
+
fail()
+
return
+
}
+
+
client, err := knotclient.NewSignedClient(domain, secret, k.Config.Core.Dev)
+
if err != nil {
+
l.Error("failed to create knotclient", "err", err)
+
fail()
+
return
+
}
+
+
resp, err := client.Init(user.Did)
+
if err != nil {
+
k.Pages.Notice(w, noticeId, fmt.Sprintf("Failed to make request: %s", err.Error()))
+
l.Error("failed to make init request", "err", err)
+
return
+
}
+
+
if resp.StatusCode == http.StatusConflict {
+
k.Pages.Notice(w, noticeId, "This knot is already registered")
+
l.Error("knot already registered", "statuscode", resp.StatusCode)
+
return
+
}
+
+
if resp.StatusCode != http.StatusNoContent {
+
k.Pages.Notice(w, noticeId, fmt.Sprintf("Received status %d from knot, expected %d", resp.StatusCode, http.StatusNoContent))
+
l.Error("incorrect statuscode returned", "statuscode", resp.StatusCode, "expected", http.StatusNoContent)
+
return
+
}
+
+
// verify response mac
+
signature := resp.Header.Get("X-Signature")
+
signatureBytes, err := hex.DecodeString(signature)
+
if err != nil {
+
return
+
}
+
+
expectedMac := hmac.New(sha256.New, []byte(secret))
+
expectedMac.Write([]byte("ok"))
+
+
if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) {
+
k.Pages.Notice(w, noticeId, "Response signature mismatch, consider regenerating the secret and retrying.")
+
l.Error("signature mismatch", "bytes", signatureBytes)
+
return
+
}
+
+
tx, err := k.Db.BeginTx(r.Context(), nil)
+
if err != nil {
+
l.Error("failed to start tx", "err", err)
+
fail()
+
return
+
}
+
defer func() {
+
tx.Rollback()
+
err = k.Enforcer.E.LoadPolicy()
+
if err != nil {
+
l.Error("rollback failed", "err", err)
+
}
+
}()
+
+
// mark as registered
+
err = db.Register(tx, domain)
+
if err != nil {
+
l.Error("failed to register domain", "err", err)
+
fail()
+
return
+
}
+
+
// set permissions for this did as owner
+
reg, err := db.RegistrationByDomain(tx, domain)
+
if err != nil {
+
l.Error("failed get registration by domain", "err", err)
+
fail()
+
return
+
}
+
+
// add basic acls for this domain
+
err = k.Enforcer.AddKnot(domain)
+
if err != nil {
+
l.Error("failed to add knot to enforcer", "err", err)
+
fail()
+
return
+
}
+
+
// add this did as owner of this domain
+
err = k.Enforcer.AddKnotOwner(domain, reg.ByDid)
+
if err != nil {
+
l.Error("failed to add knot owner to enforcer", "err", err)
+
fail()
+
return
+
}
+
+
err = tx.Commit()
+
if err != nil {
+
l.Error("failed to commit changes", "err", err)
+
fail()
+
return
+
}
+
+
err = k.Enforcer.E.SavePolicy()
+
if err != nil {
+
l.Error("failed to update ACLs", "err", err)
+
fail()
+
return
+
}
+
+
// add this knot to knotstream
+
go k.Knotstream.AddSource(
+
context.Background(),
+
eventconsumer.NewKnotSource(domain),
+
)
+
+
k.Pages.KnotListing(w, pages.KnotListingParams{
+
Registration: *reg,
+
})
+
}
+
+
func (k *Knots) dashboard(w http.ResponseWriter, r *http.Request) {
+
l := k.Logger.With("handler", "dashboard")
+
fail := func() {
+
w.WriteHeader(http.StatusInternalServerError)
+
}
+
+
domain := chi.URLParam(r, "domain")
+
if domain == "" {
+
http.Error(w, "malformed url", http.StatusBadRequest)
+
return
+
}
+
l = l.With("domain", domain)
+
+
user := k.OAuth.GetUser(r)
+
l = l.With("did", user.Did)
+
+
// dashboard is only available to owners
+
ok, err := k.Enforcer.IsKnotOwner(user.Did, domain)
+
if err != nil {
+
l.Error("failed to query enforcer", "err", err)
+
fail()
+
}
+
if !ok {
+
http.Error(w, "only owners can view dashboards", http.StatusUnauthorized)
+
return
+
}
+
+
reg, err := db.RegistrationByDomain(k.Db, domain)
+
if err != nil {
+
l.Error("failed to get registration by domain", "err", err)
+
fail()
+
return
+
}
+
+
var members []string
+
if reg.Registered != nil {
+
members, err = k.Enforcer.GetUserByRole("server:member", domain)
+
if err != nil {
+
l.Error("failed to get members list", "err", err)
+
fail()
+
return
+
}
+
}
+
+
repos, err := db.GetRepos(
+
k.Db,
+
db.FilterEq("knot", domain),
+
db.FilterIn("did", members),
+
)
+
if err != nil {
+
l.Error("failed to get repos list", "err", err)
+
fail()
+
return
+
}
+
// convert to map
+
repoByMember := make(map[string][]db.Repo)
+
for _, r := range repos {
+
repoByMember[r.Did] = append(repoByMember[r.Did], r)
+
}
+
+
var didsToResolve []string
+
for _, m := range members {
+
didsToResolve = append(didsToResolve, m)
+
}
+
didsToResolve = append(didsToResolve, reg.ByDid)
+
resolvedIds := k.IdResolver.ResolveIdents(r.Context(), didsToResolve)
+
didHandleMap := make(map[string]string)
+
for _, identity := range resolvedIds {
+
if !identity.Handle.IsInvalidHandle() {
+
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
+
} else {
+
didHandleMap[identity.DID.String()] = identity.DID.String()
+
}
+
}
+
+
k.Pages.Knot(w, pages.KnotParams{
+
LoggedInUser: user,
+
DidHandleMap: didHandleMap,
+
Registration: reg,
+
Members: members,
+
Repos: repoByMember,
+
IsOwner: true,
+
})
+
}
+
+
// list members of domain, requires auth and requires owner status
+
func (k *Knots) members(w http.ResponseWriter, r *http.Request) {
+
l := k.Logger.With("handler", "members")
+
+
domain := chi.URLParam(r, "domain")
+
if domain == "" {
+
http.Error(w, "malformed url", http.StatusBadRequest)
+
return
+
}
+
l = l.With("domain", domain)
+
+
// list all members for this domain
+
memberDids, err := k.Enforcer.GetUserByRole("server:member", domain)
+
if err != nil {
+
w.Write([]byte("failed to fetch member list"))
+
return
+
}
+
+
w.Write([]byte(strings.Join(memberDids, "\n")))
+
return
+
}
+
+
// add member to domain, requires auth and requires invite access
+
func (k *Knots) addMember(w http.ResponseWriter, r *http.Request) {
+
l := k.Logger.With("handler", "members")
+
+
domain := chi.URLParam(r, "domain")
+
if domain == "" {
+
http.Error(w, "malformed url", http.StatusBadRequest)
+
return
+
}
+
l = l.With("domain", domain)
+
+
reg, err := db.RegistrationByDomain(k.Db, domain)
+
if err != nil {
+
l.Error("failed to get registration by domain", "err", err)
+
http.Error(w, "malformed url", http.StatusBadRequest)
+
return
+
}
+
+
noticeId := fmt.Sprintf("add-member-error-%d", reg.Id)
+
l = l.With("notice-id", noticeId)
+
defaultErr := "Failed to add member. Try again later."
+
fail := func() {
+
k.Pages.Notice(w, noticeId, defaultErr)
+
}
+
+
subjectIdentifier := r.FormValue("subject")
+
if subjectIdentifier == "" {
+
http.Error(w, "malformed form", http.StatusBadRequest)
+
return
+
}
+
l = l.With("subjectIdentifier", subjectIdentifier)
+
+
subjectIdentity, err := k.IdResolver.ResolveIdent(r.Context(), subjectIdentifier)
+
if err != nil {
+
l.Error("failed to resolve identity", "err", err)
+
k.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.")
+
return
+
}
+
l = l.With("subjectDid", subjectIdentity.DID)
+
+
l.Info("adding member to knot")
+
+
// announce this relation into the firehose, store into owners' pds
+
client, err := k.OAuth.AuthorizedClient(r)
+
if err != nil {
+
l.Error("failed to create client", "err", err)
+
fail()
+
return
+
}
+
+
currentUser := k.OAuth.GetUser(r)
+
createdAt := time.Now().Format(time.RFC3339)
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
+
Collection: tangled.KnotMemberNSID,
+
Repo: currentUser.Did,
+
Rkey: appview.TID(),
+
Record: &lexutil.LexiconTypeDecoder{
+
Val: &tangled.KnotMember{
+
Subject: subjectIdentity.DID.String(),
+
Domain: domain,
+
CreatedAt: createdAt,
+
}},
+
})
+
// invalid record
+
if err != nil {
+
l.Error("failed to write to PDS", "err", err)
+
fail()
+
return
+
}
+
l = l.With("at-uri", resp.Uri)
+
l.Info("wrote record to PDS")
+
+
secret, err := db.GetRegistrationKey(k.Db, domain)
+
if err != nil {
+
l.Error("failed to get registration key", "err", err)
+
fail()
+
return
+
}
+
+
ksClient, err := knotclient.NewSignedClient(domain, secret, k.Config.Core.Dev)
+
if err != nil {
+
l.Error("failed to create client", "err", err)
+
fail()
+
return
+
}
+
+
ksResp, err := ksClient.AddMember(subjectIdentity.DID.String())
+
if err != nil {
+
l.Error("failed to reach knotserver", "err", err)
+
k.Pages.Notice(w, noticeId, "Failed to reach to knotserver.")
+
return
+
}
+
+
if ksResp.StatusCode != http.StatusNoContent {
+
l.Error("status mismatch", "got", ksResp.StatusCode, "expected", http.StatusNoContent)
+
k.Pages.Notice(w, noticeId, fmt.Sprintf("Unexpected status code from knotserver %d, expected %d", ksResp.StatusCode, http.StatusNoContent))
+
return
+
}
+
+
err = k.Enforcer.AddKnotMember(domain, subjectIdentity.DID.String())
+
if err != nil {
+
l.Error("failed to add member to enforcer", "err", err)
+
fail()
+
return
+
}
+
+
// success
+
k.Pages.HxRedirect(w, fmt.Sprintf("/knots/%s", domain))
+
}
+
+
func (k *Knots) removeMember(w http.ResponseWriter, r *http.Request) {
+
}
+19 -18
appview/state/router.go
···
"github.com/go-chi/chi/v5"
"github.com/gorilla/sessions"
"tangled.sh/tangled.sh/core/appview/issues"
+
"tangled.sh/tangled.sh/core/appview/knots"
"tangled.sh/tangled.sh/core/appview/middleware"
oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler"
"tangled.sh/tangled.sh/core/appview/pipelines"
···
r.Get("/", s.Timeline)
-
r.Route("/knots", func(r chi.Router) {
-
r.Use(middleware.AuthMiddleware(s.oauth))
-
r.Get("/", s.Knots)
-
r.Post("/key", s.RegistrationKey)
-
-
r.Route("/{domain}", func(r chi.Router) {
-
r.Post("/init", s.InitKnotServer)
-
r.Get("/", s.KnotServerInfo)
-
r.Route("/member", func(r chi.Router) {
-
r.Use(mw.KnotOwner())
-
r.Get("/", s.ListMembers)
-
r.Put("/", s.AddMember)
-
r.Delete("/", s.RemoveMember)
-
})
-
})
-
})
-
r.Route("/repo", func(r chi.Router) {
r.Route("/new", func(r chi.Router) {
r.Use(middleware.AuthMiddleware(s.oauth))
···
})
r.Mount("/settings", s.SettingsRouter())
+
r.Mount("/knots", s.KnotsRouter(mw))
r.Mount("/spindles", s.SpindlesRouter())
r.Mount("/", s.OAuthRouter())
···
return spindles.Router()
}
+
func (s *State) KnotsRouter(mw *middleware.Middleware) http.Handler {
+
logger := log.New("knots")
+
+
knots := &knots.Knots{
+
Db: s.db,
+
OAuth: s.oauth,
+
Pages: s.pages,
+
Config: s.config,
+
Enforcer: s.enforcer,
+
IdResolver: s.idResolver,
+
Knotstream: s.knotstream,
+
Logger: logger,
+
}
+
+
return knots.Router(mw)
+
}
+
func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler {
issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog)
return issues.Router(mw)
-
}
func (s *State) PullsRouter(mw *middleware.Middleware) http.Handler {
+326 -323
appview/state/state.go
···
import (
"context"
-
"crypto/hmac"
-
"crypto/sha256"
-
"encoding/hex"
"fmt"
"log"
"log/slog"
···
}
// requires auth
-
func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) {
-
switch r.Method {
-
case http.MethodGet:
-
// list open registrations under this did
-
-
return
-
case http.MethodPost:
-
session, err := s.oauth.Stores().Get(r, oauth.SessionName)
-
if err != nil || session.IsNew {
-
log.Println("unauthorized attempt to generate registration key")
-
http.Error(w, "Forbidden", http.StatusUnauthorized)
-
return
-
}
-
-
did := session.Values[oauth.SessionDid].(string)
-
-
// check if domain is valid url, and strip extra bits down to just host
-
domain := r.FormValue("domain")
-
if domain == "" {
-
http.Error(w, "Invalid form", http.StatusBadRequest)
-
return
-
}
-
-
key, err := db.GenerateRegistrationKey(s.db, domain, did)
-
-
if err != nil {
-
log.Println(err)
-
http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
-
return
-
}
-
-
w.Write([]byte(key))
-
}
-
}
+
// func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) {
+
// switch r.Method {
+
// case http.MethodGet:
+
// // list open registrations under this did
+
//
+
// return
+
// case http.MethodPost:
+
// session, err := s.oauth.Stores().Get(r, oauth.SessionName)
+
// if err != nil || session.IsNew {
+
// log.Println("unauthorized attempt to generate registration key")
+
// http.Error(w, "Forbidden", http.StatusUnauthorized)
+
// return
+
// }
+
//
+
// did := session.Values[oauth.SessionDid].(string)
+
//
+
// // check if domain is valid url, and strip extra bits down to just host
+
// domain := r.FormValue("domain")
+
// if domain == "" {
+
// http.Error(w, "Invalid form", http.StatusBadRequest)
+
// return
+
// }
+
//
+
// key, err := db.GenerateRegistrationKey(s.db, domain, did)
+
//
+
// if err != nil {
+
// log.Println(err)
+
// http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
+
// return
+
// }
+
//
+
// w.Write([]byte(key))
+
// }
+
// }
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
user := chi.URLParam(r, "user")
···
}
// create a signed request and check if a node responds to that
-
func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
-
user := s.oauth.GetUser(r)
-
-
domain := chi.URLParam(r, "domain")
-
if domain == "" {
-
http.Error(w, "malformed url", http.StatusBadRequest)
-
return
-
}
-
log.Println("checking ", domain)
-
-
secret, err := db.GetRegistrationKey(s.db, domain)
-
if err != nil {
-
log.Printf("no key found for domain %s: %s\n", domain, err)
-
return
-
}
-
-
client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
-
if err != nil {
-
log.Println("failed to create client to ", domain)
-
}
-
-
resp, err := client.Init(user.Did)
-
if err != nil {
-
w.Write([]byte("no dice"))
-
log.Println("domain was unreachable after 5 seconds")
-
return
-
}
-
-
if resp.StatusCode == http.StatusConflict {
-
log.Println("status conflict", resp.StatusCode)
-
w.Write([]byte("already registered, sorry!"))
-
return
-
}
-
-
if resp.StatusCode != http.StatusNoContent {
-
log.Println("status nok", resp.StatusCode)
-
w.Write([]byte("no dice"))
-
return
-
}
-
-
// verify response mac
-
signature := resp.Header.Get("X-Signature")
-
signatureBytes, err := hex.DecodeString(signature)
-
if err != nil {
-
return
-
}
-
-
expectedMac := hmac.New(sha256.New, []byte(secret))
-
expectedMac.Write([]byte("ok"))
-
-
if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) {
-
log.Printf("response body signature mismatch: %x\n", signatureBytes)
-
return
-
}
-
-
tx, err := s.db.BeginTx(r.Context(), nil)
-
if err != nil {
-
log.Println("failed to start tx", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
defer func() {
-
tx.Rollback()
-
err = s.enforcer.E.LoadPolicy()
-
if err != nil {
-
log.Println("failed to rollback policies")
-
}
-
}()
-
-
// mark as registered
-
err = db.Register(tx, domain)
-
if err != nil {
-
log.Println("failed to register domain", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
// set permissions for this did as owner
-
reg, err := db.RegistrationByDomain(tx, domain)
-
if err != nil {
-
log.Println("failed to register domain", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
// add basic acls for this domain
-
err = s.enforcer.AddKnot(domain)
-
if err != nil {
-
log.Println("failed to setup owner of domain", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
// add this did as owner of this domain
-
err = s.enforcer.AddKnotOwner(domain, reg.ByDid)
-
if err != nil {
-
log.Println("failed to setup owner of domain", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
err = tx.Commit()
-
if err != nil {
-
log.Println("failed to commit changes", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
err = s.enforcer.E.SavePolicy()
-
if err != nil {
-
log.Println("failed to update ACLs", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
// add this knot to knotstream
-
go s.knotstream.AddSource(
-
context.Background(),
-
eventconsumer.NewKnotSource(domain),
-
)
-
-
w.Write([]byte("check success"))
-
}
-
-
func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) {
-
domain := chi.URLParam(r, "domain")
-
if domain == "" {
-
http.Error(w, "malformed url", http.StatusBadRequest)
-
return
-
}
-
-
user := s.oauth.GetUser(r)
-
reg, err := db.RegistrationByDomain(s.db, domain)
-
if err != nil {
-
w.Write([]byte("failed to pull up registration info"))
-
return
-
}
-
-
var members []string
-
if reg.Registered != nil {
-
members, err = s.enforcer.GetUserByRole("server:member", domain)
-
if err != nil {
-
w.Write([]byte("failed to fetch member list"))
-
return
-
}
-
}
-
-
var didsToResolve []string
-
for _, m := range members {
-
didsToResolve = append(didsToResolve, m)
-
}
-
didsToResolve = append(didsToResolve, reg.ByDid)
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
-
ok, err := s.enforcer.IsKnotOwner(user.Did, domain)
-
isOwner := err == nil && ok
-
-
p := pages.KnotParams{
-
LoggedInUser: user,
-
DidHandleMap: didHandleMap,
-
Registration: reg,
-
Members: members,
-
IsOwner: isOwner,
-
}
-
-
s.pages.Knot(w, p)
-
}
+
// func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
+
// user := s.oauth.GetUser(r)
+
//
+
// noticeId := "operation-error"
+
// defaultErr := "Failed to register spindle. Try again later."
+
// fail := func() {
+
// s.pages.Notice(w, noticeId, defaultErr)
+
// }
+
//
+
// domain := chi.URLParam(r, "domain")
+
// if domain == "" {
+
// http.Error(w, "malformed url", http.StatusBadRequest)
+
// return
+
// }
+
// log.Println("checking ", domain)
+
//
+
// secret, err := db.GetRegistrationKey(s.db, domain)
+
// if err != nil {
+
// log.Printf("no key found for domain %s: %s\n", domain, err)
+
// return
+
// }
+
//
+
// client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
+
// if err != nil {
+
// log.Println("failed to create client to ", domain)
+
// }
+
//
+
// resp, err := client.Init(user.Did)
+
// if err != nil {
+
// w.Write([]byte("no dice"))
+
// log.Println("domain was unreachable after 5 seconds")
+
// return
+
// }
+
//
+
// if resp.StatusCode == http.StatusConflict {
+
// log.Println("status conflict", resp.StatusCode)
+
// w.Write([]byte("already registered, sorry!"))
+
// return
+
// }
+
//
+
// if resp.StatusCode != http.StatusNoContent {
+
// log.Println("status nok", resp.StatusCode)
+
// w.Write([]byte("no dice"))
+
// return
+
// }
+
//
+
// // verify response mac
+
// signature := resp.Header.Get("X-Signature")
+
// signatureBytes, err := hex.DecodeString(signature)
+
// if err != nil {
+
// return
+
// }
+
//
+
// expectedMac := hmac.New(sha256.New, []byte(secret))
+
// expectedMac.Write([]byte("ok"))
+
//
+
// if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) {
+
// log.Printf("response body signature mismatch: %x\n", signatureBytes)
+
// return
+
// }
+
//
+
// tx, err := s.db.BeginTx(r.Context(), nil)
+
// if err != nil {
+
// log.Println("failed to start tx", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
// defer func() {
+
// tx.Rollback()
+
// err = s.enforcer.E.LoadPolicy()
+
// if err != nil {
+
// log.Println("failed to rollback policies")
+
// }
+
// }()
+
//
+
// // mark as registered
+
// err = db.Register(tx, domain)
+
// if err != nil {
+
// log.Println("failed to register domain", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
//
+
// // set permissions for this did as owner
+
// reg, err := db.RegistrationByDomain(tx, domain)
+
// if err != nil {
+
// log.Println("failed to register domain", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
//
+
// // add basic acls for this domain
+
// err = s.enforcer.AddKnot(domain)
+
// if err != nil {
+
// log.Println("failed to setup owner of domain", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
//
+
// // add this did as owner of this domain
+
// err = s.enforcer.AddKnotOwner(domain, reg.ByDid)
+
// if err != nil {
+
// log.Println("failed to setup owner of domain", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
//
+
// err = tx.Commit()
+
// if err != nil {
+
// log.Println("failed to commit changes", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
//
+
// err = s.enforcer.E.SavePolicy()
+
// if err != nil {
+
// log.Println("failed to update ACLs", err)
+
// http.Error(w, err.Error(), http.StatusInternalServerError)
+
// return
+
// }
+
//
+
// // add this knot to knotstream
+
// go s.knotstream.AddSource(
+
// context.Background(),
+
// eventconsumer.NewKnotSource(domain),
+
// )
+
//
+
// w.Write([]byte("check success"))
+
// }
+
+
// func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) {
+
// domain := chi.URLParam(r, "domain")
+
// if domain == "" {
+
// http.Error(w, "malformed url", http.StatusBadRequest)
+
// return
+
// }
+
//
+
// user := s.oauth.GetUser(r)
+
// reg, err := db.RegistrationByDomain(s.db, domain)
+
// if err != nil {
+
// w.Write([]byte("failed to pull up registration info"))
+
// return
+
// }
+
//
+
// var members []string
+
// if reg.Registered != nil {
+
// members, err = s.enforcer.GetUserByRole("server:member", domain)
+
// if err != nil {
+
// w.Write([]byte("failed to fetch member list"))
+
// return
+
// }
+
// }
+
//
+
// var didsToResolve []string
+
// for _, m := range members {
+
// didsToResolve = append(didsToResolve, m)
+
// }
+
// didsToResolve = append(didsToResolve, reg.ByDid)
+
// resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
+
// didHandleMap := make(map[string]string)
+
// for _, identity := range resolvedIds {
+
// if !identity.Handle.IsInvalidHandle() {
+
// didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
+
// } else {
+
// didHandleMap[identity.DID.String()] = identity.DID.String()
+
// }
+
// }
+
//
+
// ok, err := s.enforcer.IsKnotOwner(user.Did, domain)
+
// isOwner := err == nil && ok
+
//
+
// p := pages.KnotParams{
+
// LoggedInUser: user,
+
// DidHandleMap: didHandleMap,
+
// Registration: reg,
+
// Members: members,
+
// IsOwner: isOwner,
+
// }
+
//
+
// s.pages.Knot(w, p)
+
// }
// get knots registered by this user
-
func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
-
// for now, this is just pubkeys
-
user := s.oauth.GetUser(r)
-
registrations, err := db.RegistrationsByDid(s.db, user.Did)
-
if err != nil {
-
log.Println(err)
-
}
-
-
s.pages.Knots(w, pages.KnotsParams{
-
LoggedInUser: user,
-
Registrations: registrations,
-
})
-
}
+
// func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
+
// // for now, this is just pubkeys
+
// user := s.oauth.GetUser(r)
+
// registrations, err := db.RegistrationsByDid(s.db, user.Did)
+
// if err != nil {
+
// log.Println(err)
+
// }
+
//
+
// s.pages.Knots(w, pages.KnotsParams{
+
// LoggedInUser: user,
+
// Registrations: registrations,
+
// })
+
// }
// list members of domain, requires auth and requires owner status
-
func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) {
-
domain := chi.URLParam(r, "domain")
-
if domain == "" {
-
http.Error(w, "malformed url", http.StatusBadRequest)
-
return
-
}
-
-
// list all members for this domain
-
memberDids, err := s.enforcer.GetUserByRole("server:member", domain)
-
if err != nil {
-
w.Write([]byte("failed to fetch member list"))
-
return
-
}
-
-
w.Write([]byte(strings.Join(memberDids, "\n")))
-
return
-
}
+
// func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) {
+
// domain := chi.URLParam(r, "domain")
+
// if domain == "" {
+
// http.Error(w, "malformed url", http.StatusBadRequest)
+
// return
+
// }
+
//
+
// // list all members for this domain
+
// memberDids, err := s.enforcer.GetUserByRole("server:member", domain)
+
// if err != nil {
+
// w.Write([]byte("failed to fetch member list"))
+
// return
+
// }
+
//
+
// w.Write([]byte(strings.Join(memberDids, "\n")))
+
// return
+
// }
// add member to domain, requires auth and requires invite access
-
func (s *State) AddMember(w http.ResponseWriter, r *http.Request) {
-
domain := chi.URLParam(r, "domain")
-
if domain == "" {
-
http.Error(w, "malformed url", http.StatusBadRequest)
-
return
-
}
-
-
subjectIdentifier := r.FormValue("subject")
-
if subjectIdentifier == "" {
-
http.Error(w, "malformed form", http.StatusBadRequest)
-
return
-
}
-
-
subjectIdentity, err := s.idResolver.ResolveIdent(r.Context(), subjectIdentifier)
-
if err != nil {
-
w.Write([]byte("failed to resolve member did to a handle"))
-
return
-
}
-
log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain)
-
-
// announce this relation into the firehose, store into owners' pds
-
client, err := s.oauth.AuthorizedClient(r)
-
if err != nil {
-
http.Error(w, "failed to authorize client", http.StatusInternalServerError)
-
return
-
}
-
currentUser := s.oauth.GetUser(r)
-
createdAt := time.Now().Format(time.RFC3339)
-
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
-
Collection: tangled.KnotMemberNSID,
-
Repo: currentUser.Did,
-
Rkey: appview.TID(),
-
Record: &lexutil.LexiconTypeDecoder{
-
Val: &tangled.KnotMember{
-
Subject: subjectIdentity.DID.String(),
-
Domain: domain,
-
CreatedAt: createdAt,
-
}},
-
})
-
-
// invalid record
-
if err != nil {
-
log.Printf("failed to create record: %s", err)
-
return
-
}
-
log.Println("created atproto record: ", resp.Uri)
-
-
secret, err := db.GetRegistrationKey(s.db, domain)
-
if err != nil {
-
log.Printf("no key found for domain %s: %s\n", domain, err)
-
return
-
}
-
-
ksClient, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
-
if err != nil {
-
log.Println("failed to create client to ", domain)
-
return
-
}
-
-
ksResp, err := ksClient.AddMember(subjectIdentity.DID.String())
-
if err != nil {
-
log.Printf("failed to make request to %s: %s", domain, err)
-
return
-
}
-
-
if ksResp.StatusCode != http.StatusNoContent {
-
w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err)))
-
return
-
}
-
-
err = s.enforcer.AddKnotMember(domain, subjectIdentity.DID.String())
-
if err != nil {
-
w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
-
return
-
}
-
-
w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String())))
-
}
-
-
func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) {
-
}
+
// func (s *State) AddMember(w http.ResponseWriter, r *http.Request) {
+
// domain := chi.URLParam(r, "domain")
+
// if domain == "" {
+
// http.Error(w, "malformed url", http.StatusBadRequest)
+
// return
+
// }
+
//
+
// subjectIdentifier := r.FormValue("subject")
+
// if subjectIdentifier == "" {
+
// http.Error(w, "malformed form", http.StatusBadRequest)
+
// return
+
// }
+
//
+
// subjectIdentity, err := s.idResolver.ResolveIdent(r.Context(), subjectIdentifier)
+
// if err != nil {
+
// w.Write([]byte("failed to resolve member did to a handle"))
+
// return
+
// }
+
// log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain)
+
//
+
// // announce this relation into the firehose, store into owners' pds
+
// client, err := s.oauth.AuthorizedClient(r)
+
// if err != nil {
+
// http.Error(w, "failed to authorize client", http.StatusInternalServerError)
+
// return
+
// }
+
// currentUser := s.oauth.GetUser(r)
+
// createdAt := time.Now().Format(time.RFC3339)
+
// resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
+
// Collection: tangled.KnotMemberNSID,
+
// Repo: currentUser.Did,
+
// Rkey: appview.TID(),
+
// Record: &lexutil.LexiconTypeDecoder{
+
// Val: &tangled.KnotMember{
+
// Subject: subjectIdentity.DID.String(),
+
// Domain: domain,
+
// CreatedAt: createdAt,
+
// }},
+
// })
+
//
+
// // invalid record
+
// if err != nil {
+
// log.Printf("failed to create record: %s", err)
+
// return
+
// }
+
// log.Println("created atproto record: ", resp.Uri)
+
//
+
// secret, err := db.GetRegistrationKey(s.db, domain)
+
// if err != nil {
+
// log.Printf("no key found for domain %s: %s\n", domain, err)
+
// return
+
// }
+
//
+
// ksClient, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
+
// if err != nil {
+
// log.Println("failed to create client to ", domain)
+
// return
+
// }
+
//
+
// ksResp, err := ksClient.AddMember(subjectIdentity.DID.String())
+
// if err != nil {
+
// log.Printf("failed to make request to %s: %s", domain, err)
+
// return
+
// }
+
//
+
// if ksResp.StatusCode != http.StatusNoContent {
+
// w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err)))
+
// return
+
// }
+
//
+
// err = s.enforcer.AddKnotMember(domain, subjectIdentity.DID.String())
+
// if err != nil {
+
// w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
+
// return
+
// }
+
//
+
// w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String())))
+
// }
+
+
// func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) {
+
// }
func validateRepoName(name string) error {
// check for path traversal attempts