Skip to content
quarry Artifact [ad13357244]
Login

Artifact [ad13357244]

Artifact ad13357244d30653710952778b745a58e423ff575ad5e8ee1fd8a36af8f5dc42:


// inbound.go holds what RFC 0014 Phase 1 needs the forge to remember
// about replies that arrive through notify: the secret notify signs
// its deliveries with, and which replies have already been applied.
//
// Both are forge-local state, not truth: a lost secret is rotated
// and stored again, a lost dedup row can at worst let one retried
// delivery post twice.
package oltp

import (
	"database/sql"
	"time"
)

const inboundSchema = `
CREATE TABLE IF NOT EXISTS notify_hook (
	id          INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
	secret      TEXT NOT NULL,
	endpoint_id TEXT NOT NULL DEFAULT '',
	url         TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS inbound_reply (
	message_id TEXT NOT NULL PRIMARY KEY, -- the mail's Message-ID, notify's delivery key
	txn_id     TEXT NOT NULL,
	account    TEXT NOT NULL,
	outcome    TEXT NOT NULL,             -- what the reply became, or why it did not
	at         TEXT NOT NULL
);`

// NotifyHookSecret is the key notify signs reply deliveries with, or
// empty when the forge holds none yet. notify mints it and shows it
// once, at the endpoint's creation or rotation; the forge keeps that
// one copy here. No key means every delivery is refused, which is the
// closed side of the door.
func (d *DB) NotifyHookSecret() (string, error) {
	var secret string
	err := d.db.QueryRow(`SELECT secret FROM notify_hook WHERE id = 1`).Scan(&secret)
	if err == sql.ErrNoRows {
		return "", nil
	}
	return secret, err
}

// SetNotifyHook records the endpoint notify created for the forge and
// the secret it signs with.
func (d *DB) SetNotifyHook(id, url, secret string) error {
	_, err := d.db.Exec(`INSERT OR REPLACE INTO notify_hook (id, secret, endpoint_id, url)
		VALUES (1, ?, ?, ?)`, secret, id, url)
	return err
}

// InboundReplySeen answers whether a delivery was already applied and
// what it became. notify redelivers on any non-2xx and on transport
// error, so the same mail can arrive more than once; one mail is one
// post.
func (d *DB) InboundReplySeen(messageID string) (outcome string, seen bool, err error) {
	err = d.db.QueryRow(`SELECT outcome FROM inbound_reply WHERE message_id = ?`, messageID).Scan(&outcome)
	if err == sql.ErrNoRows {
		return "", false, nil
	}
	if err != nil {
		return "", false, err
	}
	return outcome, true, nil
}

// RecordInboundReply remembers what a delivery became.
func (d *DB) RecordInboundReply(messageID, txnID, account, outcome string) error {
	_, err := d.db.Exec(`INSERT OR REPLACE INTO inbound_reply (message_id, txn_id, account, outcome, at)
		VALUES (?, ?, ?, ?, ?)`, messageID, txnID, account, outcome, time.Now().UTC().Format(time.RFC3339))
	return err
}