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"
"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 == "" {

View File

@ -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
}

View File

@ -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
return buildSession(c, id)
}
s, _ := readSessionFromStore(c, id)
return s
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, "")
}

View File

@ -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 {

View File

@ -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())
}

View File

@ -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)
}