star-cloud/app/Http/Controllers/Auth/TenantLoginController.php
sky121113 11eca730f7 [FEAT] 實作自訂品牌樣式設定與專屬登入頁面預覽功能
1. 實作自訂品牌樣式設定,支援上傳 Logo、登入大背景圖與左側卡片背景圖。
2. 整合 ImageHandler 處理上傳圖片,自動壓縮並轉為優質 webp 格式以提升載入速度。
3. 將品牌設定自 Modal 拆分,新增獨立的 branding.blade.php 設定頁面以優化操作體驗。
4. 於 TenantLoginController 新增登入頁預覽 (preview) 與專屬首頁重導向 (indexRedirect) 路由邏輯。
5. 調整 auth/login.blade.php 以支援動態呈現自訂背景圖、Logo、主副標題,且在預覽模式下阻擋登入並顯示頂部橫條。
6. 修正租戶登入安全性比對,加強 (int) 型態校驗並豐富 Log 警示資訊。
7. 更新並對齊 zh_TW.json、en.json 與 ja.json 三語系檔之品牌客製化相關辭彙。
8. 補充完整 Feature Test (CompanySettingsTest),全面涵蓋物理刪除、上傳限制、預覽渲染、跳轉與登入安全性限制之測試。
2026-06-05 08:44:38 +08:00

145 lines
4.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\System\Company;
use App\Models\System\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class TenantLoginController extends Controller
{
/**
* 顯示專屬客戶的客製化登入頁面
*/
public function showLoginForm($company_code)
{
// 1. 查詢啟用的公司
$company = Company::active()->where('code', $company_code)->first();
// 2. 安全防護:若公司不存在,或沒有啟用品牌客製化功能授權,一律自動重導向至通用登入頁
if (!$company) {
return redirect()->route('login');
}
$settings = $company->settings ?? [];
$enableCustomBranding = filter_var($settings['enable_custom_branding'] ?? false, FILTER_VALIDATE_BOOLEAN);
if (!$enableCustomBranding) {
Log::warning("Tenant login attempted for unauthorized company [{$company_code}]. Redirecting to main login.");
return redirect()->route('login');
}
// 3. 渲染共用的 login Blade傳入當前公司的上下文
return view('auth.login', compact('company'));
}
/**
* 處理專屬客戶的登入請求 (安全性加強版)
*/
public function login($company_code, Request $request)
{
// 1. 查詢並確認該公司
$company = Company::active()->where('code', $company_code)->first();
if (!$company) {
return redirect()->route('login');
}
$settings = $company->settings ?? [];
$enableCustomBranding = filter_var($settings['enable_custom_branding'] ?? false, FILTER_VALIDATE_BOOLEAN);
if (!$enableCustomBranding) {
return redirect()->route('login');
}
// 2. 驗證輸入欄位
$request->validate([
'username' => 'required|string',
'password' => 'required|string',
]);
// 3. 查詢使用者
$user = User::where('username', $request->username)->first();
// 4. 【關鍵安全防錯】驗證此使用者是否確實屬於該公司
if (!$user || (int)$user->company_id !== (int)$company->id) {
Log::warning("Tenant login security violation: User [{$request->username}] attempted login at company [{$company_code}] portal. User Company ID: " . var_export($user ? $user->company_id : null, true) . ", Target Company ID: {$company->id}");
return back()->withErrors([
'username' => __('This account does not belong to this company.'),
])->withInput($request->only('username', 'remember'));
}
// 5. 驗證帳號狀態
if ($user->status !== 1) {
return back()->withErrors([
'username' => __('Your account is disabled.'),
])->withInput($request->only('username', 'remember'));
}
// 6. 驗證密碼
if (!Hash::check($request->password, $user->password)) {
return back()->withErrors([
'password' => __('auth.failed'),
])->withInput($request->only('username', 'remember'));
}
// 7. 執行登入與安全 Session 重新生成
Auth::login($user, $request->boolean('remember'));
$request->session()->regenerate();
Log::info("User [{$user->username}] successfully logged in via company portal [{$company_code}].");
return redirect()->intended(route('admin.dashboard'));
}
/**
* 預覽專屬客戶的客製化登入頁面 (不受 guest 中間件阻擋,供後台預覽用)
*/
public function preview($company_code)
{
// 1. 查詢啟用的公司
$company = Company::active()->where('code', $company_code)->first();
// 2. 若公司不存在,重導向至主登入頁
if (!$company) {
return redirect()->route('login');
}
$settings = $company->settings ?? [];
$enableCustomBranding = filter_var($settings['enable_custom_branding'] ?? false, FILTER_VALIDATE_BOOLEAN);
if (!$enableCustomBranding) {
return redirect()->route('login');
}
$isPreview = true;
// 3. 渲染共用的 login Blade並傳入預覽標記
return view('auth.login', compact('company', 'isPreview'));
}
/**
* 處理打 /c/{company_code} 的跳轉邏輯
*/
public function indexRedirect($company_code)
{
// 1. 查詢啟用的公司
$company = Company::active()->where('code', $company_code)->first();
if (!$company) {
return redirect()->route('login');
}
// 2. 已登入則去 dashboard未登入則去專屬登入頁
if (auth()->check()) {
return redirect()->route('admin.dashboard');
}
return redirect()->route('tenant.login', $company_code);
}
}