Server: send each item at the revision its ciphertext is sealed at, keep team archiving with the owner, refuse suspended accounts, and stop enrolment failing after an hour

- Pull hydrated every historical change entry with the item's current ciphertext under that entry's
  old revision. The AAD binds the revision, so a client opened it at the wrong version, failed, and
  parked its own pending edit as undecryptable. Entries an item has since moved past are left out; the
  later entry carries the same payload under the right number, and a fresh device no longer downloads
  an item once per edit it ever had.
- Deleting a team's last vault archived the team whoever deleted it, so an admin could archive it out
  from under its owner. Only the owner's delete archives it now, and TeamService.ArchiveAsync checks.
- UserStatus was checked nowhere on the request path. ActiveAccountRequirement on both policies answers
  403 account-not-active for a suspended or deprovisioned account.
- The hourly last-seen update moves xmin under the tracked user, and enrolment then saved that user
  against the stale token: a 500 for anybody enrolling more than an hour after first signing in. The
  entity is reloaded after the update.
This commit is contained in:
jaap-jan committed 2026-10-02 13:16:48 +02:00
1 parent f332d9a56f
commit 6846f67c20
14 files changed
+277 -12

No files matched your search

+9 -2
View File
@@ -62,7 +62,14 @@ internal static class Auth
private static void AddDodoPolicies(AuthorizationOptions options)
{
options.AddPolicy(AuthenticatedPolicy, policy => policy.RequireAuthenticatedUser());
// Both policies also require the account to be active. A suspended or deprovisioned account still
// holds a token the identity provider will vouch for until it expires — the status is this server's
// own decision, so it is this server that has to refuse. See ActiveAccountHandler.
options.AddPolicy(
AuthenticatedPolicy,
policy => policy
.RequireAuthenticatedUser()
.AddRequirements(new Authorization.ActiveAccountRequirement()));
// Enrollment state lives in the database, so this is satisfied by EnrolledHandler.
// An unmet EnrolledRequirement is rewritten into a ProblemDetails carrying
@@ -72,7 +79,7 @@ internal static class Auth
EnrolledPolicy,
policy => policy
.RequireAuthenticatedUser()
.AddRequirements(new Authorization.EnrolledRequirement()));
.AddRequirements(new Authorization.ActiveAccountRequirement(), new Authorization.EnrolledRequirement()));
// Deny by default: an endpoint without an explicit policy still requires a caller.
options.FallbackPolicy = options.GetPolicy(AuthenticatedPolicy);