Files
ERP/scripts/migrate-per-business-identity.ts
T
denis_ireri 9e3e59a804 Make email and phone unique per business, not platform-wide
One person could not work for two businesses, and signup and staff
creation told anyone whether an email or phone was registered anywhere
on the platform.

- User email and phone are now unique per business: indexes on
  (businessId, email) and (businessId, phoneNumber). Duplicate checks in
  signup and branch-manager creation are scoped to the business, so they
  no longer reveal other businesses' users.
- Login finds the email in every business and checks the password
  against each account. One match signs straight in; several return 300
  with the businesses to choose from, and the login page shows a picker.
  A wrong password returns the same 401 as before and reveals nothing;
  failures count against each candidate account, and a correct password
  only resets the accounts it opens.
- Platform-operator access now also needs a platformAdmin flag on the
  account. With per-business emails, an email match alone would have let
  any owner create a staff account with the operator's email and open the
  cross-tenant console. The flag is only set by the seed and the
  migration; removing the email from PLATFORM_ADMIN_EMAILS still revokes.
- npm run migrate:identity flags today's operators (while each listed
  email still matches one account) and syncs the User indexes, dropping
  the old unique email_1 and phoneNumber_1. Checked against a database in
  the old shape; safe to re-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:31:45 +03:00

50 lines
1.9 KiB
TypeScript

import dotenv from "dotenv";
import path from "path";
dotenv.config({ path: path.resolve(__dirname, "../.env.local") });
import mongoose from "mongoose";
import { connectDb } from "../src/lib/server/db";
import { User } from "../src/lib/server/models/User";
import { platformAdminEmails } from "../src/lib/server/platform";
/**
* One-off, safe to re-run. Moves user identity from platform-wide to
* per-business uniqueness:
*
* 1. Marks today's platform operators. Until now an operator was anyone
* whose email was in PLATFORM_ADMIN_EMAILS; emails were unique, so
* that was one account each. From now on emails are only unique per
* business, so access also needs the platformAdmin flag on the account.
* Run this BEFORE dropping the old index, while each listed email
* still matches exactly one account.
* 2. Syncs the User indexes: drops the old unique `email_1` and
* `phoneNumber_1`, creates unique (businessId, email) and
* (businessId, phoneNumber), and a plain email lookup index.
*
* npm run migrate:identity
*/
async function main() {
await connectDb();
for (const email of platformAdminEmails()) {
const accounts = await User.find({ email }).select("_id").lean();
if (accounts.length === 1) {
await User.updateOne({ _id: accounts[0]._id }, { $set: { platformAdmin: true } });
console.log(`Platform operator: ${email}`);
} else if (accounts.length === 0) {
console.warn(`No account for ${email} - create it with npm run seed.`);
} else {
console.warn(`${email} has ${accounts.length} accounts - set platformAdmin on the right one by hand.`);
}
}
const dropped = await User.syncIndexes();
console.log(`User indexes synced. Dropped: ${dropped.length ? dropped.join(", ") : "none"}.`);
await mongoose.disconnect();
}
main().catch((err) => {
console.error("migration failed:", err.message);
process.exit(1);
});