invitation process upgrade
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
ycc
2026-04-02 18:50:04 +02:00
committed by yc
parent 9f130a80b7
commit 1906431061
21 changed files with 1185 additions and 638 deletions

View File

@@ -129,9 +129,20 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
if err != nil {
return nil, nil, "ReadMessage: Unmarshal FromServerMessage", err
}
// check if invitation answer
// check if invitation answer (step-2 answer waiting for the initiator)
if fromServerMessage.Invitation != nil {
invitationGetAnswerReadResponse(fromServerMessage.Invitation)
peer, _, _, invErr := InvitationStep3ProcessAnswer(fromServerMessage.Invitation)
if invErr == nil && peer != nil {
// Auto-send step-3 CC to invitee's servers.
msgs, _, sendErr := InvitationStep3Message(peer.InvitationId)
if sendErr == nil {
for i, bytemsg := range msgs {
if i < len(peer.ContactPullServers) {
meowlib.HttpPostMessage(peer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut)
}
}
}
}
}
// Chat messages
if len(fromServerMessage.Chat) > 0 {
@@ -142,12 +153,41 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
if peer == nil {
return nil, nil, "ReadMessage: GetFromMyLookupKey", errors.New("no visible peer for that message")
}
// Unpack the message
usermsg, err := peer.ProcessInboundUserMessage(packedUserMessage)
// Unpack the message — step-3 messages arrive before the initiator's identity
// key is known, so skip signature verification for pending peers.
var usermsg *meowlib.UserMessage
if peer.InvitationPending() {
usermsg, err = peer.ProcessInboundStep3UserMessage(packedUserMessage)
} else {
usermsg, err = peer.ProcessInboundUserMessage(packedUserMessage)
}
if err != nil {
return nil, nil, "ReadMessage: ProcessInboundUserMessage", err
}
// Handle invitation step 3: initiator's full ContactCard arriving at the invitee.
if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 {
finalizedPeer, _, finalErr := InvitationStep4ProcessStep3(usermsg)
if finalErr == nil && finalizedPeer != nil {
// Auto-send step-4 confirmation to initiator's servers.
step4msgs, _, sendErr := InvitationStep4Message(finalizedPeer.InvitationId)
if sendErr == nil {
for i, bytemsg := range step4msgs {
if i < len(finalizedPeer.ContactPullServers) {
meowlib.HttpPostMessage(finalizedPeer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut)
}
}
}
}
continue
}
// Handle invitation step 4: invitee's confirmation arriving at the initiator.
if usermsg.Invitation != nil && usermsg.Invitation.Step == 4 {
// Contact is fully active — nothing more to do on the initiator side.
continue
}
// Check for received or processed already filled => it's an ack for one of our sent messages
if len(usermsg.Data) == 0 && usermsg.Status != nil && usermsg.Status.Uuid != "" &&
(usermsg.Status.Received != 0 || usermsg.Status.Processed != 0) {

View File

@@ -1,161 +1,110 @@
package helpers
import (
"C"
"fmt"
"errors"
"os"
"strings"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
)
import (
"errors"
)
// InvitationAnswer
func InvitationAnswer(cc *meowlib.ContactCard, nickname string, myNickname string, serverUids []string) (*client.Peer, string, error) {
// InvitationStep2Answer creates the invitee's peer from an InvitationInitPayload and returns
// the new peer (STEP_2, invitee side — in-memory, no server involved).
func InvitationStep2Answer(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*client.Peer, string, error) {
mynick := myNickname
// my nickname for that contact
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
// build my contact card for that friend
peer, err := client.GetConfig().GetIdentity().AnswerInvitation(mynick, nickname, serverUids, cc)
peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload)
if err != nil {
return nil, "InvitationAnswer: AnswerInvitation", err
return nil, "InvitationStep2Answer: InvitationStep2", err
}
//peerstr, err := json.Marshal(peer)
//fmt.Println("InvitationAnswer: " + string(peerstr))
c := client.GetConfig()
c.GetIdentity().Save()
client.GetConfig().GetIdentity().Save()
return peer, "", nil
}
// InvitationAnswerFile
func InvitationAnswerFile(invitationFile string, nickname string, myNickname string, serverUids []string) (string, error) {
format := "qr"
var filename string = ""
var cc *meowlib.ContactCard
c := client.GetConfig()
// InvitationStep2AnswerFile reads an InvitationInitPayload from a .mwiv file and creates the
// invitee's peer. It also writes the invitee's ContactCard response to a file (STEP_2_SEND, file variant).
func InvitationStep2AnswerFile(invitationFile string, nickname string, myNickname string, serverUids []string) (string, error) {
if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
return "InvitationAnswerFile : os.Stat", err
return "InvitationStep2AnswerFile: os.Stat", err
}
if strings.HasSuffix(invitationFile, ".mwiv") {
format = "mwiv"
data, err := os.ReadFile(invitationFile)
if err != nil {
return "InvitationAnswerFile : os.ReadFile", err
}
cc, err = meowlib.NewContactCardFromCompressed(data)
if err != nil {
return "InvitationAnswerFile : NewContactCardFromCompressed", err
}
if !strings.HasSuffix(invitationFile, ".mwiv") {
return "InvitationStep2AnswerFile: unsupported format", errors.New("only .mwiv files are supported")
}
identity := client.GetConfig().GetIdentity()
if cc != nil {
isAnswer, proposed, received, _ := identity.CheckInvitation(cc)
if isAnswer {
fmt.Fprintln(os.Stdout, "This is already a response "+proposed+" to your invitation.")
fmt.Fprintln(os.Stdout, "You cannot answer again.")
fmt.Fprintln(os.Stdout, "You should finalize it by importing "+proposed+" contact card to your meow.")
fmt.Fprintln(os.Stdout, "Use : 'meow invitation finalize "+invitationFile+"' to do it.")
} else {
mynick := myNickname
// my nickname for that contact
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
response, err := identity.AnswerInvitation(mynick, nickname, serverUids, cc)
if err != nil {
return "InvitationAnswerFile : AnswerInvitation", err
}
fmt.Fprintln(os.Stdout, "Invitation sent by "+received)
if format == "qr" {
filename = c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".png"
response.GetMyContact().WriteQr(filename)
} else {
filename = c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
response.GetMyContact().WriteCompressed(filename)
}
client.GetConfig().GetIdentity().Save()
}
data, err := os.ReadFile(invitationFile)
if err != nil {
return "InvitationStep2AnswerFile: os.ReadFile", err
}
payload, err := meowlib.NewInvitationInitPayloadFromCompressed(data)
if err != nil {
return "InvitationStep2AnswerFile: NewInvitationInitPayloadFromCompressed", err
}
mynick := myNickname
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
c := client.GetConfig()
response, err := c.GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload)
if err != nil {
return "InvitationStep2AnswerFile: InvitationStep2", err
}
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
if err := response.GetMyContact().WriteCompressed(filename); err != nil {
return "InvitationStep2AnswerFile: WriteCompressed", err
}
c.GetIdentity().Save()
return "", nil
}
// InvitationAnswerMessage
func InvitationAnswerMessage(invitationId string, invitationServerUid string, timeout int) ([]byte, string, error) {
// find the peer with that invitation id
/*var peer *client.Peer
for i := len(client.GetConfig().GetIdentity().Peers) - 1; i >= 0; i-- { //! to allow self invitation : testing only, findinc the received peer before myself
// for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ {
if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId {
peer = client.GetConfig().GetIdentity().Peers[i]
break
}
}*/
// InvitationStep2AnswerMessage builds and returns the packed server message that posts the
// invitee's ContactCard (encrypted with the initiator's temp key) to the invitation server
// (STEP_2_SEND, through-server variant).
func InvitationStep2AnswerMessage(invitationId string, invitationServerUid string, timeout int) ([]byte, string, error) {
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitationId)
if peer == nil {
// declare a custom go error for no peer found
return nil, "InvitationAnswerMessage: loop for peer", errors.New("no peer with that invitation id")
return nil, "InvitationStep2AnswerMessage: peer not found", errors.New("no peer with that invitation id")
}
answermsg, err := peer.BuildInvitationAnswerMessage(peer.GetMyContact())
answermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
if err != nil {
return nil, "InvitationAnswerMessage: BuildInvitationAnswserMessage", err
return nil, "InvitationStep2AnswerMessage: BuildInvitationStep2Message", err
}
// Server: get the invitation server
invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationAnswerMessage: LoadServer", err
return nil, "InvitationStep2AnswerMessage: LoadServer", err
}
// this will be the invitation's payload
packedMsg, err := peer.ProcessOutboundUserMessage(answermsg)
if err != nil {
return nil, "InvitationAnswerMessage: ProcessOutboundUserMessage", err
}
// Creating Server message for transporting the user message
toServerMessage, err := invitationServer.BuildToServerMessageInvitationAnswer(packedMsg, peer.MyIdentity.Public, invitationId, timeout)
if err != nil {
return nil, "InvitationAnswerMessage: BuildToServerMessageInvitationAnswer", err
return nil, "InvitationStep2AnswerMessage: ProcessOutboundUserMessage", err
}
toServerMessage, err := invitationServer.BuildToServerMessageInvitationAnswer(packedMsg, peer.MyIdentity.Public, invitationId, timeout)
if err != nil {
return nil, "InvitationStep2AnswerMessage: BuildToServerMessageInvitationAnswer", err
}
// Server outbound processing
bytemsg, err := invitationServer.ProcessOutboundMessage(toServerMessage)
if err != nil {
return nil, "InvitationAnswerMessage: ProcessOutboundMessage", err
return nil, "InvitationStep2AnswerMessage: ProcessOutboundMessage", err
}
return bytemsg, "", nil
}
// InvitationAnswerMessageReadResponse
// Called by the invitation receiver
// invitationData: the data received from the server
// invitationServerUid: the uid of the server holding the invitation
func InvitationAnswerMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.Invitation, string, error) {
// InvitationStep2AnswerMessageReadResponse reads the server acknowledgement of a Step2 answer.
func InvitationStep2AnswerMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.Invitation, string, error) {
server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationAnswerMessageReadResponse: LoadServer", err
return nil, "InvitationStep2AnswerMessageReadResponse: LoadServer", err
}
// Server inbound processing : get the invitation server
serverMsg, err := server.ProcessInboundServerResponse(invitationData)
if err != nil {
return nil, "InvitationAnswerMessageReadResponse: ProcessInboundServerResponse", err
return nil, "InvitationStep2AnswerMessageReadResponse: ProcessInboundServerResponse", err
}
return serverMsg.Invitation, "", nil
}

