cleaner invitation step messages

This commit is contained in:
yc
2026-04-14 19:12:09 +02:00
parent 327bd390c4
commit 00e4e6b046
9 changed files with 208 additions and 179 deletions
+25 -16
View File
@@ -133,14 +133,20 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
} }
// check if invitation answer (step-2 answer waiting for the initiator) // check if invitation answer (step-2 answer waiting for the initiator)
if fromServerMessage.Invitation != nil { if fromServerMessage.Invitation != nil {
peer, _, invErr := invmsgs.Step3InitiatorFinalizesInviteeAndCreatesContactCard(fromServerMessage.Invitation) invBytes, marshalErr := proto.Marshal(fromServerMessage.Invitation)
if invErr == nil && peer != nil { if marshalErr == nil {
// Auto-send step-3 CC to invitee's servers. step3Bytes, invErr := invmsgs.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invBytes)
msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId) if invErr == nil && step3Bytes != nil {
if sendErr == nil { peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(fromServerMessage.Invitation.Uuid)
for i, bytemsg := range msgs { if peer != nil {
if i < len(peer.ContactPullServers) { // Auto-send step-3 CC to invitee's servers.
meowlib.HttpPostMessage(peer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) 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. // Handle invitation step 3: initiator's full ContactCard arriving at the invitee.
if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 { if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 {
finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(usermsg) invBytes, marshalErr := proto.Marshal(usermsg.Invitation)
if finalErr == nil && finalizedPeer != nil { if marshalErr == nil {
// Auto-send step-4 confirmation to initiator's servers. finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(invBytes)
step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId) if finalErr == nil && finalizedPeer != nil {
if sendErr == nil { // Auto-send step-4 confirmation to initiator's servers.
for i, bytemsg := range step4msgs { step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId)
if i < len(finalizedPeer.ContactPullServers) { if sendErr == nil {
meowlib.HttpPostMessage(finalizedPeer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) for i, bytemsg := range step4msgs {
if i < len(finalizedPeer.ContactPullServers) {
meowlib.HttpPostMessage(finalizedPeer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut)
}
} }
} }
} }
+17 -13
View File
@@ -3,28 +3,32 @@ package files
import ( import (
"os" "os"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client" "forge.redroom.link/yves/meowlib/client"
"forge.redroom.link/yves/meowlib/client/invitation/messages" "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. // 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. // 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) { func Step1Write(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) error {
payload, peer, err := messages.Step1InitiatorCreatesInviteeAndTempKey(contactName, myNickname, invitationMessage, serverUids) payloadBytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey(contactName, myNickname, invitationMessage, serverUids)
if err != nil { 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() c := client.GetConfig()
if format == "qr" { if format == "qr" {
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png" filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".png"
if err := payload.WriteQr(filename); err != nil { return payload.WriteQr(filename)
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
}
} }
return peer, nil filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".mwiv"
return payload.WriteCompressed(filename)
} }
+9 -21
View File
@@ -12,8 +12,8 @@ import (
) )
// Step2ReadAndAnswer reads an InvitationInitPayload from a .mwiv file, creates the // Step2ReadAndAnswer reads an InvitationInitPayload from a .mwiv file, creates the
// invitee's peer entry, and writes the encrypted ContactCard (PackedUserMessage) to a // invitee's peer entry, and writes the serialized Invitation (step=2) to a .mwiv file
// .mwiv file for the initiator to pick up and process in step 3. // for the initiator to pick up and process in step 3.
func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname string, serverUids []string) error { func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname string, serverUids []string) error {
if _, err := os.Stat(invitationFile); os.IsNotExist(err) { if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
return err return err
@@ -29,35 +29,23 @@ func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname strin
if err != nil { if err != nil {
return err return err
} }
payloadBytes, err := proto.Marshal(payload)
if err != nil {
return err
}
mynick := myNickname mynick := myNickname
if mynick == "" { if mynick == "" {
mynick = client.GetConfig().GetIdentity().Nickname mynick = client.GetConfig().GetIdentity().Nickname
} }
packed, peer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, nickname, mynick, serverUids) // messages.Step2 returns a serialized Invitation ready to write directly to file.
if err != nil { invBytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payloadBytes, nickname, mynick, serverUids)
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)
if err != nil { if err != nil {
return err return err
} }
c := client.GetConfig() c := client.GetConfig()
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv" filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
return os.WriteFile(filename, out, 0600) return os.WriteFile(filename, invBytes, 0600)
} }
+78 -84
View File
@@ -29,114 +29,108 @@ func setupIdentity(t *testing.T, nickname string) (*client.Identity, func()) {
return id, cleanup return id, cleanup
} }
// TestStep2ProducesPackedUserMessage verifies that Step2 returns a PackedUserMessage // TestStep1ReturnsBinaryPayload verifies that Step1 returns non-empty bytes that
// (not just a peer) and that the message is encrypted with the initiator's temp key // deserialise to a valid InvitationInitPayload, and that the pending peer is stored
// so Step3 can decrypt it. // with only a temp keypair (no real identity keys yet).
func TestStep2ProducesPackedUserMessage(t *testing.T) { func TestStep1ReturnsBinaryPayload(t *testing.T) {
cfg := client.GetConfig() cfg := client.GetConfig()
cfg.SetMemPass("testpass") //nolint:errcheck cfg.SetMemPass("testpass") //nolint:errcheck
// --- STEP 1: initiator creates temp keypair and payload ---
initiator, cleanInit := setupIdentity(t, "alice") initiator, cleanInit := setupIdentity(t, "alice")
defer cleanInit() defer cleanInit()
payload, initPeer, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil) step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
require.NoError(t, err) 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) require.NotNil(t, initPeer)
// Initiator has only the temp keypair at this stage.
assert.Nil(t, initPeer.MyIdentity) assert.Nil(t, initPeer.MyIdentity)
assert.NotNil(t, initPeer.InvitationKp) assert.NotNil(t, initPeer.InvitationKp)
}
// --- STEP 2: invitee receives payload, creates peer, returns packed message --- // TestFullInvitationFlow runs all four steps end-to-end, passing the binary output of
_, cleanInvitee := setupIdentity(t, "bob") // 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() defer cleanInvitee()
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard( step2Bytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(step1Bytes, "Alice", "Bob", nil)
payload, "Alice", "Bob", nil,
)
require.NoError(t, err) 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) 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.MyIdentity)
assert.NotNil(t, inviteePeer.MyEncryptionKp) assert.NotNil(t, inviteePeer.MyEncryptionKp)
assert.NotNil(t, inviteePeer.MyLookupKp) 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) cfg.SetIdentity(initiator)
// Simulate how the server delivers the step-2 answer: marshal the PackedUserMessage step3Bytes, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(step2Bytes)
// into an Invitation.Payload.
packedBytes, err := proto.Marshal(packed)
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, step3Bytes)
invitation := &meowlib.Invitation{ // Initiator's peer must now hold invitee's real keys; temp keypair must be gone.
Uuid: payload.Uuid, initPeer := initiator.Peers.GetFromName("Bob")
Step: 2, require.NotNil(t, initPeer)
From: inviteePeer.MyIdentity.Public, assert.Equal(t, inviteePeer.MyIdentity.Public, initPeer.ContactPublicKey)
Payload: packedBytes, 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.NoError(t, err)
require.NotNil(t, peer) require.NotNil(t, finalPeer)
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. // Invitee's peer must now hold initiator's real keys and the invitation must be complete.
assert.Equal(t, inviteePeer.MyIdentity.Public, peer.ContactPublicKey) assert.Equal(t, initPeer.MyIdentity.Public, finalPeer.ContactPublicKey)
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, peer.ContactEncryption) assert.Equal(t, initPeer.MyEncryptionKp.Public, finalPeer.ContactEncryption)
assert.Equal(t, inviteePeer.MyLookupKp.Public, peer.ContactLookupKey) assert.Equal(t, initPeer.MyLookupKp.Public, finalPeer.ContactLookupKey)
assert.Nil(t, peer.InvitationKp, "temp keypair must be cleared after step3") assert.False(t, finalPeer.InvitationPending(), "invitation must be fully finalized")
assert.NotEmpty(t, myCC.DrRootKey) assert.NotEmpty(t, finalPeer.DrRootKey)
assert.NotEmpty(t, myCC.DrPublicKey) assert.NotEmpty(t, finalPeer.ContactDrPublicKey)
}
// 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)
} }
+8 -7
View File
@@ -1,22 +1,23 @@
package messages package messages
import ( import (
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client" "forge.redroom.link/yves/meowlib/client"
"google.golang.org/protobuf/proto"
) )
// Step1InitiatorCreatesInviteeAndTempKey creates a minimal pending peer and a temporary // Step1InitiatorCreatesInviteeAndTempKey creates a minimal pending peer and a temporary
// keypair, and returns the InvitationInitPayload to be transmitted to the invitee // keypair, and returns the serialized InvitationInitPayload bytes to be transmitted to
// via any transport (file, QR, server…). // the invitee via any transport (file, QR, server…). The peer is already persisted by
func Step1InitiatorCreatesInviteeAndTempKey(contactName string, myNickname string, invitationMessage string, serverUids []string) (*meowlib.InvitationInitPayload, *client.Peer, error) { // InvitationStep1 so no peer reference is returned.
func Step1InitiatorCreatesInviteeAndTempKey(contactName string, myNickname string, invitationMessage string, serverUids []string) ([]byte, error) {
mynick := myNickname mynick := myNickname
if mynick == "" { if mynick == "" {
mynick = client.GetConfig().GetIdentity().Nickname 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 { if err != nil {
return nil, nil, err return nil, err
} }
client.GetConfig().GetIdentity().Save() client.GetConfig().GetIdentity().Save()
return payload, peer, nil return proto.Marshal(payload)
} }
+26 -12
View File
@@ -3,31 +3,45 @@ package messages
import ( import (
"forge.redroom.link/yves/meowlib" "forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client" "forge.redroom.link/yves/meowlib/client"
"google.golang.org/protobuf/proto"
) )
// Step2InviteeCreatesInitiatorAndEncryptedContactCard creates the invitee's peer entry // Step2InviteeCreatesInitiatorAndEncryptedContactCard deserialises the step-1 payload bytes,
// from an InvitationInitPayload, then builds the invitee's ContactCard and returns it // creates the invitee's peer entry, builds and encrypts the invitee's ContactCard, and returns
// as a PackedUserMessage asymmetrically encrypted with the initiator's temporary public // a serialized Invitation (step=2) whose Payload is the PackedUserMessage encrypted with the
// key. The packed message is ready to be transmitted to the initiator via any transport // initiator's temporary public key. The bytes are transport-ready and consumed directly by
// (file, QR, server…); Step3InitiatorFinalizesInviteeAndCreatesContactCard on the // Step3InitiatorFinalizesInviteeAndCreatesContactCard.
// initiator side will decrypt and process it. func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payloadBytes []byte, nickname string, myNickname string, serverUids []string) ([]byte, error) {
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*meowlib.PackedUserMessage, *client.Peer, error) {
mynick := myNickname mynick := myNickname
if mynick == "" { if mynick == "" {
mynick = client.GetConfig().GetIdentity().Nickname 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 { if err != nil {
return nil, nil, err return nil, err
} }
usermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact()) usermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
packed, err := peer.ProcessOutboundUserMessage(usermsg) packed, err := peer.ProcessOutboundUserMessage(usermsg)
if err != nil { 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() client.GetConfig().GetIdentity().Save()
return packed, peer, nil return proto.Marshal(inv)
} }
+29 -13
View File
@@ -8,39 +8,55 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
// Step3InitiatorFinalizesInviteeAndCreatesContactCard is called by the initiator when a // Step3InitiatorFinalizesInviteeAndCreatesContactCard is called by the initiator when the
// step-2 answer (invitee's encrypted ContactCard) arrives. It decrypts the card, upgrades // step-2 answer (serialized Invitation bytes) arrives. It decrypts the invitee's ContactCard,
// the invitee's peer entry with the real keys, and returns the initiator's own ContactCard // upgrades the pending peer with the invitee's real keys, and returns a serialized Invitation
// ready to be sent to the invitee via any transport. // (step=3) whose Payload is the initiator's ContactCard, ready to be consumed directly by
func Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation *meowlib.Invitation) (*client.Peer, *meowlib.ContactCard, error) { // 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 var invitationAnswer meowlib.PackedUserMessage
if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil { if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil {
return nil, nil, err return nil, err
} }
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid) peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid)
if peer == nil { 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). // Guard against duplicate delivery (e.g., same answer from multiple servers).
if peer.InvitationKp == nil { if peer.InvitationKp == nil {
return nil, nil, nil return nil, nil
} }
usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From) usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
var inviteeCC meowlib.ContactCard var inviteeCC meowlib.ContactCard
if err := proto.Unmarshal(usermsg.Invitation.Payload, &inviteeCC); err != nil { 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 { if err != nil {
return nil, nil, err return nil, err
} }
client.GetConfig().GetIdentity().Save() 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)
} }
+11 -13
View File
@@ -1,27 +1,25 @@
package messages package messages
import ( import (
"errors"
"forge.redroom.link/yves/meowlib" "forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client" "forge.redroom.link/yves/meowlib/client"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
// Step4InviteeFinalizesInitiator is called by the invitee's message processor when a // Step4InviteeFinalizesInitiator is called by the invitee when the step-3 answer
// UserMessage with invitation.step == 3 arrives. It unmarshals the initiator's ContactCard // (serialized Invitation bytes) arrives. It unmarshals the initiator's ContactCard and
// and completes the invitee's peer entry with the initiator's real keys. // completes the invitee's peer entry with the initiator's real keys.
func Step4InviteeFinalizesInitiator(usermsg *meowlib.UserMessage) (*client.Peer, error) { func Step4InviteeFinalizesInitiator(invitationBytes []byte) (*client.Peer, error) {
if usermsg.Invitation == nil || usermsg.Invitation.Step != 3 { var inv meowlib.Invitation
return nil, errors.New("expected invitation step 3") if err := proto.Unmarshal(invitationBytes, &inv); err != nil {
} return nil, err
var initiatorCC meowlib.ContactCard }
if err := proto.Unmarshal(usermsg.Invitation.Payload, &initiatorCC); err != nil { var initiatorCC meowlib.ContactCard
if err := proto.Unmarshal(inv.Payload, &initiatorCC); err != nil {
return nil, err return nil, err
} }
// Patch the invitation ID from the outer message in case it was not set in the CC.
if initiatorCC.InvitationId == "" { if initiatorCC.InvitationId == "" {
initiatorCC.InvitationId = usermsg.Invitation.Uuid initiatorCC.InvitationId = inv.Uuid
} }
if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil { if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil {
return nil, err return nil, err
+5
View File
@@ -1,13 +1,18 @@
@startuml General Invitation Steps @startuml General Invitation Steps
InitiatingUser -> InitiatingUser : STEP_1 = Create InivitedUser_Id generate a public key, invitation uid & message for InvitedUser optionnally password protected 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 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 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 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) 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 InitiatingUser -> InvitedUser: STEP_3_SEND=Send answer through invited user's message servers from contact card
InvitedUser -> InvitedUser : Finalize InitiatingUser from its ContactCard 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 InvitedUser -> InitiatingUser: STEP_4= Send confirmation to InitiatingUser that communication is possible through initiating user's message servers from contact card
@enduml @enduml