Files
ERP/src/app/api/auth/me/route.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

55 lines
2.0 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { connectDb } from "@/lib/server/db";
import { User } from "@/lib/server/models/User";
import { Branch } from "@/lib/server/models/Branch";
import { Business } from "@/lib/server/models/Business";
import { getAuth } from "@/lib/server/auth";
import { isPlatformAdmin } from "@/lib/server/platform";
import { isMpesaEnabled } from "@/lib/server/env";
import { TIERS, effectiveMaxBranches, type TierId, type SubscriptionStatus } from "@/lib/tiers";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export async function GET(req: NextRequest) {
await connectDb();
const auth = getAuth(req);
if (!auth) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
const user = await User.findById(auth.sub).lean();
if (!user || !user.isActive) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}
const branch = user.branchId ? await Branch.findById(user.branchId).lean() : null;
const business = await Business.findById(user.businessId).lean();
const tier = (business?.tier as TierId) ?? "STARTER";
const status = (business?.subscriptionStatus as SubscriptionStatus) ?? "TRIALING";
const tierDef = TIERS[tier];
return NextResponse.json({
user: {
id: user._id,
name: user.name,
role: user.role,
businessId: user.businessId,
businessName: business?.name ?? null,
branchId: user.branchId,
branchName: branch?.branchName ?? null,
phoneNumber: user.phoneNumber,
email: user.email,
isPlatformAdmin: isPlatformAdmin(user),
},
subscription: {
tier,
tierName: tierDef.name,
priceKsh: tierDef.priceKsh,
status,
trialEndsAt: business?.trialEndsAt ?? null,
currentPeriodEnd: business?.currentPeriodEnd ?? null,
maxBranches: effectiveMaxBranches(tier, status),
maxUsers: tierDef.maxUsers,
},
features: { mpesa: isMpesaEnabled() },
});
}