mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 02:11:40 +00:00
feature/data persistence (#1180)
- **feat: add documentation and GitHub Actions workflow for publishing documentation** - **docs(concepts): add documentation for chain modules** - **refactor: Simplify session management with SQLite storage and remove deprecated code** - **refactor: Simplify database initialization and remove DatabaseContext** - **refactor: move connection handling logic to resolver package** - **feat: implement session management with database persistence** - **feat: Ensure config directory exists when creating database path** - **feat: Add SetUserHandle function to set user handle in session** - **feat: Add public methods to set session fields with database save** - **refactor: Remove unused session setter functions** - **feat: Add getter methods for all Session Model properties** - **feat: enhance Session model with user name details** - **feat: add Motr support and update UI elements** - **<no value>** - **feat: Add unique handle constraint and method to check handle existence** - **docs: update site URL to onsonr.dev** - **fix: correct import statement for database package** - **test: updated CI to run tests on pull requests and merge groups** - **docs: remove reference to develop branch in workflow** - **feat: add WebAuthn support for user registration** - **fix: correct smart account attenuation preset name** - **feat: add ComputeIssuerDID and ComputeSonrAddr functions to ucan package** - **test: add unit tests for MPC keyset and keyshare** - **feat: introduce new script to streamline GitHub issue creation**
This commit is contained in:
@@ -1,184 +1,168 @@
|
||||
package session
|
||||
|
||||
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"
|
||||
"github.com/onsonr/sonr/pkg/gateway/internal/database"
|
||||
)
|
||||
|
||||
const kWebAuthnTimeout = 6000
|
||||
// ╭───────────────────────────────────────────────────────╮
|
||||
// │ DB Setter Functions │
|
||||
// ╰───────────────────────────────────────────────────────╯
|
||||
|
||||
// HTTPContext is the context for HTTP endpoints.
|
||||
type HTTPContext struct {
|
||||
echo.Context
|
||||
role common.PeerRole
|
||||
id string
|
||||
chal string
|
||||
bn string
|
||||
bv string
|
||||
// SetUserHandle sets the user handle in the session
|
||||
func SetUserHandle(c echo.Context, handle string) error {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Session().UserHandle = handle
|
||||
return sess.db.Save(sess.Session()).Error
|
||||
}
|
||||
|
||||
// initHTTPContext loads the headers from the request.
|
||||
func initHTTPContext(c echo.Context) *HTTPContext {
|
||||
if c == nil {
|
||||
return &HTTPContext{}
|
||||
// SetFirstName sets the first name in the session
|
||||
func SetFirstName(c echo.Context, name string) error {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Session().FirstName = name
|
||||
return sess.db.Save(sess.Session()).Error
|
||||
}
|
||||
|
||||
// SetLastInitial sets the last initial in the session
|
||||
func SetLastInitial(c echo.Context, initial string) error {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Session().LastInitial = initial
|
||||
return sess.db.Save(sess.Session()).Error
|
||||
}
|
||||
|
||||
// SetVaultAddress sets the vault address in the session
|
||||
func SetVaultAddress(c echo.Context, address string) error {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Session().VaultAddress = address
|
||||
return sess.db.Save(sess.Session()).Error
|
||||
}
|
||||
|
||||
// ╭───────────────────────────────────────────────────────╮
|
||||
// │ DB Getter Functions │
|
||||
// ╰───────────────────────────────────────────────────────╯
|
||||
|
||||
// GetID returns the session ID
|
||||
func GetID(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().ID, nil
|
||||
}
|
||||
|
||||
// GetBrowserName returns the browser name
|
||||
func GetBrowserName(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().BrowserName, nil
|
||||
}
|
||||
|
||||
// GetBrowserVersion returns the browser version
|
||||
func GetBrowserVersion(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().BrowserVersion, nil
|
||||
}
|
||||
|
||||
// GetUserArchitecture returns the user architecture
|
||||
func GetUserArchitecture(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().UserArchitecture, nil
|
||||
}
|
||||
|
||||
// GetPlatform returns the platform
|
||||
func GetPlatform(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().Platform, nil
|
||||
}
|
||||
|
||||
// GetPlatformVersion returns the platform version
|
||||
func GetPlatformVersion(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().PlatformVersion, nil
|
||||
}
|
||||
|
||||
// GetDeviceModel returns the device model
|
||||
func GetDeviceModel(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().DeviceModel, nil
|
||||
}
|
||||
|
||||
// GetUserHandle returns the user handle
|
||||
func GetUserHandle(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().UserHandle, nil
|
||||
}
|
||||
|
||||
// GetFirstName returns the first name
|
||||
func GetFirstName(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().FirstName, nil
|
||||
}
|
||||
|
||||
// GetLastInitial returns the last initial
|
||||
func GetLastInitial(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().LastInitial, nil
|
||||
}
|
||||
|
||||
// GetVaultAddress returns the vault address
|
||||
func GetVaultAddress(c echo.Context) (string, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sess.Session().VaultAddress, nil
|
||||
}
|
||||
|
||||
// HandleExists checks if a handle already exists in any session
|
||||
func HandleExists(c echo.Context, handle string) (bool, error) {
|
||||
sess, err := Get(c)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
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,
|
||||
var count int64
|
||||
if err := sess.db.Model(&database.Session{}).Where("user_handle = ?", handle).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ 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
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user