github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/seitan_invitelink.go (about)

     1  package teams
     2  
     3  import (
     4  	"context"
     5  	cryptorand "crypto/rand"
     6  	"fmt"
     7  	"regexp"
     8  	"strings"
     9  
    10  	"github.com/keybase/client/go/libkb"
    11  	"github.com/keybase/client/go/msgpack"
    12  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    13  )
    14  
    15  // documented in go/teams/seitan.go
    16  const SeitanEncodedIKeyInvitelinkLength = 28
    17  const seitanEncodedIKeyInvitelinkPlusOffset = 7
    18  
    19  func GenerateSeitanIKeyInvitelink() (ikey keybase1.SeitanIKeyInvitelink, err error) {
    20  	str, err := generateIKey(SeitanEncodedIKeyInvitelinkLength, seitanEncodedIKeyInvitelinkPlusOffset)
    21  	if err != nil {
    22  		return ikey, err
    23  	}
    24  	return keybase1.SeitanIKeyInvitelink(str), nil
    25  }
    26  
    27  func ParseIKeyInvitelinkFromString(token string) (ikey keybase1.SeitanIKeyInvitelink, err error) {
    28  	if len(token) != SeitanEncodedIKeyInvitelinkLength {
    29  		return ikey, fmt.Errorf("invalid token length: expected %d characters, got %d", SeitanEncodedIKeyLength, len(token))
    30  	}
    31  
    32  	return keybase1.SeitanIKeyInvitelink(strings.ToLower(token)), nil
    33  }
    34  
    35  type SeitanSIKeyInvitelink [SeitanScryptKeylen]byte
    36  
    37  func GenerateSIKeyInvitelink(ikey keybase1.SeitanIKeyInvitelink) (sikey SeitanSIKeyInvitelink, err error) {
    38  	buf, err := generateSIKey(ikey.String())
    39  	if err != nil {
    40  		return sikey, err
    41  	}
    42  	copy(sikey[:], buf)
    43  	return sikey, nil
    44  }
    45  
    46  func (sikey SeitanSIKeyInvitelink) generateMsgpackPayload() ([]byte, error) {
    47  	return msgpack.Encode(NewSeitanInviteIDPayload(SeitanVersionInvitelink))
    48  }
    49  
    50  func (sikey SeitanSIKeyInvitelink) GenerateTeamInviteID() (id SCTeamInviteID, err error) {
    51  	payload, err := sikey.generateMsgpackPayload()
    52  	if err != nil {
    53  		return id, err
    54  	}
    55  	return generateTeamInviteID(sikey[:], payload)
    56  }
    57  
    58  func (sikey SeitanSIKeyInvitelink) GenerateShortTeamInviteID() (id SCTeamInviteIDShort, err error) {
    59  	payload, err := sikey.generateMsgpackPayload()
    60  	if err != nil {
    61  		return id, err
    62  	}
    63  	return generateShortTeamInviteID(sikey[:], payload)
    64  }
    65  
    66  func generatePackedEncryptedKeyWithSecretKeyInvitelink(ikey keybase1.SeitanIKeyInvitelink,
    67  	secretKey keybase1.Bytes32, gen keybase1.PerTeamKeyGeneration, nonce keybase1.BoxNonce,
    68  	label keybase1.SeitanKeyLabel) (pkey SeitanPKey, encoded string, err error) {
    69  	var keyAndLabel keybase1.SeitanKeyAndLabelInvitelink
    70  	keyAndLabel.I = ikey
    71  	keyAndLabel.L = label
    72  
    73  	packedKeyAndLabel, err := msgpack.Encode(keybase1.NewSeitanKeyAndLabelWithInvitelink(keyAndLabel))
    74  	if err != nil {
    75  		return pkey, encoded, err
    76  	}
    77  	return packAndEncryptKeyWithSecretKey(secretKey, gen, nonce, packedKeyAndLabel, SeitanVersionInvitelink)
    78  }
    79  
    80  func GeneratePackedEncryptedKeyInvitelink(ctx context.Context, ikey keybase1.SeitanIKeyInvitelink,
    81  	team *Team, label keybase1.SeitanKeyLabel) (pkey SeitanPKey, encoded string, err error) {
    82  	appKey, err := team.SeitanInviteTokenKeyLatest(ctx)
    83  	if err != nil {
    84  		return pkey, encoded, err
    85  	}
    86  
    87  	var nonce keybase1.BoxNonce
    88  	if _, err = cryptorand.Read(nonce[:]); err != nil {
    89  		return pkey, encoded, err
    90  	}
    91  
    92  	return generatePackedEncryptedKeyWithSecretKeyInvitelink(ikey, appKey.Key,
    93  		appKey.KeyGeneration, nonce, label)
    94  }
    95  
    96  func GenerateSeitanInvitelinkAcceptanceKey(sikey []byte, uid keybase1.UID, eldestSeqno keybase1.Seqno, unixTimestampSeconds int64) (akey SeitanAKey, encoded string, err error) {
    97  	type AKeyPayload struct {
    98  		Stage       string         `codec:"stage" json:"stage"`
    99  		UID         keybase1.UID   `codec:"uid" json:"uid"`
   100  		EldestSeqno keybase1.Seqno `codec:"eldest_seqno" json:"eldest_seqno"`
   101  		CTime       int64          `codec:"ctime" json:"ctime"`
   102  		Version     SeitanVersion  `codec:"version" json:"version"`
   103  	}
   104  
   105  	akeyPayload, err := msgpack.Encode(AKeyPayload{
   106  		Stage:       "accept",
   107  		UID:         uid,
   108  		EldestSeqno: eldestSeqno,
   109  		CTime:       unixTimestampSeconds,
   110  		Version:     SeitanVersionInvitelink,
   111  	})
   112  	if err != nil {
   113  		return akey, encoded, err
   114  	}
   115  	return generateAcceptanceKey(akeyPayload, sikey)
   116  }
   117  
   118  // bound from SeitanEncodedIKeyInvitelinkLength
   119  var invitelinkIKeyRxx = regexp.MustCompile(`/i/t/([a-zA-Z0-9]{16,28})#([a-z0-9+]{16,28})`)
   120  
   121  func generateInvitelinkURLPrefix(mctx libkb.MetaContext) string {
   122  	// NOTE: if you change this url, change invitelinkIKeyRxx too!
   123  	return fmt.Sprintf("%s/i/t/", libkb.SiteURILookup[mctx.G().Env.GetRunMode()])
   124  }
   125  
   126  func GenerateInvitelinkURL(
   127  	mctx libkb.MetaContext,
   128  	ikey keybase1.SeitanIKeyInvitelink,
   129  	id SCTeamInviteIDShort,
   130  ) string {
   131  	return fmt.Sprintf("%s%s#%s", generateInvitelinkURLPrefix(mctx), id, ikey)
   132  }
   133  
   134  type TeamInviteLinkDetails struct {
   135  	libkb.AppStatusEmbed
   136  	InviterResetOrDel bool                                         `json:"inviter_reset_or_del"`
   137  	InviterUID        keybase1.UID                                 `json:"inviter_uid"`
   138  	InviterUsername   string                                       `json:"inviter_username"`
   139  	IsMember          bool                                         `json:"is_member"`
   140  	TeamAvatars       map[keybase1.AvatarFormat]keybase1.AvatarUrl `json:"team_avatars"`
   141  	TeamDescription   string                                       `json:"team_desc"`
   142  	TeamID            keybase1.TeamID                              `json:"team_id"`
   143  	TeamIsOpen        bool                                         `json:"team_is_open"`
   144  	TeamName          string                                       `json:"team_name"`
   145  	TeamNumMembers    int                                          `json:"team_num_members"`
   146  }
   147  
   148  func GetInviteLinkDetails(mctx libkb.MetaContext, inviteID keybase1.TeamInviteID) (info keybase1.InviteLinkDetails, err error) {
   149  	arg := libkb.APIArg{
   150  		Endpoint:    "team/get_invite_details",
   151  		SessionType: libkb.APISessionTypeOPTIONAL,
   152  		Args: libkb.HTTPArgs{
   153  			"invite_id": libkb.S{Val: string(inviteID)},
   154  		},
   155  	}
   156  
   157  	var resp TeamInviteLinkDetails
   158  	if err = mctx.G().API.GetDecode(mctx, arg, &resp); err != nil {
   159  		// The server knows about invite IDs (but not keys), so it is fine to
   160  		// put this in the log.
   161  		mctx.Debug("GetInviteLinkDetails: failed to get team invite details for %v: %s", inviteID, err)
   162  		return info, err
   163  	}
   164  
   165  	teamName, err := keybase1.TeamNameFromString(resp.TeamName)
   166  	if err != nil {
   167  		return info, err
   168  	}
   169  
   170  	return keybase1.InviteLinkDetails{
   171  		InviteID:          inviteID,
   172  		InviterResetOrDel: resp.InviterResetOrDel,
   173  		InviterUID:        resp.InviterUID,
   174  		InviterUsername:   resp.InviterUsername,
   175  		IsMember:          resp.IsMember,
   176  		TeamAvatars:       resp.TeamAvatars,
   177  		TeamDesc:          resp.TeamDescription,
   178  		TeamID:            resp.TeamID,
   179  		TeamIsOpen:        resp.TeamIsOpen,
   180  		TeamName:          teamName,
   181  		TeamNumMembers:    resp.TeamNumMembers,
   182  	}, nil
   183  }