Skip to content

Commit

Permalink
refactor: Add auth token handling in SonrContext & wallet package
Browse files Browse the repository at this point in the history
  • Loading branch information
prnk28 committed Jun 3, 2024
1 parent 934fc59 commit c0d82b0
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 106 deletions.
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ import (
oracletypes "github.com/di-dao/sonr/x/oracle/types"
)

const appName = "core"
const appName = "sonr"

var (
NodeDir = ".sonr"
Expand Down
27 changes: 27 additions & 0 deletions internal/local/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package local

import (
"time"

"github.com/bool64/cache"
)

var (
baseSessionCache *cache.FailoverOf[session]
authorizedSessionCache *cache.FailoverOf[authorizedSession]
)

// setupCache configures cache and inital settings for proxy.
func setupCache() {
// Setup cache for session.
baseSessionCache = cache.NewFailoverOf(func(cfg *cache.FailoverConfigOf[session]) {
// Using last 30 seconds of 5m TTL for background update.
cfg.MaxStaleness = 1 * time.Hour
cfg.BackendConfig.TimeToLive = 2*time.Hour - cfg.MaxStaleness
})
authorizedSessionCache = cache.NewFailoverOf(func(cfg *cache.FailoverConfigOf[authorizedSession]) {
// Using last 30 seconds of 5m TTL for background update.
cfg.MaxStaleness = 30 * time.Minute
cfg.BackendConfig.TimeToLive = 1*time.Hour - cfg.MaxStaleness
})
}
64 changes: 46 additions & 18 deletions internal/local/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/segmentio/ksuid"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
Expand All @@ -12,16 +13,22 @@ import (
// Default Key in gRPC Metadata for the Session ID
const kMetadataSessionIDKey = "sonr-session-id"

// Default Key in gRPC Metadata for the Session Authentication JWT Token
const kMetadataAuthTokenKey = "sonr-auth-token"

var (
chainID = "testnet"
valAddr = "val1"
)

// SonrContext is the context for the Sonr API
type SonrContext struct {
Context context.Context
SessionID string
ValidatorAddress string
ChainID string
Token string
SDKContext sdk.Context
}

// SetLocalContextSessionID sets the session ID for the local context
Expand All @@ -36,39 +43,60 @@ func SetContextChainID(id string) {

// UnwrapContext uses context.Context to retreive grpc.Metadata
func UnwrapContext(ctx context.Context) SonrContext {
sessionID, err := firstValueForKey(ctx, kMetadataSessionIDKey)
if err != nil {
return WrapContext(ctx)
}
return SonrContext{
SessionID: sessionID,
sctx := SonrContext{
SDKContext: sdk.UnwrapSDKContext(ctx),
Context: ctx,
ValidatorAddress: valAddr,
ChainID: chainID,
}
sctx.SessionID = findOrSetSessionID(ctx)
if token, err := fetchSessionAuthToken(ctx); err == nil {
sctx.Token = token
}
return sctx
}

// WrapContext wraps a context with a session ID
func WrapContext(ctx context.Context) SonrContext {
sessionId := ksuid.New().String()
// create a header that the gateway will watch for
header := metadata.Pairs(kMetadataSessionIDKey, sessionId)
// send the header back to the gateway
grpc.SendHeader(ctx, header)
return SonrContext{
SessionID: sessionId,
ValidatorAddress: valAddr,
ChainID: chainID,
func WrapContext(ctx SonrContext) context.Context {
refreshGrpcHeaders(ctx)
return ctx.Context
}

// findOrSetSessionID finds the session ID in the metadata or sets a new one
func findOrSetSessionID(ctx context.Context) string {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return ksuid.New().String()
}
vals := md.Get(kMetadataSessionIDKey)
if len(vals) == 0 {
return ksuid.New().String()
}
return vals[0]
}

func firstValueForKey(ctx context.Context, key string) (string, error) {
// fetchSessionAuthToken fetches the auth token from the context
func fetchSessionAuthToken(ctx context.Context) (string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", errors.New("no metadata found")
}
vals := md.Get(key)
vals := md.Get(kMetadataAuthTokenKey)
if len(vals) == 0 {
return "", errors.New("no values found")
}
return vals[0], nil
}

// refreshGrpcHeaders refreshes the grpc headers for the Context
func refreshGrpcHeaders(ctx SonrContext) {
// function to send a header to the gateway
sendHeader := func(key, value string) error {
header := metadata.Pairs(key, value)
return grpc.SendHeader(ctx.Context, header)
}
sendHeader(kMetadataSessionIDKey, ctx.SessionID)
if ctx.Token != "" {
sendHeader(kMetadataAuthTokenKey, ctx.Token)
}
}
2 changes: 1 addition & 1 deletion internal/local/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func keysetFile() string {
return path.Join(defaultNodeHome, "daead_keyset.json")
}

func init() {
func setupKeyHandle() {
if _, err := os.Stat(keysetFile()); os.IsNotExist(err) {
// If the keyset file doesn't exist, generate a new key handle
kh, _ = NewKeyHandle()
Expand Down
6 changes: 6 additions & 0 deletions internal/local/local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package local

func Initialize() {
setupCache()
setupKeyHandle()
}
135 changes: 135 additions & 0 deletions internal/local/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package local

import (
"errors"
"time"

"github.com/go-webauthn/webauthn/protocol"
)

// Session is the reference to the clients current session over gRPC/HTTP in the local cache.
type Session interface {
// GetAddress returns the currently authenticated Users Sonr Address for the Session.
GetAddress() (string, error)

// GetChallenge returns the existing challenge or a new challenge to use for validation
GetChallenge() []byte

// IsAuthorized returns true if the Session has an attached JWT Token
IsAuthorized() bool

// SessionID returns the ksuid for the current session
SessionID() string
}

// session is a proxy session.
type session struct {
// ID is the ksuid of the Session
ID string `json:"id"`

// Validator is the address of the associated validator node address for the session.
Validator string `json:"validator"`

// ChainID is the current sonr blockchain network chain ID for the session.
ChainID string `json:"chain_id"`

// Challenge is used for authenticating credentials for the Session
Challenge []byte `json:"challenge"`
}

// GetAddress returns the session address
func (s session) GetAddress() (string, error) {
return "", errors.New("session does not have attached address")
}

// GetValidator returns the associated validator address
func (s session) GetValidator() (string, error) {
if s.Validator == "" {
return "", errors.New("session does not have attached address")
}
return s.Validator, nil
}

// GetChallenge returns the URL Encoded byte challenge
func (s session) GetChallenge() []byte {
if s.Challenge == nil {
bz, err := protocol.CreateChallenge()
if err != nil {
panic(err)
}
s.Challenge = bz
}
return s.Challenge
}

// IsAuthorized returns true or false for if it is authorized
func (s session) IsAuthorized() bool {
return false
}

// SessionID returns the string ksuid for the session
func (s session) SessionID() string {
return s.ID
}

// session is a proxy session.
type authorizedSession struct {
// ID is the ksuid of the Session
ID string `json:"id"`

// Address is the address of the session.
Address string `json:"address"`

// Validator is the address of the associated validator node address for the session.
Validator string `json:"validator"`

// ChainID is the current sonr blockchain network chain ID for the session.
ChainID string `json:"chain_id"`

// Token is the token of the session.
Token string `json:"token"`

// Expires is the expiration time of the session.
Expires time.Time `json:"expires"`

// Challenge is used for authenticating credentials for the Session
Challenge []byte `json:"challenge"`
}

// GetAddress returns the session address
func (s authorizedSession) GetAddress() (string, error) {
if s.Address == "" {
return "", errors.New("session does not have attached address")
}
return s.Address, nil
}

// GetValidator returns the associated validator address
func (s authorizedSession) GetValidator() (string, error) {
if s.Address == "" {
return "", errors.New("session does not have attached address")
}
return s.Address, nil
}

// GetChallenge returns the URL Encoded byte challenge
func (s authorizedSession) GetChallenge() []byte {
if s.Challenge == nil {
bz, err := protocol.CreateChallenge()
if err != nil {
panic(err)
}
s.Challenge = bz
}
return s.Challenge
}

// IsAuthorized returns true or false for if it is authorized
func (s authorizedSession) IsAuthorized() bool {
return true
}

// SessionID returns the string ksuid for the session
func (s authorizedSession) SessionID() string {
return s.ID
}
File renamed without changes.
43 changes: 0 additions & 43 deletions internal/session/context.go

This file was deleted.

6 changes: 3 additions & 3 deletions internal/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ func Initialize() {

// Get returns a session from cache given a key.
func Get(ctx context.Context) (Session, error) {
id := unwrapFromContext(ctx)
snrCtx := local.UnwrapContext(ctx)

return baseSessionCache.Get(
context.Background(),
[]byte(snrCtx.SessionID),
func(ctx context.Context) (session, error) {
// Build value or return error on failure.
return session{
ID: id,
ID: snrCtx.SessionID,
Validator: snrCtx.ValidatorAddress,
ChainID: snrCtx.ChainID,
}, nil
},
)
Expand Down
22 changes: 4 additions & 18 deletions pkg/auth/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,10 @@ import "github.com/go-webauthn/webauthn/protocol"

// Authenticator contains all needed information about an authenticator for storage.
type Authenticator struct {
// The AAGUID of the authenticator. An AAGUID is defined as an array containing the globally unique
// identifier of the authenticator model being sought.
AAGUID []byte `json:"AAGUID"`

// SignCount -Upon a new login operation, the Relying Party compares the stored signature counter value
// with the new signCount value returned in the assertion’s authenticator data. If this new
// signCount value is less than or equal to the stored value, a cloned authenticator may
// exist, or the authenticator may be malfunctioning.
SignCount uint32 `json:"signCount"`

// CloneWarning - This is a signal that the authenticator may be cloned, i.e. at least two copies of the
// credential private key may exist and are being used in parallel. Relying Parties should incorporate
// this information into their risk scoring. Whether the Relying Party updates the stored signature
// counter value in this case, or not, or fails the authentication ceremony or not, is Relying Party-specific.
CloneWarning bool `json:"cloneWarning"`

// Attachment is the authenticatorAttachment value returned by the request.
Attachment protocol.AuthenticatorAttachment `json:"attachment"`
Attachment protocol.AuthenticatorAttachment `json:"attachment"`
AAGUID []byte `json:"AAGUID"`
SignCount uint32 `json:"signCount"`
CloneWarning bool `json:"cloneWarning"`
}

// SelectAuthenticator allow for easy marshaling of authenticator options that are provided to the user.
Expand Down
Loading

0 comments on commit c0d82b0

Please sign in to comment.