refactor: move session management to dedicated database module

This commit is contained in:
Prad Nukala
2024-12-10 13:40:41 -05:00
parent 518109e9df
commit c67a7823a6
16 changed files with 39 additions and 57 deletions
+156
View File
@@ -0,0 +1,156 @@
package context
import (
"regexp"
"strings"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4"
"github.com/segmentio/ksuid"
"github.com/onsonr/sonr/pkg/common"
)
const kWebAuthnTimeout = 6000
// TODO: Returns fixed chain ID for testing.
func GetChainID(c echo.Context) string {
return "sonr-testnet-1"
}
// SetVaultAddress sets the address of the vault
func SetVaultAddress(c echo.Context, address string) error {
return common.WriteCookie(c, common.SonrAddress, address)
}
// SetVaultAuthorization sets the UCAN CID of the vault
func SetVaultAuthorization(c echo.Context, ucanCID string) error {
common.HeaderWrite(c, common.Authorization, formatAuth(ucanCID))
return nil
}
// ╭───────────────────────────────────────────────────────────╮
// │ Initialization │
// ╰───────────────────────────────────────────────────────────╯
func loadOrGenKsuid(c echo.Context) error {
var (
sessionID string
err error
)
// Setup genKsuid function
genKsuid := func() string {
return ksuid.New().String()
}
// Attempt to read the session ID from the "session" cookie
if ok := common.CookieExists(c, common.SessionID); !ok {
sessionID = genKsuid()
} else {
sessionID, err = common.ReadCookie(c, common.SessionID)
if err != nil {
sessionID = genKsuid()
}
}
common.WriteCookie(c, common.SessionID, sessionID)
return nil
}
// ╭───────────────────────────────────────────────────────────╮
// │ Extraction │
// ╰───────────────────────────────────────────────────────────╯
func extractPeerInfo(c echo.Context) (string, string) {
var chal protocol.URLEncodedBase64
id, _ := common.ReadCookie(c, common.SessionID)
chalRaw, _ := common.ReadCookieBytes(c, common.SessionChallenge)
chal.UnmarshalJSON(chalRaw)
return id, common.Base64Encode(chal)
}
func extractBrowserInfo(c echo.Context) (string, string) {
secCHUA := common.HeaderRead(c, common.UserAgent)
// If common.is empty, return empty BrowserInfo
if secCHUA == "" {
return "N/A", "-1"
}
// Split the common.into individual browser entries
var (
name string
ver string
)
entries := strings.Split(strings.TrimSpace(secCHUA), ",")
for _, entry := range entries {
// Remove leading/trailing spaces and quotes
entry = strings.TrimSpace(entry)
// Use regex to extract the browser name and version
re := regexp.MustCompile(`"([^"]+)";v="([^"]+)"`)
matches := re.FindStringSubmatch(entry)
if len(matches) == 3 {
browserName := matches[1]
version := matches[2]
// Skip "Not A;Brand"
if !validBrowser(browserName) {
continue
}
// Store the first valid browser info as fallback
name = browserName
ver = version
}
}
return name, ver
}
func validBrowser(name string) bool {
return name != common.BrowserNameUnknown.String() && name != common.BrowserNameChromium.String()
}
// ╭───────────────────────────────────────────────────────────╮
// │ Authentication │
// ╰───────────────────────────────────────────────────────────╯
func buildUserEntity(userID string) protocol.UserEntity {
return protocol.UserEntity{
ID: userID,
}
}
// returns the base options for registering a new user without challenge or user entity.
func baseRegisterOptions() *protocol.PublicKeyCredentialCreationOptions {
return &protocol.PublicKeyCredentialCreationOptions{
Timeout: kWebAuthnTimeout,
Attestation: protocol.PreferDirectAttestation,
AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: "platform",
ResidentKey: protocol.ResidentKeyRequirementPreferred,
UserVerification: "preferred",
},
Parameters: []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
},
}
}
func formatAuth(ucanCID string) string {
return "Bearer " + ucanCID
}
+90
View File
@@ -0,0 +1,90 @@
package context
import (
"encoding/json"
"net/http"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/internal/vault/types"
"github.com/onsonr/sonr/pkg/common"
)
type SessionCtx interface {
ID() string
BrowserName() string
BrowserVersion() string
}
type contextKey string
// Context keys
const (
DataContextKey contextKey = "http_session_data"
)
// Get returns the session.Context from the echo context.
func Get(c echo.Context) (SessionCtx, error) {
ctx, ok := c.(*HTTPContext)
if !ok {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found")
}
return ctx, nil
}
// WebNodeMiddleware establishes a Session Cookie.
func Middleware(config *types.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
err := injectConfig(c, config)
if err != nil {
return err
}
cc := injectSession(c, common.RoleMotr)
return next(cc)
}
}
}
func injectConfig(c echo.Context, config *types.Config) error {
common.HeaderWrite(c, common.SonrAPIURL, config.SonrApiUrl)
common.HeaderWrite(c, common.SonrRPCURL, config.SonrRpcUrl)
common.WriteCookie(c, common.SonrAddress, config.MotrAddress)
schemaBz, err := json.Marshal(config.VaultSchema)
if err != nil {
return err
}
common.WriteCookieBytes(c, common.VaultSchema, schemaBz)
return nil
}
// injectSession returns the session injectSession from the cookies.
func injectSession(c echo.Context, role common.PeerRole) *HTTPContext {
if c == nil {
return initHTTPContext(nil)
}
common.WriteCookie(c, common.SessionRole, role.String())
// Continue even if there are errors, just ensure we have valid session data
if err := loadOrGenKsuid(c); err != nil {
// Log error but continue
}
return initHTTPContext(c)
}
// HasAuthorization checks if the request has an authorization header
func HasAuthorization(c echo.Context) bool {
return common.HeaderExists(c, common.Authorization)
}
// HasUserHandle checks if the request has a user handle cookie
func HasUserHandle(c echo.Context) bool {
return common.CookieExists(c, common.UserHandle)
}
// HasVaultAddress checks if the request has a vault address cookie
func HasVaultAddress(c echo.Context) bool {
return common.CookieExists(c, common.SonrAddress)
}
+51
View File
@@ -0,0 +1,51 @@
package context
import (
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
)
// HTTPContext is the context for HTTP endpoints.
type HTTPContext struct {
echo.Context
role common.PeerRole
id string
chal string
bn string
bv string
}
// initHTTPContext loads the headers from the request.
func initHTTPContext(c echo.Context) *HTTPContext {
if c == nil {
return &HTTPContext{}
}
id, chal := extractPeerInfo(c)
bn, bv := extractBrowserInfo(c)
cc := &HTTPContext{
Context: c,
role: common.PeerRole(common.ReadCookieUnsafe(c, common.SessionRole)),
id: id,
chal: chal,
bn: bn,
bv: bv,
}
// Set the session data in both contexts
return cc
}
func (s *HTTPContext) ID() string {
return s.id
}
func (s *HTTPContext) BrowserName() string {
return s.bn
}
func (s *HTTPContext) BrowserVersion() string {
return s.bv
}