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"; "gsmol-bridge: serverHost and serverPublicKey must be set together, or not at all";
stdenvNoCC.mkDerivation { stdenvNoCC.mkDerivation {
pname = "gsmol-bridge"; pname = "gsmol-bridge";
version = "0.2.1"; version = "0.2.0";
# Respects the repo's own .gitignore, so .venv/.devbox/.jj never enter the # Respects the repo's own .gitignore, so .venv/.devbox/.jj never enter the
# store path even when building from an unclean local checkout. # 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 // A signed Reply-To lets a first-time recipient name and answer us (§5.5
// allows unknown keys); "anonymous" omits it. // allows unknown keys); "anonymous" omits it.
if (account && !anonymous) fields.push(["Reply-To", account.uri(me.publicKey)]); if (account && !anonymous) fields.push(["Reply-To", account.uri(me.publicKey)]);
// §5.8: hand an accepted correspondent the token for our own mailbox, once, // §5.8: hand an accepted correspondent the token for our own mailbox, so a
// so a reply from them reaches our main tier. See store.tokenToDeliver for // first reply from them reaches our main tier.
// why this isn't on every message. const accepted = store.accepted(addr.short);
const owed = store.tokenToDeliver(addr.short); if (accepted?.active) fields.push(["Accept", b32encode(tokenFor(master, accepted.identity))]);
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.
const bodyBytes = utf8Bytes(buildFrontmatter(fields.filter(([, v]) => v), const bodyBytes = utf8Bytes(buildFrontmatter(fields.filter(([, v]) => v),
body.trim())); body.replace(/\s+$/, "") + "\n"));
const envelope = seal(me, recipient, bodyBytes); const envelope = seal(me, recipient, bodyBytes);
// §5.8: our token for their mailbox, if they have given us one. // §5.8: our token for their mailbox, if they have given us one.
const held = store.tokenFrom(addr.short); const held = store.tokenFrom(addr.short);
@ -969,9 +962,6 @@ async function sendMail(toText, subject, body, replyTo, anonymous) {
} finally { } finally {
opened.session.stream.close(); 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. // §5.6: the ephemeral is gone, so keep a copy sealed to ourselves.
await store.storeMessage("sent", { await store.storeMessage("sent", {
id: hex(messageId(envelope)), id: hex(messageId(envelope)),

View file

@ -247,32 +247,7 @@ export function allContacts() {
export function accepted(address) { export function accepted(address) {
const a = (load().accepted || {})[address]; const a = (load().accepted || {})[address];
return a ? { identity: b32decode(a.identity), active: a.active, delivered: !!a.delivered } : null; return a ? { identity: b32decode(a.identity), active: a.active } : 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;
});
} }
// Admit a contact to the main tier. The identity is frozen at acceptance — a // 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), identity: previous?.identity ?? b32encode(identityBytes),
active: true, active: true,
addedAt: previous?.addedAt ?? Date.now(), 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; state.accepted = table;
}); });