View File

@@ -7,84 +7,31 @@ import (
"forge.redroom.link/yves/meowlib/client"
)
// InvitationCheck
// todo
/*
func InvitationCheck(invitationdata []byte) *C.char {
var jsoninv map[string]interface{}
err := json.Unmarshal([]byte(C.GoString(invitationdata)), &jsoninv)
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
var cc *meowlib.ContactCard
if _, err := os.Stat(jsoninv["filename"].(string)); os.IsNotExist(err) {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
if strings.HasSuffix(jsoninv["filename"].(string), ".mwiv") {
data, err := os.ReadFile(jsoninv["filename"].(string))
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
cc, err = meowlib.NewContactCardFromCompressed(data)
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
}
identity := client.GetConfig().GetIdentity()
result := map[string]string{}
if cc != nil {
isAnswer, proposed, received, invitationMessage := identity.CheckInvitation(cc)
if isAnswer { // answer to infitation
result["type"] = "answer"
result["to"] = proposed
result["from"] = received
result["invitation_message"] = invitationMessage
//fmt.Fprintln(os.Stdout, "Invitation sent to "+proposed+" received with "+received+" as suggested nickname")
} else { // finalization message
result["type"] = "finalize"
result["from"] = received
//fmt.Fprintln(os.Stdout, "Invitation sent by "+received)
}
}
val, err := json.Marshal(result)
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
return C.CString(string(val))
}*/
// InvitationGetMessage
// Called by the invitation receiver
// invitationUrl: the url of server holding the invitation
// serverPublicKey: the public key of the server holding the invitation
// invitationPassword: the password of the invitation
func InvitationGetMessage(invitationUrl string, serverPublicKey string, invitationPassword string) ([]byte, string, error) {
// InvitationStep2GetMessage builds and returns the packed server message that retrieves
// the InvitationInitPayload from the server using the shortcode URL (STEP_2, invitee side).
func InvitationStep2GetMessage(invitationUrl string, serverPublicKey string, invitationPassword string) ([]byte, string, error) {
meowurl := strings.Split(invitationUrl, "?")
shortcode := meowurl[1]
srv, err := client.CreateServerFromMeowUrl(meowurl[0])
if err != nil {
return nil, "InvitationGetMessage: CreateServerFromMeowUrl", err
return nil, "InvitationStep2GetMessage: CreateServerFromMeowUrl", err
}
// check if already in msg servers
// Reuse the server entry if already known.
dbsrv, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(srv.Url)
if err != nil {
return nil, "InvitationGetMessage: LoadServer", err
return nil, "InvitationStep2GetMessage: LoadServer", err
}
if dbsrv == nil {
// create a server object with url & pubkey
srv.PublicKey = serverPublicKey
k, err := meowlib.NewKeyPair()
if err != nil {
return nil, "InvitationGetMessage: NewKeyPair", err
return nil, "InvitationStep2GetMessage: NewKeyPair", err
}
srv.UserKp = k
// save it
err = client.GetConfig().GetIdentity().MessageServers.StoreServer(srv)
if err != nil {
return nil, "InvitationGetMessage: StoreServer", err
if err := client.GetConfig().GetIdentity().MessageServers.StoreServer(srv); err != nil {
return nil, "InvitationStep2GetMessage: StoreServer", err
}
} else {
if dbsrv.PublicKey != serverPublicKey {
@@ -92,42 +39,32 @@ func InvitationGetMessage(invitationUrl string, serverPublicKey string, invitati
}
srv = dbsrv
}
// buildserver message
toSrvMsg, err := srv.BuildToServerMessageInvitationRequest(shortcode, invitationPassword)
if err != nil {
return nil, "InvitationGetMessage: BuildToServerMessageInvitationRequest", err
return nil, "InvitationStep2GetMessage: BuildToServerMessageInvitationRequest", err
}
// processoutbound
bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg)
if err != nil {
return nil, "InvitationGetMessage: ProcessOutboundMessage", err
return nil, "InvitationStep2GetMessage: ProcessOutboundMessage", err
}
return bytemsg, "", nil
}
// InvitationGetMessageReadResponse
// Called by the invitation receiver
// invitationData: the data received from the server
// invitationServerUid: the uid of the server holding the invitation
func InvitationGetMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.ContactCard, string, error) {
// InvitationStep2ReadResponse decodes the server response to a Step2 get-message and returns
// the InvitationInitPayload sent by the initiator.
func InvitationStep2ReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.InvitationInitPayload, string, error) {
server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationGetMessageReadResponse: LoadServer", err
return nil, "InvitationStep2ReadResponse: LoadServer", err
}
// Server inbound processing : get the invitation server
serverMsg, err := server.ProcessInboundServerResponse(invitationData)
if err != nil {
return nil, "InvitationGetMessageReadResponse: ProcessInboundServerResponse", err
return nil, "InvitationStep2ReadResponse: ProcessInboundServerResponse", err
}
// fmt.Println("Inbound OK, Invitation Step: ", serverMsg.Invitation.Step, len(serverMsg.Invitation.Payload))
// fmt.Println("Invitation Check")
// fmt.Println(hex.EncodeToString(serverMsg.Invitation.Payload))
// contactCard decode
cc, err := meowlib.NewContactCardFromCompressed(serverMsg.Invitation.Payload)
payload, err := meowlib.NewInvitationInitPayloadFromCompressed(serverMsg.Invitation.Payload)
if err != nil {
return nil, "InvitationGetMessageReadResponse: NewContactCardFromCompressed", err
return nil, "InvitationStep2ReadResponse: NewInvitationInitPayloadFromCompressed", err
}
return cc, "", nil
return payload, "", nil
}

