forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview: do /settings/keys + misc cleanups

Changed files
+211 -51
.air
appview
git
+2 -2
.air.toml .air/appview.toml
···
[build]
-
cmd = "go build -o knot ./cmd/knotserver/main.go"
-
bin = "knot"
root = "."
exclude_regex = [".*_templ.go"]
···
[build]
+
cmd = "go build -o .bin/app ./cmd/appview/main.go"
+
bin = ".bin/app"
root = "."
exclude_regex = [".*_templ.go"]
+7
.air/knotserver.toml
···
···
+
[build]
+
cmd = "go build -o .bin/knot ./cmd/knotserver/main.go"
+
bin = ".bin/knot"
+
root = "."
+
+
exclude_regex = [""]
+
include_ext = ["go", "templ"]
+1
.gitignore
···
.direnv/
tmp
*.db
···
.direnv/
tmp
*.db
+
.bin/
+40 -22
appview/auth/auth.go
···
import (
"context"
-
"encoding/json"
"fmt"
"net/http"
"time"
···
return dir.Lookup(ctx, *id)
}
-
func (a *Auth) CreateInitialSession(ctx context.Context, username, appPassword string) (*comatproto.ServerCreateSession_Output, error) {
-
resolved, err := ResolveIdent(ctx, username)
-
if err != nil {
-
return nil, fmt.Errorf("invalid handle: %s", err)
-
}
pdsUrl := resolved.PDSEndpoint()
client := xrpc.Client{
···
return s.Status
}
-
func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish) error {
-
var didDoc identity.DIDDocument
-
-
bytes, _ := json.Marshal(atSessionish.GetDidDoc())
-
err := json.Unmarshal(bytes, &didDoc)
-
if err != nil {
-
return fmt.Errorf("invalid did document for session")
-
}
-
-
identity := identity.ParseIdentity(&didDoc)
-
pdsEndpoint := identity.PDSEndpoint()
-
-
if pdsEndpoint == "" {
-
return fmt.Errorf("no pds endpoint found")
-
}
-
clientSession, _ := a.Store.Get(r, appview.SESSION_NAME)
clientSession.Values[appview.SESSION_HANDLE] = atSessionish.GetHandle()
clientSession.Values[appview.SESSION_DID] = atSessionish.GetDid()
···
return clientSession.Save(r, w)
}
···
import (
"context"
"fmt"
"net/http"
"time"
···
return dir.Lookup(ctx, *id)
}
+
func (a *Auth) CreateInitialSession(ctx context.Context, resolved *identity.Identity, appPassword string) (*comatproto.ServerCreateSession_Output, error) {
pdsUrl := resolved.PDSEndpoint()
client := xrpc.Client{
···
return s.Status
}
+
func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish, pdsEndpoint string) error {
clientSession, _ := a.Store.Get(r, appview.SESSION_NAME)
clientSession.Values[appview.SESSION_HANDLE] = atSessionish.GetHandle()
clientSession.Values[appview.SESSION_DID] = atSessionish.GetDid()
···
return clientSession.Save(r, w)
}
+
+
func (a *Auth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) {
+
clientSession, err := a.Store.Get(r, "appview-session")
+
+
if err != nil || clientSession.IsNew {
+
return nil, err
+
}
+
+
did := clientSession.Values["did"].(string)
+
pdsUrl := clientSession.Values["pds"].(string)
+
accessJwt := clientSession.Values["accessJwt"].(string)
+
refreshJwt := clientSession.Values["refreshJwt"].(string)
+
+
client := &xrpc.Client{
+
Host: pdsUrl,
+
Auth: &xrpc.AuthInfo{
+
AccessJwt: accessJwt,
+
RefreshJwt: refreshJwt,
+
Did: did,
+
},
+
}
+
+
return client, nil
+
}
+
+
func (a *Auth) GetSession(r *http.Request) (*sessions.Session, error) {
+
return a.Store.Get(r, appview.SESSION_NAME)
+
}
+
+
func (a *Auth) GetDID(r *http.Request) string {
+
clientSession, _ := a.Store.Get(r, appview.SESSION_NAME)
+
return clientSession.Values[appview.SESSION_DID].(string)
+
}
+
+
func (a *Auth) GetHandle(r *http.Request) string {
+
clientSession, _ := a.Store.Get(r, appview.SESSION_NAME)
+
return clientSession.Values[appview.SESSION_HANDLE].(string)
+
}
+9 -5
appview/db/db.go
···
"log"
"github.com/google/uuid"
_ "github.com/mattn/go-sqlite3"
)
···
did text not null,
secret text not null,
created integer default (strftime('%s', 'now')),
-
registered integer
);
`)
if err != nil {
···
}
secret := uuid.New().String()
-
-
if err != nil {
-
return "", err
-
}
_, err = d.db.Exec(`
insert into registrations (domain, did, secret)
···
"log"
"github.com/google/uuid"
+
_ "github.com/mattn/go-sqlite3"
)
···
did text not null,
secret text not null,
created integer default (strftime('%s', 'now')),
+
registered integer);
+
create table if not exists public_keys (
+
id integer primary key autoincrement,
+
did text not null,
+
name text not null,
+
key text not null,
+
created timestamp default current_timestamp,
+
unique(did, name, key)
);
`)
if err != nil {
···
}
secret := uuid.New().String()
_, err = d.db.Exec(`
insert into registrations (domain, did, secret)
+70
appview/db/pubkeys.go
···
···
+
package db
+
+
import "time"
+
+
func (d *DB) AddPublicKey(did, name, key string) error {
+
query := `insert into public_keys (did, name, key) values (?, ?, ?)`
+
_, err := d.db.Exec(query, did, name, key)
+
return err
+
}
+
+
func (d *DB) RemovePublicKey(did string) error {
+
query := `delete from public_keys where did = ?`
+
_, err := d.db.Exec(query, did)
+
return err
+
}
+
+
type PublicKey struct {
+
Key string
+
Name string
+
DID string
+
Created time.Time
+
}
+
+
func (d *DB) GetAllPublicKeys() ([]PublicKey, error) {
+
var keys []PublicKey
+
+
rows, err := d.db.Query(`select key, name, did, created from public_keys`)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var publicKey PublicKey
+
if err := rows.Scan(&publicKey.Key, &publicKey.Name, &publicKey.DID, &publicKey.Created); err != nil {
+
return nil, err
+
}
+
keys = append(keys, publicKey)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return keys, nil
+
}
+
+
func (d *DB) GetPublicKeys(did string) ([]PublicKey, error) {
+
var keys []PublicKey
+
+
rows, err := d.db.Query(`select did, key, name, created from public_keys where did = ?`, did)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var publicKey PublicKey
+
if err := rows.Scan(&publicKey.DID, &publicKey.Key, &publicKey.Name, &publicKey.Created); err != nil {
+
return nil, err
+
}
+
keys = append(keys, publicKey)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return keys, nil
+
}
+2 -2
appview/state/middleware.go
···
func AuthMiddleware(s *State) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
session, _ := s.Auth.Store.Get(r, appview.SESSION_NAME)
authorized, ok := session.Values[appview.SESSION_AUTHENTICATED].(bool)
if !ok || !authorized {
···
sessionish := auth.RefreshSessionWrapper{atSession}
-
err = s.Auth.StoreSession(r, w, &sessionish)
if err != nil {
log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err)
return
···
func AuthMiddleware(s *State) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
session, _ := s.auth.Store.Get(r, appview.SESSION_NAME)
authorized, ok := session.Values[appview.SESSION_AUTHENTICATED].(bool)
if !ok || !authorized {
···
sessionish := auth.RefreshSessionWrapper{atSession}
+
err = s.auth.StoreSession(r, w, &sessionish, pdsUrl)
if err != nil {
log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err)
return
+79 -15
appview/state/state.go
···
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/icyphox/bild/appview"
"github.com/icyphox/bild/appview/auth"
"github.com/icyphox/bild/appview/db"
)
type State struct {
-
Db *db.DB
-
Auth *auth.Auth
}
func Make() (*State, error) {
···
log.Println("unimplemented")
return
case http.MethodPost:
-
username := r.FormValue("username")
-
appPassword := r.FormValue("password")
-
atSession, err := s.Auth.CreateInitialSession(ctx, username, appPassword)
if err != nil {
log.Printf("creating initial session: %s", err)
return
}
-
sessionish := auth.CreateSessionWrapper{atSession}
-
err = s.Auth.StoreSession(r, w, &sessionish)
if err != nil {
log.Printf("storing session: %s", err)
return
···
}
// requires auth
-
func (s *State) Key(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.Auth.Store.Get(r, appview.SESSION_NAME)
if err != nil || session.IsNew {
log.Println("unauthorized attempt to generate registration key")
http.Error(w, "Forbidden", http.StatusUnauthorized)
···
// check if domain is valid url, and strip extra bits down to just host
domain := r.FormValue("domain")
-
if domain == "" || err != nil {
-
log.Println(err)
http.Error(w, "Invalid form", http.StatusBadRequest)
return
}
-
key, err := s.Db.GenerateRegistrationKey(domain, did)
if err != nil {
log.Println(err)
···
}
w.Write([]byte(key))
return
}
}
···
log.Println("checking ", domain)
-
secret, err := s.Db.GetRegistrationKey(domain)
if err != nil {
log.Printf("no key found for domain %s: %s\n", domain, err)
return
···
w.Write([]byte("check success"))
// mark as registered
-
err = s.Db.Register(domain)
if err != nil {
log.Println("failed to register domain", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
···
r.Group(func(r chi.Router) {
r.Use(AuthMiddleware(s))
-
r.Post("/key", s.Key)
})
})
return r
···
"net/http"
"time"
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
+
lexutil "github.com/bluesky-social/indigo/lex/util"
+
"github.com/gliderlabs/ssh"
"github.com/go-chi/chi/v5"
+
"github.com/google/uuid"
+
shbild "github.com/icyphox/bild/api/bild"
"github.com/icyphox/bild/appview"
"github.com/icyphox/bild/appview/auth"
"github.com/icyphox/bild/appview/db"
)
type State struct {
+
db *db.DB
+
auth *auth.Auth
}
func Make() (*State, error) {
···
log.Println("unimplemented")
return
case http.MethodPost:
+
handle := r.FormValue("handle")
+
appPassword := r.FormValue("app_password")
+
fmt.Println("handle", handle)
+
fmt.Println("app_password", appPassword)
+
+
resolved, err := auth.ResolveIdent(ctx, handle)
+
if err != nil {
+
log.Printf("resolving identity: %s", err)
+
return
+
}
+
+
atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword)
if err != nil {
log.Printf("creating initial session: %s", err)
return
}
+
sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession}
+
err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint())
if err != nil {
log.Printf("storing session: %s", err)
return
···
}
// 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.auth.Store.Get(r, appview.SESSION_NAME)
if err != nil || session.IsNew {
log.Println("unauthorized attempt to generate registration key")
http.Error(w, "Forbidden", http.StatusUnauthorized)
···
// 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 := s.db.GenerateRegistrationKey(domain, did)
if err != nil {
log.Println(err)
···
}
w.Write([]byte(key))
+
}
+
}
+
+
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
+
switch r.Method {
+
case http.MethodGet:
+
w.Write([]byte("unimplemented"))
+
log.Println("unimplemented")
+
return
+
case http.MethodPut:
+
did := s.auth.GetDID(r)
+
key := r.FormValue("key")
+
name := r.FormValue("name")
+
client, _ := s.auth.AuthorizedClient(r)
+
+
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
+
if err != nil {
+
log.Printf("parsing public key: %s", err)
+
return
+
}
+
+
if err := s.db.AddPublicKey(did, name, key); err != nil {
+
log.Printf("adding public key: %s", err)
+
return
+
}
+
+
// store in pds too
+
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+
Collection: "sh.bild.publicKey",
+
Repo: did,
+
Rkey: uuid.New().String(),
+
Record: &lexutil.LexiconTypeDecoder{Val: &shbild.PublicKey{
+
Created: time.Now().String(),
+
Key: key,
+
Name: name,
+
}},
+
})
+
+
// invalid record
+
if err != nil {
+
log.Printf("failed to create record: %s", err)
+
return
+
}
+
+
log.Println("created atproto record: ", resp.Uri)
+
return
}
}
···
log.Println("checking ", domain)
+
secret, err := s.db.GetRegistrationKey(domain)
if err != nil {
log.Printf("no key found for domain %s: %s\n", domain, err)
return
···
w.Write([]byte("check success"))
// mark as registered
+
err = s.db.Register(domain)
if err != nil {
log.Println("failed to register domain", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
···
r.Group(func(r chi.Router) {
r.Use(AuthMiddleware(s))
+
r.Post("/key", s.RegistrationKey)
})
+
})
+
+
r.Route("/settings", func(r chi.Router) {
+
r.Use(AuthMiddleware(s))
+
r.Put("/keys", s.Keys)
})
return r
+1 -5
git/git.go
···
}
func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
-
branches = append(branches, []string{
-
"main",
-
"master",
-
"trunk",
-
}...)
for _, b := range branches {
_, err := g.r.ResolveRevision(plumbing.Revision(b))
if err == nil {
···
}
func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
+
for _, b := range branches {
_, err := g.r.ResolveRevision(plumbing.Revision(b))
if err == nil {