1import { NextRequest, NextResponse } from 'next/server';
2import { fieldpineServerApi } from '@/lib/server/fieldpineApi';
3import { getStoreById, transformManifestStore } from '@/lib/stores';
4import { cookies } from 'next/headers';
5import jwt from 'jsonwebtoken';
6import { promises as fs } from 'fs';
7import path from 'path';
9interface LoginRequest {
15interface StaffRecord {
16 f100?: string; // Staff ID
17 f101?: string; // Staff Name
18 f102?: string; // Active status
19 f103?: string; // Password
20 f134?: string; // Is Customer Rep
21 f135?: string; // Is Account Rep
24const MANIFEST_PATH = path.join(process.cwd(), 'data', 'config', 'stores-manifest.json');
27 * Read stores from manifest file
29async function readManifestStores() {
31 const fileContent = await fs.readFile(MANIFEST_PATH, 'utf-8');
32 const manifest = JSON.parse(fileContent);
33 return manifest.stores || [];
35 console.warn('[Auth] Failed to read manifest:', error);
41 * Find store by friendly name (e.g., "iig", "cowra", "murwillumbah")
43async function findStoreByName(storeName: string) {
44 // First try static stores
45 const staticStore = getStoreById(storeName);
50 // Then check manifest
51 const manifestStores = await readManifestStores();
52 for (const manifestStore of manifestStores) {
53 const transformed = transformManifestStore(manifestStore);
54 if (transformed && transformed.id === storeName) {
63 * Determine user role based on staff record
65function determineUserRole(staffData: StaffRecord): 'staff' | 'employee' | 'admin' {
66 const isCustomerRep = staffData.f134 === '1';
67 const isAccountRep = staffData.f135 === '1';
69 if (isCustomerRep || isAccountRep) {
76export async function POST(request: NextRequest) {
78 const body: LoginRequest = await request.json();
79 const { username, password, storeId } = body;
81 console.log('[Auth] Login request received:', { username, storeId, hasPassword: !!password });
83 if (!username || !password || !storeId) {
84 return NextResponse.json(
85 { error: 'Username, password, and store are required' },
90 // Get store configuration by friendly name
91 const store = await findStoreByName(storeId.toLowerCase().trim());
92 console.log('[Auth] Store lookup result:', store ? `Found: ${store.name}` : 'Not found', 'storeId:', storeId);
95 return NextResponse.json(
96 { error: `Store "${storeId}" not found. Please check the store name.` },
101 // Rate limiting - Get client IP from headers
102 const clientId = request.headers.get('x-forwarded-for') ||
103 request.headers.get('x-real-ip') ||
104 request.headers.get('cf-connecting-ip') ||
106 if (!fieldpineServerApi.checkClientRateLimit(clientId)) {
107 return NextResponse.json(
108 { error: 'Rate limit exceeded' },
113 console.log('[Auth] Login attempt for user:', username, 'at store:', store.name);
115 // Authenticate with Fieldpine at the selected store URL
116 const authResult = await fieldpineServerApi.authenticate(username, password, store.url);
118 if (!authResult.success) {
119 return NextResponse.json(
120 { error: authResult.error || 'Authentication failed' },
125 // Get staff record to determine role
126 let role: 'staff' | 'employee' | 'admin' = 'employee';
128 const buckParams: Record<string, string> = {
129 "3": "retailmax.elink.staff.list",
130 "9": `f101,0,${username}`,
134 const staffResponse = await fieldpineServerApi.buckApiCall(
140 if (staffResponse?.DATS && staffResponse.DATS.length > 0) {
141 const staffData: StaffRecord = staffResponse.DATS[0];
142 role = determineUserRole(staffData);
145 console.log('[Auth] Could not fetch staff role, defaulting to employee');
148 // Create secure session with auth data and store info
149 const sessionData = {
154 timestamp: Date.now(),
156 storeName: store.name,
158 storeType: store.type,
160 valid: authResult.data?.valid,
161 HomePage: authResult.data?.HomePage,
162 RmSystem: authResult.data?.RmSystem
164 apiKey: authResult.cookie
167 // Set secure HTTP-only cookie
168 const cookieStore = await cookies();
170 // Determine if we should use secure cookies
171 // In production, always use secure. Also check X-Forwarded-Proto for proxy scenarios
172 const isProduction = process.env.NODE_ENV === 'production';
173 const isHttps = request.headers.get('x-forwarded-proto') === 'https' ||
174 request.url.startsWith('https://');
175 const shouldBeSecure = isProduction || isHttps;
177 console.log('[Auth] Cookie settings:', {
181 protocol: request.headers.get('x-forwarded-proto'),
182 url: request.url.substring(0, 50)
185 cookieStore.set('fieldpine-session', JSON.stringify(sessionData), {
187 secure: shouldBeSecure,
189 maxAge: 60 * 60 * 8, // 8 hours
190 path: '/' // Explicitly set path
193 console.log('[Auth] Login successful for user:', username, 'role:', role, 'store:', store.name);
195 return NextResponse.json({
203 valid: authResult.data?.valid
211 data: authResult.data,
212 redirect: authResult.data?.HomePage || '/pages/home'
216 console.error('Login API error:', error);
217 return NextResponse.json(
218 { error: 'Internal server error' },