fix(cycle): keep the calendar feed to the owner and a managed profile's guardian

A calendar feed is a standing credential whose contents end up in a
third party's calendar cloud. Putting a record's cycle dates there is
the decision of the person they are about, or of the guardian of
somebody who cannot make it, and no delegate of an adult's record,
MANAGE included.

The subscription routes resolve through requireGuardianAuth instead of
requireRecordAuth("manage", "cycle"): the owner on their own record, or
a MANAGE grant on a record carrying the managed-profile marker. Every
verb follows, the revoke too. The feed re-reads the marker on each fetch
as well as the guardian's grant, so a row minted for somebody else on an
adult's record is a 404 whatever grant stands behind it. The settings
card is offered only where the routes answer.

The routes move from the delegable and MANAGE literals to the guardian
list, and the MANAGE handler inventory is back to 93.
This commit is contained in:
Marc-André Bombeck committed 2026-11-06 18:03:37 +01:00
1 parent e8ef590af7
commit 3b3ef5c0a1
18 files changed
+227 -171

No files matched your search

+9 -7
View File
@@ -790,11 +790,12 @@ paths:
description: "Anonymous: a calendar provider polls this URL, so the raw `hlf_` token in the path (optionally followed by
`.ics`) is the ONLY credential. Rate-limited per source address before any lookup and per token after it. Every
gate is re-read on each fetch: the record and the minting account are not pending deletion, the operator's
external-access switch is on, cycle tracking is available, and a feed minted by a delegate still has a live
MANAGE grant over the cycle section. Every miss (malformed, unknown, revoked, switched off, deleted, lapsed
grant) is the same 404. The body is RFC 5545: all-day events, dates only, discreet titles by default, the next
few periods with widening bands (self-capped), optionally past periods and, for TRYING_TO_CONCEIVE, the next
fertile window. `ETag` is the content hash; a matching `If-None-Match` answers 304."
external-access switch is on, cycle tracking is available, and a feed minted by a managed profile's guardian
still sits on a managed profile the guardian holds a live MANAGE grant over. Every miss (malformed, unknown,
revoked, switched off, deleted, lapsed grant) is the same 404. The body is RFC 5545: all-day events, dates only,
discreet titles by default, the next few periods with widening bands (self-capped), optionally past periods and,
for TRYING_TO_CONCEIVE, the next fertile window. `ETag` is the content hash; a matching `If-None-Match` answers
304."
security: []
parameters:
- in: path
@@ -847,8 +848,9 @@ paths:
generic auth gate answers it before the route's own checks run. Otherwise: Calendar subscriptions are
managed over a browser session only: any Bearer token gets `cycle.feed.cookie_only`. Cycle tracking switched
off for the record answers `cycle.disabled`; the operator's external-access switch answers
`cycle.feed.api_disabled` on turning it on and on rotating. Reached by the record's owner and by a MANAGE
delegate whose grant covers the cycle section, never by a READ or WRITE delegate.
`cycle.feed.api_disabled` on turning it on and on rotating. Reached by the record's owner on their own
record and by the guardian of a managed profile; a delegate on an adult's record is refused at every grant
level, MANAGE included.
Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`).