mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
feat(custom-metrics): accept the measurement token on value logging
A script or a home-automation rule holding a measurement token can now
post a value to POST /api/custom-metrics/{id}/entries for a custom metric
its owner already created. The route names MEASUREMENTS_WRITE_SCOPE on
the POST only; the value list, the metric list and every create, edit,
delete and restore leg still name no scope and refuse the token 403.
The token stays on its owner's record: requireAuth refuses any account
selector, and an id that is unknown, deleted or another account's answers
the same 404. The entry has no source column, so the audit row names the
token that wrote it.
The bearer-scope guard now freezes the scope's reach at three files, with
the custom-metric file naming it once and only on the POST. Integration
cases cover the admitted write, idempotent retry, the foreign and deleted
ids, five other narrow scopes, every other custom-metric leg, and a shared
record. The OpenAPI descriptions and the token card text in every locale
say what the token can do and where a metric's id is found.
Refs #1377
This commit is contained in:
1 parent
deb7db2ffa
commit
2167e251c1
17 files changed
+422
-41
No files matched your search
+26
-11
@@ -3617,21 +3617,24 @@ paths:
|
||||
the only place it ever exists; it is stored as an HMAC and no path re-reveals it.
|
||||
|
||||
|
||||
**What it can do.** `POST /api/measurements` and `POST /api/measurements/batch`, on its owner's own record. Rows
|
||||
it writes carry `source: EXTERNAL`, resolved from the credential rather than taken from the body, so a reading
|
||||
pushed by a bridge stays distinguishable from one typed in by hand and can be picked out with the source filter.
|
||||
A body naming ANY source is refused 422 rather than relabelled — honouring it would defeat the label, overriding
|
||||
it silently would hand the caller rows it did not ask for. `APPLE_HEALTH` is the one that would do real damage:
|
||||
it is half a dedup key the phone also writes into, it participates in the cross-source merge, and it is what
|
||||
**What it can do.** `POST /api/measurements` and `POST /api/measurements/batch`, on its owner's own record, and
|
||||
`POST /api/custom-metrics/{id}/entries` for a custom metric its owner already created (it cannot list, create,
|
||||
edit or delete metrics, so the id comes from the metric's page in the app). Measurement rows it writes carry
|
||||
`source: EXTERNAL`, resolved from the credential rather than taken from the body, so a reading pushed by a
|
||||
bridge stays distinguishable from one typed in by hand and can be picked out with the source filter. A body
|
||||
naming ANY source is refused 422 rather than relabelled — honouring it would defeat the label, overriding it
|
||||
silently would hand the caller rows it did not ask for. `APPLE_HEALTH` is the one that would do real damage: it
|
||||
is half a dedup key the phone also writes into, it participates in the cross-source merge, and it is what
|
||||
decides the Apple Health card may claim a sync happened. For the same reason a write through this credential
|
||||
does not move the native client's sync checkpoint. The rows stay editable by their owner, unlike a connected
|
||||
provider's — the hardware behind the token is the user's own.
|
||||
|
||||
|
||||
**What it cannot do.** Everything else, including the measurement reads on the same paths, the edit and delete
|
||||
legs, and the export — a scope grants what it names and nothing adjacent. It cannot be pointed at a shared
|
||||
record: a request carrying the account selector is refused 403 before any grant is read, whatever grants its
|
||||
holder actually has. And it cannot mint another token, this endpoint included.
|
||||
**What it cannot do.** Everything else, including the measurement reads on the same paths, the custom-metric
|
||||
list and value reads, the edit and delete legs, and the export — a scope grants what it names and nothing
|
||||
adjacent. It cannot be pointed at a shared record: a request carrying the account selector is refused 403 before
|
||||
any grant is read, whatever grants its holder actually has. And it cannot mint another token, this endpoint
|
||||
included.
|
||||
|
||||
|
||||
Minting requires a COOKIE SESSION with a fresh proof: a sign-in or `POST /api/auth/reproof` within the last five
|
||||
@@ -21140,7 +21143,19 @@ paths:
|
||||
tags:
|
||||
- Custom metrics
|
||||
summary: Log a value
|
||||
description: Records a value against the custom metric, snapshotting the metric's unit. Audits as `customMetricEntry.create`.
|
||||
description: >-
|
||||
Records a value against the custom metric, snapshotting the metric's unit. Audits as `customMetricEntry.create`.
|
||||
|
||||
|
||||
**Narrow token.** Also reachable with a `measurements:write` Bearer (minted at `POST /api/tokens/measurements`),
|
||||
the supported way to push a custom metric's value from a script or a home-automation rule. Such a credential
|
||||
writes only to its owner's own live metrics: an id that is unknown, deleted, or another account's answers the
|
||||
same 404, and a request carrying the account selector is refused 403 before any sharing grant is read. This POST
|
||||
is all the scope reaches on custom metrics. The value list on this same path, the metric list, and creating,
|
||||
editing or deleting a metric or a value name no scope and refuse it 403, so the metric id has to come from its
|
||||
owner: it is the path segment after `/custom-metrics/` in the address of the metric's page in the app. The entry
|
||||
carries no source of its own; the audit row names the token that wrote it. Every other narrow token,
|
||||
`workouts:write` included, is refused 403.
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
|
||||
Reference in new issue
Block a user