2020"""
2121import json
2222import platform
23+ from urllib .parse import urlparse
2324
2425import falcon
2526
2627from hio .base import doing
2728from hio .core import http
2829from hio .help import decking
2930
30- from ..kering import Vrsn_1_0 , Roles , Ilks
31+ from ..kering import Vrsn_1_0 , Roles , Ilks , Schemes
3132from ..core import Kevery , parsing , routing , serdering
3233from ..end import loadEnds as loadEndingEnds
3334from ..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+
160216def 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