this repo has no description

knotserver: get/put ssh keys

Changed files
+194 -73
cmd
knotserver
knotserver
+2 -2
.air.toml
··· 1 1 [build] 2 - cmd = "go build -o bild ./cmd/legit/main.go" 3 - bin = "bild" 2 + cmd = "go build -o knot ./cmd/knotserver/main.go" 3 + bin = "knot" 4 4 root = "." 5 5 6 6 exclude_regex = [".*_templ.go"]
+7 -6
cmd/knotserver/main.go
··· 10 10 11 11 "github.com/icyphox/bild/knotserver" 12 12 "github.com/icyphox/bild/knotserver/config" 13 + "github.com/icyphox/bild/knotserver/db" 13 14 ) 14 15 15 16 func main() { ··· 23 24 if err != nil { 24 25 log.Fatal(err) 25 26 } 26 - // db, err := db.Setup(c.Server.DBPath) 27 - // if err != nil { 28 - // log.Fatalf("failed to setup db: %s", err) 29 - // } 27 + db, err := db.Setup(c.Server.DBPath) 28 + if err != nil { 29 + log.Fatalf("failed to setup db: %s", err) 30 + } 30 31 31 - mux, err := knotserver.Setup(c, nil) 32 + mux, err := knotserver.Setup(c, db) 32 33 if err != nil { 33 34 log.Fatal(err) 34 35 } 35 36 36 - addr := fmt.Sprintf("%s:%d", c.Host, c.Port) 37 + addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) 37 38 38 39 log.Println("starting main server on", addr) 39 40 log.Fatal(http.ListenAndServe(addr, mux))
+2
go.mod
··· 9 9 github.com/bluekeyes/go-gitdiff v0.8.0 10 10 github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20 11 11 github.com/dustin/go-humanize v1.0.1 12 + github.com/gliderlabs/ssh v0.3.5 12 13 github.com/go-chi/chi/v5 v5.2.0 13 14 github.com/go-git/go-git/v5 v5.12.0 14 15 github.com/google/uuid v1.6.0 ··· 28 29 github.com/Microsoft/go-winio v0.6.2 // indirect 29 30 github.com/ProtonMail/go-crypto v1.0.0 // indirect 30 31 github.com/acomagu/bufpipe v1.0.4 // indirect 32 + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 31 33 github.com/aymerick/douceur v0.2.0 // indirect 32 34 github.com/beorn7/perks v1.0.1 // indirect 33 35 github.com/carlmjohnson/versioninfo v0.22.5 // indirect
+9 -5
knotserver/config/config.go
··· 12 12 MainBranch []string `env:"MAIN_BRANCH"` 13 13 } 14 14 15 - type Config struct { 16 - Host string `env:"KNOTSERVER_HOST, default=0.0.0.0"` 17 - Port int `env:"KNOTSERVER_PORT, default=5555"` 18 - Secret string `env:"KNOTSERVER_SECRET, required"` 15 + type Server struct { 16 + Host string `env:"HOST, default=0.0.0.0"` 17 + Port int `env:"PORT, default=5555"` 18 + Secret string `env:"SECRET, required"` 19 + DBPath string `env:"DB_PATH, default=knotserver.db"` 20 + } 19 21 20 - Repo Repo `env:",prefix=KNOTSERVER_REPO_"` 22 + type Config struct { 23 + Repo Repo `env:",prefix=KNOT_REPO_"` 24 + Server Server `env:",prefix=KNOT_SERVER_"` 21 25 } 22 26 23 27 func Load(ctx context.Context) (*Config, error) {
+51
knotserver/db/init.go
··· 1 + package db 2 + 3 + import ( 4 + "database/sql" 5 + 6 + _ "github.com/mattn/go-sqlite3" 7 + ) 8 + 9 + type DB struct { 10 + db *sql.DB 11 + } 12 + 13 + func Setup(dbPath string) (*DB, error) { 14 + db, err := sql.Open("sqlite3", dbPath) 15 + if err != nil { 16 + return nil, err 17 + } 18 + 19 + _, err = db.Exec(` 20 + create table if not exists public_keys ( 21 + id integer primary key autoincrement, 22 + did text not null, 23 + name text not null, 24 + key text not null, 25 + created timestamp default current_timestamp, 26 + unique(did, name, key) 27 + ); 28 + create table if not exists repos ( 29 + id integer primary key autoincrement, 30 + did text not null, 31 + name text not null, 32 + description text not null, 33 + created timestamp default current_timestamp, 34 + unique(did, name) 35 + ); 36 + create table if not exists access_levels ( 37 + id integer primary key autoincrement, 38 + repo_id integer not null, 39 + did text not null, 40 + access text not null check (access in ('OWNER', 'WRITER')), 41 + created timestamp default current_timestamp, 42 + unique(repo_id, did), 43 + foreign key (repo_id) references repos(id) on delete cascade 44 + ); 45 + `) 46 + if err != nil { 47 + return nil, err 48 + } 49 + 50 + return &DB{db: db}, nil 51 + }
+80
knotserver/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, created) values (?, ?, ?, ?)` 7 + _, err := d.db.Exec(query, did, name, key, time.Now()) 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 (pk *PublicKey) JSON() map[string]interface{} { 25 + return map[string]interface{}{ 26 + pk.DID: map[string]interface{}{ 27 + "key": pk.Key, 28 + "name": pk.Name, 29 + "created": pk.Created, 30 + }, 31 + } 32 + } 33 + 34 + func (d *DB) GetAllPublicKeys() ([]PublicKey, error) { 35 + var keys []PublicKey 36 + 37 + rows, err := d.db.Query(`select key, name, did, created from public_keys`) 38 + if err != nil { 39 + return nil, err 40 + } 41 + defer rows.Close() 42 + 43 + for rows.Next() { 44 + var publicKey PublicKey 45 + if err := rows.Scan(&publicKey.Key, &publicKey.Name, &publicKey.DID, &publicKey.Created); err != nil { 46 + return nil, err 47 + } 48 + keys = append(keys, publicKey) 49 + } 50 + 51 + if err := rows.Err(); err != nil { 52 + return nil, err 53 + } 54 + 55 + return keys, nil 56 + } 57 + 58 + func (d *DB) GetPublicKeys(did string) ([]PublicKey, error) { 59 + var keys []PublicKey 60 + 61 + rows, err := d.db.Query(`select did, key, name, created from public_keys where did = ?`, did) 62 + if err != nil { 63 + return nil, err 64 + } 65 + defer rows.Close() 66 + 67 + for rows.Next() { 68 + var publicKey PublicKey 69 + if err := rows.Scan(&publicKey.DID, &publicKey.Key, &publicKey.Name, &publicKey.Created); err != nil { 70 + return nil, err 71 + } 72 + keys = append(keys, publicKey) 73 + } 74 + 75 + if err := rows.Err(); err != nil { 76 + return nil, err 77 + } 78 + 79 + return keys, nil 80 + }
+5 -7
knotserver/handler.go
··· 5 5 "net/http" 6 6 7 7 "github.com/go-chi/chi/v5" 8 - "github.com/icyphox/bild/db" 9 8 "github.com/icyphox/bild/knotserver/config" 9 + "github.com/icyphox/bild/knotserver/db" 10 10 ) 11 11 12 12 func Setup(c *config.Config, db *db.DB) (http.Handler, error) { ··· 17 17 db: db, 18 18 } 19 19 20 - // r.Group(func(r chi.Router) { 21 - // r.Route("/settings", func(r chi.Router) { 22 - // r.Get("/keys", h.Keys) 23 - // r.Put("/keys", h.Keys) 24 - // }) 25 - // }) 20 + r.Route("/settings", func(r chi.Router) { 21 + r.Get("/keys", h.Keys) 22 + r.Put("/keys", h.Keys) 23 + }) 26 24 27 25 r.Get("/", h.Index) 28 26 r.Route("/{did}", func(r chi.Router) {
+1 -1
knotserver/middleware.go
··· 22 22 } 23 23 24 24 func (h *Handle) verifyHMAC(signature string, r *http.Request) bool { 25 - secret := h.c.Secret 25 + secret := h.c.Server.Secret 26 26 timestamp := r.Header.Get("X-Timestamp") 27 27 if timestamp == "" { 28 28 return false
+37 -52
knotserver/routes.go
··· 15 15 "strconv" 16 16 "strings" 17 17 18 + "github.com/gliderlabs/ssh" 18 19 "github.com/go-chi/chi/v5" 19 20 "github.com/go-git/go-git/v5/plumbing" 20 21 "github.com/go-git/go-git/v5/plumbing/object" 22 + "github.com/icyphox/bild/db" 21 23 "github.com/icyphox/bild/knotserver/git" 22 24 "github.com/russross/blackfriday/v2" 23 25 ) ··· 324 326 return 325 327 } 326 328 327 - // func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 328 - // session, _ := h.s.Get(r, "bild-session") 329 - // did := session.Values["did"].(string) 329 + func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 330 + switch r.Method { 331 + case http.MethodGet: 332 + keys, err := h.db.GetAllPublicKeys() 333 + if err != nil { 334 + writeError(w, err.Error(), http.StatusInternalServerError) 335 + log.Println(err) 336 + return 337 + } 330 338 331 - // switch r.Method { 332 - // case http.MethodGet: 333 - // keys, err := h.db.GetPublicKeys(did) 334 - // if err != nil { 335 - // h.WriteOOBNotice(w, "keys", "Failed to list keys. Try again later.") 336 - // log.Println(err) 337 - // return 338 - // } 339 + data := make([]map[string]interface{}, 0) 340 + for _, key := range keys { 341 + j := key.JSON() 342 + data = append(data, j) 343 + } 344 + writeJSON(w, data) 345 + return 339 346 340 - // data := make(map[string]interface{}) 341 - // data["keys"] = keys 342 - // if err := h.t.ExecuteTemplate(w, "settings/keys", data); err != nil { 343 - // log.Println(err) 344 - // return 345 - // } 346 - // case http.MethodPut: 347 - // key := r.FormValue("key") 348 - // name := r.FormValue("name") 349 - // client, _ := h.auth.AuthorizedClient(r) 347 + case http.MethodPut: 348 + pk := db.PublicKey{} 349 + if err := json.NewDecoder(r.Body).Decode(&pk); err != nil { 350 + writeError(w, "invalid request body", http.StatusBadRequest) 351 + return 352 + } 350 353 351 - // _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 352 - // if err != nil { 353 - // h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.") 354 - // log.Printf("parsing public key: %s", err) 355 - // return 356 - // } 354 + _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key)) 355 + if err != nil { 356 + writeError(w, "invalid pubkey", http.StatusBadRequest) 357 + } 357 358 358 - // if err := h.db.AddPublicKey(did, name, key); err != nil { 359 - // h.WriteOOBNotice(w, "keys", "Failed to add key.") 360 - // log.Printf("adding public key: %s", err) 361 - // return 362 - // } 359 + if err := h.db.AddPublicKey(pk.DID, pk.Name, pk.Key); err != nil { 360 + writeError(w, err.Error(), http.StatusInternalServerError) 361 + log.Printf("adding public key: %s", err) 362 + return 363 + } 363 364 364 - // h.WriteOOBNotice(w, "keys", "Key added!") 365 - // return 366 - // } 367 - // } 365 + w.WriteHeader(http.StatusNoContent) 366 + return 367 + } 368 + } 368 369 369 370 func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) { 370 371 data := struct { ··· 389 390 390 391 w.WriteHeader(http.StatusNoContent) 391 392 } 392 - 393 - // func (h *Handle) Timeline(w http.ResponseWriter, r *http.Request) { 394 - // session, err := h.s.Get(r, "bild-session") 395 - // user := make(map[string]string) 396 - // if err != nil || session.IsNew { 397 - // // user is not logged in 398 - // } else { 399 - // user["handle"] = session.Values["handle"].(string) 400 - // user["did"] = session.Values["did"].(string) 401 - // } 402 - 403 - // if err := h.t.ExecuteTemplate(w, "timeline", user); err != nil { 404 - // log.Println(err) 405 - // return 406 - // } 407 - // } 408 393 409 394 func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 410 395 log.Println("got health check")