Skip to content

Commit

Permalink
user sends their initial graph with registration
Browse files Browse the repository at this point in the history
  • Loading branch information
whyrusleeping committed Jan 5, 2022
1 parent 1dd9ba1 commit eb26c55
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 97 deletions.
90 changes: 76 additions & 14 deletions cmd/bsky/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import (
"gorm.io/gorm/clause"
)

const TwitterDid = "did:key:z6Mkmi4eUvWtRAP6PNB7MnGfUFdLkGe255ftW9sGo28uv44g"

func main() {

app := cli.NewApp()
Expand Down Expand Up @@ -247,6 +249,7 @@ func (p *Posts) Get(ctx context.Context, id int64) (*types.Post, error) {
var registerCmd = &cli.Command{
Name: "register",
Action: func(cctx *cli.Context) error {
ctx := cctx.Context
bskyd, err := homedir.Expand(cctx.String("repo"))
if err != nil {
return err
Expand All @@ -257,47 +260,80 @@ var registerCmd = &cli.Command{
return err
}

b, err := json.Marshal(map[string]interface{}{
"DID": r.Account.DID,
})
userCid, err := createInitialUser(ctx, r.Blockstore, r.Account.DID, r.Account.Name)
if err != nil {
return err
}

req, err := http.NewRequest("POST", r.Account.Server+"/register", bytes.NewReader(b))
dserv := merkledag.NewDAGService(blockservice.New(r.Blockstore, nil))

sroot := &types.SignedRoot{
User: userCid,
}
cst := cbor.NewCborStore(r.Blockstore)
rootcid, err := cst.Put(ctx, sroot)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
buf := new(bytes.Buffer)
if err := car.WriteCar(ctx, dserv, []cid.Cid{rootcid}, buf); err != nil {
return err
}
defer resp.Body.Close()

u, root, err := readUserCar(context.TODO(), resp.Body, r.Blockstore)
req, err := http.NewRequest("POST", r.Account.Server+"/register", buf)
if err != nil {
return fmt.Errorf("reading car response: %w", err)
return err
}

r.Account.Root = root
if err := r.SaveAccount(r.Account); err != nil {
if err := addUcanAuthToRequest(req, r.Account); err != nil {
return err
}

ub, err := json.Marshal(u)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
return fmt.Errorf("got bad status code back: %d", resp.StatusCode)
}

r.Account.Root = userCid
if err := r.SaveAccount(r.Account); err != nil {
return err
}

fmt.Println("registration complete")
fmt.Println(string(ub))

return nil
},
}

func createInitialUser(ctx context.Context, bs blockstore.Blockstore, did string, name string) (cid.Cid, error) {
cst := cbor.NewCborStore(bs)

n := hamt.NewNode(cst)
proot, err := cst.Put(ctx, n)
if err != nil {
return cid.Undef, err
}

u := &types.User{
DID: did,
Name: name,
PostsRoot: proot,
}

uroot, err := cst.Put(ctx, u)
if err != nil {
return cid.Undef, err
}

return uroot, nil
}

var postCmd = &cli.Command{
Name: "post",
Action: func(cctx *cli.Context) error {
Expand Down Expand Up @@ -360,6 +396,10 @@ func PushUpdate(ctx context.Context, acc *Account, oldroot cid.Cid, bs blockstor
return err
}

if err := addUcanAuthToRequest(req, acc); err != nil {
return err
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
Expand All @@ -372,6 +412,28 @@ func PushUpdate(ctx context.Context, acc *Account, oldroot cid.Cid, bs blockstor
return nil
}

func addUcanAuthToRequest(req *http.Request, acc *Account) error {
src, err := ucan.NewPrivKeySource(acc.Key)
if err != nil {
return err
}

caps := ucan.NewNestedCapabilities("POST")
att := ucan.Attenuations{
{caps.Cap("POST"), ucan.NewStringLengthResource("twitter", acc.Name)},
}

// TwitterDid is the DID of the service we are registering with
tok, err := src.NewOriginToken(TwitterDid, att, nil, time.Now(), time.Now().Add(time.Hour*24*365))
if err != nil {
return err
}
fmt.Println("TOKEN: ", tok.Raw)

req.Header.Set("Authorization", "Bearer "+tok.Raw)
return nil
}

func AddPost(ctx context.Context, root cid.Cid, bs blockstore.Blockstore, p *types.Post) (cid.Cid, error) {
cst := cbor.NewCborStore(bs)
var u types.User
Expand Down
164 changes: 81 additions & 83 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (s *Server) handleUserUpdate(e echo.Context) error {
return fmt.Errorf("Ucan not directed to twitter server")
}

checkUser := func(user string) bool {
checkUser := func(u *types.User) error {
att := ucan.Attenuation{
Rsc: newAccountResource("twitter", "dholms"),
Cap: twitterCaps.Cap("POST"),
Expand All @@ -140,32 +140,32 @@ func (s *Server) handleUserUpdate(e echo.Context) error {
isGood := token.Attenuations.Contains(ucan.Attenuations{att})

if !isGood {
return false
return fmt.Errorf("Ucan attenuation check failed")
}

// user not registerd
if s.UserDids[user] == nil {
return false
if s.UserDids[u.Name] == nil {
return fmt.Errorf("user not registered")
}

// ucan issuer does not match user's DID
if token.Issuer.String() != s.UserDids[user].String() {
return false
if token.Issuer.String() != s.UserDids[u.Name].String() {
return fmt.Errorf("issuer does not match users DID")
}

return true
return nil
}

return s.updateUser(ctx, e.Request(), checkUser)
}

func (s *Server) updateUser(ctx context.Context, req *http.Request, checkUser func(user string) bool) error {
// The body of the request should be a car file containing any *changed* blocks
cr, err := car.NewCarReader(req.Body)
carr, err := car.NewCarReader(e.Request().Body)
if err != nil {
return err
}

return s.updateUser(ctx, carr, checkUser)
}

func (s *Server) updateUser(ctx context.Context, cr *car.CarReader, authCheck func(u *types.User) error) error {
// The body of the request should be a car file containing any *changed* blocks
roots := cr.Header.Roots

if len(roots) != 1 {
Expand All @@ -192,41 +192,42 @@ func (s *Server) updateUser(ctx context.Context, req *http.Request, checkUser fu

rblk, err := tmpbs.Get(ctx, roots[0])
if err != nil {
return fmt.Errorf("getting root: %w", err)
}

var sroot types.SignedRoot
if err := sroot.UnmarshalCBOR(bytes.NewReader(rblk.RawData())); err != nil {
return err
}

// TODO: accept signed root & Verify signature
// var sroot types.SignedRoot
// if err := sroot.UnmarshalCBOR(bytes.NewReader(rblk.RawData())); err != nil {
// return err
// }
// TODO: check signature

// ublk, err := tmpbs.Get(sroot.User)
// if err != nil {
// return err
// }
ublk, err := tmpbs.Get(ctx, sroot.User)
if err != nil {
return fmt.Errorf("reading user object under signed root: %w", err)
}

var user types.User
if err := user.UnmarshalCBOR(bytes.NewReader(rblk.RawData())); err != nil {
if err := user.UnmarshalCBOR(bytes.NewReader(ublk.RawData())); err != nil {
return err
}

if !checkUser(user.Name) {
return fmt.Errorf("Ucan does not properly permission user")
if err := s.ensureGraphWalkability(ctx, &user, tmpbs); err != nil {
return fmt.Errorf("checking graph walkability failed: %w", err)
}

fmt.Println("user update: ", user.Name, user.NextPost, user.PostsRoot)

if err := s.ensureGraphWalkability(ctx, &user, tmpbs); err != nil {
return err
if err := authCheck(&user); err != nil {
return fmt.Errorf("auth check failed: %w", err)
}

fmt.Println("user update: ", user.Name, user.NextPost, user.PostsRoot)

if err := Copy(ctx, tmpbs, s.Blockstore); err != nil {
return err
return fmt.Errorf("copy from temp blockstore failed: %w", err)
}

if err := s.updateUserRoot(user.DID, roots[0]); err != nil {
return err
return fmt.Errorf("failed to update user: %w", err)
}

return nil
Expand Down Expand Up @@ -265,76 +266,73 @@ func (s *Server) handleGetUser(c echo.Context) error {
return car.WriteCar(ctx, ds, []cid.Cid{ucid}, c.Response().Writer)
}

// TODO: this is the register method I wrote for working with the CLI tool, the
// interesting thing here is that it constructs the beginning of the user data
// object on behalf of the user, registers that information locally, and sends
// it all back to the user
// We need to decide if we like this approach, or if we instead want to have
// the user just send us their graph with/after registration.
func (s *Server) handleRegisterUserAlt(c echo.Context) error {
ctx := c.Request().Context()
var body userRegisterBody
if err := c.Bind(&body); err != nil {
return err
}

cst := cbor.NewCborStore(s.Blockstore)

u := new(types.User)
//u.DID = body.DID
u.Name = body.Name

rcid, err := s.getEmptyPostsRoot(ctx, cst)
if err != nil {
return fmt.Errorf("failed to get empty posts root: %w", err)
}
u.PostsRoot = rcid

cc, err := cst.Put(ctx, u)
if err != nil {
return fmt.Errorf("failed to write user to blockstore: %w", err)
}

s.updateUserRoot(u.DID, cc)

ds := merkledag.NewDAGService(bserv.New(s.Blockstore, nil))
if err := car.WriteCar(ctx, ds, []cid.Cid{cc}, c.Response().Writer); err != nil {
return fmt.Errorf("failed to write car: %w", err)
}
return nil
}

type userRegisterBody struct {
Name string `json:"name"`
type registerResponse struct {
OK bool
}

func (s *Server) handleRegister(e echo.Context) error {
ctx := e.Request().Context()
encoded := getBearer(e.Request())

var body userRegisterBody
if err := e.Bind(&body); err != nil {
return err
}

// TODO: understand why this DID stuff works the way it does
p := ucan.NewTokenParser(emptyAC, ucan.StringDIDPubKeyResolver{}, s.UcanStore.(ucan.CIDBytesResolver))
token, err := p.ParseAndVerify(ctx, encoded)
if err != nil {
return err
return fmt.Errorf("parsing ucan auth token: %w", err)
}

// 'TwitterDid' here is really just the DID of this server instance
if token.Audience.String() != TwitterDid {
return fmt.Errorf("Ucan not directed to twitter server")
}

// TODO: this needs a lock
if s.UserDids[body.Name] != nil {
return fmt.Errorf("Username already taken")
limr := io.LimitReader(e.Request().Body, 1<<20)

carr, err := car.NewCarReader(limr)
if err != nil {
return fmt.Errorf("reading register body data: %w", err)
}

s.UserDids[body.Name] = &token.Issuer
checkUser := func(u *types.User) error {
/*
att := ucan.Attenuation{
Rsc: newAccountResource("twitter", "dholms"), // ?????
Cap: twitterCaps.Cap("POST"),
}
return nil
isGood := token.Attenuations.Contains(ucan.Attenuations{att})
if !isGood {
return false
}
*/

// TODO: this needs a lock
_, ok := s.UserDids[u.Name]
if ok {
return fmt.Errorf("username already registered")
}
// TODO: register user info in a real database

s.UserDids[u.Name] = &token.Issuer

/*
// ucan issuer does not match user's DID
if token.Issuer.String() != s.UserDids[user].String() {
return false
}
*/

return nil
}

if err := s.updateUser(ctx, carr, checkUser); err != nil {
return err
}

return e.JSON(200, &registerResponse{
OK: true,
})
}

func Copy(ctx context.Context, from, to blockstore.Blockstore) error {
Expand Down

0 comments on commit eb26c55

Please sign in to comment.