mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 18:01:39 +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:
@@ -25,24 +25,30 @@ func HandleIndex(c echo.Context) error {
|
||||
|
||||
// Initial users have no authorization, user handle, or vault address
|
||||
func isInitial(c echo.Context) bool {
|
||||
noAuth := !session.HasAuthorization(c)
|
||||
noUserHandle := !session.HasUserHandle(c)
|
||||
noVaultAddress := !session.HasVaultAddress(c)
|
||||
return noUserHandle && noVaultAddress && noAuth
|
||||
sess, err := session.Get(c)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
data := sess.Session()
|
||||
return data.UserHandle == "" && data.VaultAddress == ""
|
||||
}
|
||||
|
||||
// Expired users have either a user handle or vault address
|
||||
func isExpired(c echo.Context) bool {
|
||||
noAuth := !session.HasAuthorization(c)
|
||||
hasUserHandle := session.HasUserHandle(c)
|
||||
hasVaultAddress := session.HasVaultAddress(c)
|
||||
return noAuth && hasUserHandle || noAuth && hasVaultAddress
|
||||
sess, err := session.Get(c)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
data := sess.Session()
|
||||
return data.UserHandle != "" || data.VaultAddress != ""
|
||||
}
|
||||
|
||||
// Returning users have a valid authorization, and either a user handle or vault address
|
||||
func isReturning(c echo.Context) bool {
|
||||
hasAuth := session.HasAuthorization(c)
|
||||
hasUserHandle := session.HasUserHandle(c)
|
||||
hasVaultAddress := session.HasVaultAddress(c)
|
||||
return hasAuth && (hasUserHandle || hasVaultAddress)
|
||||
sess, err := session.Get(c)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
data := sess.Session()
|
||||
return data.UserHandle != "" && data.VaultAddress != ""
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package handlers
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/btcutil/bech32"
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/protocol/webauthncose"
|
||||
"github.com/labstack/echo/v4"
|
||||
@@ -22,23 +21,14 @@ func HandleRegisterView(env config.Env) echo.HandlerFunc {
|
||||
}
|
||||
|
||||
func HandleRegisterStart(c echo.Context) error {
|
||||
firstName := c.FormValue("first_name")
|
||||
lastName := c.FormValue("last_name")
|
||||
handle := c.FormValue("handle")
|
||||
|
||||
if firstName == "" || lastName == "" || handle == "" {
|
||||
return response.RedirectLanding(c)
|
||||
}
|
||||
|
||||
ks, err := mpc.NewKeyset()
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
adr, err := bech32.Encode("idx", ks.Val().GetPublicKey())
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
req := getLinkCredentialRequest(c, adr, handle, ks.UserJSON())
|
||||
|
||||
req := getLinkCredentialRequest(c, ks.Address(), handle, ks.UserJSON())
|
||||
return response.TemplEcho(c, register.LinkCredentialView(req))
|
||||
}
|
||||
|
||||
@@ -60,14 +50,15 @@ func getLinkCredentialRequest(c echo.Context, addr string, handle string, userKS
|
||||
RegisterOptions: buildRegisterOptions(buildUserEntity(addr, handle), buildLargeBlob(userKSJSON), buildServiceEntity(c)),
|
||||
}
|
||||
}
|
||||
data := cc.Session()
|
||||
usr := buildUserEntity(addr, handle)
|
||||
blob := buildLargeBlob(userKSJSON)
|
||||
service := buildServiceEntity(c)
|
||||
|
||||
return register.LinkCredentialRequest{
|
||||
Platform: cc.BrowserName(),
|
||||
Handle: handle,
|
||||
DeviceModel: cc.BrowserVersion(),
|
||||
Platform: data.BrowserName,
|
||||
Handle: data.UserHandle,
|
||||
DeviceModel: data.BrowserVersion,
|
||||
Address: addr,
|
||||
RegisterOptions: buildRegisterOptions(usr, blob, service),
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidCredentials = echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
|
||||
ErrInvalidSubject = echo.NewHTTPError(http.StatusBadRequest, "Invalid subject")
|
||||
ErrInvalidUser = echo.NewHTTPError(http.StatusBadRequest, "Invalid user")
|
||||
|
||||
ErrUserAlreadyExists = echo.NewHTTPError(http.StatusConflict, "User already exists")
|
||||
ErrUserNotFound = echo.NewHTTPError(http.StatusNotFound, "User not found")
|
||||
)
|
||||
@@ -1,57 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/pkg/gateway/config"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DatabaseContext struct {
|
||||
echo.Context
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func Middleware(env config.Env) echo.MiddlewareFunc {
|
||||
cc := initDB(env.GetSqliteFile())
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc.Context = c
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DatabaseContext) HasDB() bool {
|
||||
return c.db != nil
|
||||
}
|
||||
|
||||
func initDB(path string) *DatabaseContext {
|
||||
cc := new(DatabaseContext)
|
||||
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{})
|
||||
if err != nil {
|
||||
cc.db = nil
|
||||
return cc
|
||||
}
|
||||
// Migrate the schema
|
||||
db.AutoMigrate(&Session{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
return &DatabaseContext{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func formatDBPath(path string) string {
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if home == "" {
|
||||
home = "."
|
||||
}
|
||||
return filepath.Join(home, ".config", "hway", path)
|
||||
}
|
||||
@@ -1,19 +1,41 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidCredentials = echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
|
||||
ErrInvalidSubject = echo.NewHTTPError(http.StatusBadRequest, "Invalid subject")
|
||||
ErrInvalidUser = echo.NewHTTPError(http.StatusBadRequest, "Invalid user")
|
||||
|
||||
ErrUserAlreadyExists = echo.NewHTTPError(http.StatusConflict, "User already exists")
|
||||
ErrUserNotFound = echo.NewHTTPError(http.StatusNotFound, "User not found")
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Address string `json:"address"`
|
||||
Handle string `json:"handle"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastInitial string `json:"lastInitial"`
|
||||
VaultCID string `json:"vaultCID"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID string `json:"id"`
|
||||
gorm.Model
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
BrowserName string `json:"browserName"`
|
||||
BrowserVersion string `json:"browserVersion"`
|
||||
UserArchitecture string `json:"userArchitecture"`
|
||||
Platform string `json:"platform"`
|
||||
PlatformVersion string `json:"platformVersion"`
|
||||
DeviceModel string `json:"deviceModel"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Address string `json:"address"`
|
||||
Handle string `json:"handle"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
VaultCID string `json:"vaultCID"`
|
||||
UserHandle string `json:"userHandle"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastInitial string `json:"lastInitial"`
|
||||
VaultAddress string `json:"vaultAddress"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/gateway/config"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// InitDB initializes and returns a configured database connection
|
||||
func InitDB(env config.Env) (*gorm.DB, error) {
|
||||
path := formatDBPath(env.GetSqliteFile())
|
||||
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Migrate the schema
|
||||
db.AutoMigrate(&Session{})
|
||||
db.AutoMigrate(&User{})
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func formatDBPath(path string) string {
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if home == "" {
|
||||
home = "."
|
||||
}
|
||||
|
||||
configDir := filepath.Join(home, ".config", "hway")
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
// If we can't create the directory, fall back to current directory
|
||||
return path
|
||||
}
|
||||
|
||||
return filepath.Join(configDir, path)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ templ InitialView() {
|
||||
@layout.Container() {
|
||||
@text.Header("Sonr.ID", "The decentralized identity layer for the web.")
|
||||
<div class="pt-3 flex flex-col items-center justify-center h-full">
|
||||
<sl-button hx-target="#container" hx-get="/register" type="button">
|
||||
<sl-button hx-target="#container" hx-get="/register" hx-push-url="/register" type="button">
|
||||
<sl-icon slot="prefix" library="sonr" name="sonr"></sl-icon>
|
||||
Get Started
|
||||
<sl-icon slot="suffix" library="sonr" name="arrow-right"></sl-icon>
|
||||
|
||||
@@ -62,7 +62,7 @@ func InitialView() templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"pt-3 flex flex-col items-center justify-center h-full\"><sl-button hx-target=\"#container\" hx-get=\"/register\" type=\"button\"><sl-icon slot=\"prefix\" library=\"sonr\" name=\"sonr\"></sl-icon> Get Started <sl-icon slot=\"suffix\" library=\"sonr\" name=\"arrow-right\"></sl-icon></sl-button></div>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"pt-3 flex flex-col items-center justify-center h-full\"><sl-button hx-target=\"#container\" hx-get=\"/register\" hx-push-url=\"/register\" type=\"button\"><sl-icon slot=\"prefix\" library=\"sonr\" name=\"sonr\"></sl-icon> Get Started <sl-icon slot=\"suffix\" library=\"sonr\" name=\"arrow-right\"></sl-icon></sl-button></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -2,26 +2,18 @@ package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/pkg/common"
|
||||
"github.com/onsonr/sonr/pkg/gateway/internal/database"
|
||||
"github.com/segmentio/ksuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
// Context keys
|
||||
const (
|
||||
DataContextKey contextKey = "http_session_data"
|
||||
)
|
||||
|
||||
type SessionCtx interface {
|
||||
ID() string
|
||||
BrowserName() string
|
||||
BrowserVersion() string
|
||||
}
|
||||
|
||||
// Get returns the session.Context from the echo context.
|
||||
func Get(c echo.Context) (SessionCtx, error) {
|
||||
// Get returns the HTTPContext from the echo context
|
||||
func Get(c echo.Context) (*HTTPContext, error) {
|
||||
ctx, ok := c.(*HTTPContext)
|
||||
if !ok {
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found")
|
||||
@@ -29,18 +21,89 @@ func Get(c echo.Context) (SessionCtx, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// TODO: Returns fixed chain ID for testing.
|
||||
func GetChainID(c echo.Context) string {
|
||||
return "sonr-testnet-1"
|
||||
// HTTPContext is the context for HTTP endpoints.
|
||||
type HTTPContext struct {
|
||||
echo.Context
|
||||
db *gorm.DB
|
||||
sess *database.Session
|
||||
}
|
||||
|
||||
// SetVaultAddress sets the address of the vault
|
||||
func SetVaultAddress(c echo.Context, address string) error {
|
||||
return common.WriteCookie(c, common.SonrAddress, address)
|
||||
// NewHTTPContext creates a new session context
|
||||
func NewHTTPContext(c echo.Context, db *gorm.DB) *HTTPContext {
|
||||
return &HTTPContext{
|
||||
Context: c,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// SetVaultAuthorization sets the UCAN CID of the vault
|
||||
func SetVaultAuthorization(c echo.Context, ucanCID string) error {
|
||||
common.HeaderWrite(c, common.Authorization, formatAuth(ucanCID))
|
||||
// Session returns the current session
|
||||
func (s *HTTPContext) Session() *database.Session {
|
||||
return s.sess
|
||||
}
|
||||
|
||||
// InitSession initializes or loads an existing session
|
||||
func (s *HTTPContext) InitSession() error {
|
||||
sessionID := s.getOrCreateSessionID()
|
||||
|
||||
// Try to load existing session
|
||||
var sess database.Session
|
||||
result := s.db.Where("id = ?", sessionID).First(&sess)
|
||||
if result.Error != nil {
|
||||
// Create new session if not found
|
||||
bn, bv := extractBrowserInfo(s.Context)
|
||||
sess = database.Session{
|
||||
ID: sessionID,
|
||||
BrowserName: bn,
|
||||
BrowserVersion: bv,
|
||||
}
|
||||
if err := s.db.Create(&sess).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.sess = &sess
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HTTPContext) getOrCreateSessionID() string {
|
||||
if ok := common.CookieExists(s.Context, common.SessionID); !ok {
|
||||
sessionID := ksuid.New().String()
|
||||
common.WriteCookie(s.Context, common.SessionID, sessionID)
|
||||
return sessionID
|
||||
}
|
||||
|
||||
sessionID, err := common.ReadCookie(s.Context, common.SessionID)
|
||||
if err != nil {
|
||||
sessionID = ksuid.New().String()
|
||||
common.WriteCookie(s.Context, common.SessionID, sessionID)
|
||||
}
|
||||
return sessionID
|
||||
}
|
||||
|
||||
func extractBrowserInfo(c echo.Context) (string, string) {
|
||||
userAgent := common.HeaderRead(c, common.UserAgent)
|
||||
if userAgent == "" {
|
||||
return "N/A", "-1"
|
||||
}
|
||||
|
||||
var name, ver string
|
||||
entries := strings.Split(strings.TrimSpace(userAgent), ",")
|
||||
for _, entry := range entries {
|
||||
entry = strings.TrimSpace(entry)
|
||||
re := regexp.MustCompile(`"([^"]+)";v="([^"]+)"`)
|
||||
matches := re.FindStringSubmatch(entry)
|
||||
|
||||
if len(matches) == 3 {
|
||||
browserName := matches[1]
|
||||
version := matches[2]
|
||||
|
||||
if browserName != common.BrowserNameUnknown.String() &&
|
||||
browserName != common.BrowserNameChromium.String() {
|
||||
name = browserName
|
||||
ver = version
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return name, ver
|
||||
}
|
||||
|
||||
@@ -2,48 +2,18 @@ package session
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/common"
|
||||
"github.com/onsonr/sonr/pkg/gateway/config"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Middleware establishes a Session Cookie.
|
||||
func Middleware(env config.Env) echo.MiddlewareFunc {
|
||||
// Middleware creates a new session middleware
|
||||
func Middleware(db *gorm.DB) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc := injectSession(c, common.RoleHway)
|
||||
cc := NewHTTPContext(c, db)
|
||||
if err := cc.InitSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+11
-4
@@ -10,16 +10,23 @@ import (
|
||||
"github.com/onsonr/sonr/pkg/gateway/internal/session"
|
||||
)
|
||||
|
||||
func RegisterRoutes(e *echo.Echo, env config.Env) {
|
||||
func RegisterRoutes(e *echo.Echo, env config.Env) error {
|
||||
// Custom error handler for gateway
|
||||
e.HTTPErrorHandler = response.RedirectOnError("http://localhost:3000")
|
||||
|
||||
// Inject session middleware
|
||||
e.Use(session.Middleware(env))
|
||||
e.Use(database.Middleware(env))
|
||||
// Initialize database
|
||||
db, err := database.InitDB(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inject session middleware with database connection
|
||||
e.Use(session.Middleware(db))
|
||||
|
||||
// Register routes
|
||||
e.GET("/", handlers.HandleIndex)
|
||||
e.GET("/register", handlers.HandleRegisterView(env))
|
||||
e.POST("/register/start", handlers.HandleRegisterStart)
|
||||
e.POST("/register/finish", handlers.HandleRegisterFinish)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user