diff --git a/client/helpers/bgPollHelper.go b/client/helpers/bgPollHelper.go index 2ae64ee..04d0b71 100644 --- a/client/helpers/bgPollHelper.go +++ b/client/helpers/bgPollHelper.go @@ -133,14 +133,20 @@ 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 { - // Auto-send step-3 CC to invitee's servers. - msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId) - if sendErr == nil { - for i, bytemsg := range msgs { - if i < len(peer.ContactPullServers) { - meowlib.HttpPostMessage(peer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) + 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 { + for i, bytemsg := range msgs { + if i < len(peer.ContactPullServers) { + meowlib.HttpPostMessage(peer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) + } + } } } } @@ -169,14 +175,17 @@ 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) - if finalErr == nil && finalizedPeer != nil { - // Auto-send step-4 confirmation to initiator's servers. - step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId) - if sendErr == nil { - for i, bytemsg := range step4msgs { - if i < len(finalizedPeer.ContactPullServers) { - meowlib.HttpPostMessage(finalizedPeer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) + 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) + if sendErr == nil { + for i, bytemsg := range step4msgs { + if i < len(finalizedPeer.ContactPullServers) { + meowlib.HttpPostMessage(finalizedPeer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) + } } } } diff --git a/client/invitation/files/step1.go b/client/invitation/files/step1.go index ba8ef0f..60bc946 100644 --- a/client/invitation/files/step1.go +++ b/client/invitation/files/step1.go @@ -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 - } - } else { - filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv" - if err := payload.WriteCompressed(filename); err != nil { - return nil, err - } + filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".png" + return payload.WriteQr(filename) } - return peer, nil + filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".mwiv" + return payload.WriteCompressed(filename) } diff --git a/client/invitation/files/step2.go b/client/invitation/files/step2.go index f83fdeb..4302fc3 100644 --- a/client/invitation/files/step2.go +++ b/client/invitation/files/step2.go @@ -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) } diff --git a/client/invitation/messages/flow_test.go b/client/invitation/messages/flow_test.go index 191554e..2376add 100644 --- a/client/invitation/messages/flow_test.go +++ b/client/invitation/messages/flow_test.go @@ -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, - } + // 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) - peer, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation) + // 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, peer) - require.NotNil(t, myCC, "step3 must produce the initiator's ContactCard to send to invitee") + require.NotNil(t, finalPeer) - // 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) + // 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) } diff --git a/client/invitation/messages/step1.go b/client/invitation/messages/step1.go index 9933081..1924bdf 100644 --- a/client/invitation/messages/step1.go +++ b/client/invitation/messages/step1.go @@ -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) } diff --git a/client/invitation/messages/step2.go b/client/invitation/messages/step2.go index 2a37e86..39dd9ae 100644 --- a/client/invitation/messages/step2.go +++ b/client/invitation/messages/step2.go @@ -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) } diff --git a/client/invitation/messages/step3.go b/client/invitation/messages/step3.go index 53d9602..4481f86 100644 --- a/client/invitation/messages/step3.go +++ b/client/invitation/messages/step3.go @@ -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) } diff --git a/client/invitation/messages/step4.go b/client/invitation/messages/step4.go index 1d8e529..f9941f3 100644 --- a/client/invitation/messages/step4.go +++ b/client/invitation/messages/step4.go @@ -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 diff --git a/doc/invitation/sq_invitation.puml b/doc/invitation/sq_invitation.puml index f896505..89f8d5b 100644 --- a/doc/invitation/sq_invitation.puml +++ b/doc/invitation/sq_invitation.puml @@ -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 \ No newline at end of file