Skip to content

Add DOI recording and duplicate-check endpoints - #9

Open
baltierra wants to merge 1 commit into
masterfrom
feature/doi-tracking-endpoints
Open

Add DOI recording and duplicate-check endpoints#9
baltierra wants to merge 1 commit into
masterfrom
feature/doi-tracking-endpoints

Conversation

@baltierra

Copy link
Copy Markdown

Adds two new endpoints on top of the DOI-tracking tables introduced in archive-core (see companion PR: feature/doi-tracking-tables), letting consumers like Hermes record newly minted DOIs and check whether messages have already been used in one.

POST /doi/
Records a newly minted DOI and the message UUIDs it covers. Requires a valid, authenticated HopAuth identity (resolved via a whoami lookup) — but does not require topic-specific write permission, since minting a DOI about messages the caller has already read is a distinct action from writing to a topic, and the real authorization boundary for "can this person mint a DOI at all" is enforced one layer up by the calling service.

GET /doi/check

Given one or more candidate message UUIDs, returns which existing DOI(s), if any, they overlap with. Each UUID's topic is looked up and gated behind the same per-topic Read permission check used elsewhere in this API — a UUID is only included in the response if the caller can actually read its topic.

The response is deliberately privacy-conscious: for each matched DOI it returns only total_message_count, matched_count, and matched_uuids (a subset of the caller's own requested UUIDs) — never the identity of any other message belonging to that DOI. This lets a caller compute an exact-duplicate-vs-partial-overlap distinction using simple arithmetic, without archive-api ever exposing message identities the caller didn't already know about.

Shared helper: _hopauth_multi()
Both endpoints route their HopAuth calls through a new shared helper that correctly relays HopAuth's own SCRAM challenge back to the original caller when a handshake is incomplete, mirroring fetch_message's existing inline behavior exactly, rather than treating an interim 401 as a hard failure. This matters because SCRAM is a two-hop interactive protocol: an optimistic first attempt is expected to be challenged, and swallowing that challenge into a generic error breaks authentication for every caller, every time.

@baltierra
baltierra requested a review from cnweaver August 26, 2026 16:49
Comment thread scripts/archive_api.py
return Response(status_code=200, content=bson.dumps({}), headers=resp_headers)


async def _hopauth_multi(authorization: str, ops_path: Optional[str] = None):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should be given a descriptive name for its purpose, rather than just the fact that it works by making requests to the hopauth multi-request endpoint. If this is always used identically to fetch_message, then that should be refactored to use the extracted function.

Comment thread scripts/archive_api.py
topic = metadata.topic
if topic not in topic_permission_cache:
base_topic_name = effective_topic_name_for_access(topic)
relay, hop_json, extra_headers = await _hopauth_multi(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks structurally problematic: Forwarding authentication to hopauth is a stateful process when SCRAM authentication is used, causing the client to have to repeat the request, and all logic before the forwarding to hopauth in this server to have to re-run. Forwarding to hopauth should happen at one point (not in a loop) as early as possible in handling the request to prevent duplicated work. I suspect that for tests so far topic_permission_cache has hidden this, but that will only work when the forwarded requests would be fully redundant, which they may not be if multiple topics are involved.

Comment thread scripts/archive_api.py
if authorization is None:
return authentication_required()

relay, hop_json, resp_headers = await _hopauth_multi(authorization)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't appear to do any authorization checks beyond that the requesting user is known. I thought our plan was that a user would ned to have write access to the topic on which a message exists to be able to request a DOI for it; did we change that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants