Skip to content

Commit c35eb01

Browse files
committed
Auto-make mailbox non-transferable AID on startup; test script
Signed-off-by: Kent Bull <kent@kentbull.com>
1 parent 7523e75 commit c35eb01

6 files changed

Lines changed: 686 additions & 37 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
5+
BASE="${BASE:-${KERI_SCRIPT_DIR}}"
6+
MAILBOX_URL="${MAILBOX_URL:-http://127.0.0.1:9000/}"
7+
MAILBOX_LOG="${MAILBOX_LOG:-${BASE}/mailbox.log}"
8+
9+
if [[ -f "${KERI_SCRIPT_DIR}/keri/cf/main/mailbox.json" ]]; then
10+
MAILBOX_CONFIG_DIR="${KERI_SCRIPT_DIR}"
11+
MAILBOX_CONFIG_FILE="main/mailbox.json"
12+
elif [[ -f "${KERI_SCRIPT_DIR}/keri/cf/mailbox.json" ]]; then
13+
MAILBOX_CONFIG_DIR="${KERI_SCRIPT_DIR}"
14+
MAILBOX_CONFIG_FILE="mailbox.json"
15+
else
16+
echo "Could not find a mailbox config file under scripts/keri/cf." >&2
17+
exit 1
18+
fi
19+
20+
ALICE_NAME="${ALICE_NAME:-alice-demo}"
21+
BOB_NAME="${BOB_NAME:-bob-demo}"
22+
ALICE_ALIAS="${ALICE_ALIAS:-alice}"
23+
BOB_ALIAS="${BOB_ALIAS:-bob}"
24+
MAILBOX_NAME="${MAILBOX_NAME:-mailbox}"
25+
MAILBOX_ALIAS="${MAILBOX_ALIAS:-mailbox}"
26+
27+
ALICE_SALT="${ALICE_SALT:-0ACDEyMzQ1Njc4OWxtbm9aBc}"
28+
BOB_SALT="${BOB_SALT:-0ACDEyMzQ1Njc4OWdoaWpsaw}"
29+
30+
wait_for_mailbox() {
31+
local url="${1}"
32+
for _ in $(seq 1 40); do
33+
if curl -fsS "${url}/health" >/dev/null 2>&1; then
34+
return 0
35+
fi
36+
sleep 0.25
37+
done
38+
39+
echo "Mailbox host did not become healthy at ${url}/health" >&2
40+
return 1
41+
}
42+
43+
cleanup() {
44+
if [[ -n "${MAILBOX_PID:-}" ]]; then
45+
kill "${MAILBOX_PID}" >/dev/null 2>&1 || true
46+
wait "${MAILBOX_PID}" >/dev/null 2>&1 || true
47+
fi
48+
}
49+
50+
trap cleanup EXIT
51+
52+
mkdir -p "$(dirname "${MAILBOX_LOG}")"
53+
54+
echo "Using mailbox config: ${MAILBOX_CONFIG_DIR}/keri/cf/${MAILBOX_CONFIG_FILE}"
55+
echo "Mailbox log: ${MAILBOX_LOG}"
56+
57+
# Start mailbox host
58+
kli mailbox start \
59+
--name "${MAILBOX_NAME}" \
60+
--alias "${MAILBOX_ALIAS}" \
61+
--config-dir "${MAILBOX_CONFIG_DIR}" \
62+
--config-file "${MAILBOX_CONFIG_FILE}" \
63+
--loglevel INFO \
64+
>"${MAILBOX_LOG}" 2>&1 &
65+
MAILBOX_PID=$!
66+
67+
wait_for_mailbox "${MAILBOX_URL%/}"
68+
69+
# Create Alice
70+
kli init --name "${ALICE_NAME}" --salt "${ALICE_SALT}" --nopasscode
71+
kli incept \
72+
--name "${ALICE_NAME}" \
73+
--alias "${ALICE_ALIAS}" \
74+
--file "${KERI_SCRIPT_DIR}"/demo/data/transferable-sample.json
75+
76+
# Create Bob
77+
kli init --name "${BOB_NAME}" --salt "${BOB_SALT}" --nopasscode
78+
kli incept \
79+
--name "${BOB_NAME}" \
80+
--alias "${BOB_ALIAS}" \
81+
--file "${KERI_SCRIPT_DIR}"/demo/data/transferable-sample.json
82+
83+
MAILBOX_CTRL_OOBI="$(kli oobi generate --name "${MAILBOX_NAME}" --alias "${MAILBOX_ALIAS}" --role controller | tail -n 1)"
84+
MAILBOX_AID="$(echo "${MAILBOX_CTRL_OOBI}" | awk -F/ '{print $(NF-1)}')"
85+
86+
echo
87+
echo "Mailbox controller OOBI: ${MAILBOX_CTRL_OOBI}"
88+
echo "Mailbox AID: ${MAILBOX_AID}"
89+
90+
# Resolve mailbox controller OOBI into both local stores
91+
kli oobi resolve \
92+
--name "${ALICE_NAME}" \
93+
--oobi-alias "${MAILBOX_ALIAS}" \
94+
--oobi "${MAILBOX_CTRL_OOBI}"
95+
96+
kli oobi resolve \
97+
--name "${BOB_NAME}" \
98+
--oobi-alias "${MAILBOX_ALIAS}" \
99+
--oobi "${MAILBOX_CTRL_OOBI}"
100+
101+
# Add the mailbox to both AIDs
102+
kli mailbox add \
103+
--name "${ALICE_NAME}" \
104+
--alias "${ALICE_ALIAS}" \
105+
--mailbox "${MAILBOX_ALIAS}"
106+
107+
kli mailbox add \
108+
--name "${BOB_NAME}" \
109+
--alias "${BOB_ALIAS}" \
110+
--mailbox "${MAILBOX_ALIAS}"
111+
112+
echo
113+
echo "Mailbox lists after authorization:"
114+
echo " alice mailbox: "
115+
echo " $(kli mailbox list --name ${ALICE_NAME} --alias ${ALICE_ALIAS})"
116+
echo " bob mailbox: "
117+
echo " $(kli mailbox list --name ${BOB_NAME} --alias ${BOB_ALIAS})"
118+
119+
ALICE_MBX_OOBI="$(kli oobi generate --name "${ALICE_NAME}" --alias "${ALICE_ALIAS}" --role mailbox | tail -n 1)"
120+
BOB_MBX_OOBI="$(kli oobi generate --name "${BOB_NAME}" --alias "${BOB_ALIAS}" --role mailbox | tail -n 1)"
121+
122+
echo
123+
echo "Alice mailbox OOBI: ${ALICE_MBX_OOBI}"
124+
echo "Bob mailbox OOBI: ${BOB_MBX_OOBI}"
125+
126+
# Exchange mailbox OOBIs so each side can contact the other through the mailbox
127+
kli oobi resolve \
128+
--name "${ALICE_NAME}" \
129+
--oobi-alias "${BOB_ALIAS}" \
130+
--oobi "${BOB_MBX_OOBI}"
131+
132+
kli oobi resolve \
133+
--name "${BOB_NAME}" \
134+
--oobi-alias "${ALICE_ALIAS}" \
135+
--oobi "${ALICE_MBX_OOBI}"
136+
137+
WORDS_ALICE="$(kli challenge generate --out string)"
138+
WORDS_BOB="$(kli challenge generate --out string)"
139+
140+
echo
141+
echo "Alice challenges Bob with: ${WORDS_ALICE}"
142+
# Alice -> Bob challenge over mailbox delivery
143+
kli challenge respond \
144+
--name "${ALICE_NAME}" \
145+
--alias "${ALICE_ALIAS}" \
146+
--recipient "${BOB_ALIAS}" \
147+
--words "${WORDS_ALICE}"
148+
kli challenge verify \
149+
--name "${BOB_NAME}" \
150+
--signer "${ALICE_ALIAS}" \
151+
--words "${WORDS_ALICE}"
152+
153+
echo
154+
echo "Bob challenges Alice with: ${WORDS_BOB}"
155+
# Bob -> Alice challenge over mailbox delivery
156+
kli challenge respond \
157+
--name "${BOB_NAME}" \
158+
--alias "${BOB_ALIAS}" \
159+
--recipient "${ALICE_ALIAS}" \
160+
--words "${WORDS_BOB}"
161+
kli challenge verify \
162+
--name "${ALICE_NAME}" \
163+
--signer "${BOB_ALIAS}" \
164+
--words "${WORDS_BOB}"
165+
166+
echo
167+
echo "Mailbox debug snapshots:"
168+
# Inspect mailbox state from each side
169+
kli mailbox debug \
170+
--name "${ALICE_NAME}" \
171+
--alias "${ALICE_ALIAS}" \
172+
--witness "${MAILBOX_AID}" \
173+
--verbose
174+
175+
kli mailbox debug \
176+
--name "${BOB_NAME}" \
177+
--alias "${BOB_ALIAS}" \
178+
--witness "${MAILBOX_AID}" \
179+
--verbose
180+
181+
echo
182+
echo "Mailbox host log: ${MAILBOX_LOG}"
183+
echo "Done."

