validate([ 'machine' => 'required|string', 'Su_Account' => 'required|string', 'Su_Password' => 'required|string', 'ip' => 'nullable|string', 'type' => 'nullable|string', ]); // 2. 取得機台物件 (需優先於帳密驗證,以便記錄日誌到正確機台) $machine = Machine::withoutGlobalScopes()->where('serial_no', $validated['machine'])->first(); if (!$machine) { Log::warning("B000 機台登入失敗: 伺服器找不到該機台", [ 'machine_serial' => $validated['machine'] ]); return response()->json(['message' => 'Failed']); } // 3. 透過帳號尋找使用者 (允許使用 username 或 email) $user = User::where('username', $validated['Su_Account']) ->orWhere('email', $validated['Su_Account']) ->first(); // 4. 驗證密碼 if (!$user || !Hash::check($validated['Su_Password'], $user->password)) { Log::warning("B000 機台登入失敗: 帳密錯誤", [ 'account' => $validated['Su_Account'], 'machine' => $validated['machine'] ]); // 寫入機台日誌 ProcessStateLog::dispatch( $machine->id, $machine->company_id, __("Login failed: :account", ['account' => $validated['Su_Account']]), 'warning', [], 'login' ); return response()->json(['message' => 'Failed']); } // 5. RBAC 權限驗證 (遵循多租戶與機台授權規範) $isAuthorized = false; if ($user->isSystemAdmin()) { $isAuthorized = true; } elseif ($user->is_admin) { if ($machine->company_id === $user->company_id) { $isAuthorized = true; } } else { if ($user->machines()->where('machine_id', $machine->id)->exists()) { $isAuthorized = true; } } if (!$isAuthorized) { Log::warning("B000 機台登入失敗: 權限不足", [ 'user_id' => $user->id, 'machine_id' => $machine->id ]); ProcessStateLog::dispatch( $machine->id, $machine->company_id, __("Unauthorized login attempt: :account", ['account' => $user->username]), 'warning', [], 'login' ); return response()->json(['message' => 'Forbidden']); } // 6. 驗證完美通過! Log::info("B000 機台登入成功", [ 'account' => $user->username, 'machine' => $machine->serial_no ]); // 寫入成功登入日誌 ProcessStateLog::dispatch( $machine->id, $machine->company_id, __("User logged in: :name", ['name' => $user->name ?? $user->username]), 'info', [], 'login' ); return response()->json([ 'message' => 'Success', 'token' => $user->createToken('technician-setup')->plainTextToken ]); } }