mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
fix(backup): keep an erased account's copy out of admin accounts
A restore read the account settings under the operator's AI endpoint policy whenever the receiving account was an admin. For a copy put into a chosen account, the settings were written by the erased account's owner, not by the operator, yet an admin account could be chosen as the target, and an ordinary target promoted later got the same trust. Admin accounts are now refused as a target, in the check and the picker, with backup.target.admin_account. A copy uploaded into a chosen account is stored as RETARGETED_UPLOAD_<ms>, and its restore never reads its settings as the operator's, whatever the account's role is by then.
This commit is contained in:
1 parent
dfe34386be
commit
16d1b542a5
24 files changed
+276
-46
No files matched your search
+19
-19
@@ -25189,10 +25189,10 @@ paths:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"409":
|
||||
description: "The chosen target cannot take the copy, and nothing was stored: a managed profile
|
||||
(`backup.target.managed_profile`), an account pending deletion (`backup.target.pending_deletion`), an
|
||||
account that already holds records (`backup.target.holds_records`, `meta.models` naming them), a username
|
||||
already taken (`backup.target.username_taken`), or a file whose own account is still here
|
||||
(`backup.owner.exists`)."
|
||||
(`backup.target.managed_profile`), an account pending deletion (`backup.target.pending_deletion`), an admin
|
||||
account (`backup.target.admin_account`), an account that already holds records
|
||||
(`backup.target.holds_records`, `meta.models` naming them), a username already taken
|
||||
(`backup.target.username_taken`), or a file whose own account is still here (`backup.owner.exists`)."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -38717,21 +38717,21 @@ components:
|
||||
auth.session.unavailable, auth.stepup.required, auth.token.expired, auth.token.invalid; backup —
|
||||
backup.foreign_reference, backup.key.missing, backup.owner.exists, backup.owner.missing,
|
||||
backup.payload.undecryptable, backup.restore.active, backup.section.missing,
|
||||
backup.target.holds_records, backup.target.managed_profile, backup.target.not_found,
|
||||
backup.target.pending_deletion, backup.target.unconfirmed, backup.target.username_taken,
|
||||
backup.upload.too_large; biomarkers — biomarkers.unit.locked; body-site — body-site.invalid; coach —
|
||||
coach.budget.exceeded, coach.conversation.invalidTitle, coach.conversation.notFound,
|
||||
coach.fenced.attachmentConflict, coach.fenced.attachmentLimit, coach.fenced.attachmentNotFound,
|
||||
coach.fenced.attachmentNotIndexed, coach.fenced.attachmentRequired, coach.fenced.attachmentUnavailable,
|
||||
coach.fenced.invalid, coach.request.invalid; coach-prefs — coach-prefs.body.invalid_json,
|
||||
coach-prefs.body.invalid_shape; consent — consent.ai.confirmRequired, consent.ai.required; cycle —
|
||||
cycle.bulk.invalid, cycle.bulk.too_large, cycle.calendar.invalid, cycle.calendar.range,
|
||||
cycle.cycles.invalid, cycle.day-log.conflict, cycle.day-log.invalid, cycle.disabled,
|
||||
cycle.period.invalid, cycle.symptom.custom.invalid, cycle.symptom.custom.limit; cycle-prefs —
|
||||
cycle-prefs.body.invalid_json, cycle-prefs.invalid; disable-coach — disable-coach.body.invalid_json;
|
||||
data_wipe — data_wipe.copy_failed; dawarich — dawarich.credentials_rejected,
|
||||
dawarich.home_place_unknown, dawarich.invalid, dawarich.not_connected, dawarich.rate_limited,
|
||||
dawarich.unreachable; day — day.invalid; demo — demo.readOnly; documents —
|
||||
backup.target.admin_account, backup.target.holds_records, backup.target.managed_profile,
|
||||
backup.target.not_found, backup.target.pending_deletion, backup.target.unconfirmed,
|
||||
backup.target.username_taken, backup.upload.too_large; biomarkers — biomarkers.unit.locked; body-site —
|
||||
body-site.invalid; coach — coach.budget.exceeded, coach.conversation.invalidTitle,
|
||||
coach.conversation.notFound, coach.fenced.attachmentConflict, coach.fenced.attachmentLimit,
|
||||
coach.fenced.attachmentNotFound, coach.fenced.attachmentNotIndexed, coach.fenced.attachmentRequired,
|
||||
coach.fenced.attachmentUnavailable, coach.fenced.invalid, coach.request.invalid; coach-prefs —
|
||||
coach-prefs.body.invalid_json, coach-prefs.body.invalid_shape; consent — consent.ai.confirmRequired,
|
||||
consent.ai.required; cycle — cycle.bulk.invalid, cycle.bulk.too_large, cycle.calendar.invalid,
|
||||
cycle.calendar.range, cycle.cycles.invalid, cycle.day-log.conflict, cycle.day-log.invalid,
|
||||
cycle.disabled, cycle.period.invalid, cycle.symptom.custom.invalid, cycle.symptom.custom.limit;
|
||||
cycle-prefs — cycle-prefs.body.invalid_json, cycle-prefs.invalid; disable-coach —
|
||||
disable-coach.body.invalid_json; data_wipe — data_wipe.copy_failed; dawarich —
|
||||
dawarich.credentials_rejected, dawarich.home_place_unknown, dawarich.invalid, dawarich.not_connected,
|
||||
dawarich.rate_limited, dawarich.unreachable; day — day.invalid; demo — demo.readOnly; documents —
|
||||
documents.chat.conversationNotFound, documents.chat.invalid, documents.inbound.alreadyConfirmed,
|
||||
documents.inbound.alreadyPartlyConfirmed, documents.inbound.budgetExceeded,
|
||||
documents.inbound.decryptFailed, documents.inbound.encounterNotFound, documents.inbound.episodeNotFound,
|
||||
|
||||
@@ -112,14 +112,21 @@ exists, and the console then asks where the records should go:
|
||||
count (the setup answers and the cycle settings): the restore replaces both
|
||||
with the copy's. A reading or medication entered on setup's last screen is a
|
||||
record, and the account is refused until it is deleted, because the restore
|
||||
would delete it too. Managed profiles and accounts pending deletion are not
|
||||
offered.
|
||||
would delete it too. Managed profiles, accounts pending deletion and admin
|
||||
accounts are not offered: an admin account is the operator's, its settings
|
||||
are read under the operator's wider AI endpoint policy, and its role reaches
|
||||
every account, so another person's record does not go into it.
|
||||
|
||||
The file is then uploaded a second time, and its owner is changed to the chosen
|
||||
account as it is read. Nothing else in the file changes. The stored copy is
|
||||
then that account's copy in every respect: its preview, its download and its
|
||||
restore are the ordinary ones, and **Restore** on its row brings everything
|
||||
back under the chosen account. The erased account's id is not stored, not in
|
||||
then that account's copy in every respect but one: its preview, its download
|
||||
and its restore are the ordinary ones, and **Restore** on its row brings
|
||||
everything back under the chosen account. The one difference is that it is
|
||||
stored as `RETARGETED_UPLOAD_<time>` rather than `MANUAL_UPLOAD_<time>`, and its
|
||||
restore reads the file's settings as the file's author's, never as the
|
||||
operator's: an AI endpoint on a private address comes back only when this host
|
||||
grants it to ordinary accounts (`AI_PRIVATE_ORIGINS`), even if the account has
|
||||
been made an admin since. The erased account's id is not stored, not in
|
||||
the copy and not in the audit trail.
|
||||
|
||||
A copy whose own account still exists on this server can only go back into
|
||||
|
||||
Reference in new issue
Block a user