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
+12 -3
View File
@@ -133,8 +133,12 @@ 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 {
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. // Auto-send step-3 CC to invitee's servers.
msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId) msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId)
if sendErr == nil { if sendErr == nil {
@@ -146,6 +150,8 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
} }
} }
} }
}
}
// Chat messages // Chat messages
if len(fromServerMessage.Chat) > 0 { if len(fromServerMessage.Chat) > 0 {
for _, packedUserMessage := range fromServerMessage.Chat { 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. // 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 marshalErr == nil {
finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(invBytes)
if finalErr == nil && finalizedPeer != nil { if finalErr == nil && finalizedPeer != nil {
// Auto-send step-4 confirmation to initiator's servers. // Auto-send step-4 confirmation to initiator's servers.
step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId) step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId)
@@ -181,6 +189,7 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
} }
} }
} }
}
continue continue
} }
+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) + mynick + "-" + contactName + ".mwiv"
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv" return payload.WriteCompressed(filename)
if err := payload.WriteCompressed(filename); err != nil {
return nil, err
}
}
return peer, nil
} }
+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)
} }
+81 -87
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")
peer, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation) assert.NotEmpty(t, initPeer.DrKpPublic)
require.NoError(t, err) assert.NotEmpty(t, initPeer.DrRootKey)
require.NotNil(t, peer)
require.NotNil(t, myCC, "step3 must produce the initiator's ContactCard to send to invitee") // The step-3 wire format is a serialized Invitation.
var inv3 meowlib.Invitation
// Initiator's peer must now hold invitee's real keys. require.NoError(t, proto.Unmarshal(step3Bytes, &inv3))
assert.Equal(t, inviteePeer.MyIdentity.Public, peer.ContactPublicKey) assert.EqualValues(t, 3, inv3.Step)
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, peer.ContactEncryption) assert.NotEmpty(t, inv3.Uuid)
assert.Equal(t, inviteePeer.MyLookupKp.Public, peer.ContactLookupKey) assert.NotEmpty(t, inv3.Payload)
assert.Nil(t, peer.InvitationKp, "temp keypair must be cleared after step3")
assert.NotEmpty(t, myCC.DrRootKey) // --- STEP 4: invitee finalises initiator ---
assert.NotEmpty(t, myCC.DrPublicKey) cfg.SetIdentity(invitee)
}
finalPeer, err := messages.Step4InviteeFinalizesInitiator(step3Bytes)
// TestStep2Step3RoundTripPayload verifies that the PackedUserMessage produced by step2 require.NoError(t, err)
// actually carries the invitee's ContactCard when decrypted by the initiator. require.NotNil(t, finalPeer)
func TestStep2Step3RoundTripPayload(t *testing.T) {
cfg := client.GetConfig() // Invitee's peer must now hold initiator's real keys and the invitation must be complete.
cfg.SetMemPass("testpass") //nolint:errcheck assert.Equal(t, initPeer.MyIdentity.Public, finalPeer.ContactPublicKey)
assert.Equal(t, initPeer.MyEncryptionKp.Public, finalPeer.ContactEncryption)
initiator, cleanInit := setupIdentity(t, "alice2") assert.Equal(t, initPeer.MyLookupKp.Public, finalPeer.ContactLookupKey)
defer cleanInit() assert.False(t, finalPeer.InvitationPending(), "invitation must be fully finalized")
assert.NotEmpty(t, finalPeer.DrRootKey)
payload, _, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil) assert.NotEmpty(t, finalPeer.ContactDrPublicKey)
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