mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 01:41:44 +00:00
feature/1115 execute ucan token (#1177)
- **deps: remove tigerbeetle-go dependency** - **refactor: remove unused landing page components and models** - **feat: add pin and publish vault handlers** - **refactor: move payment and credential services to webui browser package** - **refactor: remove unused credentials management components** - **feat: add landing page components and middleware for credentials and payments** - **refactor: remove unused imports in vault config** - **refactor: remove unused bank, DID, and DWN gRPC clients** - **refactor: rename client files and improve code structure** - **feat: add session middleware helpers and landing page components** - **feat: add user profile registration flow** - **feat: Implement WebAuthn registration flow** - **feat: add error view for users without WebAuthn devices** - **chore: update htmx to include extensions** - **refactor: rename pin handler to claim handler and update routes** - **chore: update import paths after moving UI components and styles** - **fix: address potential server errors by handling and logging them properly** - **refactor: move vault config to gateway package and update related dependencies** - **style: simplify form styling and remove unnecessary components** - **feat: improve UI design for registration flow** - **feat: implement passkey-based authentication** - **refactor: migrate registration forms to use reusable form components** - **refactor: remove tailwindcss setup and use CDN instead** - **style: update submit button style to use outline variant** - **refactor: refactor server and IPFS client, remove MPC encryption** - **refactor: Abstract keyshare functionality and improve message encoding** - **refactor: improve keyset JSON marshaling and error handling** - **feat: add support for digital signatures using MPC keys** - **fix: Refactor MarshalJSON to use standard json.Marshal for Message serialization** - **fix: Encode messages before storing in keyshare structs** - **style: update form input styles for improved user experience** - **refactor: improve code structure in registration handlers** - **refactor: consolidate signer middleware and IPFS interaction** - **refactor: rename MPC signing and refresh protocol functions** - **refactor: update hway configuration loading mechanism** - **feat: integrate database support for sessions and users** - **refactor: remove devnet infrastructure and simplify build process** - **docs(guides): add Sonr DID module guide** - **feat: integrate progress bar into registration form** - **refactor: migrate WebAuthn dependencies to protocol package** - **feat: enhance user registration with passkey integration and improved form styling** - **refactor: move gateway view handlers to internal pages package** - **refactor: Move address package to MPC module** - **feat: integrate turnstile for registration** - **style: remove unnecessary size attribute from buttons** - **refactor: rename cookie package to session/cookie** - **refactor: remove unnecessary types.Session dependency** - **refactor: rename pkg/core to pkg/chain** - **refactor: simplify deployment process by removing testnet-specific Taskfile and devbox configuration** - **feat: add error redirect functionality and improve routes** - **feat: implement custom error handling for gateway** - **chore: update version number to 0.0.7 in template** - **feat: add IPFS client implementation** - **feat: Implement full IPFS client interface with comprehensive methods** - **refactor: improve IPFS client path handling** - **refactor: Move UCAN middleware to controller package** - **feat: add UCAN middleware to motr** - **refactor: update libp2p dependency** - **docs: add UCAN specification document** - **refactor: move UCAN controller logic to common package** - **refactor: rename exports.go to common.go** - **feat: add UCAN token support** - **refactor: migrate UCAN token parsing to dedicated package** - **refactor: improve CometBFT and app config initialization** - **refactor: improve deployment scripts and documentation** - **feat: integrate IPFS and producer middleware** - **refactor: rename agent directory to aider** - **fix: correct libp2p import path** - **refactor: remove redundant dependency** - **cleanup: remove unnecessary test files** - **refactor: move attention types to crypto/ucan package** - **feat: expand capabilities and resource types for UCANs** - **refactor: rename sonr.go to codec.go and update related imports** - **feat: add IPFS-based token store** - **feat: Implement IPFS-based token store with caching and UCAN integration** - **feat: Add dynamic attenuation constructor for UCAN presets** - **fix: Handle missing or invalid attenuation data with EmptyAttenuation** - **fix: Update UCAN attenuation tests with correct capability types** - **feat: integrate UCAN-based authorization into the producer middleware** - **refactor: remove unused dependency on go-ucan** - **refactor: Move address handling logic to DID module** - **feat: Add support for compressed and uncompressed Secp256k1 public keys in didkey** - **test: Add test for generating DID key from MPC keyshares** - **feat: Add methods for extracting compressed and uncompressed public keys in share types** - **feat: Add BaseKeyshare struct with public key conversion methods** - **refactor: Use compressed and uncompressed public keys in keyshare, fix public key usage in tests and verification** - **feat: add support for key generation policy type** - **fix: correct typo in VaultPermissions constant** - **refactor: move JWT related code to ucan package** - **refactor: move UCAN JWT and source code to spec package**
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/crypto/mpc"
|
||||
)
|
||||
|
||||
// UCANConfig defines the configuration for UCAN middleware
|
||||
type UCANConfig struct {
|
||||
// Skipper defines a function to skip middleware
|
||||
Skipper func(c echo.Context) bool
|
||||
|
||||
// KeySource provides the source for validating UCANs
|
||||
KeySource mpc.KeyshareSource
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "param:<name>"
|
||||
// - "cookie:<name>"
|
||||
TokenLookup string
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
}
|
||||
|
||||
// DefaultUCANConfig is the default UCAN middleware config
|
||||
var DefaultUCANConfig = UCANConfig{
|
||||
Skipper: nil,
|
||||
TokenLookup: "header:Authorization",
|
||||
AuthScheme: "Bearer",
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Option func(c *UCANConfig)
|
||||
|
||||
func WithSkipper(skipper func(c echo.Context) bool) Option {
|
||||
return func(c *UCANConfig) {
|
||||
c.Skipper = skipper
|
||||
}
|
||||
}
|
||||
|
||||
func WithAuthScheme(scheme string) Option {
|
||||
return func(c *UCANConfig) {
|
||||
c.AuthScheme = scheme
|
||||
}
|
||||
}
|
||||
|
||||
// WithTokenLookup sets the token lookup strategy
|
||||
func WithTokenLookup(lookup string) Option {
|
||||
return func(c *UCANConfig) {
|
||||
c.TokenLookup = lookup
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package clients
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/pkg/gateway/config"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type ClientsContext struct {
|
||||
echo.Context
|
||||
addr string
|
||||
}
|
||||
|
||||
func GetClientConn(c echo.Context) (*grpc.ClientConn, error) {
|
||||
cc, ok := c.(*ClientsContext)
|
||||
if !ok {
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "ClientsContext not found")
|
||||
}
|
||||
grpcConn, err := grpc.NewClient(cc.addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Failed to dial gRPC")
|
||||
}
|
||||
return grpcConn, nil
|
||||
}
|
||||
|
||||
func Middleware(env config.Env) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc := &ClientsContext{Context: c, addr: env.GetSonrGrpcUrl()}
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package clients
|
||||
|
||||
import (
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
"github.com/labstack/echo/v4"
|
||||
didv1 "github.com/onsonr/sonr/api/did/v1"
|
||||
dwnv1 "github.com/onsonr/sonr/api/dwn/v1"
|
||||
svcv1 "github.com/onsonr/sonr/api/svc/v1"
|
||||
)
|
||||
|
||||
func BankQueryClient(c echo.Context) (bankv1beta1.QueryClient, error) {
|
||||
conn, err := GetClientConn(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bankv1beta1.NewQueryClient(conn), nil
|
||||
}
|
||||
|
||||
func DIDQueryClient(c echo.Context) (didv1.QueryClient, error) {
|
||||
conn, err := GetClientConn(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return didv1.NewQueryClient(conn), nil
|
||||
}
|
||||
|
||||
func DWNQueryClient(c echo.Context) (dwnv1.QueryClient, error) {
|
||||
conn, err := GetClientConn(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dwnv1.NewQueryClient(conn), nil
|
||||
}
|
||||
|
||||
func SVCQueryClient(c echo.Context) (svcv1.QueryClient, error) {
|
||||
conn, err := GetClientConn(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svcv1.NewQueryClient(conn), nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
type LargeBlob struct {
|
||||
Support string `json:"support"`
|
||||
Write string `json:"write"`
|
||||
}
|
||||
|
||||
type BrowserName string
|
||||
|
||||
const (
|
||||
BrowserNameUnknown BrowserName = " Not A;Brand"
|
||||
BrowserNameChromium BrowserName = "Chromium"
|
||||
)
|
||||
|
||||
func (n BrowserName) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
type PeerRole string
|
||||
|
||||
const (
|
||||
RoleUnknown PeerRole = "none"
|
||||
RoleHway PeerRole = "hway"
|
||||
RoleMotr PeerRole = "motr"
|
||||
)
|
||||
|
||||
func (r PeerRole) Is(role PeerRole) bool {
|
||||
return r == role
|
||||
}
|
||||
|
||||
func (r PeerRole) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func Base64Encode(data []byte) string {
|
||||
return base64.RawURLEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
func Base64Decode(data string) ([]byte, error) {
|
||||
return base64.RawURLEncoding.DecodeString(data)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/crypto/ucan/spec"
|
||||
)
|
||||
|
||||
// ControllerConfig defines the configuration for UCAN middleware
|
||||
type ControllerConfig struct {
|
||||
// Skipper defines a function to skip middleware
|
||||
Skipper func(c echo.Context) bool
|
||||
|
||||
// KeySource provides the source for validating UCANs
|
||||
KeySource spec.KeyshareSource
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "param:<name>"
|
||||
// - "cookie:<name>"
|
||||
TokenLookup string
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
}
|
||||
|
||||
// DefaultControllerConfig is the default UCAN middleware config
|
||||
var DefaultControllerConfig = ControllerConfig{
|
||||
Skipper: nil,
|
||||
TokenLookup: "header:Authorization",
|
||||
AuthScheme: "Bearer",
|
||||
}
|
||||
|
||||
type Option func(c *ControllerConfig)
|
||||
|
||||
func WithSkipper(skipper func(c echo.Context) bool) Option {
|
||||
return func(c *ControllerConfig) {
|
||||
c.Skipper = skipper
|
||||
}
|
||||
}
|
||||
|
||||
func WithAuthScheme(scheme string) Option {
|
||||
return func(c *ControllerConfig) {
|
||||
c.AuthScheme = scheme
|
||||
}
|
||||
}
|
||||
|
||||
// WithTokenLookup sets the token lookup strategy
|
||||
func WithTokenLookup(lookup string) Option {
|
||||
return func(c *ControllerConfig) {
|
||||
c.TokenLookup = lookup
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
package auth
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/crypto/mpc"
|
||||
"github.com/onsonr/sonr/crypto/ucan/spec"
|
||||
)
|
||||
|
||||
// UCAN returns middleware to validate UCAN tokens
|
||||
func UCAN(source mpc.KeyshareSource, opts ...Option) echo.MiddlewareFunc {
|
||||
c := DefaultUCANConfig
|
||||
// Middleware returns middleware to validate Middleware tokens
|
||||
func Middleware(source spec.KeyshareSource, opts ...Option) echo.MiddlewareFunc {
|
||||
c := DefaultControllerConfig
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
c.KeySource = source
|
||||
return UCANWithConfig(c)
|
||||
return initWithConfig(c)
|
||||
}
|
||||
|
||||
// UCANWithConfig returns UCAN middleware with custom config
|
||||
func UCANWithConfig(config UCANConfig) echo.MiddlewareFunc {
|
||||
// initWithConfig returns UCAN middleware with custom config
|
||||
func initWithConfig(config ControllerConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultUCANConfig.Skipper
|
||||
config.Skipper = DefaultControllerConfig.Skipper
|
||||
}
|
||||
if config.TokenLookup == "" {
|
||||
config.TokenLookup = DefaultUCANConfig.TokenLookup
|
||||
config.TokenLookup = DefaultControllerConfig.TokenLookup
|
||||
}
|
||||
if config.AuthScheme == "" {
|
||||
config.AuthScheme = DefaultUCANConfig.AuthScheme
|
||||
config.AuthScheme = DefaultControllerConfig.AuthScheme
|
||||
}
|
||||
|
||||
// Initialize
|
||||
@@ -1,35 +0,0 @@
|
||||
package cookie
|
||||
|
||||
// Key is a type alias for string.
|
||||
type Key string
|
||||
|
||||
const (
|
||||
// SessionID is the key for the session ID cookie.
|
||||
SessionID Key = "session.id"
|
||||
|
||||
// SessionChallenge is the key for the session challenge cookie.
|
||||
SessionChallenge Key = "session.challenge"
|
||||
|
||||
// SessionRole is the key for the session role cookie.
|
||||
SessionRole Key = "session.role"
|
||||
|
||||
// SonrAddress is the key for the Sonr address cookie.
|
||||
SonrAddress Key = "sonr.address"
|
||||
|
||||
// SonrDID is the key for the Sonr DID cookie.
|
||||
SonrDID Key = "sonr.did"
|
||||
|
||||
// UserHandle is the key for the User Handle cookie.
|
||||
UserHandle Key = "user.handle"
|
||||
|
||||
// VaultCID is the key for the Vault CID cookie.
|
||||
VaultCID Key = "vault.cid"
|
||||
|
||||
// VaultSchema is the key for the Vault schema cookie.
|
||||
VaultSchema Key = "vault.schema"
|
||||
)
|
||||
|
||||
// String returns the string representation of the CookieKey.
|
||||
func (c Key) String() string {
|
||||
return string(c)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package cookie
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func Exists(c echo.Context, key Key) bool {
|
||||
ck, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return ck != nil
|
||||
}
|
||||
|
||||
func Read(c echo.Context, key Key) (string, error) {
|
||||
cookie, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
// Cookie not found or other error
|
||||
return "", err
|
||||
}
|
||||
if cookie == nil || cookie.Value == "" {
|
||||
// Cookie is empty
|
||||
return "", http.ErrNoCookie
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
func ReadBytes(c echo.Context, key Key) ([]byte, error) {
|
||||
cookie, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
// Cookie not found or other error
|
||||
return nil, err
|
||||
}
|
||||
if cookie == nil || cookie.Value == "" {
|
||||
// Cookie is empty
|
||||
return nil, http.ErrNoCookie
|
||||
}
|
||||
return base64.RawURLEncoding.DecodeString(cookie.Value)
|
||||
}
|
||||
|
||||
func ReadUnsafe(c echo.Context, key Key) string {
|
||||
ck, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ck.Value
|
||||
}
|
||||
|
||||
func Write(c echo.Context, key Key, value string) error {
|
||||
cookie := &http.Cookie{
|
||||
Name: key.String(),
|
||||
Value: value,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
// Add Secure and SameSite attributes as needed
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteBytes(c echo.Context, key Key, value []byte) error {
|
||||
cookie := &http.Cookie{
|
||||
Name: key.String(),
|
||||
Value: base64.RawURLEncoding.EncodeToString(value),
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
// Add Secure and SameSite attributes as needed
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// CookieKey is a type alias for string.
|
||||
type CookieKey string
|
||||
|
||||
const (
|
||||
// SessionID is the key for the session ID cookie.
|
||||
SessionID CookieKey = "session.id"
|
||||
|
||||
// SessionChallenge is the key for the session challenge cookie.
|
||||
SessionChallenge CookieKey = "session.challenge"
|
||||
|
||||
// SessionRole is the key for the session role cookie.
|
||||
SessionRole CookieKey = "session.role"
|
||||
|
||||
// SonrAddress is the key for the Sonr address cookie.
|
||||
SonrAddress CookieKey = "sonr.address"
|
||||
|
||||
// SonrDID is the key for the Sonr DID cookie.
|
||||
SonrDID CookieKey = "sonr.did"
|
||||
|
||||
// UserAvatar is the key for the User Avatar cookie.
|
||||
UserAvatar CookieKey = "user.avatar"
|
||||
|
||||
// UserHandle is the key for the User Handle cookie.
|
||||
UserHandle CookieKey = "user.handle"
|
||||
|
||||
// UserName is the key for the User Name cookie.
|
||||
UserName CookieKey = "user.full_name"
|
||||
|
||||
// VaultAddress is the key for the Vault address cookie.
|
||||
VaultAddress CookieKey = "vault.address"
|
||||
|
||||
// VaultCID is the key for the Vault CID cookie.
|
||||
VaultCID CookieKey = "vault.cid"
|
||||
|
||||
// VaultSchema is the key for the Vault schema cookie.
|
||||
VaultSchema CookieKey = "vault.schema"
|
||||
)
|
||||
|
||||
// String returns the string representation of the CookieKey.
|
||||
func (c CookieKey) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Utility Methods │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
func CookieExists(c echo.Context, key CookieKey) bool {
|
||||
ck, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return ck != nil
|
||||
}
|
||||
|
||||
func ReadCookie(c echo.Context, key CookieKey) (string, error) {
|
||||
cookie, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
// Cookie not found or other error
|
||||
return "", err
|
||||
}
|
||||
if cookie == nil || cookie.Value == "" {
|
||||
// Cookie is empty
|
||||
return "", http.ErrNoCookie
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
func ReadCookieBytes(c echo.Context, key CookieKey) ([]byte, error) {
|
||||
cookie, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
// Cookie not found or other error
|
||||
return nil, err
|
||||
}
|
||||
if cookie == nil || cookie.Value == "" {
|
||||
// Cookie is empty
|
||||
return nil, http.ErrNoCookie
|
||||
}
|
||||
return base64.RawURLEncoding.DecodeString(cookie.Value)
|
||||
}
|
||||
|
||||
func ReadCookieUnsafe(c echo.Context, key CookieKey) string {
|
||||
ck, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ck.Value
|
||||
}
|
||||
|
||||
func WriteCookie(c echo.Context, key CookieKey, value string) error {
|
||||
cookie := &http.Cookie{
|
||||
Name: key.String(),
|
||||
Value: value,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
// Add Secure and SameSite attributes as needed
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteCookieBytes(c echo.Context, key CookieKey, value []byte) error {
|
||||
cookie := &http.Cookie{
|
||||
Name: key.String(),
|
||||
Value: base64.RawURLEncoding.EncodeToString(value),
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
// Add Secure and SameSite attributes as needed
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
return nil
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package header
|
||||
|
||||
type Key string
|
||||
|
||||
const (
|
||||
Authorization Key = "Authorization"
|
||||
|
||||
// User Agent
|
||||
Architecture Key = "Sec-CH-UA-Arch"
|
||||
Bitness Key = "Sec-CH-UA-Bitness"
|
||||
FullVersionList Key = "Sec-CH-UA-Full-Version-List"
|
||||
Mobile Key = "Sec-CH-UA-Mobile"
|
||||
Model Key = "Sec-CH-UA-Model"
|
||||
Platform Key = "Sec-CH-UA-Platform"
|
||||
PlatformVersion Key = "Sec-CH-UA-Platform-Version"
|
||||
UserAgent Key = "Sec-CH-UA"
|
||||
|
||||
// Sonr Injected
|
||||
SonrAPIURL Key = "X-Sonr-API"
|
||||
SonrgRPCURL Key = "X-Sonr-GRPC"
|
||||
SonrRPCURL Key = "X-Sonr-RPC"
|
||||
SonrWSURL Key = "X-Sonr-WS"
|
||||
)
|
||||
|
||||
func (h Key) String() string {
|
||||
return string(h)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package header
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
func Equals(c echo.Context, key Key, value string) bool {
|
||||
return c.Response().Header().Get(key.String()) == value
|
||||
}
|
||||
|
||||
// Exists returns true if the request has the header Key.
|
||||
func Exists(c echo.Context, key Key) bool {
|
||||
return c.Response().Header().Get(key.String()) != ""
|
||||
}
|
||||
|
||||
// Read returns the header value for the Key.
|
||||
func Read(c echo.Context, key Key) string {
|
||||
return c.Response().Header().Get(key.String())
|
||||
}
|
||||
|
||||
// Write sets the header value for the Key.
|
||||
func Write(c echo.Context, key Key, value string) {
|
||||
c.Response().Header().Set(key.String(), value)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package common
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
type HeaderKey string
|
||||
|
||||
const (
|
||||
Authorization HeaderKey = "Authorization"
|
||||
|
||||
// User Agent
|
||||
Architecture HeaderKey = "Sec-CH-UA-Arch"
|
||||
Bitness HeaderKey = "Sec-CH-UA-Bitness"
|
||||
FullVersionList HeaderKey = "Sec-CH-UA-Full-Version-List"
|
||||
Mobile HeaderKey = "Sec-CH-UA-Mobile"
|
||||
Model HeaderKey = "Sec-CH-UA-Model"
|
||||
Platform HeaderKey = "Sec-CH-UA-Platform"
|
||||
PlatformVersion HeaderKey = "Sec-CH-UA-Platform-Version"
|
||||
UserAgent HeaderKey = "Sec-CH-UA"
|
||||
|
||||
// Sonr Injected
|
||||
SonrAPIURL HeaderKey = "X-Sonr-API"
|
||||
SonrgRPCURL HeaderKey = "X-Sonr-GRPC"
|
||||
SonrRPCURL HeaderKey = "X-Sonr-RPC"
|
||||
SonrWSURL HeaderKey = "X-Sonr-WS"
|
||||
)
|
||||
|
||||
func (h HeaderKey) String() string {
|
||||
return string(h)
|
||||
}
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Utility Methods │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
func HeaderEquals(c echo.Context, key HeaderKey, value string) bool {
|
||||
return c.Response().Header().Get(key.String()) == value
|
||||
}
|
||||
|
||||
// HeaderExists returns true if the request has the header Key.
|
||||
func HeaderExists(c echo.Context, key HeaderKey) bool {
|
||||
return c.Response().Header().Get(key.String()) != ""
|
||||
}
|
||||
|
||||
// HeaderRead returns the header value for the Key.
|
||||
func HeaderRead(c echo.Context, key HeaderKey) string {
|
||||
return c.Response().Header().Get(key.String())
|
||||
}
|
||||
|
||||
// HeaderWrite sets the header value for the Key.
|
||||
func HeaderWrite(c echo.Context, key HeaderKey, value string) {
|
||||
c.Response().Header().Set(key.String(), value)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
"github.com/ipfs/boxo/path"
|
||||
"github.com/ipfs/kubo/client/rpc"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
api *rpc.HttpApi
|
||||
}
|
||||
|
||||
func NewClient() (Client, error) {
|
||||
api, err := rpc.NewLocalApi()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &client{api: api}, nil
|
||||
}
|
||||
|
||||
func (c *client) Add(data []byte) (string, error) {
|
||||
file := files.NewBytesFile(data)
|
||||
cidFile, err := c.api.Unixfs().Add(context.Background(), file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cidFile.String(), nil
|
||||
}
|
||||
|
||||
func (c *client) Get(cid string) ([]byte, error) {
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := c.api.Unixfs().Get(context.Background(), p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, ok := node.(files.File)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected node type: %T", node)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buf, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *client) IsPublished(ipns string) (bool, error) {
|
||||
_, err := c.api.Name().Resolve(context.Background(), ipns)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *client) Exists(cid string) (bool, error) {
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = c.api.Block().Stat(context.Background(), p)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *client) Pin(cid string) error {
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.api.Pin().Add(context.Background(), p)
|
||||
}
|
||||
|
||||
func (c *client) Unpin(cid string) error {
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.api.Pin().Rm(context.Background(), p)
|
||||
}
|
||||
|
||||
func (c *client) Publish(cid string, name string) (string, error) {
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result, err := c.api.Name().Publish(context.Background(), p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func (c *client) Ls(cid string) ([]string, error) {
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := c.api.Unixfs().Ls(context.Background(), p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for entry := range node {
|
||||
files = append(files, entry.Name)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
)
|
||||
|
||||
type file struct {
|
||||
files.File
|
||||
name string
|
||||
}
|
||||
|
||||
func (f *file) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func NewFile(name string, data []byte) File {
|
||||
return &file{File: files.NewBytesFile(data), name: name}
|
||||
}
|
||||
|
||||
func (c *client) AddFile(file File) (string, error) {
|
||||
cidFile, err := c.api.Unixfs().Add(context.Background(), file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cidFile.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
)
|
||||
|
||||
type Folder = files.Directory
|
||||
|
||||
func NewFolder(fs ...File) Folder {
|
||||
return files.NewMapDirectory(convertFilesToMap(fs))
|
||||
}
|
||||
|
||||
func (c *client) AddFolder(folder Folder) (string, error) {
|
||||
cidFile, err := c.api.Unixfs().Add(context.Background(), folder)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cidFile.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package ipfs
|
||||
|
||||
import "github.com/ipfs/boxo/files"
|
||||
|
||||
type Client interface {
|
||||
Add(data []byte) (string, error)
|
||||
AddFile(file File) (string, error)
|
||||
AddFolder(folder Folder) (string, error)
|
||||
Get(cid string) ([]byte, error)
|
||||
IsPublished(ipns string) (bool, error)
|
||||
Exists(cid string) (bool, error)
|
||||
Pin(cid string) error
|
||||
Unpin(cid string) error
|
||||
Publish(cid string, name string) (string, error)
|
||||
Ls(cid string) ([]string, error)
|
||||
}
|
||||
|
||||
type File interface {
|
||||
files.File
|
||||
Name() string
|
||||
}
|
||||
|
||||
func convertFilesToMap(vs []File) map[string]files.Node {
|
||||
m := make(map[string]files.Node)
|
||||
for _, f := range vs {
|
||||
m[f.Name()] = f
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package producer
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/crypto/ucan"
|
||||
"github.com/onsonr/sonr/crypto/ucan/store"
|
||||
"github.com/onsonr/sonr/pkg/common/ipfs"
|
||||
)
|
||||
|
||||
type ProducerContext struct {
|
||||
echo.Context
|
||||
// TokenParser is the attentuations assigned to the producer service
|
||||
TokenParser *ucan.TokenParser
|
||||
|
||||
// TokenStore is the token store used to store and retrieve tokens
|
||||
TokenStore store.IPFSTokenStore
|
||||
|
||||
// IPFSClient is the IPFS client used to resolve the UCAN
|
||||
IPFSClient ipfs.Client
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package producer
|
||||
|
||||
import (
|
||||
"github.com/onsonr/sonr/crypto/ucan"
|
||||
"github.com/onsonr/sonr/crypto/ucan/store"
|
||||
"github.com/onsonr/sonr/pkg/common/ipfs"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Middleware returns middleware to spawn controllers and validate UCAN tokens
|
||||
func Middleware(ipfs ipfs.Client, perms ucan.Permissions) echo.MiddlewareFunc {
|
||||
// Setup token store and parser
|
||||
store := store.NewIPFSTokenStore(ipfs)
|
||||
parser := ucan.NewTokenParser(perms.GetConstructor(), store, store)
|
||||
|
||||
// Return middleware
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := ProducerContext{
|
||||
Context: c,
|
||||
IPFSClient: ipfs,
|
||||
TokenParser: parser,
|
||||
TokenStore: store,
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package producer
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/crypto/mpc"
|
||||
)
|
||||
|
||||
func NewKeyset(c echo.Context) (mpc.Keyset, error) {
|
||||
ks, err := mpc.NewKeyset()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ks, nil
|
||||
}
|
||||
|
||||
//
|
||||
// func GetKeyset(c echo.Context) (mpc.Keyset, error) {
|
||||
// cc, ok := c.(*SignerContext)
|
||||
// if !ok {
|
||||
// return nil, errors.New("not an SignerContext")
|
||||
// }
|
||||
// if !cc.hasKeyset {
|
||||
// return nil, fmt.Errorf("keyset not found")
|
||||
// }
|
||||
// if cc.keyset == nil {
|
||||
// return nil, fmt.Errorf("keyset is nil")
|
||||
// }
|
||||
// return cc.keyset, nil
|
||||
// }
|
||||
//
|
||||
// func NewSource(c echo.Context) (mpc.KeyshareSource, error) {
|
||||
// cc, ok := c.(*SignerContext)
|
||||
// if !ok {
|
||||
// return nil, errors.New("not an SignerContext")
|
||||
// }
|
||||
// if !cc.hasKeyset {
|
||||
// return nil, fmt.Errorf("keyset not found")
|
||||
// }
|
||||
// if cc.keyset == nil {
|
||||
// return nil, fmt.Errorf("keyset is nil")
|
||||
// }
|
||||
// src, err := mpc.NewSource(cc.keyset)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// cc.signer = src
|
||||
// cc.hasSigner = true
|
||||
// return src, nil
|
||||
// }
|
||||
//
|
||||
// func GetSource(c echo.Context) (mpc.KeyshareSource, error) {
|
||||
// cc, ok := c.(*SignerContext)
|
||||
// if !ok {
|
||||
// return nil, errors.New("not an SignerContext")
|
||||
// }
|
||||
// if !cc.hasSigner {
|
||||
// return nil, fmt.Errorf("signer not found")
|
||||
// }
|
||||
// if cc.signer == nil {
|
||||
// return nil, fmt.Errorf("signer is nil")
|
||||
// }
|
||||
// return cc.signer, nil
|
||||
// }
|
||||
@@ -1 +0,0 @@
|
||||
package request
|
||||
@@ -1 +0,0 @@
|
||||
package request
|
||||
@@ -1 +0,0 @@
|
||||
package request
|
||||
@@ -1 +0,0 @@
|
||||
package request
|
||||
@@ -1 +0,0 @@
|
||||
package response
|
||||
@@ -1 +0,0 @@
|
||||
package response
|
||||
@@ -0,0 +1,18 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func RedirectOnError(target string) echo.HTTPErrorHandler {
|
||||
return func(err error, c echo.Context) {
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
// Log the error if needed
|
||||
c.Logger().Errorf("Error: %v", he.Message)
|
||||
}
|
||||
// Redirect to main site
|
||||
c.Redirect(http.StatusFound, target)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package response
|
||||
@@ -10,10 +10,10 @@ func RedirectLanding(c echo.Context) error {
|
||||
return c.Redirect(http.StatusFound, "http://localhost:3000")
|
||||
}
|
||||
|
||||
func RedirectVaultCID(c echo.Context, cid string) error {
|
||||
func RedirectIPFS(c echo.Context, cid string) error {
|
||||
return c.Redirect(http.StatusFound, cid)
|
||||
}
|
||||
|
||||
func RedirectVaultIPNS(c echo.Context, ipns string) error {
|
||||
return c.Redirect(http.StatusFound, ipns)
|
||||
func RedirectAuth(c echo.Context) error {
|
||||
return c.Redirect(http.StatusFound, "http://auth.localhost:3000")
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/onsonr/sonr/pkg/common/types"
|
||||
)
|
||||
|
||||
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 SessionCtx interface {
|
||||
ID() string
|
||||
|
||||
LoginOptions(credentials []CredDescriptor) *LoginOptions
|
||||
RegisterOptions(subject string) *RegisterOptions
|
||||
|
||||
GetData() *types.Session
|
||||
}
|
||||
|
||||
type (
|
||||
CredDescriptor = protocol.CredentialDescriptor
|
||||
LoginOptions = protocol.PublicKeyCredentialRequestOptions
|
||||
RegisterOptions = protocol.PublicKeyCredentialCreationOptions
|
||||
)
|
||||
|
||||
type BrowserName string
|
||||
|
||||
const (
|
||||
BrowserNameUnknown BrowserName = " Not A;Brand"
|
||||
BrowserNameChromium BrowserName = "Chromium"
|
||||
)
|
||||
|
||||
func (n BrowserName) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
type PeerRole string
|
||||
|
||||
const (
|
||||
RoleUnknown PeerRole = "none"
|
||||
RoleHway PeerRole = "hway"
|
||||
RoleMotr PeerRole = "motr"
|
||||
)
|
||||
|
||||
func (r PeerRole) Is(role PeerRole) bool {
|
||||
return r == role
|
||||
}
|
||||
|
||||
func (r PeerRole) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func Base64Encode(data []byte) string {
|
||||
return base64.RawURLEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
func Base64Decode(data string) ([]byte, error) {
|
||||
return base64.RawURLEncoding.DecodeString(data)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
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{}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Code generated from Pkl module `sonr.hway.Ctx`. DO NOT EDIT.
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/apple/pkl-go/pkl"
|
||||
)
|
||||
|
||||
type Ctx struct {
|
||||
}
|
||||
|
||||
// LoadFromPath loads the pkl module at the given path and evaluates it into a Ctx
|
||||
func LoadFromPath(ctx context.Context, path string) (ret *Ctx, err error) {
|
||||
evaluator, err := pkl.NewEvaluator(ctx, pkl.PreconfiguredOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
cerr := evaluator.Close()
|
||||
if err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
ret, err = Load(ctx, evaluator, pkl.FileSource(path))
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a Ctx
|
||||
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*Ctx, error) {
|
||||
var ret Ctx
|
||||
if err := evaluator.EvaluateModule(ctx, source, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Code generated from Pkl module `sonr.hway.Ctx`. DO NOT EDIT.
|
||||
package types
|
||||
|
||||
type Session struct {
|
||||
Id string `pkl:"id" json:"id,omitempty"`
|
||||
|
||||
Challenge string `pkl:"challenge" json:"challenge,omitempty"`
|
||||
|
||||
BrowserName string `pkl:"browserName" json:"browserName,omitempty"`
|
||||
|
||||
BrowserVersion string `pkl:"browserVersion" json:"browserVersion,omitempty"`
|
||||
|
||||
UserArchitecture string `pkl:"userArchitecture" json:"userArchitecture,omitempty"`
|
||||
|
||||
Platform string `pkl:"platform" json:"platform,omitempty"`
|
||||
|
||||
PlatformVersion string `pkl:"platformVersion" json:"platformVersion,omitempty"`
|
||||
|
||||
DeviceModel string `pkl:"deviceModel" json:"deviceModel,omitempty"`
|
||||
|
||||
IsMobile bool `pkl:"isMobile" json:"isMobile,omitempty"`
|
||||
|
||||
VaultAddress string `pkl:"vaultAddress" json:"vaultAddress,omitempty"`
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Code generated from Pkl module `sonr.hway.Ctx`. DO NOT EDIT.
|
||||
package types
|
||||
|
||||
import "github.com/apple/pkl-go/pkl"
|
||||
|
||||
func init() {
|
||||
pkl.RegisterMapping("sonr.hway.Ctx", Ctx{})
|
||||
pkl.RegisterMapping("sonr.hway.Ctx#Session", Session{})
|
||||
}
|
||||
Reference in New Issue
Block a user