Skip to content

Migrate legacy User compatibility to a BaseUser-backed mask #1943

Description

@david-rocca

Goal

Make BaseUser the sole persistence source for user reads and writes while preserving legacy user API behavior through a compatibility mask.

Normal application flows must no longer query, create, update, or delete records in the legacy User collection.

Dependency

Depends on the BaseOrg migration ticket. User membership and admin status must be resolved from BaseOrg.users and BaseOrg.admins.

Background

BaseUserRepository currently supports legacy-shaped operations, but it still reads and dual-writes the old User collection.

Legacy user data stores organization and admin information on the user:

  • org_UUID
  • active
  • authority.active_roles
  • time.created / time.modified

The new data model stores account data in BaseUser, but organization membership and admin state in BaseOrg:

  • BaseUser.status
  • BaseOrg.users
  • BaseOrg.admins

Proposed approach

Create a LegacyUserMaskRepository backed only by BaseUser and BaseOrg.

It must expose the legacy UserRepository contract while deriving legacy fields:

Source Legacy field
BaseOrg user membership org_UUID
BaseOrg admin membership authority.active_roles: ["ADMIN"]
BaseUser.status active
BaseUser.created time.created
BaseUser.last_updated time.modified
BaseUser identity/name/secret Same-named legacy fields

Update RepositoryFactory.getUserRepository() to return this compatibility repository so legacy CVE/CVE-ID consumers continue receiving legacy-shaped data without using User.

Refactor BaseUserRepository so legacy-format options mask BaseUser data rather than accessing User.

Required work

  • Extract and test pure helpers:
    • toLegacyUser(baseUser, org)
    • toBaseUserPatch(legacyPayload)
    • BaseOrg membership and admin lookups
    • legacy query and projection translation where needed
  • Move legacy reads and listings to BaseUser plus BaseOrg membership.
  • Remove legacy User writes from create, update, full update, reset-secret, and delete flows.
  • Ensure create, reassignment, deletion, and admin-role changes atomically update:
    • BaseUser
    • source BaseOrg.users / BaseOrg.admins
    • destination BaseOrg.users / BaseOrg.admins
  • Derive org_UUID and authority.active_roles rather than persisting duplicate values in BaseUser.
  • Preserve legacy responses for /api/users and /api/org/:shortname/user* endpoints.
  • Preserve registry user behavior, including hydrated role: "ADMIN" responses.
  • Remove production dependencies on src/model/user.js and src/repositories/userRepository.js.

Important decision

BaseOrg.admins must be the authoritative source for ADMIN membership.

The BaseUser Mongoose model does not persist a dedicated role field, and current registry controllers already hydrate admin status from BaseOrg.admins. This migration should formalize that model.

Data migration and rollout

  • Verify every legacy User has a matching BaseUser UUID.
  • Verify every legacy org_UUID corresponds to exactly one BaseOrg membership.
  • Verify legacy ADMIN users occur in the matching BaseOrg admins array.
  • Report and resolve:
    • orphaned users
    • users with no org or multiple orgs
    • mismatched admin state
    • mismatched username, status, name, or secret data
  • Add temporary telemetry proving runtime code no longer accesses User.

Acceptance criteria

  • Legacy user endpoints work with only BaseUser and BaseOrg records present.
  • No normal request path reads or writes the User collection.
  • Legacy responses retain org_UUID, active, authority.active_roles, and time.
  • Registry responses retain status and return role: "ADMIN" for BaseOrg admins.
  • User create, update, move, delete, grant/revoke-admin, and reset-secret keep BaseUser/BaseOrg consistent.
  • CVE and CVE-ID flows continue to resolve requester users and names correctly.
  • Integration coverage includes legacy and registry user CRUD, reassignment, admin grant/revoke, authorization, reset-secret, and membership cleanup.
  • Existing integration suite passes.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

Status
Needs Triage
Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions