9e3e59a804
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>
55 lines
2.0 KiB
TypeScript
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() },
|
|
});
|
|
} |