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:
Prad Nukala
2024-12-05 20:36:58 -05:00
committed by GitHub
parent e62ec45e82
commit bd51342fdf
256 changed files with 10823 additions and 7096 deletions
-36
View File
@@ -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",
}
-26
View File
@@ -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
}
}
+35
View File
@@ -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)
}
}
}
+41
View File
@@ -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
}
+45
View File
@@ -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)
}
+57
View File
@@ -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
-35
View File
@@ -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)
}
-77
View File
@@ -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
}
+124
View File
@@ -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
}
-27
View File
@@ -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)
}
-22
View File
@@ -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)
}
+52
View File
@@ -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)
}
+121
View File
@@ -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
}
+28
View File
@@ -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
}
+21
View File
@@ -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
}
+29
View File
@@ -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
}
+20
View File
@@ -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
}
+29
View File
@@ -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)
}
}
}
+63
View File
@@ -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
View File
@@ -1 +0,0 @@
package request
-1
View File
@@ -1 +0,0 @@
package request
-1
View File
@@ -1 +0,0 @@
package request
-1
View File
@@ -1 +0,0 @@
package request
-1
View File
@@ -1 +0,0 @@
package response
-1
View File
@@ -1 +0,0 @@
package response
+18
View File
@@ -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
View File
@@ -1 +0,0 @@
package response
+3 -3
View File
@@ -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")
}
-69
View File
@@ -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)
}
-60
View File
@@ -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{}
}
-42
View File
@@ -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
}
-68
View File
@@ -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)
}
-84
View File
@@ -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
}
-192
View File
@@ -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
}
-36
View File
@@ -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
}
-24
View File
@@ -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"`
}
-9
View File
@@ -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{})
}