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

     1  package teams
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/keybase/client/go/libkb"
     8  	"github.com/keybase/client/go/protocol/keybase1"
     9  )
    10  
    11  func ParseAndAcceptSeitanToken(mctx libkb.MetaContext, ui keybase1.TeamsUiInterface,
    12  	tok string) (wasSeitan bool, err error) {
    13  
    14  	seitanVersion, err := DeriveSeitanVersionFromToken(tok)
    15  	if err != nil {
    16  		return false, err
    17  	}
    18  	switch seitanVersion {
    19  	case SeitanVersion1:
    20  		wasSeitan, err = parseAndAcceptSeitanTokenV1(mctx, tok)
    21  	case SeitanVersion2:
    22  		wasSeitan, err = parseAndAcceptSeitanTokenV2(mctx, tok)
    23  	case SeitanVersionInvitelink:
    24  		wasSeitan, err = parseAndAcceptSeitanTokenInvitelink(mctx, ui, tok)
    25  	default:
    26  		wasSeitan = false
    27  		err = fmt.Errorf("Unexpected SeitanVersion %d", seitanVersion)
    28  	}
    29  	return wasSeitan, err
    30  }
    31  
    32  // Seitan V1:
    33  
    34  type acceptedSeitanV1 struct {
    35  	unixNow  int64
    36  	inviteID SCTeamInviteID
    37  	akey     SeitanAKey
    38  	encoded  string // base64 encoded akey
    39  }
    40  
    41  func generateAcceptanceSeitanV1(ikey SeitanIKey, uv keybase1.UserVersion, unixNow int64) (ret acceptedSeitanV1, err error) {
    42  	sikey, err := ikey.GenerateSIKey()
    43  	if err != nil {
    44  		return ret, err
    45  	}
    46  
    47  	inviteID, err := sikey.GenerateTeamInviteID()
    48  	if err != nil {
    49  		return ret, err
    50  	}
    51  
    52  	akey, encoded, err := sikey.GenerateAcceptanceKey(uv.Uid, uv.EldestSeqno, unixNow)
    53  	if err != nil {
    54  		return ret, err
    55  	}
    56  
    57  	return acceptedSeitanV1{
    58  		unixNow:  unixNow,
    59  		inviteID: inviteID,
    60  		akey:     akey,
    61  		encoded:  encoded,
    62  	}, nil
    63  }
    64  
    65  func postSeitanV1(mctx libkb.MetaContext, acceptedSeitan acceptedSeitanV1) error {
    66  	arg := apiArg("team/seitan")
    67  	arg.Args.Add("akey", libkb.S{Val: acceptedSeitan.encoded})
    68  	arg.Args.Add("now", libkb.I64{Val: acceptedSeitan.unixNow})
    69  	arg.Args.Add("invite_id", libkb.S{Val: string(acceptedSeitan.inviteID)})
    70  	_, err := mctx.G().API.Post(mctx, arg)
    71  	return err
    72  }
    73  
    74  func parseAndAcceptSeitanTokenV1(mctx libkb.MetaContext, tok string) (wasSeitan bool, err error) {
    75  	seitan, err := ParseIKeyFromString(tok)
    76  	if err != nil {
    77  		mctx.Debug("ParseIKeyFromString error: %s", err)
    78  		mctx.Debug("returning TeamInviteBadToken instead")
    79  		return false, libkb.TeamInviteBadTokenError{}
    80  	}
    81  	uv := mctx.CurrentUserVersion()
    82  	unixNow := mctx.G().Clock().Now().Unix()
    83  	acpt, err := generateAcceptanceSeitanV1(seitan, uv, unixNow)
    84  	if err != nil {
    85  		return true, err
    86  	}
    87  	err = postSeitanV1(mctx, acpt)
    88  	return true, err
    89  }
    90  
    91  // Seitan V2:
    92  
    93  type acceptedSeitanV2 struct {
    94  	now      keybase1.Time
    95  	inviteID SCTeamInviteID
    96  	sig      SeitanSig
    97  	encoded  string // base64 encoded sig
    98  }
    99  
   100  func generateAcceptanceSeitanV2(ikey SeitanIKeyV2, uv keybase1.UserVersion, timeNow keybase1.Time) (ret acceptedSeitanV2, err error) {
   101  	sikey, err := ikey.GenerateSIKey()
   102  	if err != nil {
   103  		return ret, err
   104  	}
   105  
   106  	inviteID, err := sikey.GenerateTeamInviteID()
   107  	if err != nil {
   108  		return ret, err
   109  	}
   110  
   111  	sig, encoded, err := sikey.GenerateSignature(uv.Uid, uv.EldestSeqno, inviteID, timeNow)
   112  	if err != nil {
   113  		return ret, err
   114  	}
   115  
   116  	return acceptedSeitanV2{
   117  		now:      timeNow,
   118  		inviteID: inviteID,
   119  		sig:      sig,
   120  		encoded:  encoded,
   121  	}, nil
   122  }
   123  
   124  func postSeitanV2(mctx libkb.MetaContext, acceptedSeitan acceptedSeitanV2) error {
   125  	arg := apiArg("team/seitan_v2")
   126  	arg.Args.Add("sig", libkb.S{Val: acceptedSeitan.encoded})
   127  	arg.Args.Add("now", libkb.HTTPTime{Val: acceptedSeitan.now})
   128  	arg.Args.Add("invite_id", libkb.S{Val: string(acceptedSeitan.inviteID)})
   129  	_, err := mctx.G().API.Post(mctx, arg)
   130  	return err
   131  }
   132  
   133  func parseAndAcceptSeitanTokenV2(mctx libkb.MetaContext, tok string) (wasSeitan bool, err error) {
   134  	seitan, err := ParseIKeyV2FromString(tok)
   135  	if err != nil {
   136  		mctx.Debug("ParseIKeyV2FromString error: %s", err)
   137  		mctx.Debug("returning TeamInviteBadToken instead")
   138  		return false, libkb.TeamInviteBadTokenError{}
   139  	}
   140  	uv := mctx.CurrentUserVersion()
   141  	timeNow := keybase1.ToTime(mctx.G().Clock().Now())
   142  	acpt, err := generateAcceptanceSeitanV2(seitan, uv, timeNow)
   143  	if err != nil {
   144  		return true, nil
   145  	}
   146  	err = postSeitanV2(mctx, acpt)
   147  	return true, err
   148  
   149  }
   150  
   151  // Seitan V3 (Seitan Invite Link):
   152  
   153  type acceptedSeitanInviteLink struct {
   154  	unixNow  int64
   155  	inviteID SCTeamInviteID
   156  	akey     SeitanAKey
   157  	encoded  string // base64 encoded akey
   158  }
   159  
   160  func generateAcceptanceSeitanInviteLink(ikey keybase1.SeitanIKeyInvitelink, uv keybase1.UserVersion, unixNow int64) (ret acceptedSeitanInviteLink, err error) {
   161  	sikey, err := GenerateSIKeyInvitelink(ikey)
   162  	if err != nil {
   163  		return ret, err
   164  	}
   165  
   166  	inviteID, err := sikey.GenerateTeamInviteID()
   167  	if err != nil {
   168  		return ret, err
   169  	}
   170  
   171  	akey, encoded, err := GenerateSeitanInvitelinkAcceptanceKey(sikey[:], uv.Uid, uv.EldestSeqno, unixNow)
   172  	if err != nil {
   173  		return ret, err
   174  	}
   175  
   176  	return acceptedSeitanInviteLink{
   177  		unixNow:  unixNow,
   178  		inviteID: inviteID,
   179  		akey:     akey,
   180  		encoded:  encoded,
   181  	}, nil
   182  }
   183  
   184  func postSeitanInviteLink(mctx libkb.MetaContext, acceptedSeitan acceptedSeitanInviteLink) error {
   185  	arg := apiArg("team/seitan_invitelink")
   186  	arg.Args.Add("akey", libkb.S{Val: acceptedSeitan.encoded})
   187  	arg.Args.Add("unix_timestamp", libkb.I64{Val: acceptedSeitan.unixNow})
   188  	arg.Args.Add("invite_id", libkb.S{Val: string(acceptedSeitan.inviteID)})
   189  	_, err := mctx.G().API.Post(mctx, arg)
   190  	return err
   191  }
   192  
   193  // presentInviteLinkInUI calls the TeamsUI ConfirmInviteLinkAccept which should
   194  // present a modal or CLI prompt for the user, where they can confirm or
   195  // decline accepting the invite.
   196  func presentInviteLinkInUI(mctx libkb.MetaContext, ui keybase1.TeamsUiInterface, inviteID SCTeamInviteID) error {
   197  	teamInviteID, err := inviteID.TeamInviteID()
   198  	if err != nil {
   199  		return err
   200  	}
   201  	details, err := GetInviteLinkDetails(mctx, teamInviteID)
   202  	if err != nil {
   203  		mctx.Debug("failed to get invite details for %v: %v", inviteID, err)
   204  		return err
   205  	}
   206  	if details.IsMember {
   207  		return libkb.AppStatusError{
   208  			Code: libkb.SCTeamMemberExists,
   209  			Name: "TEAM_MEMBER_EXISTS",
   210  			Desc: fmt.Sprintf("You're already a member of %s!", details.TeamName),
   211  		}
   212  	}
   213  	accepted, err := ui.ConfirmInviteLinkAccept(mctx.Ctx(), keybase1.ConfirmInviteLinkAcceptArg{Details: details})
   214  	if err != nil {
   215  		mctx.Debug("failed to confirm invite link %v: %v", inviteID, err)
   216  		return err
   217  	}
   218  	if !accepted {
   219  		mctx.Debug("invite link %v not accepted", inviteID)
   220  		return errors.New("invite acceptance not confirmed")
   221  	}
   222  	return nil
   223  }
   224  
   225  func parseAndAcceptSeitanTokenInvitelink(mctx libkb.MetaContext, ui keybase1.TeamsUiInterface,
   226  	tok string) (wasSeitan bool, err error) {
   227  
   228  	seitan, err := ParseIKeyInvitelinkFromString(tok)
   229  	if err != nil {
   230  		mctx.Debug("ParseIKeyInvitelinkFromString error: %s", err)
   231  		mctx.Debug("returning TeamInviteBadToken instead")
   232  		return false, libkb.TeamInviteBadTokenError{}
   233  	}
   234  	uv := mctx.CurrentUserVersion()
   235  	unixNow := mctx.G().Clock().Now().Unix()
   236  	acpt, err := generateAcceptanceSeitanInviteLink(seitan, uv, unixNow)
   237  	if err != nil {
   238  		return true, err
   239  	}
   240  
   241  	if ui != nil {
   242  		err = presentInviteLinkInUI(mctx, ui, acpt.inviteID)
   243  		if err != nil {
   244  			return true, err
   245  		}
   246  	} else {
   247  		mctx.Debug("`ui` is nil, skipping presentInviteLinkInUI")
   248  	}
   249  
   250  	err = postSeitanInviteLink(mctx, acpt)
   251  	return true, err
   252  }