authService = $authService; $this->activityLogger = $activityLogger; } /** * @OA\Get( * path="/client/me", * operationId="getClientMe", * tags={"Client Authentication"}, * summary="Get current client information", * description="Returns the currently authenticated client's details", * security={{"bearerAuth":{}}}, * @OA\Response( * response=200, * description="Successful operation", * @OA\JsonContent( * @OA\Property(property="customer", type="object", * @OA\Property(property="fullname", type="string", example="Nguyen Van A"), * @OA\Property(property="email", type="string", example="client@example.com"), * @OA\Property(property="phone", type="string", example="0987654321"), * @OA\Property(property="address", type="string", example="123 Street"), * @OA\Property(property="city", type="string", example="Hanoi"), * @OA\Property(property="ward", type="string", example="Ward 1"), * @OA\Property(property="province", type="string", example="Hanoi"), * @OA\Property(property="gender", type="string", example="Nam"), * @OA\Property(property="date", type="string", format="date-time", example="2024-01-01 12:00:00"), * @OA\Property(property="company", type="string", example="Company Ltd"), * @OA\Property(property="avatar", type="string", example="/uploads/avatar.jpg"), * @OA\Property(property="cmnd_after", type="string", example="cmnd_back.jpg"), * @OA\Property(property="cmnd_before", type="string", example="cmnd_front.jpg"), * @OA\Property(property="cmnd", type="string", example="123456789"), * @OA\Property(property="fb_link", type="string", nullable=true), * @OA\Property(property="active_2fa", type="integer", example=0), * @OA\Property(property="off_mail_marketing", type="integer", example=0), * @OA\Property(property="is_recharge_zalo", type="integer", example=0), * @OA\Property(property="is_verify_telegram", type="integer", example=0) * ), * @OA\Property(property="credit", type="object", * @OA\Property(property="credit", type="number", example=1000000), * @OA\Property(property="total", type="number", example=5000000), * @OA\Property(property="point", type="number", example=100), * @OA\Property(property="point_used", type="integer", example=0), * @OA\Property(property="point_total", type="number", example=100) * ), * @OA\Property(property="service", type="object", * @OA\Property(property="vps", type="integer", example=2), * @OA\Property(property="proxy", type="integer", example=0), * @OA\Property(property="server", type="integer", example=0), * @OA\Property(property="hosting", type="integer", example=1), * @OA\Property(property="domain", type="integer", example=3) * ) * ) * ), * @OA\Response( * response=401, * description="Unauthenticated" * ) * ) */ public function me() { return response()->json([ 'error' => false, 'data' => [ 'client' => new ClientResource(auth('client')->user()->loadCount(['vps', 'hosting', 'domain'])), ], ]); } /** * @OA\Post( * path="/client/login", * operationId="clientLogin", * tags={"Client Authentication"}, * summary="Client login", * description="Authenticate a client and return access token + refresh token", * @OA\RequestBody( * required=true, * @OA\JsonContent( * required={"email","password"}, * @OA\Property(property="email", type="string", format="email", example="client@example.com"), * @OA\Property(property="password", type="string", format="password", example="password123") * ) * ), * @OA\Response( * response=200, * description="Login successful", * @OA\JsonContent( * @OA\Property(property="error", type="boolean", example=false), * @OA\Property(property="data", type="object", * @OA\Property(property="client", type="object"), * @OA\Property(property="token", type="string", example="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."), * @OA\Property(property="refresh_token", type="string", example="random64charstring..."), * @OA\Property(property="expires_in", type="integer", example=900) * ) * ) * ), * @OA\Response( * response=422, * description="Validation error" * ) * ) */ public function login(ClientAuthRequest $request) { $result = $this->authService->authenticateClient($request->validated()); $client = $result['client']; $this->activityLogger->logAuth('login', 'customer', $client->id, $client->email); return response()->json([ 'error' => false, 'data' => [ 'client' => new ClientResource($client->loadCount(['vps', 'hosting', 'domain'])), 'token' => $result['token'], 'refresh_token' => $result['refresh_token'], 'expires_in' => config('jwt.ttl', 60) * 60, // seconds ], ]); } /** * @OA\Post( * path="/client/register", * operationId="clientRegister", * tags={"Client Authentication"}, * summary="Client registration", * description="Register a new client account", * @OA\RequestBody( * required=true, * @OA\JsonContent( * required={"fullname","phone","email","password","password_confirmation"}, * @OA\Property(property="fullname", type="string", example="Nguyen Van A"), * @OA\Property(property="phone", type="string", example="0987654321"), * @OA\Property(property="email", type="string", format="email", example="client@example.com"), * @OA\Property(property="password", type="string", format="password", example="password123"), * @OA\Property(property="password_confirmation", type="string", format="password", example="password123") * ) * ), * @OA\Response( * response=200, * description="Registration successful", * @OA\JsonContent( * @OA\Property(property="error", type="boolean", example=false), * @OA\Property(property="data", type="object", * @OA\Property(property="client", type="object"), * @OA\Property(property="token", type="string", example="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."), * @OA\Property(property="refresh_token", type="string", example="random64charstring..."), * @OA\Property(property="expires_in", type="integer", example=900) * ) * ) * ), * @OA\Response( * response=422, * description="Validation error" * ) * ) */ public function register(ClientRegisterRequest $request) { $data = $request->validated(); if (!empty($data['trial_config'])) { $decoded = json_decode((string) $data['trial_config'], true); $data['trial_config'] = is_array($decoded) ? $decoded : null; } $result = $this->authService->registerClient($data); $client = $result['client']; $this->activityLogger->logAuth('register', 'customer', $client->id, $client->email); return response()->json([ 'error' => false, 'message' => 'Đăng ký thành công. Vui lòng kiểm tra email và nhập mã xác thực để kích hoạt tài khoản.', ]); } public function verifyEmail(Request $request) { $request->validate([ 'email' => ['required', 'email', 'max:255'], 'code' => ['required', 'string', 'size:6'], ]); $result = $this->authService->verifyEmail( $request->input('email'), $request->input('code'), ); $client = $result['client']; $this->activityLogger->logAuth('email_verified', 'customer', $client->id, $client->email); return response()->json([ 'error' => false, 'data' => [ 'client' => new ClientResource($client->loadCount(['vps', 'hosting', 'domain'])), 'token' => $result['token'], 'refresh_token' => $result['refresh_token'], 'expires_in' => config('jwt.ttl', 60) * 60, ], ]); } public function resendVerifyCode(Request $request) { $request->validate([ 'email' => ['required', 'email', 'max:255'], ]); $this->authService->resendVerifyCode($request->input('email')); return response()->json([ 'error' => false, 'message' => 'Nếu email tồn tại và chưa xác thực, mã xác thực mới đã được gửi.', ]); } /** * @OA\Post( * path="/client/logout", * operationId="clientLogout", * tags={"Client Authentication"}, * summary="Client logout", * description="Logout the currently authenticated client and revoke refresh token", * security={{"bearerAuth":{}}}, * @OA\RequestBody( * @OA\JsonContent( * @OA\Property(property="refresh_token", type="string", example="random64charstring...") * ) * ), * @OA\Response( * response=200, * description="Logout successful", * @OA\JsonContent( * @OA\Property(property="error", type="boolean", example=false), * @OA\Property(property="message", type="string", example="Đã đăng xuất thành công.") * ) * ), * @OA\Response( * response=401, * description="Unauthenticated" * ) * ) */ public function logout(Request $request) { $customer = auth('client')->user(); if ($customer) { $this->activityLogger->logAuth('logout', 'customer', $customer->id, $customer->email); } if ($rawToken = $request->input('refresh_token')) { $this->authService->revokeRefreshToken($rawToken); } auth('client')->logout(); return response()->json([ 'error' => false, 'message' => 'Đã đăng xuất thành công.', ]); } /** * @OA\Post( * path="/client/refresh", * operationId="clientRefresh", * tags={"Client Authentication"}, * summary="Refresh access token", * description="Exchange a valid refresh token for a new access token + rotated refresh token", * @OA\RequestBody( * required=true, * @OA\JsonContent( * required={"refresh_token"}, * @OA\Property(property="refresh_token", type="string", example="random64charstring...") * ) * ), * @OA\Response( * response=200, * description="Token refreshed", * @OA\JsonContent( * @OA\Property(property="error", type="boolean", example=false), * @OA\Property(property="data", type="object", * @OA\Property(property="token", type="string"), * @OA\Property(property="refresh_token", type="string"), * @OA\Property(property="expires_in", type="integer", example=900), * @OA\Property(property="client", type="object") * ) * ) * ), * @OA\Response( * response=401, * description="Invalid or expired refresh token" * ) * ) */ public function refresh(Request $request) { $request->validate([ 'refresh_token' => ['required', 'string'], ]); try { $result = $this->authService->rotateRefreshToken($request->input('refresh_token')); return response()->json([ 'error' => false, 'data' => [ 'token' => $result['token'], 'refresh_token' => $result['refresh_token'], 'expires_in' => config('jwt.ttl', 60) * 60, 'client' => new ClientResource($result['client']->loadCount(['vps', 'hosting', 'domain'])), ], ]); } catch (\Illuminate\Validation\ValidationException $e) { return response()->json([ 'error' => true, 'message' => collect($e->errors())->flatten()->first(), ], 401); } } /** * Đổi mã impersonate (do admin cấp) lấy access token + refresh token. * Endpoint public — code chỉ dùng được 1 lần và hết hạn sau 120s. */ public function impersonateExchange(Request $request) { $request->validate([ 'code' => ['required', 'string', 'max:128'], ]); $code = (string) $request->input('code'); $payload = \Illuminate\Support\Facades\Cache::pull("impersonate:{$code}"); if (!$payload) { return response()->json([ 'error' => true, 'message' => 'Mã đăng nhập không hợp lệ hoặc đã hết hạn.', ], 401); } return response()->json([ 'error' => false, 'data' => [ 'token' => $payload['token'], 'refresh_token' => $payload['refresh_token'], 'client' => $payload['client'], 'expires_in' => config('jwt.ttl', 60) * 60, ], ]); } public function forgotPassword(Request $request) { $request->validate([ 'email' => ['required', 'email', 'max:255'], ]); $customer = $this->authService->resetForgottenPassword($request->input('email')); if ($customer) { $this->activityLogger->log( event: 'password_forgot', logName: 'Customer', description: "Customer {$customer->email} yêu cầu cấp lại mật khẩu", subject: $customer, ); } // Do not disclose whether the email exists. return response()->json([ 'error' => false, 'message' => 'Nếu email tồn tại, mật khẩu mới đã được gửi đến hộp thư của bạn.', ]); } public function update(ClientUpdateRequest $request) { $result = $this->authService->updateProfile(auth('client')->user(), $request->validated()); $user = $result['client']; if (!empty($result['dirty'])) { $this->activityLogger->log( event: 'profile_update', logName: 'Customer', description: "Customer {$user->email} cập nhật thông tin cá nhân: " . implode(', ', array_keys($result['dirty'])), subject: $user, oldValues: $result['old'], newValues: array_diff_key($result['dirty'], array_flip(['password', 'remember_token'])), ); } return response()->json([ 'error' => false, 'message' => 'Cập nhật thông tin thành công', 'data' => [ 'client' => new ClientResource($user->loadCount(['vps', 'hosting', 'domain'])), ], ]); } public function updateCccd(ClientUpdateCccdRequest $request) { $frontUrl = Storage::disk('public')->url($request->file('cmnd_before')->store('cccd', 'public')); $backUrl = Storage::disk('public')->url($request->file('cmnd_after')->store('cccd', 'public')); $result = $this->authService->updateCccd( auth('client')->user(), $request->validated('cmnd'), $frontUrl, $backUrl, ); $user = $result['client']; if (!empty($result['dirty'])) { $this->activityLogger->log( event: 'cccd_update', logName: 'Customer', description: "Customer {$user->email} cập nhật chứng minh thư", subject: $user, oldValues: $result['old'], newValues: $result['dirty'], ); } return response()->json([ 'error' => false, 'message' => 'Cập nhật chứng minh thư thành công', 'data' => [ 'client' => new ClientResource($user->loadCount(['vps', 'hosting', 'domain'])), ], ]); } public function changePassword(ClientChangePasswordRequest $request) { $user = auth('client')->user(); try { $this->authService->changePassword( $user, $request->input('current_password'), $request->input('password'), ); } catch (\Illuminate\Validation\ValidationException $e) { return response()->json([ 'error' => true, 'message' => collect($e->errors())->flatten()->first(), ], 422); } $this->activityLogger->log( event: 'password_change', logName: 'Customer', description: "Customer {$user->email} đã đổi mật khẩu", subject: $user, ); return response()->json([ 'error' => false, 'message' => 'Đổi mật khẩu thành công', ]); } }