Compare commits

..

No commits in common. "d19568c1c04b46d6a9292b2f5d26d945c412b852" and "3a03fab876f9b7dbff3a7143438afdef1792194e" have entirely different histories.

3 changed files with 7 additions and 46 deletions

View file

@ -21,7 +21,7 @@ assert lib.assertMsg ((serverHost == null) == (serverPublicKey == null))
"gsmol-bridge: serverHost and serverPublicKey must be set together, or not at all";
stdenvNoCC.mkDerivation {
pname = "gsmol-bridge";
version = "0.2.1";
version = "0.2.0";
# Respects the repo's own .gitignore, so .venv/.devbox/.jj never enter the
# store path even when building from an unclean local checkout.

View file

@ -946,19 +946,12 @@ async function sendMail(toText, subject, body, replyTo, anonymous) {
// A signed Reply-To lets a first-time recipient name and answer us (§5.5
// allows unknown keys); "anonymous" omits it.
if (account && !anonymous) fields.push(["Reply-To", account.uri(me.publicKey)]);
// §5.8: hand an accepted correspondent the token for our own mailbox, once,
// so a reply from them reaches our main tier. See store.tokenToDeliver for
// why this isn't on every message.
const owed = store.tokenToDeliver(addr.short);
if (owed) fields.push(["Accept", b32encode(tokenFor(master, owed))]);
// Trimmed before the payload is signed, so the signature covers exactly the
// text the author meant. A trailing newline is not content — but it becomes
// content the moment it is signed, and nothing downstream can strip it
// without invalidating the signature. smolboid, for one, refuses a post
// whose text carries a line break, because a published record is one line
// of a feed.
// §5.8: hand an accepted correspondent the token for our own mailbox, so a
// first reply from them reaches our main tier.
const accepted = store.accepted(addr.short);
if (accepted?.active) fields.push(["Accept", b32encode(tokenFor(master, accepted.identity))]);
const bodyBytes = utf8Bytes(buildFrontmatter(fields.filter(([, v]) => v),
body.trim()));
body.replace(/\s+$/, "") + "\n"));
const envelope = seal(me, recipient, bodyBytes);
// §5.8: our token for their mailbox, if they have given us one.
const held = store.tokenFrom(addr.short);
@ -969,9 +962,6 @@ async function sendMail(toText, subject, body, replyTo, anonymous) {
} finally {
opened.session.stream.close();
}
// Only now, with the message taken by their server: a send that threw
// earlier leaves the token still owing, so the next attempt carries it.
if (owed) store.markDelivered(addr.short);
// §5.6: the ephemeral is gone, so keep a copy sealed to ourselves.
await store.storeMessage("sent", {
id: hex(messageId(envelope)),

View file

@ -247,32 +247,7 @@ export function allContacts() {
export function accepted(address) {
const a = (load().accepted || {})[address];
return a ? { identity: b32decode(a.identity), active: a.active, delivered: !!a.delivered } : null;
}
// The identity to derive a token for, but only while that token still needs
// handing over. Null once they have it.
//
// §5.8 has the owner *deliver* each token to its correspondent; it does not
// say every message carries one. Attaching it to all of them puts a secret in
// the body of ordinary mail, and a recipient that republishes what it gets
// can't carry one: a published record is checked by rebuilding the exact
// bytes that were signed, and the token must not be among them.
export function tokenToDeliver(address) {
const a = (load().accepted || {})[address];
return a && a.active && !a.delivered ? b32decode(a.identity) : null;
}
// Records that this correspondent now holds our token, so later messages
// carry no secret. Deliberately not undone by block(): they keep the value
// either way, and re-accepting hands back the same one, because it is derived
// from the master and the identity frozen at acceptance.
export function markDelivered(address) {
update(state => {
const table = { ...(state.accepted || {}) };
if (table[address]) table[address] = { ...table[address], delivered: true };
state.accepted = table;
});
return a ? { identity: b32decode(a.identity), active: a.active } : null;
}
// Admit a contact to the main tier. The identity is frozen at acceptance — a
@ -286,10 +261,6 @@ export function accept(address, identityBytes) {
identity: previous?.identity ?? b32encode(identityBytes),
active: true,
addedAt: previous?.addedAt ?? Date.now(),
// A contact accepted before this field existed is treated as still
// owing, so the next message carries the token once more. That is what
// every message did before, so it cannot break anything that works.
delivered: !!previous?.delivered,
};
state.accepted = table;
});