Monorepo for Tangled tangled.org

appview: do /settings/keys + misc cleanups

Changed files
+211 -51
.air
appview
git
+2 -2
.air.toml .air/appview.toml
··· 1 1 [build] 2 - cmd = "go build -o knot ./cmd/knotserver/main.go" 3 - bin = "knot" 2 + cmd = "go build -o .bin/app ./cmd/appview/main.go" 3 + bin = ".bin/app" 4 4 root = "." 5 5 6 6 exclude_regex = [".*_templ.go"]
+7
.air/knotserver.toml
··· 1 + [build] 2 + cmd = "go build -o .bin/knot ./cmd/knotserver/main.go" 3 + bin = ".bin/knot" 4 + root = "." 5 + 6 + exclude_regex = [""] 7 + include_ext = ["go", "templ"]
+1
.gitignore
··· 1 1 .direnv/ 2 2 tmp 3 3 *.db 4 + .bin/
+40 -22
appview/auth/auth.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "encoding/json" 6 5 "fmt" 7 6 "net/http" 8 7 "time" ··· 44 43 return dir.Lookup(ctx, *id) 45 44 } 46 45 47 - func (a *Auth) CreateInitialSession(ctx context.Context, username, appPassword string) (*comatproto.ServerCreateSession_Output, error) { 48 - resolved, err := ResolveIdent(ctx, username) 49 - if err != nil { 50 - return nil, fmt.Errorf("invalid handle: %s", err) 51 - } 46 + func (a *Auth) CreateInitialSession(ctx context.Context, resolved *identity.Identity, appPassword string) (*comatproto.ServerCreateSession_Output, error) { 52 47 53 48 pdsUrl := resolved.PDSEndpoint() 54 49 client := xrpc.Client{ ··· 143 138 return s.Status 144 139 } 145 140 146 - func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish) error { 147 - var didDoc identity.DIDDocument 148 - 149 - bytes, _ := json.Marshal(atSessionish.GetDidDoc()) 150 - err := json.Unmarshal(bytes, &didDoc) 151 - if err != nil { 152 - return fmt.Errorf("invalid did document for session") 153 - } 154 - 155 - identity := identity.ParseIdentity(&didDoc) 156 - pdsEndpoint := identity.PDSEndpoint() 157 - 158 - if pdsEndpoint == "" { 159 - return fmt.Errorf("no pds endpoint found") 160 - } 161 - 141 + func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish, pdsEndpoint string) error { 162 142 clientSession, _ := a.Store.Get(r, appview.SESSION_NAME) 163 143 clientSession.Values[appview.SESSION_HANDLE] = atSessionish.GetHandle() 164 144 clientSession.Values[appview.SESSION_DID] = atSessionish.GetDid() ··· 170 150 171 151 return clientSession.Save(r, w) 172 152 } 153 + 154 + func (a *Auth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) { 155 + clientSession, err := a.Store.Get(r, "appview-session") 156 + 157 + if err != nil || clientSession.IsNew { 158 + return nil, err 159 + } 160 + 161 + did := clientSession.Values["did"].(string) 162 + pdsUrl := clientSession.Values["pds"].(string) 163 + accessJwt := clientSession.Values["accessJwt"].(string) 164 + refreshJwt := clientSession.Values["refreshJwt"].(string) 165 + 166 + client := &xrpc.Client{ 167 + Host: pdsUrl, 168 + Auth: &xrpc.AuthInfo{ 169 + AccessJwt: accessJwt, 170 + RefreshJwt: refreshJwt, 171 + Did: did, 172 + }, 173 + } 174 + 175 + return client, nil 176 + } 177 + 178 + func (a *Auth) GetSession(r *http.Request) (*sessions.Session, error) { 179 + return a.Store.Get(r, appview.SESSION_NAME) 180 + } 181 + 182 + func (a *Auth) GetDID(r *http.Request) string { 183 + clientSession, _ := a.Store.Get(r, appview.SESSION_NAME) 184 + return clientSession.Values[appview.SESSION_DID].(string) 185 + } 186 + 187 + func (a *Auth) GetHandle(r *http.Request) string { 188 + clientSession, _ := a.Store.Get(r, appview.SESSION_NAME) 189 + return clientSession.Values[appview.SESSION_HANDLE].(string) 190 + }
+9 -5
appview/db/db.go
··· 6 6 "log" 7 7 8 8 "github.com/google/uuid" 9 + 9 10 _ "github.com/mattn/go-sqlite3" 10 11 ) 11 12 ··· 25 26 did text not null, 26 27 secret text not null, 27 28 created integer default (strftime('%s', 'now')), 28 - registered integer 29 + registered integer); 30 + create table if not exists public_keys ( 31 + id integer primary key autoincrement, 32 + did text not null, 33 + name text not null, 34 + key text not null, 35 + created timestamp default current_timestamp, 36 + unique(did, name, key) 29 37 ); 30 38 `) 31 39 if err != nil { ··· 85 93 } 86 94 87 95 secret := uuid.New().String() 88 - 89 - if err != nil { 90 - return "", err 91 - } 92 96 93 97 _, err = d.db.Exec(` 94 98 insert into registrations (domain, did, secret)
+70
appview/db/pubkeys.go
··· 1 + package db 2 + 3 + import "time" 4 + 5 + func (d *DB) AddPublicKey(did, name, key string) error { 6 + query := `insert into public_keys (did, name, key) values (?, ?, ?)` 7 + _, err := d.db.Exec(query, did, name, key) 8 + return err 9 + } 10 + 11 + func (d *DB) RemovePublicKey(did string) error { 12 + query := `delete from public_keys where did = ?` 13 + _, err := d.db.Exec(query, did) 14 + return err 15 + } 16 + 17 + type PublicKey struct { 18 + Key string 19 + Name string 20 + DID string 21 + Created time.Time 22 + } 23 + 24 + func (d *DB) GetAllPublicKeys() ([]PublicKey, error) { 25 + var keys []PublicKey 26 + 27 + rows, err := d.db.Query(`select key, name, did, created from public_keys`) 28 + if err != nil { 29 + return nil, err 30 + } 31 + defer rows.Close() 32 + 33 + for rows.Next() { 34 + var publicKey PublicKey 35 + if err := rows.Scan(&publicKey.Key, &publicKey.Name, &publicKey.DID, &publicKey.Created); err != nil { 36 + return nil, err 37 + } 38 + keys = append(keys, publicKey) 39 + } 40 + 41 + if err := rows.Err(); err != nil { 42 + return nil, err 43 + } 44 + 45 + return keys, nil 46 + } 47 + 48 + func (d *DB) GetPublicKeys(did string) ([]PublicKey, error) { 49 + var keys []PublicKey 50 + 51 + rows, err := d.db.Query(`select did, key, name, created from public_keys where did = ?`, did) 52 + if err != nil { 53 + return nil, err 54 + } 55 + defer rows.Close() 56 + 57 + for rows.Next() { 58 + var publicKey PublicKey 59 + if err := rows.Scan(&publicKey.DID, &publicKey.Key, &publicKey.Name, &publicKey.Created); err != nil { 60 + return nil, err 61 + } 62 + keys = append(keys, publicKey) 63 + } 64 + 65 + if err := rows.Err(); err != nil { 66 + return nil, err 67 + } 68 + 69 + return keys, nil 70 + }
+2 -2
appview/state/middleware.go
··· 16 16 func AuthMiddleware(s *State) Middleware { 17 17 return func(next http.Handler) http.Handler { 18 18 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 - session, _ := s.Auth.Store.Get(r, appview.SESSION_NAME) 19 + session, _ := s.auth.Store.Get(r, appview.SESSION_NAME) 20 20 authorized, ok := session.Values[appview.SESSION_AUTHENTICATED].(bool) 21 21 22 22 if !ok || !authorized { ··· 56 56 57 57 sessionish := auth.RefreshSessionWrapper{atSession} 58 58 59 - err = s.Auth.StoreSession(r, w, &sessionish) 59 + err = s.auth.StoreSession(r, w, &sessionish, pdsUrl) 60 60 if err != nil { 61 61 log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err) 62 62 return
+79 -15
appview/state/state.go
··· 9 9 "net/http" 10 10 "time" 11 11 12 + comatproto "github.com/bluesky-social/indigo/api/atproto" 13 + lexutil "github.com/bluesky-social/indigo/lex/util" 14 + "github.com/gliderlabs/ssh" 12 15 "github.com/go-chi/chi/v5" 16 + "github.com/google/uuid" 17 + shbild "github.com/icyphox/bild/api/bild" 13 18 "github.com/icyphox/bild/appview" 14 19 "github.com/icyphox/bild/appview/auth" 15 20 "github.com/icyphox/bild/appview/db" 16 21 ) 17 22 18 23 type State struct { 19 - Db *db.DB 20 - Auth *auth.Auth 24 + db *db.DB 25 + auth *auth.Auth 21 26 } 22 27 23 28 func Make() (*State, error) { ··· 42 47 log.Println("unimplemented") 43 48 return 44 49 case http.MethodPost: 45 - username := r.FormValue("username") 46 - appPassword := r.FormValue("password") 50 + handle := r.FormValue("handle") 51 + appPassword := r.FormValue("app_password") 47 52 48 - atSession, err := s.Auth.CreateInitialSession(ctx, username, appPassword) 53 + fmt.Println("handle", handle) 54 + fmt.Println("app_password", appPassword) 55 + 56 + resolved, err := auth.ResolveIdent(ctx, handle) 57 + if err != nil { 58 + log.Printf("resolving identity: %s", err) 59 + return 60 + } 61 + 62 + atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword) 49 63 if err != nil { 50 64 log.Printf("creating initial session: %s", err) 51 65 return 52 66 } 53 - sessionish := auth.CreateSessionWrapper{atSession} 67 + sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession} 54 68 55 - err = s.Auth.StoreSession(r, w, &sessionish) 69 + err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint()) 56 70 if err != nil { 57 71 log.Printf("storing session: %s", err) 58 72 return ··· 65 79 } 66 80 67 81 // requires auth 68 - func (s *State) Key(w http.ResponseWriter, r *http.Request) { 82 + func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) { 69 83 switch r.Method { 70 84 case http.MethodGet: 71 85 // list open registrations under this did 72 86 73 87 return 74 88 case http.MethodPost: 75 - session, err := s.Auth.Store.Get(r, appview.SESSION_NAME) 89 + session, err := s.auth.Store.Get(r, appview.SESSION_NAME) 76 90 if err != nil || session.IsNew { 77 91 log.Println("unauthorized attempt to generate registration key") 78 92 http.Error(w, "Forbidden", http.StatusUnauthorized) ··· 83 97 84 98 // check if domain is valid url, and strip extra bits down to just host 85 99 domain := r.FormValue("domain") 86 - if domain == "" || err != nil { 87 - log.Println(err) 100 + if domain == "" { 88 101 http.Error(w, "Invalid form", http.StatusBadRequest) 89 102 return 90 103 } 91 104 92 - key, err := s.Db.GenerateRegistrationKey(domain, did) 105 + key, err := s.db.GenerateRegistrationKey(domain, did) 93 106 94 107 if err != nil { 95 108 log.Println(err) ··· 98 111 } 99 112 100 113 w.Write([]byte(key)) 114 + } 115 + } 116 + 117 + func (s *State) Keys(w http.ResponseWriter, r *http.Request) { 118 + switch r.Method { 119 + case http.MethodGet: 120 + w.Write([]byte("unimplemented")) 121 + log.Println("unimplemented") 122 + return 123 + case http.MethodPut: 124 + did := s.auth.GetDID(r) 125 + key := r.FormValue("key") 126 + name := r.FormValue("name") 127 + client, _ := s.auth.AuthorizedClient(r) 128 + 129 + _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 130 + if err != nil { 131 + log.Printf("parsing public key: %s", err) 132 + return 133 + } 134 + 135 + if err := s.db.AddPublicKey(did, name, key); err != nil { 136 + log.Printf("adding public key: %s", err) 137 + return 138 + } 139 + 140 + // store in pds too 141 + resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 142 + Collection: "sh.bild.publicKey", 143 + Repo: did, 144 + Rkey: uuid.New().String(), 145 + Record: &lexutil.LexiconTypeDecoder{Val: &shbild.PublicKey{ 146 + Created: time.Now().String(), 147 + Key: key, 148 + Name: name, 149 + }}, 150 + }) 151 + 152 + // invalid record 153 + if err != nil { 154 + log.Printf("failed to create record: %s", err) 155 + return 156 + } 157 + 158 + log.Println("created atproto record: ", resp.Uri) 159 + 101 160 return 102 161 } 103 162 } ··· 114 173 115 174 log.Println("checking ", domain) 116 175 117 - secret, err := s.Db.GetRegistrationKey(domain) 176 + secret, err := s.db.GetRegistrationKey(domain) 118 177 if err != nil { 119 178 log.Printf("no key found for domain %s: %s\n", domain, err) 120 179 return ··· 164 223 w.Write([]byte("check success")) 165 224 166 225 // mark as registered 167 - err = s.Db.Register(domain) 226 + err = s.db.Register(domain) 168 227 if err != nil { 169 228 log.Println("failed to register domain", err) 170 229 http.Error(w, err.Error(), http.StatusInternalServerError) ··· 201 260 202 261 r.Group(func(r chi.Router) { 203 262 r.Use(AuthMiddleware(s)) 204 - r.Post("/key", s.Key) 263 + r.Post("/key", s.RegistrationKey) 205 264 }) 265 + }) 266 + 267 + r.Route("/settings", func(r chi.Router) { 268 + r.Use(AuthMiddleware(s)) 269 + r.Put("/keys", s.Keys) 206 270 }) 207 271 208 272 return r
+1 -5
git/git.go
··· 202 202 } 203 203 204 204 func (g *GitRepo) FindMainBranch(branches []string) (string, error) { 205 - branches = append(branches, []string{ 206 - "main", 207 - "master", 208 - "trunk", 209 - }...) 205 + 210 206 for _, b := range branches { 211 207 _, err := g.r.ResolveRevision(plumbing.Revision(b)) 212 208 if err == nil {