mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
A bridge that pushes workouts from a watch vendor had no credential that reached POST /api/workouts/batch: the route called requireAuth() with no scope, so under the fail-closed default only a cookie session or a wildcard token got through, and a measurements:write token was refused 403. The route's header still described the pre-fail-closed behaviour. Add a separate workouts:write scope rather than widening measurements:write, so a token already handed to a scale does not start accepting workouts. - POST /api/tokens/workouts mints it, mirroring the measurement and document mints: cookie session plus a fresh proof, a literal permission array, 10 mints a minute, at most 10 live tokens (409 tokens.workouts.ceiling_reached), a one-year default lifetime, audited. - POST /api/workouts/batch names the scope. Sessions and wildcard tokens behave as before. A scoped caller's workouts are stored as EXTERNAL, resolved from the credential, and a batch in which any entry names a source is refused 422 (workout.batch.source_not_permitted), the same rule the measurement batch applies. EXTERNAL is on no source ladder, so when the phone recorded the same session the phone's copy is shown. - Settings > API offers a workout token card next to the measurement one, and the endpoint list shows the batch route. - Guards: the mint site, the declared scope vocabulary, the recent-proof routes, and a new check that only the batch declares the scope. - OpenAPI documents the mint, the batch's narrow-token behaviour and both error codes; docs/api/README.md lists the workout and document mints. Refs #1054
22 lines
1.0 KiB
TypeScript
22 lines
1.0 KiB
TypeScript
/**
|
|
* The Bearer scope for pushing workouts in from another system (#1054): a
|
|
* watch vendor's bridge, a script replaying an export.
|
|
*
|
|
* A zero-import leaf for the reason `MEASUREMENTS_WRITE_SCOPE` is one (see
|
|
* `@/lib/measurements/scopes`): the mint route, the batch route, the settings
|
|
* card and the guards all read it, and none of them should inherit an import
|
|
* graph for a string.
|
|
*
|
|
* Its own scope rather than a second door for `measurements:write`. A token
|
|
* someone already pasted into a scale's uploader should not quietly start
|
|
* accepting workouts because a later release widened what the name means; a
|
|
* scope grants what it names, and a bridge that sends both mints both.
|
|
*
|
|
* Deliberately alone. There is no read counterpart and must not be one: the
|
|
* workout list and detail stay cookie-equivalent, so a credential pasted into
|
|
* a bridge can add a session and learn nothing about the ones already stored.
|
|
*/
|
|
|
|
/** The scope a narrow token must carry to write workouts. */
|
|
export const WORKOUTS_WRITE_SCOPE = "workouts:write";
|