// Package common contains the common types used by the Sonr network. // This package provides convenient helper methods for simplified usage // of the ipfs and webauthn modules by external libraries. package common import ( "encoding/base64" "encoding/json" "fmt" "github.com/sonr-io/common/ipfs" "github.com/sonr-io/common/webauthn" ) // IPFS Helper Functions // These functions provide simplified interfaces for common IPFS operations. // NewIPFSClient creates a new IPFS client connected to the local IPFS daemon. // Returns a detailed error if the connection fails, helping developers // identify configuration issues quickly. func NewIPFSClient() (ipfs.IPFSClient, error) { client, err := ipfs.GetClient() if err != nil { return nil, fmt.Errorf("failed to connect to local IPFS daemon: %w (ensure IPFS daemon is running)", err) } return client, nil } // MustGetIPFSClient creates a new IPFS client or panics if it fails. // This is useful for applications where IPFS connectivity is critical // and should cause immediate failure during initialization. func MustGetIPFSClient() ipfs.IPFSClient { client, err := NewIPFSClient() if err != nil { panic(err) } return client } // StoreData stores raw byte data in IPFS and returns the CID. // This is a convenience wrapper that handles client creation and data storage. func StoreData(data []byte) (cid string, err error) { client, err := NewIPFSClient() if err != nil { return "", err } return client.Add(data) } // RetrieveData retrieves content from IPFS using the provided CID. // This is a convenience wrapper that handles client creation and data retrieval. func RetrieveData(cid string) ([]byte, error) { client, err := NewIPFSClient() if err != nil { return nil, err } return client.Get(cid) } // IsIPFSDaemonRunning checks if the local IPFS daemon is running and accessible. // Returns true if the daemon is available, false otherwise. func IsIPFSDaemonRunning() bool { client, err := ipfs.GetClient() if err != nil { return false } // Try to get node status as a connectivity check _, err = client.NodeStatus() return err == nil } // StoreFile stores a file with metadata in IPFS and returns the CID. // This helper creates an ipfs.File from the provided name and data, // then stores it in IPFS. func StoreFile(name string, data []byte) (cid string, err error) { client, err := NewIPFSClient() if err != nil { return "", err } file := ipfs.NewFile(name, data) return client.AddFile(file) } // StoreFolder stores multiple files as a folder in IPFS and returns the root CID. // The files map should contain filename -> file data pairs. func StoreFolder(files map[string][]byte) (cid string, err error) { client, err := NewIPFSClient() if err != nil { return "", err } // Convert map to File slice ipfsFiles := make([]ipfs.File, 0, len(files)) for name, data := range files { ipfsFiles = append(ipfsFiles, ipfs.NewFile(name, data)) } folder := ipfs.NewFolder(ipfsFiles...) return client.AddFolder(folder) } // WebAuthn Helper Functions // These functions provide simplified interfaces for common WebAuthn operations. // NewChallenge generates a new cryptographic challenge for WebAuthn ceremonies. // The challenge is 32 bytes of random data, URL-safe base64 encoded. // Returns the challenge as a string that can be sent to clients. func NewChallenge() (string, error) { challenge, err := webauthn.CreateChallenge() if err != nil { return "", fmt.Errorf("failed to create WebAuthn challenge: %w", err) } return challenge.String(), nil } // VerifyOrigin checks if the provided origin matches any of the allowed origins. // This is a simplified wrapper around the webauthn origin verification logic. // Origins should be fully qualified (e.g., "https://example.com"). func VerifyOrigin(origin string, allowedOrigins []string) error { fqOrigin, err := webauthn.FullyQualifiedOrigin(origin) if err != nil { return fmt.Errorf("invalid origin format: %w", err) } for _, allowed := range allowedOrigins { if fqOrigin == allowed { return nil } } return fmt.Errorf("origin %s is not in the allowed origins list", fqOrigin) } // EncodeBase64URL encodes data to URL-safe base64 format (without padding). // This is the encoding format required by WebAuthn specifications. func EncodeBase64URL(data []byte) string { return base64.RawURLEncoding.EncodeToString(data) } // DecodeBase64URL decodes URL-safe base64 data (with or without padding). // This handles the base64 format used in WebAuthn responses. func DecodeBase64URL(encoded string) ([]byte, error) { decoded, err := base64.RawURLEncoding.DecodeString(encoded) if err != nil { return nil, fmt.Errorf("failed to decode base64 URL: %w", err) } return decoded, nil } // ChallengeLength returns the standard challenge length used for WebAuthn. // This can be useful for validation and testing. func ChallengeLength() int { return webauthn.ChallengeLength } // UnmarshalCredentialCreation unmarshals JSON data into a CredentialCreationResponse. // This is used when receiving a credential creation response from the client during registration. func UnmarshalCredentialCreation(data []byte) (*webauthn.CredentialCreationResponse, error) { var ccr webauthn.CredentialCreationResponse if err := decodeJSON(data, &ccr); err != nil { return nil, fmt.Errorf("failed to unmarshal credential creation response: %w", err) } return &ccr, nil } // MarshalCredentialCreation marshals a CredentialCreationResponse into JSON data. // This can be used for storing or transmitting credential creation responses. func MarshalCredentialCreation(ccr *webauthn.CredentialCreationResponse) ([]byte, error) { data, err := encodeJSON(ccr) if err != nil { return nil, fmt.Errorf("failed to marshal credential creation response: %w", err) } return data, nil } // ParseCredentialCreation parses and validates a credential creation response from JSON bytes. // Returns a parsed credential that has been validated and is ready for verification. func ParseCredentialCreation(data []byte) (*webauthn.ParsedCredentialCreationData, error) { parsed, err := webauthn.ParseCredentialCreationResponseBytes(data) if err != nil { return nil, fmt.Errorf("failed to parse credential creation response: %w", err) } return parsed, nil } // UnmarshalCredentialAssertion unmarshals JSON data into a CredentialAssertionResponse. // This is used when receiving an assertion response from the client during authentication. func UnmarshalCredentialAssertion(data []byte) (*webauthn.CredentialAssertionResponse, error) { var car webauthn.CredentialAssertionResponse if err := decodeJSON(data, &car); err != nil { return nil, fmt.Errorf("failed to unmarshal credential assertion response: %w", err) } return &car, nil } // MarshalCredentialAssertion marshals a CredentialAssertionResponse into JSON data. // This can be used for storing or transmitting credential assertion responses. func MarshalCredentialAssertion(car *webauthn.CredentialAssertionResponse) ([]byte, error) { data, err := encodeJSON(car) if err != nil { return nil, fmt.Errorf("failed to marshal credential assertion response: %w", err) } return data, nil } // ParseCredentialAssertion parses and validates a credential assertion response from JSON bytes. // Returns a parsed assertion that has been validated and is ready for verification. func ParseCredentialAssertion(data []byte) (*webauthn.ParsedCredentialAssertionData, error) { parsed, err := webauthn.ParseCredentialRequestResponseBytes(data) if err != nil { return nil, fmt.Errorf("failed to parse credential assertion response: %w", err) } return parsed, nil } // Helper functions for JSON encoding/decoding func decodeJSON(data []byte, v interface{}) error { return json.Unmarshal(data, v) } func encodeJSON(v interface{}) ([]byte, error) { return json.Marshal(v) }