2025-10-10 10:17:22 -04:00
|
|
|
// Package common contains the common types used by the Sonr network.
|
2025-10-10 10:38:37 -04:00
|
|
|
// This package provides convenient helper methods for simplified usage
|
|
|
|
|
// of the ipfs and webauthn modules by external libraries.
|
2025-10-10 10:17:22 -04:00
|
|
|
package common
|
2025-10-10 10:38:37 -04:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|