mirror of
https://github.com/sonr-io/common.git
synced 2026-01-12 04:09:13 +00:00
refactor(common): simplify WebAuthn metadata handling
This commit is contained in:
221
common.go
221
common.go
@@ -1,2 +1,223 @@
|
||||
// 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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user