security(auth): 移除公開 admin/register 端點,改由 app:create-admin 建立管理員

P0 漏洞:原 POST /api/admin/register 無任何防護,任何人可註冊管理員帳號。
- 移除路由、AuthController::registerAdmin、對應 Swagger 文件
- 新增 app:create-admin artisan command(密碼門檻 min:8)
- 補 AdminAccountCreationTest 防止端點被重新打開
- 同步 admin-auth 規格,並收錄 2026-06-11 稽核報告與路線圖文檔

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 17:36:33 +08:00
parent ca5b843362
commit aeb8c975e2
8 changed files with 401 additions and 107 deletions
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace App\Console\Commands;
use App\Models\AdminProfile;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class CreateAdminUser extends Command
{
protected $signature = 'app:create-admin
{name : 管理員姓名}
{email : 登入用電子郵件}
{--password= : 密碼(至少 8 碼,未提供時互動式輸入)}
{--position= : 職位}
{--department= : 部門}';
protected $description = '建立管理員帳號(公開 /api/admin/register 端點已移除,管理員一律由主機端建立)';
public function handle(): int
{
$password = $this->option('password') ?: $this->secret('請輸入密碼(至少 8 碼)');
$validator = Validator::make([
'name' => $this->argument('name'),
'email' => $this->argument('email'),
'password' => $password,
], [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
// 管理權限影響全平台,密碼門檻高於一般使用者的 min:6
'password' => 'required|string|min:8',
]);
if ($validator->fails()) {
foreach ($validator->errors()->all() as $error) {
$this->error($error);
}
return self::FAILURE;
}
$user = User::create([
'name' => $this->argument('name'),
'email' => $this->argument('email'),
'password' => Hash::make($password),
'role' => 'admin',
]);
AdminProfile::create([
'user_id' => $user->id,
'position' => $this->option('position'),
'department' => $this->option('department'),
]);
$this->info("管理員帳號已建立:{$user->email}id={$user->id}");
return self::SUCCESS;
}
}
-52
View File
@@ -825,58 +825,6 @@ class AuthApiDoc
{
}
/**
* 管理員註冊
*
* @OA\Post(
* path="/admin/register",
* summary="管理員註冊",
* description="建立新的管理員帳號",
* operationId="registerAdmin",
* tags={"管理員"},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"name", "email", "password", "password_confirmation"},
* @OA\Property(property="name", type="string", example="張管理", description="使用者姓名"),
* @OA\Property(property="email", type="string", format="email", example="admin@example.com", description="電子郵件"),
* @OA\Property(property="password", type="string", format="password", example="password123", description="密碼"),
* @OA\Property(property="password_confirmation", type="string", format="password", example="password123", description="確認密碼"),
* @OA\Property(property="phone", type="string", example="0912345678", description="電話號碼"),
* @OA\Property(property="position", type="string", example="系統管理員", description="職位"),
* @OA\Property(property="department", type="string", example="IT部門", description="部門")
* )
* ),
* @OA\Response(
* response=201,
* description="管理員註冊成功",
* @OA\JsonContent(
* @OA\Property(property="status", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="管理員註冊成功"),
* @OA\Property(
* property="data",
* type="object",
* @OA\Property(property="user", type="object", ref="#/components/schemas/User"),
* @OA\Property(property="token", type="string", example="1|abcdef1234567890"),
* @OA\Property(property="token_type", type="string", example="Bearer")
* )
* )
* ),
* @OA\Response(
* response=422,
* description="驗證失敗",
* @OA\JsonContent(
* @OA\Property(property="status", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="驗證失敗"),
* @OA\Property(property="errors", type="object")
* )
* )
* )
*/
public function registerAdmin()
{
}
/**
* 管理員登入
*
@@ -846,59 +846,6 @@ class AuthController extends Controller
]);
}
/**
* 管理員註冊
*/
public function registerAdmin(Request $request)
{
// 驗證請求數據
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
'phone' => 'nullable|string|max:20',
'position' => 'nullable|string|max:100',
'department' => 'nullable|string|max:100',
]);
if ($validator->fails()) {
return response()->json([
'status' => false,
'message' => '驗證失敗',
'errors' => $validator->errors()
], 422);
}
// 創建用戶
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'phone' => $request->phone,
'role' => 'admin', // 強制為管理員角色
]);
// 創建管理員資料
AdminProfile::create([
'user_id' => $user->id,
'position' => $request->position,
'department' => $request->department,
]);
// 創建 API token
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'status' => true,
'message' => '管理員註冊成功',
'data' => [
'user' => $user,
'token' => $token,
'token_type' => 'Bearer',
]
], 201);
}
/**
* 管理員登入
*/