View File

@@ -8,136 +8,95 @@ import (
"forge.redroom.link/yves/meowlib/client"
)
// InvitationCreatePeer creates a new peer and returns it
// Called by invitation initiator
// name: the name of the peer
// myNickname: my nickname for that peer
// invitationMessage: the message to send to the peer
// serverUids: the list of server uids
func InvitationCreatePeer(name string, myNickname string, invitationMessage string, serverUids []string) (*client.Peer, string, error) {
// InvitationStep1CreatePeer creates a minimal pending peer and returns the InvitationInitPayload
// to be transmitted to the invitee (STEP_1).
func InvitationStep1CreatePeer(contactName string, myNickname string, invitationMessage string, serverUids []string) (*meowlib.InvitationInitPayload, *client.Peer, string, error) {
mynick := myNickname
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
// build my contact card for that friend
peer, err := client.GetConfig().GetIdentity().InvitePeer(mynick, name, serverUids, invitationMessage)
payload, peer, err := client.GetConfig().GetIdentity().InvitationStep1(mynick, contactName, serverUids, invitationMessage)
if err != nil {
return nil, "InvitationCreate: InvitePeer", err
return nil, nil, "InvitationStep1CreatePeer: InvitationStep1", err
}
client.GetConfig().GetIdentity().Save()
return peer, "", nil
return payload, peer, "", nil
}
// InvitationCreateFile creates a new peer and writes the invitation to a file
// Called by invitation initiator
// name: the name of the peer
// myNickname: my nickname for that peer
// invitationMessage: the message to send to the peer
// serverUids: the list of server uids
// format: the format of the file (qr or mwiv)
func InvitationCreateFile(name string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, string, error) {
peer, errdata, err := InvitationCreatePeer(name, myNickname, invitationMessage, serverUids)
// InvitationStep1File creates a pending peer and writes the InvitationInitPayload to a file
// (format: "qr" for QR-code PNG, anything else for compressed binary .mwiv).
func InvitationStep1File(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, string, error) {
payload, peer, errdata, err := InvitationStep1CreatePeer(contactName, myNickname, invitationMessage, serverUids)
if err != nil {
return nil, errdata, err
}
c := client.GetConfig()
var filename string = ""
if format == "qr" {
filename = c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png"
err := peer.GetMyContact().WriteQr(filename)
if err != nil {
return nil, "InvitationCreateFile: WriteQr", err
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png"
if err := payload.WriteQr(filename); err != nil {
return nil, "InvitationStep1File: WriteQr", err
}
} else {
filename = c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv"
err := peer.GetMyContact().WriteCompressed(filename)
if err != nil {
return nil, "InvitationCreateFile: WriteCompressed", err
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv"
if err := payload.WriteCompressed(filename); err != nil {
return nil, "InvitationStep1File: WriteCompressed", err
}
}
return peer, "", nil
}
// InvitationCreateMessage creates a new invitation message for an invited peer
// Called by invitation initiator
// invitationId: the invitation id of the peer
// invitationServerUid: the uid of the server for sending the invitation
// timeOut: the timeout for the invitation
// urlLen: the length of the invitation url
// password: the password for the invitation
func InvitationCreateMessage(invitationId string, invitationServerUid string, timeOut int, urlLen int, password string) ([]byte, string, error) {
// lookup for peer with "invitation_id"
var myContact *meowlib.ContactCard
/* for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ {
if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId {
myContact = client.GetConfig().GetIdentity().Peers[i].GetMyContact()
break
}
}*/
// InvitationStep1Message builds and returns the packed server message that posts the
// InvitationInitPayload to the invitation server (STEP_1 through-server variant).
func InvitationStep1Message(invitationId string, invitationServerUid string, timeOut int, urlLen int, password string) ([]byte, string, error) {
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitationId)
myContact = peer.GetMyContact()
// todo handle not found !!
// lookup for message server with "invitation_server"
invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) //.GetServerByIdx(int(jsoninv["invitation_server"].(float64)))
if err != nil {
return nil, "InvitationCreateMessage: LoadServer", err
if peer == nil {
return nil, "InvitationStep1Message: peer not found", nil
}
// call server.buildinviattion
msg, err := invitationServer.BuildToServerMessageInvitationCreation(myContact, password, timeOut, urlLen)
if err != nil {
return nil, "InvitationCreateMessage: BuildToServerMessageInvitationCreation", err
if peer.InvitationKp == nil {
return nil, "InvitationStep1Message: peer has no InvitationKp", nil
}
initPayload := &meowlib.InvitationInitPayload{
Uuid: peer.InvitationId,
Name: peer.MyName,
PublicKey: peer.InvitationKp.Public,
InvitationMessage: peer.InvitationMessage,
}
invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationStep1Message: LoadServer", err
}
msg, err := invitationServer.BuildToServerMessageInvitationStep1(initPayload, password, timeOut, urlLen)
if err != nil {
return nil, "InvitationStep1Message: BuildToServerMessageInvitationStep1", err
}
// fmt.Println("Invitation Create")
// fmt.Println(hex.EncodeToString(msg.Invitation.Payload))
bytemsg, err := invitationServer.ProcessOutboundMessage(msg)
if err != nil {
return nil, "InvitationCreateMessage: ProcessOutboundMessage", err
return nil, "InvitationStep1Message: ProcessOutboundMessage", err
}
return bytemsg, "", nil
}
// InvitationCreateReadResponse reads the response of an invitation creation (url, expiry)
// Called by invitation initiator
// invitationServerUid: the uid of the server where we sent the invitation
// invitationResponse: the response we got from the server
func InvitationCreateReadResponse(invitationServerUid string, invitationResponse []byte) (*meowlib.Invitation, string, error) {
// InvitationStep1ReadResponse reads the server response to a Step1 message (shortcode URL + expiry).
func InvitationStep1ReadResponse(invitationServerUid string, invitationResponse []byte) (*meowlib.Invitation, string, error) {
server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationCreateReadResponse: LoadServer", err
return nil, "InvitationStep1ReadResponse: LoadServer", err
}
serverMsg, err := server.ProcessInboundServerResponse(invitationResponse)
if err != nil {
return nil, "InvitationCreateReadResponse: ProcessInboundServerResponse", err
return nil, "InvitationStep1ReadResponse: ProcessInboundServerResponse", err
}
return serverMsg.Invitation, "", nil
}
// InvitationSetUrlInfo sets the url info for an invitation
// Called by invitation initiator
// invitationId: the invitation id of the peer
// url: the url of the invitation we got from the server
// InvitationSetUrlInfo stores the shortcode URL and expiry on the pending peer.
func InvitationSetUrlInfo(invitationId string, url string, expiry int64) {
id := client.GetConfig().GetIdentity()
// lookup for peer with "invitation_id"
peer := id.Peers.GetFromInvitationId(invitationId)
if peer == nil {
return
}
peer.InvitationUrl = url
peer.InvitationExpiry = time.Unix(expiry, 0)
id.Peers.StorePeer(peer)
/* for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ {
if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId {
client.GetConfig().GetIdentity().Peers[i].InvitationUrl = url
client.GetConfig().GetIdentity().Peers[i].InvitationExpiry = time.Unix(expiry, 0)
break
}
}
client.GetConfig().GetIdentity().Save()*/
}

View File

@@ -1,53 +1,150 @@
package helpers
import (
"errors"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
"google.golang.org/protobuf/proto"
)
// Got it by the message background check
// => noInvitationGetAnswer
// invitationGetAnswerReadResponse
// Called by the initiator's background service only
// invitationAnswerData: the data received from the server
// invitationServerUid: the uid of the server holding the invitation
func invitationGetAnswerReadResponse(invitation *meowlib.Invitation) (*client.Peer, string, error) {
// decode the payload
// InvitationStep3ProcessAnswer is called by the initiator's background service when a
// step-2 answer (invitee's ContactCard) arrives via the invitation server poll.
// It decrypts the answer, calls InvitationStep3 to generate the initiator's full keypairs,
// and returns the peer and the initiator's ContactCard ready for STEP_3_SEND.
func InvitationStep3ProcessAnswer(invitation *meowlib.Invitation) (*client.Peer, *meowlib.ContactCard, string, error) {
var invitationAnswer meowlib.PackedUserMessage
err := proto.Unmarshal(invitation.Payload, &invitationAnswer)
if err != nil {
return nil, "InvitationGetAnswerReadResponse: Unmarshal", err
if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil {
return nil, nil, "InvitationStep3ProcessAnswer: Unmarshal PackedUserMessage", err
}
// retreive user public key to check usermessage signature
// contactPublikKey := serverMsg.Invitation.From
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid)
peer.ContactPublicKey = invitation.From
if peer != nil {
// process the packed user message
usermsg, err := peer.ProcessInboundUserMessage(&invitationAnswer)
if err != nil {
return nil, "InvitationGetAnswerReadResponse: ProcessInboundUserMessage", err
}
decodedInvitation := usermsg.Invitation
var cc meowlib.ContactCard
err = proto.Unmarshal(decodedInvitation.Payload, &cc)
if err != nil {
return nil, "InvitationGetAnswerReadResponse: Unmarshal", err
}
// finalize the invitation
// id := client.GetConfig().GetIdentity()
peer.ContactLookupKey = cc.ContactPublicKey
peer.ContactEncryption = cc.EncryptionPublicKey
for _, server := range cc.PullServers {
peer.ContactPullServers = append(peer.ContactPullServers, server.GetUid())
}
client.GetConfig().GetIdentity().Save()
if peer == nil {
return nil, nil, "InvitationStep3ProcessAnswer: peer not found", 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
}
// Decrypt invitee's ContactCard using the initiator's temporary InvitationKp.
usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From)
if err != nil {
return nil, nil, "InvitationStep3ProcessAnswer: ProcessInboundStep2UserMessage", err
}
var inviteeCC meowlib.ContactCard
if err := proto.Unmarshal(usermsg.Invitation.Payload, &inviteeCC); err != nil {
return nil, nil, "InvitationStep3ProcessAnswer: Unmarshal ContactCard", err
}
myCC, peer, err := client.GetConfig().GetIdentity().InvitationStep3(&inviteeCC)
if err != nil {
return nil, nil, "InvitationStep3ProcessAnswer: InvitationStep3", err
}
client.GetConfig().GetIdentity().Save()
return peer, myCC, "", nil
}
// InvitationStep3Message builds and returns the packed server messages that send the
// initiator's full ContactCard to the invitee through the invitee's servers (STEP_3_SEND).
func InvitationStep3Message(invitationId string) ([][]byte, string, error) {
id := client.GetConfig().GetIdentity()
peer := id.Peers.GetFromInvitationId(invitationId)
if peer == nil {
return nil, "InvitationStep3Message: peer not found", errors.New("no peer for invitation id " + invitationId)
}
step3msg, err := peer.BuildInvitationStep3Message(peer.GetMyContact())
if err != nil {
return nil, "InvitationStep3Message: BuildInvitationStep3Message", err
}
// Step-3 must NOT use DR or sym layers: the invitee hasn't received those
// keys yet (they are carried inside this very message). Use plain asym only.
serialized, err := peer.SerializeUserMessage(step3msg)
if err != nil {
return nil, "InvitationStep3Message: SerializeUserMessage", err
}
enc, err := peer.AsymEncryptMessage(serialized)
if err != nil {
return nil, "InvitationStep3Message: AsymEncryptMessage", err
}
packedMsg := peer.PackUserMessage(enc.Data, enc.Signature)
var results [][]byte
for _, srvUid := range peer.ContactPullServers {
srv, err := id.MessageServers.LoadServer(srvUid)
if err != nil {
continue
}
toSrvMsg := srv.BuildToServerMessageFromUserMessage(packedMsg)
bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg)
if err != nil {
continue
}
results = append(results, bytemsg)
}
if len(results) == 0 {
return nil, "InvitationStep3Message: no reachable invitee server", errors.New("could not build message for any invitee server")
}
return results, "", nil
}
// InvitationStep4ProcessStep3 is called by the invitee's message processing when a UserMessage
// with invitation.step==3 is received. It finalizes the initiator's peer entry.
func InvitationStep4ProcessStep3(usermsg *meowlib.UserMessage) (*client.Peer, string, error) {
if usermsg.Invitation == nil || usermsg.Invitation.Step != 3 {
return nil, "InvitationStep4ProcessStep3: unexpected step", errors.New("expected invitation step 3")
}
var initiatorCC meowlib.ContactCard
if err := proto.Unmarshal(usermsg.Invitation.Payload, &initiatorCC); err != nil {
return nil, "InvitationStep4ProcessStep3: Unmarshal ContactCard", 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
}
if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil {
return nil, "InvitationStep4ProcessStep3: InvitationStep4", err
}
client.GetConfig().GetIdentity().Save()
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(initiatorCC.InvitationId)
return peer, "", nil
}
// InvitationStep4Message builds and returns the packed server messages that send the
// invitee's confirmation to the initiator through the initiator's servers (STEP_4).
func InvitationStep4Message(invitationId string) ([][]byte, string, error) {
id := client.GetConfig().GetIdentity()
peer := id.Peers.GetFromInvitationId(invitationId)
if peer == nil {
return nil, "InvitationStep4Message: peer not found", errors.New("no peer for invitation id " + invitationId)
}
step4msg, err := peer.BuildInvitationStep4Message()
if err != nil {
return nil, "InvitationStep4Message: BuildInvitationStep4Message", err
}
packedMsg, err := peer.ProcessOutboundUserMessage(step4msg)
if err != nil {
return nil, "InvitationStep4Message: ProcessOutboundUserMessage", err
}
var results [][]byte
for _, srvUid := range peer.ContactPullServers {
srv, err := id.MessageServers.LoadServer(srvUid)
if err != nil {
continue
}
toSrvMsg := srv.BuildToServerMessageFromUserMessage(packedMsg)
bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg)
if err != nil {
continue
}
results = append(results, bytemsg)
}
if len(results) == 0 {
return nil, "InvitationStep4Message: no reachable initiator server", errors.New("could not build message for any initiator server")
}
return results, "", nil
}