feat: add User-Agent and Platform to session

This commit is contained in:
Prad Nukala 2024-10-12 12:52:20 -04:00
parent 58aa71997d
commit 104df074e9
6 changed files with 80 additions and 137 deletions

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -45,16 +46,28 @@ func SessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
} }
} }
func defaultSession(id string, s *sessions.Session) *session { func buildSession(c echo.Context, id string) *Session {
return &session{ return &Session{
session: s, ID: id,
id: id, Origin: getOrigin(c.Request().Header.Get("Host")),
origin: "", UserAgent: c.Request().Header.Get("Sec-Ch-Ua"),
address: "", Platform: c.Request().Header.Get("Sec-Ch-Ua-Platform"),
chainID: "", 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) { func getSessionID(ctx context.Context) (string, error) {
sessionID, ok := ctx.Value(ctxKeySessionID{}).(string) sessionID, ok := ctx.Value(ctxKeySessionID{}).(string)
if !ok || sessionID == "" { if !ok || sessionID == "" {

View File

@ -25,27 +25,3 @@ func GetAuthState(c echo.Context) AuthState {
s := AuthState(c.Request().Header.Get("Authorization")) s := AuthState(c.Request().Header.Get("Authorization"))
return s 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
}

View File

@ -1,120 +1,70 @@
package ctx package ctx
import ( import (
"errors"
"fmt" "fmt"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/gorilla/sessions"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
type WebBytes = protocol.URLEncodedBase64 type WebBytes = protocol.URLEncodedBase64
type Session interface { type Session struct {
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 {
// Defaults // Defaults
session *sessions.Session ID string // Generated ksuid http cookie; 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
origin string // Webauthn mapping to Relaying Party ID; Initialized on first request UserAgent string
Platform string
// Initialization // Initialization
address string // Webauthn mapping to User ID; 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 ChainID string // Macaroon mapping to location; Supplied by DWN frontend
Subject string // Webauthn mapping to User Displayable Name; Supplied by DWN frontend
// Authentication // Authentication
challenge WebBytes // Webauthn mapping to Challenge; Per session based on origin 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 { func (s *Session) GetChallenge(subject string) (WebBytes, error) {
return s.id // 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 { if s.challenge == nil {
return nil, nil chl, err := protocol.CreateChallenge()
if err != nil {
return nil, err
}
s.challenge = chl
} }
return s.challenge, nil 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 { if s.challenge == nil {
return nil return nil
} }
if s.challenge.String() != challenge.String() { if s.challenge.String() != challenge.String() {
return fmt.Errorf("invalid challenge") return fmt.Errorf("invalid challenge")
} }
s.subject = subject s.Subject = subject
return nil return nil
} }
func (s *session) SaveHTTP(c echo.Context) error { func GetSession(c echo.Context) *Session {
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 {
id, _ := getSessionID(c.Request().Context()) id, _ := getSessionID(c.Request().Context())
sess, _ := store.Get(c.Request(), id) return buildSession(c, id)
if sess.IsNew { }
s := defaultSession(id, sess)
s.SaveHTTP(c) func SetAddress(c echo.Context, address string) *Session {
return s // Write address to X-Sonr-Address header
} c.Response().Header().Set("X-Sonr-Address", address)
s, _ := readSessionFromStore(c, id) return buildSession(c, "")
return s
} }

View File

@ -5,13 +5,9 @@ import (
"github.com/go-webauthn/webauthn/protocol/webauthncose" "github.com/go-webauthn/webauthn/protocol/webauthncose"
) )
func NewCredentialCreationOptions(subject, address string) (*protocol.PublicKeyCredentialCreationOptions, error) { func NewCredentialCreationOptions(subject, address string, challenge protocol.URLEncodedBase64) *protocol.PublicKeyCredentialCreationOptions {
chl, err := protocol.CreateChallenge()
if err != nil {
return nil, err
}
return &protocol.PublicKeyCredentialCreationOptions{ return &protocol.PublicKeyCredentialCreationOptions{
Challenge: chl, Challenge: challenge,
User: protocol.UserEntity{ User: protocol.UserEntity{
DisplayName: subject, DisplayName: subject,
ID: address, ID: address,
@ -19,7 +15,7 @@ func NewCredentialCreationOptions(subject, address string) (*protocol.PublicKeyC
Attestation: defaultAttestation(), Attestation: defaultAttestation(),
AuthenticatorSelection: defaultAuthenticatorSelection(), AuthenticatorSelection: defaultAuthenticatorSelection(),
Parameters: defaultCredentialParameters(), Parameters: defaultCredentialParameters(),
}, nil }
} }
func buildUserEntity(userID string) protocol.UserEntity { func buildUserEntity(userID string) protocol.UserEntity {

View File

@ -10,9 +10,9 @@ import (
func Route(c echo.Context) error { func Route(c echo.Context) error {
s := ctx.GetSession(c) s := ctx.GetSession(c)
log.Printf("Session ID: %s", s.ID()) log.Printf("Session ID: %s", s.ID)
log.Printf("Session Origin: %s", s.Origin()) log.Printf("Session Origin: %s", s.Origin)
log.Printf("Session Address: %s", s.Address()) log.Printf("Session Address: %s", s.Address)
log.Printf("Session ChainID: %s", s.ChainID()) log.Printf("Session ChainID: %s", s.ChainID)
return ctx.RenderTempl(c, View()) return ctx.RenderTempl(c, View())
} }

View File

@ -3,6 +3,9 @@ package handlers
import ( import (
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/labstack/echo/v4" "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 { func RegisterSubjectCheck(e echo.Context) error {
credentialID := e.FormValue("credentialID") subject := e.FormValue("subject")
return e.JSON(200, credentialID) return e.JSON(200, subject)
} }
func RegisterSubjectStart(e echo.Context) error { func RegisterSubjectStart(e echo.Context) error {
opts := &protocol.PublicKeyCredentialCreationOptions{ // Get subject and address
RelyingParty: protocol.RelyingPartyEntity{ subject := e.FormValue("subject")
CredentialEntity: protocol.CredentialEntity{ address := e.FormValue("address")
Name: "Sonr",
}, // Set address in session
ID: "https://sonr.io", 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 { func RegisterSubjectFinish(e echo.Context) error {
@ -65,5 +73,5 @@ func RegisterSubjectFinish(e echo.Context) error {
// //
// // Create the Credential // // Create the Credential
// // credential := orm.NewCredential(parsedData, e.Request().Host, "") // // credential := orm.NewCredential(parsedData, e.Request().Host, "")
return e.JSON(200, ccr) return e.JSON(201, ccr)
} }