Registrant access-events panel (RDAP spec 13 Surface B) - #186
Open
OlegPhenomenon wants to merge 3 commits into
Open
OlegPhenomenon wants to merge 3 commits into
OlegPhenomenon wants to merge 3 commits into
Conversation
Add the "Who accessed my data" transparency panel to the domain page, showing which authorities have accessed a domain's registration data. - Redux accessEvents slice (keyed by domain uuid) + thunk fetchAccessEvents, wired into the store combiner and the three action-type constants. - api.fetchDomainAccessEvents(uuid) and the BFF getDomainAccessEvents handler, registered below the session-auth gate. The handler forwards only the registrant's session Bearer and attaches no client-supplied identity; ownership is re-derived independently by the registry. - DomainPage .page--block with a semantic Semantic-UI table (Institution / Category / Access time), uniquely-keyed rows, and an explicit empty state. Renders only the three fields the API returns (accessed_at, organization, category); no withheld field is displayed, logged or stored. - ET/EN i18n keys under domain.accessEvents.* (provisional copy, pending final sign-off). - Vitest: reducer slice, panel render + a11y + empty state, i18n key-set match, BFF handler (session Bearer forwarded, no body/token logging), and route-below-gate ordering.
…ccesses"
Two code-review fixes on the spec-13 Surface B transparency panel.
W1 (DomainPage): the panel showed the empty-state message ("No authority
has accessed this domain's data.") whenever the events list was falsy --
including while the fetch was in flight and permanently after a failure,
a transparency false negative. Render four distinct states instead:
loading (spinner) while in flight, error (message + retry button) on
failure, the empty-state message ONLY on a successful empty array
(Array.isArray && length === 0), and the table when populated. The
withheld fields are still never rendered; only {accessed_at, organization,
category} appear.
W2 (accessEvents reducer): isLoading/error were single top-level flags,
so one domain's in-flight/failed fetch drove another domain's panel.
Scope per-request state by uuid -- state is now { byUuid: { [uuid]:
{ events, isLoading, error } } } -- so loading/error rendering is scoped
to the domain being viewed. events stays undefined until success, letting
the panel tell "loading/failed" apart from "loaded, empty". The fetch
guard keys on the per-uuid record to fetch once and avoid a refetch loop.
i18n: add domain.accessEvents.error and domain.accessEvents.retry to
en.json and et.json (provisional copy, pending sign-off).
Tests: reducer per-uuid scoping (a failure for A leaves B untouched);
panel renders loading while in flight, error+retry on failure (not the
empty message), and the empty message only on a successful empty array.
The panel shipped as a working stub: raw ISO timestamps, the raw category enum, a bare "-" for a missing institution, and 100 lines of JSX inline in DomainPage. Move it into src/components/DomainAccessEvents/ alongside the other feature components, and render it the way the rest of the portal renders things: DD.MM.Y HH:mm dates (moment, as in DomainList/UserData), translated category labels with a circular colour dot mirroring DomainStatuses, a translated fallback for an unrecorded institution, a semantic Message for the error state, and the empty state as a header paragraph like the nameserver and DNSSEC blocks. The four states stay distinct: an in-flight or failed load still never renders as "no authority accessed your data". Show the panel only to the domain's direct registrant. The registry scopes events to Contact.registrant_user_direct_contacts, but grants domain access to tech/admin contacts and company representatives too, so those viewers get 200 with an empty array — which the panel would have stated as "no authority has accessed this domain's data". We cannot know that for them, so neither the panel nor the request happens on their behalf. Widen this together with own_ids when the registry widens it. An unrecognised category renders its raw value rather than disappearing, so a registry-side addition surfaces as data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Surface B (registrant_center portal) of RDAP spec 13 — registrant access transparency: a new "Who accessed my data" panel on the domain page that lists which authorities have accessed a domain's registration data.
The registry read API (Surface A) is implemented separately on the registry repo's
feat/rdap-data-apibranch; this PR is the front-end + BFF that consumes it.API contract consumed
GET /api/v1/registrant/domains/:uuid/access_events→200with a JSON array, most-recent-first, each item exactly:Empty (owned domain, no disclosable events) →
[]. Unknown/unowned →404. Unauthorized →401. The panel renders only these three fields; nothing else is displayed, logged or stored.Changes
FETCH_ACCESS_EVENTS_REQUEST/SUCCESS/FAILUREconstants; newaccessEventsslice keyed by domain uuid with thefetchAccessEvents(uuid)thunk; wired into the store combiner insidewithReduxStateSync(combineReducers({...})).api.fetchDomainAccessEvents(uuid).getDomainAccessEventshandler (mirrorsgetDomains) forwarding only the session Bearer viaAPI(session), attaching no client-supplied identity; route registered below theapp.all('/api/*', API.checkAuth)session gate. ReuseshandleResponseunchanged — no response-body or token logging.src/components/DomainAccessEvents/— a self-contained.page--blockpanel rendered fromDomainPage.jsx, following the portal's existing conventions:DD.MM.Y HH:mmtimestamps via moment, as inDomainList/UserData(the API's ISO-8601 value is never shown raw);DomainStatuses; an unrecognised category falls back to its raw value so a registry-side addition surfaces as data rather than vanishing;organizationis null;<Table.Header>(Institution / Category / Access time) with uniquely-keyed rows — the registry does not dedup, so identical events can repeat;Message+ retry / empty-state paragraph in the block header (like the nameserver and DNSSEC blocks) / table. An in-flight or failed load never renders as "no authority accessed your data".domain.accessEvents.*keys (incl. one label perRdapPrivilegeGrant::CATEGORIESvalue and the institution fallback) added to bothen.jsonandet.json.Audience: the panel is shown only to the domain's direct registrant
The registry scopes events to
Contact.registrant_user_direct_contacts— the ident-matched private-person registrant — butcurrent_registrant_user.domainsgrants domain access to tech/admin contacts and company representatives as well. Those viewers therefore receive200 [], which the panel would have stated as "no authority has accessed this domain's data". That is a factual claim we cannot make for them, in a feature whose entire purpose is transparency.So the panel — and the request — happens only for the direct registrant. This mirrors the spec's own under-disclose posture (
RegistrantAccessEventsQuery#build_intervals), adds no user-facing copy while the ET/EN wording is still pending sign-off, and reverses with one condition.For Timo, on return: spec 13
plan.md:21-23defers company-representative tenure as an "additive follow-up", andgrounding.md:579-583leaves the breadth ofown_idsexplicitly open. Two things to decide together:own_idswiden toregistrant_user_contactsso company representatives see their company's access history? The gate here widens with it.Tests
npm run test(lint + vitest) — 36 files, 267 tests passing.eslint --max-warnings=0clean.Notes / open items
feat/rdap-data-apiplus an eeID session. Visual confirmation still to be done on a deployed/dev stack.