Files
common/common.go

224 lines
7.7 KiB
Go
Raw Permalink Normal View History

2025-10-10 10:17:22 -04:00
// 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.
2025-10-10 10:17:22 -04:00
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)
}