Skip to content

Commit

Permalink
feat(lib): attach active user to scope
Browse files Browse the repository at this point in the history
  • Loading branch information
b5 committed Mar 4, 2021
1 parent 1c56680 commit 608540a
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 7 deletions.
5 changes: 2 additions & 3 deletions auth/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ type Token = jwt.Token
// Claims is a JWT Claims object
type Claims struct {
*jwt.StandardClaims
// TODO(b5): this needs to be replaced with a profileID
Username string `json:"username"`
ProfileID string `json:"profileID"`
}

// Parse will parse, validate and return a token
Expand Down Expand Up @@ -203,7 +202,7 @@ func (a *pkSource) CreateToken(pro *profile.Profile, ttl time.Duration) (string,
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
ExpiresAt: exp,
},
Username: pro.Peername,
ProfileID: pro.ID.String(),
}

// Creat token string
Expand Down
26 changes: 23 additions & 3 deletions lib/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (
"fmt"
"reflect"
"strings"
"time"

"github.com/qri-io/qri/auth/token"
"github.com/qri-io/qri/profile"
)

// dispatcher isolates the dispatch method
Expand Down Expand Up @@ -35,6 +39,22 @@ func (inst *Instance) Dispatch(ctx context.Context, method string, param interfa
// If the http rpc layer is engaged, use it to dispatch methods
// This happens when another process is running `qri connect`
if inst.http != nil {
if tok := token.FromCtx(ctx); tok == "" {
// If no token exists, create one from configured profile private key &
// add it to the request context
// TODO(b5): we're falling back to the configured user to make requests,
// is this the right default?
p, err := profile.NewProfile(inst.cfg.Profile)
if err != nil {
return nil, err
}
tokstr, err := token.NewPrivKeyAuthToken(p.PrivKey, time.Minute)
if err != nil {
return nil, err
}
ctx = token.AddToContext(ctx, tokstr)
}

if c, ok := inst.regMethods.lookup(method); ok {
// TODO(dustmop): This is always using the "POST" verb currently. We need some
// mechanism of tagging methods as being read-only and "GET"-able. Once that
Expand Down Expand Up @@ -62,9 +82,9 @@ func (inst *Instance) Dispatch(ctx context.Context, method string, param interfa
// or use copy-on-write semantics, so that one method running at the same time as
// another cannot modify the out-of-scope data of the other. This will mostly
// involve making copies of the right things
scope := scope{
ctx: ctx,
inst: inst,
scope, err := newScope(ctx, inst)
if err != nil {
return nil, err
}

// Construct the parameter list for the function call, then call it
Expand Down
6 changes: 5 additions & 1 deletion lib/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
apiutil "github.com/qri-io/qri/api/util"
"github.com/qri-io/qri/auth/token"
)

const jsonMimeType = "application/json"
Expand Down Expand Up @@ -72,7 +73,6 @@ func (c HTTPClient) CallMethod(ctx context.Context, apiEndpoint APIEndpoint, htt
// TODO(arqu): work out mimeType configuration/override per API endpoint
mimeType := jsonMimeType
addr := fmt.Sprintf("%s://%s%s", c.Protocol, c.Address, apiEndpoint)
// TODO(arqu): inject context values into headers

return c.do(ctx, addr, httpMethod, mimeType, params, result, false)
}
Expand Down Expand Up @@ -112,6 +112,10 @@ func (c HTTPClient) do(ctx context.Context, addr string, httpMethod string, mime
req.Header.Set("Content-Type", mimeType)
req.Header.Set("Accept", mimeType)

if s := token.FromCtx(ctx); s != "" {
req.Header.Set("authorization", fmt.Sprintf("Bearer %s", s))
}

res, err := http.DefaultClient.Do(req)
if err != nil {
return err
Expand Down
Binary file added lib/logbook.qfb
Binary file not shown.
38 changes: 38 additions & 0 deletions lib/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/qri-io/dataset"
"github.com/qri-io/qfs/muxfs"
"github.com/qri-io/qri/auth/token"
"github.com/qri-io/qri/dscache"
"github.com/qri-io/qri/dsref"
"github.com/qri-io/qri/event"
Expand All @@ -21,9 +22,46 @@ import (
type scope struct {
ctx context.Context
inst *Instance
pro *profile.Profile
// TODO(dustmop): Additional information, such as user identity, their profile, keys
}

func newScope(ctx context.Context, inst *Instance) (scope, error) {
var pro *profile.Profile
// TODO(b5): shouldn't need this nil checking
if inst != nil && inst.profiles != nil {
pro = inst.profiles.Owner()
if tokenString := token.FromCtx(ctx); tokenString != "" {
tok, err := token.ParseAuthToken(tokenString, inst.keystore)
if err != nil {
return scope{}, err
}

if claims, ok := tok.Claims.(*token.Claims); ok {
// TODO(b5): at this point we have a valid signature of a profileID string
// but no proof that this profile is owned by the key that signed the
// token. We either need ProfileID == KeyID, or we need a UCAN. we need to
// check for those, ideally in a method within the profile package that
// abstracts over profile & key agreement
pro, err = inst.profiles.GetProfile(profile.IDB58DecodeOrEmpty(claims.ProfileID))
if err != nil {
return scope{}, err
}
}
}
}

return scope{
ctx: ctx,
inst: inst,
pro: pro,
}, nil
}

func (s *scope) ActiveProfile() *profile.Profile {
return s.pro
}

// Context returns the context for this scope. Though this pattern is usually discouraged,
// we're following http.Request's lead, as scope plays the same role. The lifetime of a
// single scope matches the lifetime of the Context; this ownership is not long-lived
Expand Down

0 comments on commit 608540a

Please sign in to comment.