cleaner invitation step messages
This commit is contained in:
@@ -133,8 +133,12 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
||||
}
|
||||
// check if invitation answer (step-2 answer waiting for the initiator)
|
||||
if fromServerMessage.Invitation != nil {
|
||||
peer, _, invErr := invmsgs.Step3InitiatorFinalizesInviteeAndCreatesContactCard(fromServerMessage.Invitation)
|
||||
if invErr == nil && peer != nil {
|
||||
invBytes, marshalErr := proto.Marshal(fromServerMessage.Invitation)
|
||||
if marshalErr == nil {
|
||||
step3Bytes, invErr := invmsgs.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invBytes)
|
||||
if invErr == nil && step3Bytes != nil {
|
||||
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(fromServerMessage.Invitation.Uuid)
|
||||
if peer != nil {
|
||||
// Auto-send step-3 CC to invitee's servers.
|
||||
msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId)
|
||||
if sendErr == nil {
|
||||
@@ -146,6 +150,8 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Chat messages
|
||||
if len(fromServerMessage.Chat) > 0 {
|
||||
for _, packedUserMessage := range fromServerMessage.Chat {
|
||||
@@ -169,7 +175,9 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
||||
|
||||
// Handle invitation step 3: initiator's full ContactCard arriving at the invitee.
|
||||
if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 {
|
||||
finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(usermsg)
|
||||
invBytes, marshalErr := proto.Marshal(usermsg.Invitation)
|
||||
if marshalErr == nil {
|
||||
finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(invBytes)
|
||||
if finalErr == nil && finalizedPeer != nil {
|
||||
// Auto-send step-4 confirmation to initiator's servers.
|
||||
step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId)
|
||||
@@ -181,6 +189,7 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -3,28 +3,32 @@ package files
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.redroom.link/yves/meowlib"
|
||||
"forge.redroom.link/yves/meowlib/client"
|
||||
"forge.redroom.link/yves/meowlib/client/invitation/messages"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Step1Write creates a pending peer and writes the InvitationInitPayload to a file.
|
||||
// format: "qr" writes a QR-code PNG; anything else writes a compressed binary .mwiv file.
|
||||
func Step1Write(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, error) {
|
||||
payload, peer, err := messages.Step1InitiatorCreatesInviteeAndTempKey(contactName, myNickname, invitationMessage, serverUids)
|
||||
func Step1Write(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) error {
|
||||
payloadBytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey(contactName, myNickname, invitationMessage, serverUids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
var payload meowlib.InvitationInitPayload
|
||||
if err := proto.Unmarshal(payloadBytes, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
mynick := myNickname
|
||||
if mynick == "" {
|
||||
mynick = client.GetConfig().GetIdentity().Nickname
|
||||
}
|
||||
c := client.GetConfig()
|
||||
if format == "qr" {
|
||||
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png"
|
||||
if err := payload.WriteQr(filename); err != nil {
|
||||
return nil, err
|
||||
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".png"
|
||||
return payload.WriteQr(filename)
|
||||
}
|
||||
} else {
|
||||
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv"
|
||||
if err := payload.WriteCompressed(filename); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return peer, nil
|
||||
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".mwiv"
|
||||
return payload.WriteCompressed(filename)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
)
|
||||
|
||||
// Step2ReadAndAnswer reads an InvitationInitPayload from a .mwiv file, creates the
|
||||
// invitee's peer entry, and writes the encrypted ContactCard (PackedUserMessage) to a
|
||||
// .mwiv file for the initiator to pick up and process in step 3.
|
||||
// invitee's peer entry, and writes the serialized Invitation (step=2) to a .mwiv file
|
||||
// for the initiator to pick up and process in step 3.
|
||||
func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname string, serverUids []string) error {
|
||||
if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
|
||||
return err
|
||||
@@ -29,35 +29,23 @@ func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname strin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payloadBytes, err := proto.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mynick := myNickname
|
||||
if mynick == "" {
|
||||
mynick = client.GetConfig().GetIdentity().Nickname
|
||||
}
|
||||
|
||||
packed, peer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, nickname, mynick, serverUids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap the PackedUserMessage in an Invitation so the initiator (step3) has the
|
||||
// invitee's public key available for signature verification without an extra file.
|
||||
packedBytes, err := proto.Marshal(packed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invitation := &meowlib.Invitation{
|
||||
Uuid: peer.InvitationId,
|
||||
Step: 2,
|
||||
From: peer.MyIdentity.Public,
|
||||
Payload: packedBytes,
|
||||
}
|
||||
out, err := proto.Marshal(invitation)
|
||||
// messages.Step2 returns a serialized Invitation ready to write directly to file.
|
||||
invBytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payloadBytes, nickname, mynick, serverUids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := client.GetConfig()
|
||||
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
|
||||
return os.WriteFile(filename, out, 0600)
|
||||
return os.WriteFile(filename, invBytes, 0600)
|
||||
}
|
||||
|
||||
@@ -29,114 +29,108 @@ func setupIdentity(t *testing.T, nickname string) (*client.Identity, func()) {
|
||||
return id, cleanup
|
||||
}
|
||||
|
||||
// TestStep2ProducesPackedUserMessage verifies that Step2 returns a PackedUserMessage
|
||||
// (not just a peer) and that the message is encrypted with the initiator's temp key
|
||||
// so Step3 can decrypt it.
|
||||
func TestStep2ProducesPackedUserMessage(t *testing.T) {
|
||||
// TestStep1ReturnsBinaryPayload verifies that Step1 returns non-empty bytes that
|
||||
// deserialise to a valid InvitationInitPayload, and that the pending peer is stored
|
||||
// with only a temp keypair (no real identity keys yet).
|
||||
func TestStep1ReturnsBinaryPayload(t *testing.T) {
|
||||
cfg := client.GetConfig()
|
||||
cfg.SetMemPass("testpass") //nolint:errcheck
|
||||
|
||||
// --- STEP 1: initiator creates temp keypair and payload ---
|
||||
initiator, cleanInit := setupIdentity(t, "alice")
|
||||
defer cleanInit()
|
||||
|
||||
payload, initPeer, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
|
||||
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, payload)
|
||||
require.NotEmpty(t, step1Bytes)
|
||||
|
||||
var payload meowlib.InvitationInitPayload
|
||||
require.NoError(t, proto.Unmarshal(step1Bytes, &payload))
|
||||
assert.NotEmpty(t, payload.Uuid)
|
||||
assert.NotEmpty(t, payload.PublicKey)
|
||||
assert.Equal(t, "Alice", payload.Name)
|
||||
assert.Equal(t, "Hello!", payload.InvitationMessage)
|
||||
|
||||
// Peer is saved with temp keypair only — no real identity keys yet.
|
||||
initPeer := initiator.Peers.GetFromName("Bob")
|
||||
require.NotNil(t, initPeer)
|
||||
// Initiator has only the temp keypair at this stage.
|
||||
assert.Nil(t, initPeer.MyIdentity)
|
||||
assert.NotNil(t, initPeer.InvitationKp)
|
||||
}
|
||||
|
||||
// --- STEP 2: invitee receives payload, creates peer, returns packed message ---
|
||||
_, cleanInvitee := setupIdentity(t, "bob")
|
||||
// TestFullInvitationFlow runs all four steps end-to-end, passing the binary output of
|
||||
// each step directly to the next, and verifies that both peers end up with each other's
|
||||
// real keys after the exchange completes.
|
||||
func TestFullInvitationFlow(t *testing.T) {
|
||||
cfg := client.GetConfig()
|
||||
cfg.SetMemPass("testpass") //nolint:errcheck
|
||||
|
||||
// --- STEP 1: initiator creates temp keypair, gets binary payload ---
|
||||
initiator, cleanInit := setupIdentity(t, "alice2")
|
||||
defer cleanInit()
|
||||
|
||||
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, step1Bytes)
|
||||
|
||||
// --- STEP 2: invitee creates peer, returns serialized Invitation (step=2) ---
|
||||
invitee, cleanInvitee := setupIdentity(t, "bob2")
|
||||
defer cleanInvitee()
|
||||
|
||||
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(
|
||||
payload, "Alice", "Bob", nil,
|
||||
)
|
||||
step2Bytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(step1Bytes, "Alice", "Bob", nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, packed, "step2 must return a PackedUserMessage, not just a peer")
|
||||
require.NotEmpty(t, step2Bytes, "step2 must return non-empty invitation bytes")
|
||||
|
||||
// Invitee now has a peer with full keypairs.
|
||||
inviteePeer := invitee.Peers.GetFromName("Alice")
|
||||
require.NotNil(t, inviteePeer)
|
||||
|
||||
// The packed message destination is the initiator's temp key (used as lookup key).
|
||||
assert.Equal(t, payload.PublicKey, packed.Destination)
|
||||
|
||||
// The invitee peer has full keypairs now.
|
||||
assert.NotNil(t, inviteePeer.MyIdentity)
|
||||
assert.NotNil(t, inviteePeer.MyEncryptionKp)
|
||||
assert.NotNil(t, inviteePeer.MyLookupKp)
|
||||
|
||||
// --- STEP 3: initiator decrypts invitee's packed message and finalises ---
|
||||
// The step-2 wire format is a serialized Invitation.
|
||||
var inv2 meowlib.Invitation
|
||||
require.NoError(t, proto.Unmarshal(step2Bytes, &inv2))
|
||||
assert.EqualValues(t, 2, inv2.Step)
|
||||
assert.NotEmpty(t, inv2.Uuid)
|
||||
assert.Equal(t, inviteePeer.MyIdentity.Public, inv2.From)
|
||||
assert.NotEmpty(t, inv2.Payload)
|
||||
|
||||
// --- STEP 3: initiator decrypts invitee's card, returns serialized Invitation (step=3) ---
|
||||
cfg.SetIdentity(initiator)
|
||||
|
||||
// Simulate how the server delivers the step-2 answer: marshal the PackedUserMessage
|
||||
// into an Invitation.Payload.
|
||||
packedBytes, err := proto.Marshal(packed)
|
||||
step3Bytes, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(step2Bytes)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, step3Bytes)
|
||||
|
||||
invitation := &meowlib.Invitation{
|
||||
Uuid: payload.Uuid,
|
||||
Step: 2,
|
||||
From: inviteePeer.MyIdentity.Public,
|
||||
Payload: packedBytes,
|
||||
}
|
||||
|
||||
peer, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, peer)
|
||||
require.NotNil(t, myCC, "step3 must produce the initiator's ContactCard to send to invitee")
|
||||
|
||||
// Initiator's peer must now hold invitee's real keys.
|
||||
assert.Equal(t, inviteePeer.MyIdentity.Public, peer.ContactPublicKey)
|
||||
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, peer.ContactEncryption)
|
||||
assert.Equal(t, inviteePeer.MyLookupKp.Public, peer.ContactLookupKey)
|
||||
assert.Nil(t, peer.InvitationKp, "temp keypair must be cleared after step3")
|
||||
assert.NotEmpty(t, myCC.DrRootKey)
|
||||
assert.NotEmpty(t, myCC.DrPublicKey)
|
||||
}
|
||||
|
||||
// TestStep2Step3RoundTripPayload verifies that the PackedUserMessage produced by step2
|
||||
// actually carries the invitee's ContactCard when decrypted by the initiator.
|
||||
func TestStep2Step3RoundTripPayload(t *testing.T) {
|
||||
cfg := client.GetConfig()
|
||||
cfg.SetMemPass("testpass") //nolint:errcheck
|
||||
|
||||
initiator, cleanInit := setupIdentity(t, "alice2")
|
||||
defer cleanInit()
|
||||
|
||||
payload, _, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, cleanInvitee := setupIdentity(t, "bob2")
|
||||
defer cleanInvitee()
|
||||
|
||||
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, "Alice", "Bob", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Confirm the message serialises cleanly (transport simulation).
|
||||
packedBytes, err := proto.Marshal(packed)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, packedBytes)
|
||||
|
||||
// Switch back to initiator and run step3.
|
||||
cfg.SetIdentity(initiator)
|
||||
|
||||
var roundTripped meowlib.PackedUserMessage
|
||||
require.NoError(t, proto.Unmarshal(packedBytes, &roundTripped))
|
||||
|
||||
invitation := &meowlib.Invitation{
|
||||
Uuid: payload.Uuid,
|
||||
Step: 2,
|
||||
From: inviteePeer.MyIdentity.Public,
|
||||
Payload: packedBytes,
|
||||
}
|
||||
|
||||
_, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, myCC)
|
||||
|
||||
// The initiator's CC must reference the invitee's invitation ID so the invitee
|
||||
// can match it when step4 arrives.
|
||||
assert.Equal(t, payload.Uuid, myCC.InvitationId)
|
||||
// Initiator's peer must now hold invitee's real keys; temp keypair must be gone.
|
||||
initPeer := initiator.Peers.GetFromName("Bob")
|
||||
require.NotNil(t, initPeer)
|
||||
assert.Equal(t, inviteePeer.MyIdentity.Public, initPeer.ContactPublicKey)
|
||||
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, initPeer.ContactEncryption)
|
||||
assert.Equal(t, inviteePeer.MyLookupKp.Public, initPeer.ContactLookupKey)
|
||||
assert.Nil(t, initPeer.InvitationKp, "temp keypair must be cleared after step3")
|
||||
assert.NotEmpty(t, initPeer.DrKpPublic)
|
||||
assert.NotEmpty(t, initPeer.DrRootKey)
|
||||
|
||||
// The step-3 wire format is a serialized Invitation.
|
||||
var inv3 meowlib.Invitation
|
||||
require.NoError(t, proto.Unmarshal(step3Bytes, &inv3))
|
||||
assert.EqualValues(t, 3, inv3.Step)
|
||||
assert.NotEmpty(t, inv3.Uuid)
|
||||
assert.NotEmpty(t, inv3.Payload)
|
||||
|
||||
// --- STEP 4: invitee finalises initiator ---
|
||||
cfg.SetIdentity(invitee)
|
||||
|
||||
finalPeer, err := messages.Step4InviteeFinalizesInitiator(step3Bytes)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, finalPeer)
|
||||
|
||||
// Invitee's peer must now hold initiator's real keys and the invitation must be complete.
|
||||
assert.Equal(t, initPeer.MyIdentity.Public, finalPeer.ContactPublicKey)
|
||||
assert.Equal(t, initPeer.MyEncryptionKp.Public, finalPeer.ContactEncryption)
|
||||
assert.Equal(t, initPeer.MyLookupKp.Public, finalPeer.ContactLookupKey)
|
||||
assert.False(t, finalPeer.InvitationPending(), "invitation must be fully finalized")
|
||||
assert.NotEmpty(t, finalPeer.DrRootKey)
|
||||
assert.NotEmpty(t, finalPeer.ContactDrPublicKey)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package messages
|
||||
|
||||
import (
|
||||
"forge.redroom.link/yves/meowlib"
|
||||
"forge.redroom.link/yves/meowlib/client"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Step1InitiatorCreatesInviteeAndTempKey creates a minimal pending peer and a temporary
|
||||
// keypair, and returns the InvitationInitPayload to be transmitted to the invitee
|
||||
// via any transport (file, QR, server…).
|
||||
func Step1InitiatorCreatesInviteeAndTempKey(contactName string, myNickname string, invitationMessage string, serverUids []string) (*meowlib.InvitationInitPayload, *client.Peer, error) {
|
||||
// keypair, and returns the serialized InvitationInitPayload bytes to be transmitted to
|
||||
// the invitee via any transport (file, QR, server…). The peer is already persisted by
|
||||
// InvitationStep1 so no peer reference is returned.
|
||||
func Step1InitiatorCreatesInviteeAndTempKey(contactName string, myNickname string, invitationMessage string, serverUids []string) ([]byte, error) {
|
||||
mynick := myNickname
|
||||
if mynick == "" {
|
||||
mynick = client.GetConfig().GetIdentity().Nickname
|
||||
}
|
||||
payload, peer, err := client.GetConfig().GetIdentity().InvitationStep1(mynick, contactName, serverUids, invitationMessage)
|
||||
payload, _, err := client.GetConfig().GetIdentity().InvitationStep1(mynick, contactName, serverUids, invitationMessage)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
client.GetConfig().GetIdentity().Save()
|
||||
return payload, peer, nil
|
||||
return proto.Marshal(payload)
|
||||
}
|
||||
|
||||
@@ -3,31 +3,45 @@ package messages
|
||||
import (
|
||||
"forge.redroom.link/yves/meowlib"
|
||||
"forge.redroom.link/yves/meowlib/client"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Step2InviteeCreatesInitiatorAndEncryptedContactCard creates the invitee's peer entry
|
||||
// from an InvitationInitPayload, then builds the invitee's ContactCard and returns it
|
||||
// as a PackedUserMessage asymmetrically encrypted with the initiator's temporary public
|
||||
// key. The packed message is ready to be transmitted to the initiator via any transport
|
||||
// (file, QR, server…); Step3InitiatorFinalizesInviteeAndCreatesContactCard on the
|
||||
// initiator side will decrypt and process it.
|
||||
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*meowlib.PackedUserMessage, *client.Peer, error) {
|
||||
// Step2InviteeCreatesInitiatorAndEncryptedContactCard deserialises the step-1 payload bytes,
|
||||
// creates the invitee's peer entry, builds and encrypts the invitee's ContactCard, and returns
|
||||
// a serialized Invitation (step=2) whose Payload is the PackedUserMessage encrypted with the
|
||||
// initiator's temporary public key. The bytes are transport-ready and consumed directly by
|
||||
// Step3InitiatorFinalizesInviteeAndCreatesContactCard.
|
||||
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payloadBytes []byte, nickname string, myNickname string, serverUids []string) ([]byte, error) {
|
||||
mynick := myNickname
|
||||
if mynick == "" {
|
||||
mynick = client.GetConfig().GetIdentity().Nickname
|
||||
}
|
||||
peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload)
|
||||
var payload meowlib.InvitationInitPayload
|
||||
if err := proto.Unmarshal(payloadBytes, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, &payload)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
usermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
packed, err := peer.ProcessOutboundUserMessage(usermsg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
packedBytes, err := proto.Marshal(packed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inv := &meowlib.Invitation{
|
||||
Uuid: payload.Uuid,
|
||||
Step: 2,
|
||||
From: peer.MyIdentity.Public,
|
||||
Payload: packedBytes,
|
||||
}
|
||||
client.GetConfig().GetIdentity().Save()
|
||||
return packed, peer, nil
|
||||
return proto.Marshal(inv)
|
||||
}
|
||||
|
||||
@@ -8,39 +8,55 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Step3InitiatorFinalizesInviteeAndCreatesContactCard is called by the initiator when a
|
||||
// step-2 answer (invitee's encrypted ContactCard) arrives. It decrypts the card, upgrades
|
||||
// the invitee's peer entry with the real keys, and returns the initiator's own ContactCard
|
||||
// ready to be sent to the invitee via any transport.
|
||||
func Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation *meowlib.Invitation) (*client.Peer, *meowlib.ContactCard, error) {
|
||||
// Step3InitiatorFinalizesInviteeAndCreatesContactCard is called by the initiator when the
|
||||
// step-2 answer (serialized Invitation bytes) arrives. It decrypts the invitee's ContactCard,
|
||||
// upgrades the pending peer with the invitee's real keys, and returns a serialized Invitation
|
||||
// (step=3) whose Payload is the initiator's ContactCard, ready to be consumed directly by
|
||||
// Step4InviteeFinalizesInitiator on the invitee side.
|
||||
func Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitationBytes []byte) ([]byte, error) {
|
||||
var invitation meowlib.Invitation
|
||||
if err := proto.Unmarshal(invitationBytes, &invitation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var invitationAnswer meowlib.PackedUserMessage
|
||||
if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid)
|
||||
if peer == nil {
|
||||
return nil, nil, errors.New("no peer for invitation uuid " + invitation.Uuid)
|
||||
return nil, errors.New("no peer for invitation uuid " + invitation.Uuid)
|
||||
}
|
||||
// Guard against duplicate delivery (e.g., same answer from multiple servers).
|
||||
if peer.InvitationKp == nil {
|
||||
return nil, nil, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var inviteeCC meowlib.ContactCard
|
||||
if err := proto.Unmarshal(usermsg.Invitation.Payload, &inviteeCC); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
myCC, peer, err := client.GetConfig().GetIdentity().InvitationStep3(&inviteeCC)
|
||||
myCC, _, err := client.GetConfig().GetIdentity().InvitationStep3(&inviteeCC)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
client.GetConfig().GetIdentity().Save()
|
||||
return peer, myCC, nil
|
||||
|
||||
ccBytes, err := proto.Marshal(myCC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inv := &meowlib.Invitation{
|
||||
Uuid: myCC.InvitationId,
|
||||
Step: 3,
|
||||
Payload: ccBytes,
|
||||
}
|
||||
return proto.Marshal(inv)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
package messages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"forge.redroom.link/yves/meowlib"
|
||||
"forge.redroom.link/yves/meowlib/client"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Step4InviteeFinalizesInitiator is called by the invitee's message processor when a
|
||||
// UserMessage with invitation.step == 3 arrives. It unmarshals the initiator's ContactCard
|
||||
// and completes the invitee's peer entry with the initiator's real keys.
|
||||
func Step4InviteeFinalizesInitiator(usermsg *meowlib.UserMessage) (*client.Peer, error) {
|
||||
if usermsg.Invitation == nil || usermsg.Invitation.Step != 3 {
|
||||
return nil, errors.New("expected invitation step 3")
|
||||
}
|
||||
var initiatorCC meowlib.ContactCard
|
||||
if err := proto.Unmarshal(usermsg.Invitation.Payload, &initiatorCC); err != nil {
|
||||
// Step4InviteeFinalizesInitiator is called by the invitee when the step-3 answer
|
||||
// (serialized Invitation bytes) arrives. It unmarshals the initiator's ContactCard and
|
||||
// completes the invitee's peer entry with the initiator's real keys.
|
||||
func Step4InviteeFinalizesInitiator(invitationBytes []byte) (*client.Peer, error) {
|
||||
var inv meowlib.Invitation
|
||||
if err := proto.Unmarshal(invitationBytes, &inv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var initiatorCC meowlib.ContactCard
|
||||
if err := proto.Unmarshal(inv.Payload, &initiatorCC); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Patch the invitation ID from the outer message in case it was not set in the CC.
|
||||
if initiatorCC.InvitationId == "" {
|
||||
initiatorCC.InvitationId = usermsg.Invitation.Uuid
|
||||
initiatorCC.InvitationId = inv.Uuid
|
||||
}
|
||||
if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
@startuml General Invitation Steps
|
||||
InitiatingUser -> InitiatingUser : STEP_1 = Create InivitedUser_Id generate a public key, invitation uid & message for InvitedUser optionnally password protected
|
||||
note right of InitiatingUser #Yellow: Invitee created, only temp key
|
||||
InitiatingUser -> InvitedUser: STEP_1_SEND= transmit step 1 data (QR Code, Bluetooth, messaging, mail, ...) optional password being tranmitted through another channel
|
||||
|
||||
InvitedUser -> InvitedUser :Create InitatingUser_Id & InvitedUser ContactCard
|
||||
note right of InvitedUser #Yellow: Initiator created, empty
|
||||
InvitedUser -> InitiatingUser: STEP_2_SEND=transmit InvitedUser ContactCard (QR Codes, Bluetooth, messaging, mail, ...) encrypted with initiating user pub key
|
||||
|
||||
InitiatingUser -> InitiatingUser : STEP_3=InitiatingUser_Id Accept Invitation and create answer (Generate InitiatingUser ContactCard and create finalized InvitedUser contact)
|
||||
note right of InitiatingUser #Lime: Invitee complete
|
||||
InitiatingUser -> InvitedUser: STEP_3_SEND=Send answer through invited user's message servers from contact card
|
||||
|
||||
InvitedUser -> InvitedUser : Finalize InitiatingUser from its ContactCard
|
||||
note right of InvitedUser #Lime: Initiator complete
|
||||
|
||||
InvitedUser -> InitiatingUser: STEP_4= Send confirmation to InitiatingUser that communication is possible through initiating user's message servers from contact card
|
||||
@enduml
|
||||
Reference in New Issue
Block a user