mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 01:41:44 +00:00
feature/1121 implement ucan validation (#1176)
- **refactor: remove unused auth components** - **refactor: improve devbox configuration and deployment process** - **refactor: improve devnet and testnet setup** - **fix: update templ version to v0.2.778** - **refactor: rename pkl/net.matrix to pkl/matrix.net** - **refactor: migrate webapp components to nebula** - **refactor: protobuf types** - **chore: update dependencies for improved security and stability** - **feat: implement landing page and vault gateway servers** - **refactor: Migrate data models to new module structure and update related files** - **feature/1121-implement-ucan-validation** - **refactor: Replace hardcoded constants with model types in attns.go** - **feature/1121-implement-ucan-validation** - **chore: add origin Host struct and update main function to handle multiple hosts** - **build: remove unused static files from dwn module** - **build: remove unused static files from dwn module** - **refactor: Move DWN models to common package** - **refactor: move models to pkg/common** - **refactor: move vault web app assets to embed module** - **refactor: update session middleware import path** - **chore: configure port labels and auto-forwarding behavior** - **feat: enhance devcontainer configuration** - **feat: Add UCAN middleware for Echo with flexible token validation** - **feat: add JWT middleware for UCAN authentication** - **refactor: update package URI and versioning in PklProject files** - **fix: correct sonr.pkl import path** - **refactor: move JWT related code to auth package** - **feat: introduce vault configuration retrieval and management** - **refactor: Move vault components to gateway module and update file paths** - **refactor: remove Dexie and SQLite database implementations** - **feat: enhance frontend with PWA features and WASM integration** - **feat: add Devbox features and streamline Dockerfile** - **chore: update dependencies to include TigerBeetle** - **chore(deps): update go version to 1.23** - **feat: enhance devnet setup with PATH environment variable and updated PWA manifest** - **fix: upgrade tigerbeetle-go dependency and remove indirect dependency** - **feat: add PostgreSQL support to devnet and testnet deployments** - **refactor: rename keyshare cookie to token cookie** - **feat: upgrade Go version to 1.23.3 and update dependencies** - **refactor: update devnet and testnet configurations** - **feat: add IPFS configuration for devnet** - **I'll help you update the ipfs.config.pkl to include all the peers from the shell script. Here's the updated configuration:** - **refactor: move mpc package to crypto directory** - **feat: add BIP32 support for various cryptocurrencies** - **feat: enhance ATN.pkl with additional capabilities** - **refactor: simplify smart account and vault attenuation creation** - **feat: add new capabilities to the Attenuation type** - **refactor: Rename MPC files for clarity and consistency** - **feat: add DIDKey support for cryptographic operations** - **feat: add devnet and testnet deployment configurations** - **fix: correct key derivation in bip32 package** - **refactor: rename crypto/bip32 package to crypto/accaddr** - **fix: remove duplicate indirect dependency** - **refactor: move vault package to root directory** - **refactor: update routes for gateway and vault** - **refactor: remove obsolete web configuration file** - **refactor: remove unused TigerBeetle imports and update host configuration** - **refactor: adjust styles directory path** - **feat: add broadcastTx and simulateTx functions to gateway** - **feat: add PinVault handler**
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/common"
|
||||
"github.com/onsonr/sonr/pkg/common/types"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
// Context keys
|
||||
const (
|
||||
DataContextKey contextKey = "http_session_data"
|
||||
)
|
||||
|
||||
type Context = common.SessionCtx
|
||||
|
||||
// Get returns the session.Context from the echo context.
|
||||
func Get(c echo.Context) (Context, error) {
|
||||
ctx, ok := c.(*HTTPContext)
|
||||
if !ok {
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// WithData sets the session data in the context
|
||||
func WithData(ctx context.Context, data *types.Session) context.Context {
|
||||
return context.WithValue(ctx, DataContextKey, data)
|
||||
}
|
||||
|
||||
// GetData gets the session data from any context type
|
||||
func GetData(ctx interface{}) *types.Session {
|
||||
switch c := ctx.(type) {
|
||||
case *HTTPContext:
|
||||
if c != nil {
|
||||
return c.sessionData
|
||||
}
|
||||
case context.Context:
|
||||
if c != nil {
|
||||
if val := c.Value(DataContextKey); val != nil {
|
||||
if httpCtx, ok := val.(*types.Session); ok {
|
||||
return httpCtx
|
||||
}
|
||||
}
|
||||
}
|
||||
case echo.Context:
|
||||
if c != nil {
|
||||
if httpCtx, ok := c.(*HTTPContext); ok && httpCtx != nil {
|
||||
return httpCtx.sessionData
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return empty session rather than nil to prevent nil pointer panics
|
||||
return &types.Session{}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/pkg/common/cookie"
|
||||
"github.com/onsonr/sonr/pkg/common/header"
|
||||
"github.com/onsonr/sonr/pkg/vault/types"
|
||||
)
|
||||
|
||||
// TODO: Returns fixed chain ID for testing.
|
||||
func GetChainID(c echo.Context) string {
|
||||
return "sonr-testnet-1"
|
||||
}
|
||||
|
||||
// GetVaultConfig returns the default vault config
|
||||
func GetVaultConfig(c echo.Context, addr string, ucanCID string) *types.Config {
|
||||
return &types.Config{
|
||||
MotrToken: ucanCID,
|
||||
MotrAddress: addr,
|
||||
IpfsGatewayUrl: "http://localhost:80",
|
||||
SonrApiUrl: "http://localhost:1317",
|
||||
SonrRpcUrl: "http://localhost:26657",
|
||||
SonrChainId: GetChainID(c),
|
||||
VaultSchema: GetVaultSchema(c),
|
||||
}
|
||||
}
|
||||
|
||||
// GetVaultSchema returns the default vault schema
|
||||
func GetVaultSchema(c echo.Context) *types.Schema {
|
||||
return types.DefaultSchema()
|
||||
}
|
||||
|
||||
// SetVaultAddress sets the address of the vault
|
||||
func SetVaultAddress(c echo.Context, address string) error {
|
||||
return cookie.Write(c, cookie.SonrAddress, address)
|
||||
}
|
||||
|
||||
// SetVaultAuthorization sets the UCAN CID of the vault
|
||||
func SetVaultAuthorization(c echo.Context, ucanCID string) error {
|
||||
header.Write(c, header.Authorization, formatAuth(ucanCID))
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/common"
|
||||
"github.com/onsonr/sonr/pkg/common/cookie"
|
||||
"github.com/onsonr/sonr/pkg/common/header"
|
||||
"github.com/onsonr/sonr/pkg/vault/types"
|
||||
)
|
||||
|
||||
// HwayMiddleware establishes a Session Cookie.
|
||||
func HwayMiddleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc := injectSession(c, common.RoleHway)
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MotrMiddleware establishes a Session Cookie.
|
||||
func MotrMiddleware(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 {
|
||||
header.Write(c, header.SonrAPIURL, config.SonrApiUrl)
|
||||
header.Write(c, header.SonrRPCURL, config.SonrRpcUrl)
|
||||
|
||||
cookie.Write(c, cookie.SonrAddress, config.MotrAddress)
|
||||
schemaBz, err := json.Marshal(config.VaultSchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cookie.WriteBytes(c, cookie.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)
|
||||
}
|
||||
|
||||
cookie.Write(c, cookie.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
|
||||
}
|
||||
if err := loadOrGenChallenge(c); err != nil {
|
||||
// Log error but continue
|
||||
}
|
||||
|
||||
return initHTTPContext(c)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/onsonr/sonr/pkg/common"
|
||||
"github.com/onsonr/sonr/pkg/common/cookie"
|
||||
"github.com/onsonr/sonr/pkg/common/types"
|
||||
)
|
||||
|
||||
// HTTPContext is the context for HTTP endpoints.
|
||||
type HTTPContext struct {
|
||||
echo.Context
|
||||
role common.PeerRole
|
||||
sessionData *types.Session
|
||||
}
|
||||
|
||||
// Ensure HTTPContext implements context.Context
|
||||
func (s *HTTPContext) Deadline() (deadline time.Time, ok bool) {
|
||||
return s.Context.Request().Context().Deadline()
|
||||
}
|
||||
|
||||
func (s *HTTPContext) Done() <-chan struct{} {
|
||||
return s.Context.Request().Context().Done()
|
||||
}
|
||||
|
||||
func (s *HTTPContext) Err() error {
|
||||
return s.Context.Request().Context().Err()
|
||||
}
|
||||
|
||||
func (s *HTTPContext) Value(key interface{}) interface{} {
|
||||
return s.Context.Request().Context().Value(key)
|
||||
}
|
||||
|
||||
// initHTTPContext loads the headers from the request.
|
||||
func initHTTPContext(c echo.Context) *HTTPContext {
|
||||
if c == nil {
|
||||
return &HTTPContext{
|
||||
sessionData: &types.Session{},
|
||||
}
|
||||
}
|
||||
|
||||
sessionData := injectSessionData(c)
|
||||
if sessionData == nil {
|
||||
sessionData = &types.Session{}
|
||||
}
|
||||
|
||||
cc := &HTTPContext{
|
||||
Context: c,
|
||||
role: common.PeerRole(cookie.ReadUnsafe(c, cookie.SessionRole)),
|
||||
sessionData: sessionData,
|
||||
}
|
||||
|
||||
// Set the session data in both contexts
|
||||
c.SetRequest(c.Request().WithContext(WithData(c.Request().Context(), sessionData)))
|
||||
return cc
|
||||
}
|
||||
|
||||
func (s *HTTPContext) ID() string {
|
||||
return s.GetData().Id
|
||||
}
|
||||
|
||||
func (s *HTTPContext) LoginOptions(credentials []common.CredDescriptor) *common.LoginOptions {
|
||||
ch, _ := common.Base64Decode(s.GetData().Challenge)
|
||||
return &common.LoginOptions{
|
||||
Challenge: ch,
|
||||
Timeout: 10000,
|
||||
AllowedCredentials: credentials,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPContext) RegisterOptions(subject string) *common.RegisterOptions {
|
||||
ch, _ := common.Base64Decode(s.GetData().Challenge)
|
||||
opts := baseRegisterOptions()
|
||||
opts.Challenge = ch
|
||||
opts.User = buildUserEntity(subject)
|
||||
return opts
|
||||
}
|
||||
|
||||
func (s *HTTPContext) GetData() *types.Session {
|
||||
return s.sessionData
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
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/common/cookie"
|
||||
"github.com/onsonr/sonr/pkg/common/header"
|
||||
"github.com/onsonr/sonr/pkg/common/types"
|
||||
)
|
||||
|
||||
const kWebAuthnTimeout = 6000
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Initialization │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
func loadOrGenChallenge(c echo.Context) error {
|
||||
var (
|
||||
chal protocol.URLEncodedBase64
|
||||
chalRaw []byte
|
||||
err error
|
||||
)
|
||||
|
||||
// Setup genChal function
|
||||
genChal := func() []byte {
|
||||
ch, _ := protocol.CreateChallenge()
|
||||
bz, _ := ch.MarshalJSON()
|
||||
return bz
|
||||
}
|
||||
|
||||
// Check if there is a session challenge cookie
|
||||
if !cookie.Exists(c, cookie.SessionChallenge) {
|
||||
chalRaw = genChal()
|
||||
cookie.WriteBytes(c, cookie.SessionChallenge, chalRaw)
|
||||
} else {
|
||||
chalRaw, err = cookie.ReadBytes(c, cookie.SessionChallenge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to read the session challenge from the "session" cookie
|
||||
err = chal.UnmarshalJSON(chalRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 := cookie.Exists(c, cookie.SessionID); !ok {
|
||||
sessionID = genKsuid()
|
||||
} else {
|
||||
sessionID, err = cookie.Read(c, cookie.SessionID)
|
||||
if err != nil {
|
||||
sessionID = genKsuid()
|
||||
}
|
||||
}
|
||||
cookie.Write(c, cookie.SessionID, sessionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Extraction │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
func injectSessionData(c echo.Context) *types.Session {
|
||||
id, chal := extractPeerInfo(c)
|
||||
bn, bv := extractBrowserInfo(c)
|
||||
return &types.Session{
|
||||
Id: id,
|
||||
Challenge: chal,
|
||||
BrowserName: bn,
|
||||
BrowserVersion: bv,
|
||||
UserArchitecture: header.Read(c, header.Architecture),
|
||||
Platform: header.Read(c, header.Platform),
|
||||
PlatformVersion: header.Read(c, header.PlatformVersion),
|
||||
DeviceModel: header.Read(c, header.Model),
|
||||
IsMobile: header.Equals(c, header.Mobile, "?1"),
|
||||
}
|
||||
}
|
||||
|
||||
func extractPeerInfo(c echo.Context) (string, string) {
|
||||
var chal protocol.URLEncodedBase64
|
||||
id, _ := cookie.Read(c, cookie.SessionID)
|
||||
chalRaw, _ := cookie.ReadBytes(c, cookie.SessionChallenge)
|
||||
chal.UnmarshalJSON(chalRaw)
|
||||
|
||||
return id, common.Base64Encode(chal)
|
||||
}
|
||||
|
||||
func extractBrowserInfo(c echo.Context) (string, string) {
|
||||
secCHUA := header.Read(c, header.UserAgent)
|
||||
|
||||
// If header is empty, return empty BrowserInfo
|
||||
if secCHUA == "" {
|
||||
return "N/A", "-1"
|
||||
}
|
||||
|
||||
// Split the header 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() *common.RegisterOptions {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user