From 104df074e9eb2e770bc656a843bae7790d4df5b8 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sat, 12 Oct 2024 12:52:20 -0400 Subject: [PATCH] feat: add User-Agent and Platform to session --- internal/ctx/session.go | 27 +++++-- internal/ctx/state.go | 24 ------ internal/ctx/store.go | 118 ++++++++-------------------- internal/orm/webauthn.go | 10 +-- pkg/nebula/components/home/route.go | 8 +- pkg/workers/handlers/auth.go | 30 ++++--- 6 files changed, 80 insertions(+), 137 deletions(-) diff --git a/internal/ctx/session.go b/internal/ctx/session.go index 227645af6..01178d42a 100644 --- a/internal/ctx/session.go +++ b/internal/ctx/session.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net/http" + "net/url" "time" "github.com/gorilla/sessions" @@ -45,16 +46,28 @@ func SessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc { } } -func defaultSession(id string, s *sessions.Session) *session { - return &session{ - session: s, - id: id, - origin: "", - address: "", - chainID: "", +func buildSession(c echo.Context, id string) *Session { + return &Session{ + ID: id, + Origin: getOrigin(c.Request().Header.Get("Host")), + UserAgent: c.Request().Header.Get("Sec-Ch-Ua"), + Platform: c.Request().Header.Get("Sec-Ch-Ua-Platform"), + Address: c.Request().Header.Get("X-Sonr-Address"), + ChainID: "", } } +func getOrigin(o string) string { + if o == "" { + return "" + } + u, err := url.Parse(o) + if err != nil { + return "" + } + return u.Hostname() +} + func getSessionID(ctx context.Context) (string, error) { sessionID, ok := ctx.Value(ctxKeySessionID{}).(string) if !ok || sessionID == "" { diff --git a/internal/ctx/state.go b/internal/ctx/state.go index b0988ac8c..915b5d4e6 100644 --- a/internal/ctx/state.go +++ b/internal/ctx/state.go @@ -25,27 +25,3 @@ func GetAuthState(c echo.Context) AuthState { s := AuthState(c.Request().Header.Get("Authorization")) return s } - -func readSessionFromStore(c echo.Context, id string) (*session, error) { - sess, err := store.Get(c.Request(), id) - if err != nil { - return nil, err - } - return NewSessionFromValues(sess.Values), nil -} - -func writeSessionToStore( - c echo.Context, - id string, -) error { - sess, err := store.Get(c.Request(), id) - if err != nil { - return err - } - s := defaultSession(id, sess) - err = s.SaveHTTP(c) - if err != nil { - return err - } - return nil -} diff --git a/internal/ctx/store.go b/internal/ctx/store.go index 55c0bf299..2cfd41517 100644 --- a/internal/ctx/store.go +++ b/internal/ctx/store.go @@ -1,120 +1,70 @@ package ctx import ( + "errors" "fmt" "github.com/go-webauthn/webauthn/protocol" - "github.com/gorilla/sessions" "github.com/labstack/echo/v4" ) type WebBytes = protocol.URLEncodedBase64 -type Session interface { - ID() string - Origin() string - - Address() string - ChainID() string - - GetChallenge(subject string) (WebBytes, error) - ValidateChallenge(challenge WebBytes, subject string) error - - SaveHTTP(c echo.Context) error -} - -func NewSessionFromValues(vals map[interface{}]interface{}) *session { - s := &session{ - id: vals["id"].(string), - origin: vals["origin"].(string), - address: vals["address"].(string), - chainID: vals["chainID"].(string), - challenge: vals["challenge"].(WebBytes), - subject: vals["subject"].(string), - } - return s -} - -type session struct { +type Session struct { // Defaults - session *sessions.Session - id string // Generated ksuid http cookie; Initialized on first request - origin string // Webauthn mapping to Relaying Party ID; Initialized on first request + ID string // Generated ksuid http cookie; Initialized on first request + Origin string // Webauthn mapping to Relaying Party ID; Initialized on first request + UserAgent string + Platform string // Initialization - address string // Webauthn mapping to User ID; Supplied by DWN frontend - chainID string // Macaroon mapping to location; Supplied by DWN frontend + Address string // Webauthn mapping to User ID; Supplied by DWN frontend + ChainID string // Macaroon mapping to location; Supplied by DWN frontend + + Subject string // Webauthn mapping to User Displayable Name; Supplied by DWN frontend // Authentication challenge WebBytes // Webauthn mapping to Challenge; Per session based on origin - subject string // Webauthn mapping to User Displayable Name; Supplied by DWN frontend } -func (s *session) ID() string { - return s.id -} +func (s *Session) GetChallenge(subject string) (WebBytes, error) { + // Check if challenge is already set and subject matches + if s.Subject != "" && s.Subject != subject { + return nil, errors.New("challenge already set, and subject does not match") + } else if s.Subject == "" { + s.Subject = subject + } else { + return s.challenge, nil + } -func (s *session) Origin() string { - return s.origin -} - -func (s *session) Address() string { - return s.address -} - -func (s *session) ChainID() string { - return s.chainID -} - -func (s *session) GetChallenge(subject string) (WebBytes, error) { if s.challenge == nil { - return nil, nil + chl, err := protocol.CreateChallenge() + if err != nil { + return nil, err + } + s.challenge = chl } return s.challenge, nil } -func (s *session) ValidateChallenge(challenge WebBytes, subject string) error { +func (s *Session) ValidateChallenge(challenge WebBytes, subject string) error { if s.challenge == nil { return nil } if s.challenge.String() != challenge.String() { return fmt.Errorf("invalid challenge") } - s.subject = subject + s.Subject = subject return nil } -func (s *session) SaveHTTP(c echo.Context) error { - sess, err := store.Get(c.Request(), s.id) - if err != nil { - return err - } - sess.Values = s.Values() - err = sess.Save(c.Request(), c.Response().Writer) - if err != nil { - return err - } - return nil -} - -func (s *session) Values() map[interface{}]interface{} { - vals := make(map[interface{}]interface{}) - vals["id"] = s.id - vals["address"] = s.address - vals["chainID"] = s.chainID - vals["challenge"] = s.challenge - vals["subject"] = s.subject - return vals -} - -func GetSession(c echo.Context) Session { +func GetSession(c echo.Context) *Session { id, _ := getSessionID(c.Request().Context()) - sess, _ := store.Get(c.Request(), id) - if sess.IsNew { - s := defaultSession(id, sess) - s.SaveHTTP(c) - return s - } - s, _ := readSessionFromStore(c, id) - return s + return buildSession(c, id) +} + +func SetAddress(c echo.Context, address string) *Session { + // Write address to X-Sonr-Address header + c.Response().Header().Set("X-Sonr-Address", address) + return buildSession(c, "") } diff --git a/internal/orm/webauthn.go b/internal/orm/webauthn.go index 4927f44b5..a6352a639 100644 --- a/internal/orm/webauthn.go +++ b/internal/orm/webauthn.go @@ -5,13 +5,9 @@ import ( "github.com/go-webauthn/webauthn/protocol/webauthncose" ) -func NewCredentialCreationOptions(subject, address string) (*protocol.PublicKeyCredentialCreationOptions, error) { - chl, err := protocol.CreateChallenge() - if err != nil { - return nil, err - } +func NewCredentialCreationOptions(subject, address string, challenge protocol.URLEncodedBase64) *protocol.PublicKeyCredentialCreationOptions { return &protocol.PublicKeyCredentialCreationOptions{ - Challenge: chl, + Challenge: challenge, User: protocol.UserEntity{ DisplayName: subject, ID: address, @@ -19,7 +15,7 @@ func NewCredentialCreationOptions(subject, address string) (*protocol.PublicKeyC Attestation: defaultAttestation(), AuthenticatorSelection: defaultAuthenticatorSelection(), Parameters: defaultCredentialParameters(), - }, nil + } } func buildUserEntity(userID string) protocol.UserEntity { diff --git a/pkg/nebula/components/home/route.go b/pkg/nebula/components/home/route.go index 30e9604d0..50db28084 100644 --- a/pkg/nebula/components/home/route.go +++ b/pkg/nebula/components/home/route.go @@ -10,9 +10,9 @@ import ( func Route(c echo.Context) error { s := ctx.GetSession(c) - log.Printf("Session ID: %s", s.ID()) - log.Printf("Session Origin: %s", s.Origin()) - log.Printf("Session Address: %s", s.Address()) - log.Printf("Session ChainID: %s", s.ChainID()) + log.Printf("Session ID: %s", s.ID) + log.Printf("Session Origin: %s", s.Origin) + log.Printf("Session Address: %s", s.Address) + log.Printf("Session ChainID: %s", s.ChainID) return ctx.RenderTempl(c, View()) } diff --git a/pkg/workers/handlers/auth.go b/pkg/workers/handlers/auth.go index e12aad555..28752b780 100644 --- a/pkg/workers/handlers/auth.go +++ b/pkg/workers/handlers/auth.go @@ -3,6 +3,9 @@ package handlers import ( "github.com/go-webauthn/webauthn/protocol" "github.com/labstack/echo/v4" + + "github.com/onsonr/sonr/internal/ctx" + "github.com/onsonr/sonr/internal/orm" ) // ╭───────────────────────────────────────────────────────────╮ @@ -34,20 +37,25 @@ func LoginSubjectFinish(e echo.Context) error { // ╰───────────────────────────────────────────────────────────╯ func RegisterSubjectCheck(e echo.Context) error { - credentialID := e.FormValue("credentialID") - return e.JSON(200, credentialID) + subject := e.FormValue("subject") + return e.JSON(200, subject) } func RegisterSubjectStart(e echo.Context) error { - opts := &protocol.PublicKeyCredentialCreationOptions{ - RelyingParty: protocol.RelyingPartyEntity{ - CredentialEntity: protocol.CredentialEntity{ - Name: "Sonr", - }, - ID: "https://sonr.io", - }, + // Get subject and address + subject := e.FormValue("subject") + address := e.FormValue("address") + + // Set address in session + s := ctx.GetSession(e) + s = ctx.SetAddress(e, address) + + // Get challenge + chal, err := s.GetChallenge(subject) + if err != nil { + return err } - return e.JSON(200, opts) + return e.JSON(201, orm.NewCredentialCreationOptions(subject, address, chal)) } func RegisterSubjectFinish(e echo.Context) error { @@ -65,5 +73,5 @@ func RegisterSubjectFinish(e echo.Context) error { // // // Create the Credential // // credential := orm.NewCredential(parsedData, e.Request().Host, "") - return e.JSON(200, ccr) + return e.JSON(201, ccr) }