github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/profile/profile.go (about)

     1  // Package profile defines a qri peer profile
     2  package profile
     3  
     4  import (
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	logger "github.com/ipfs/go-log"
    10  	"github.com/libp2p/go-libp2p-core/crypto"
    11  	peer "github.com/libp2p/go-libp2p-core/peer"
    12  	ma "github.com/multiformats/go-multiaddr"
    13  	"github.com/qri-io/qri/auth/key"
    14  	"github.com/qri-io/qri/config"
    15  )
    16  
    17  var log = logger.Logger("profile")
    18  
    19  // Profile defines peer profile details
    20  type Profile struct {
    21  	// All Profiles are built on public key infrastructure
    22  	// PrivKey is the peer's private key, should only be present for the current peer
    23  	PrivKey crypto.PrivKey `json:"_,omitempty"`
    24  	// PubKey is the peer's public key
    25  	PubKey crypto.PubKey `json:"key,omitempty"`
    26  	// KeyID is the key identifier used for the keystore
    27  	KeyID key.ID `json:"key_id,omitempty"`
    28  
    29  	ID ID `json:"id"`
    30  	// Created timestamp
    31  	Created time.Time `json:"created,omitempty"`
    32  	// Updated timestamp
    33  	Updated time.Time `json:"updated,omitempty"`
    34  	// Peername a handle for the user. min 1 character, max 80. composed of [_,-,a-z,A-Z,1-9]
    35  	Peername string `json:"peername"`
    36  	// specifies weather this is a user or an organization
    37  	Type Type `json:"type"`
    38  	// user's email address
    39  	Email string `json:"email"`
    40  	// user name field. could be first[space]last, but not strictly enforced
    41  	Name string `json:"name"`
    42  	// user-filled description of self
    43  	Description string `json:"description"`
    44  	// url this user wants the world to click
    45  	HomeURL string `json:"homeUrl"`
    46  	// color this user likes to use as their theme color
    47  	Color string `json:"color"`
    48  	// Thumb path for user's thumbnail
    49  	Thumb string `json:"thumb"`
    50  	// Profile photo
    51  	Photo string `json:"photo"`
    52  	// Poster photo for users's profile page
    53  	Poster string `json:"poster"`
    54  	// Twitter is a  peer's twitter handle
    55  	Twitter string `json:"twitter"`
    56  	// Online indicates if this peer is currently connected to the network
    57  	Online bool `json:"online,omitempty"`
    58  
    59  	// PeerIDs lists any network PeerIDs associated with this profile
    60  	// in the form /network/peerID
    61  	PeerIDs []peer.ID `json:"peerIDs"`
    62  	// NetworkAddrs keeps a list of locations for this profile on the network as multiaddr strings
    63  	NetworkAddrs []ma.Multiaddr `json:"networkAddrs,omitempty"`
    64  }
    65  
    66  // NewProfile allocates a profile from a CodingProfile
    67  func NewProfile(p *config.ProfilePod) (pro *Profile, err error) {
    68  	pro = &Profile{}
    69  	err = pro.Decode(p)
    70  	return
    71  }
    72  
    73  // NewSparsePKProfile creates a sparsely-populated profile from a username and
    74  // private key
    75  func NewSparsePKProfile(username string, pk crypto.PrivKey) (*Profile, error) {
    76  	keyID, err := key.IDFromPrivKey(pk)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	id, err := IDB58Decode(keyID)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	return &Profile{
    87  		Peername: username,
    88  		ID:       id,
    89  		PubKey:   pk.GetPublic(),
    90  		PrivKey:  pk,
    91  	}, nil
    92  }
    93  
    94  // Decode turns a ProfilePod into a profile.Profile
    95  func (p *Profile) Decode(sp *config.ProfilePod) error {
    96  	id, err := IDB58Decode(sp.ID)
    97  	if err != nil {
    98  		return fmt.Errorf("parsing profile.ID %q: %w", sp.ID, err)
    99  	}
   100  
   101  	t, err := ParseType(sp.Type)
   102  	if err != nil {
   103  		return fmt.Errorf("parsing profileType %q: %w", sp.Type, err)
   104  	}
   105  
   106  	pids := make([]peer.ID, len(sp.PeerIDs))
   107  	for i, idstr := range sp.PeerIDs {
   108  		idstr = strings.TrimPrefix(idstr, "/ipfs/")
   109  		if id, err := peer.IDB58Decode(idstr); err == nil {
   110  			pids[i] = id
   111  		}
   112  	}
   113  
   114  	keyID := IDB58DecodeOrEmpty(sp.KeyID)
   115  	pro := Profile{
   116  		ID:          id,
   117  		KeyID:       key.ID(keyID),
   118  		Type:        t,
   119  		Peername:    sp.Peername,
   120  		Created:     sp.Created,
   121  		Updated:     sp.Updated,
   122  		Email:       sp.Email,
   123  		Name:        sp.Name,
   124  		Description: sp.Description,
   125  		HomeURL:     sp.HomeURL,
   126  		Color:       sp.Color,
   127  		Twitter:     sp.Twitter,
   128  		PeerIDs:     pids,
   129  	}
   130  
   131  	if sp.PrivKey != "" {
   132  		pro.PrivKey, err = key.DecodeB64PrivKey(sp.PrivKey)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		pro.PubKey = pro.PrivKey.GetPublic()
   137  		pro.KeyID = pro.GetKeyID()
   138  	}
   139  
   140  	if sp.Thumb != "" {
   141  		pro.Thumb = sp.Thumb
   142  	}
   143  
   144  	if sp.Poster != "" {
   145  		pro.Poster = sp.Poster
   146  	}
   147  
   148  	if sp.Photo != "" {
   149  		pro.Photo = sp.Photo
   150  	}
   151  
   152  	for _, addrStr := range sp.NetworkAddrs {
   153  		if maddr, err := ma.NewMultiaddr(addrStr); err == nil {
   154  			pro.NetworkAddrs = append(pro.NetworkAddrs, maddr)
   155  		}
   156  	}
   157  
   158  	*p = pro
   159  	return nil
   160  }
   161  
   162  // Encode returns a ProfilePod for a given profile
   163  func (p Profile) Encode() (*config.ProfilePod, error) {
   164  	pids := make([]string, len(p.PeerIDs))
   165  	for i, pid := range p.PeerIDs {
   166  		pids[i] = fmt.Sprintf("/ipfs/%s", pid.Pretty())
   167  	}
   168  	var addrs []string
   169  	for _, maddr := range p.NetworkAddrs {
   170  		addrs = append(addrs, maddr.String())
   171  	}
   172  	pp := &config.ProfilePod{
   173  		ID:           p.ID.Encode(),
   174  		Type:         p.Type.String(),
   175  		Peername:     p.Peername,
   176  		Created:      p.Created,
   177  		Updated:      p.Updated,
   178  		Email:        p.Email,
   179  		Name:         p.Name,
   180  		Description:  p.Description,
   181  		HomeURL:      p.HomeURL,
   182  		Color:        p.Color,
   183  		Twitter:      p.Twitter,
   184  		Poster:       p.Poster,
   185  		Photo:        p.Photo,
   186  		Thumb:        p.Thumb,
   187  		Online:       p.Online,
   188  		PeerIDs:      pids,
   189  		NetworkAddrs: addrs,
   190  	}
   191  	if p.PrivKey != nil {
   192  		var err error
   193  		pp.PrivKey, err = key.EncodePrivKeyB64(p.PrivKey)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  	}
   198  	return pp, nil
   199  }
   200  
   201  // ValidOwnerProfile checks if a profile can be used as an owner profile
   202  func (p *Profile) ValidOwnerProfile() error {
   203  	if p == nil {
   204  		return fmt.Errorf("profile cannot be nil")
   205  	}
   206  	if p.PrivKey == nil {
   207  		return fmt.Errorf("private key is required")
   208  	}
   209  	// TODO (b5) - confirm PrivKey is valid
   210  	return nil
   211  }
   212  
   213  // GetKeyID returns a KeyID assigned to the profile or falls back
   214  // to the profile ID if none is present
   215  func (p *Profile) GetKeyID() key.ID {
   216  	if p.KeyID == key.ID("") {
   217  		p.KeyID = key.ID(string(p.ID))
   218  	}
   219  	return p.KeyID
   220  }