scripts/keri/cf/mailbox.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"dt": "2022-01-20T12:57:59.823350+00:00",
3+
"mailbox": {
4+
"dt": "2022-01-20T12:57:59.823350+00:00",
5+
"curls": ["http://127.0.0.1:9000/"]
6+
},
7+
"iurls": [
8+
]
9+
}

src/keri/app/mailboxing.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
"""
2121
import json
2222
import platform
23+
from urllib.parse import urlparse
2324

2425
import falcon
2526

2627
from hio.base import doing
2728
from hio.core import http
2829
from hio.help import decking
2930

30-
from ..kering import Vrsn_1_0, Roles, Ilks
31+
from ..kering import Vrsn_1_0, Roles, Ilks, Schemes
3132
from ..core import Kevery, parsing, routing, serdering
3233
from ..end import loadEnds as loadEndingEnds
3334
from ..peer import Exchanger
@@ -157,6 +158,61 @@ def _ingestCesr(raw, *, kvy, rvy, exc, tvy=None):
157158
exc.processEscrow()
158159

159160

161+
def _mailboxAdminPath(hab):
162+
"""Return the served mailbox-admin route for one hosted mailbox habitat.
163+
164+
The route follows the historical KERIpy mailbox-admin convention: append
165+
``/mailboxes`` relative to the stored mailbox endpoint URL path.
166+
167+
This helper is intentionally strict:
168+
- mailbox admin routing must come from the loaded self ``/loc/scheme``
169+
- this path-relative rule applies only to mailbox admin, not to every
170+
other mailbox-host surface
171+
"""
172+
urls = hab.fetchUrls(eid=hab.pre, scheme=Schemes.https) or hab.fetchUrls(
173+
eid=hab.pre,
174+
scheme=Schemes.http,
175+
)
176+
if not urls:
177+
raise ValueError("mailbox admin requires a loaded self HTTP(S) location record")
178+
179+
url = urls[Schemes.https] if Schemes.https in urls else urls[Schemes.http]
180+
path = urlparse(url).path.rstrip("/")
181+
return f"{path}/mailboxes"
182+
183+
184+
def _roleEnabled(hby, cid, role, eid):
185+
"""Return True when one endpoint role record is active for startup use."""
186+
end = hby.db.ends.get(keys=(cid, role, eid))
187+
return bool(end and (end.allowed or end.enabled))
188+
189+
190+
def _requireMailboxIdentity(hby, hab):
191+
"""Require authoritative self mailbox identity state before host startup.
192+
193+
Boot-time invariant:
194+
- the hosted non-transferable mailbox AID must already advertise at
195+
least one self HTTP(S) location
196+
- it must already authorize itself as both controller and mailbox
197+
198+
This prevents the mailbox host from booting with an invented admin route or
199+
an incomplete self-description. Mailbox start is responsible for creating
200+
or reconciling this accepted self state before hosting begins; startup here
201+
still refuses to serve if that reconciliation did not actually land in the
202+
local database.
203+
"""
204+
urls = hab.fetchUrls(eid=hab.pre, scheme=Schemes.https) or hab.fetchUrls(
205+
eid=hab.pre,
206+
scheme=Schemes.http,
207+
)
208+
if not urls:
209+
raise ValueError("mailbox host startup requires a loaded self HTTP(S) location record")
210+
if not _roleEnabled(hby, hab.pre, Roles.controller, hab.pre):
211+
raise ValueError("mailbox host startup requires self controller authorization state")
212+
if not _roleEnabled(hby, hab.pre, Roles.mailbox, hab.pre):
213+
raise ValueError("mailbox host startup requires self mailbox authorization state")
214+
215+
160216
def setupMailbox(hby, alias="mailbox", mbx=None, aids=None, httpPort=5632,
161217
keypath=None, certpath=None, cafilepath=None):
162218
"""Set up one mailbox host around an existing local habitat.
@@ -166,11 +222,14 @@ def setupMailbox(hby, alias="mailbox", mbx=None, aids=None, httpPort=5632,
166222
- ``AuthorizedForwardHandler`` gates ``/fwd`` storage by mailbox authz
167223
- ``Respondant`` owns reply emission for non-stream cues
168224
- ``MailboxStart`` runs parser ingress, escrow replay, and cue routing
225+
- mailbox admin is served at ``<stored-mailbox-url-path>/mailboxes``
169226
170227
Because mailbox hosting here is separate from witness hosting, ``/fwd``
171228
storage is gated through ``AuthorizedForwardHandler`` so the host stores
172229
traffic only for recipients that currently authorize the hosted mailbox
173-
AID.
230+
AID. Only mailbox admin follows the stored location URL path in this
231+
module; served OOBIs still come from ``loadEndingEnds(...)`` at their
232+
normal root routes.
174233
"""
175234
from .indirecting import createHttpServer, HttpEnd
176235

@@ -186,6 +245,7 @@ def setupMailbox(hby, alias="mailbox", mbx=None, aids=None, httpPort=5632,
186245
raise ValueError(f"missing local mailbox alias {alias!r}")
187246
if hab.kever.prefixer.transferable:
188247
raise ValueError("mailbox host requires a non-transferable identifier")
248+
_requireMailboxIdentity(hby, hab)
189249

190250
mbx = mbx if mbx is not None else Mailboxer(name=alias, temp=hby.temp)
191251
forwarder = AuthorizedForwardHandler(hby=hby, mbx=mbx, mailboxAid=hab.pre)
@@ -212,7 +272,8 @@ def setupMailbox(hby, alias="mailbox", mbx=None, aids=None, httpPort=5632,
212272
httpEnd = HttpEnd(rxbs=parser.ims, mbx=mbx)
213273
app.add_route("/", httpEnd)
214274
app.add_route("/health", HealthEnd())
215-
app.add_route("/mailboxes", MailboxAddRemoveEnd(hby=hby, hab=hab, kvy=kvy, rvy=rvy, exc=exchanger))
275+
app.add_route(_mailboxAdminPath(hab),
276+
MailboxAddRemoveEnd(hby=hby, hab=hab, kvy=kvy, rvy=rvy, exc=exchanger))
216277

217278
server = createHttpServer(host, httpPort, app, keypath, certpath, cafilepath)
218279
if not server.reopen():

0 commit comments

Comments
 (0)