all(), [ 'email' => ['required', 'email'], 'password' => ['required'], ]); if ($validator->fails()) { return response()->json([ 'message' => 'Validation failed', 'errors' => $validator->errors(), ], 422); } $credentials = $validator->validated(); $email = strtolower($credentials['email']); $user = User::whereRaw('LOWER(email) = ?', [$email])->first(); if (!$user) { log_message('warning', 'Login failed, user not found', ['email' => $credentials['email']]); return response()->json(['error' => 'Invalid login'], 401); } $hashPreview = substr((string) $user->password, 0, 32); log_message('debug', 'Login hash preview for {email}: {hash}', [ 'email' => $credentials['email'], 'hash' => $hashPreview, ]); if (!ci_password_verify($credentials['password'], $user->password)) { log_message('warning', 'Login failed, password mismatch', [ 'email' => $credentials['email'], 'hash' => $hashPreview, ]); return response()->json(['error' => 'Invalid login'], 401); } $token = JWTAuth::fromUser($user); return response()->json($this->respondWithToken($token)); } public function me() { try { $user = JWTAuth::parseToken()->authenticate(); } catch (\Throwable $e) { return response()->json(['error' => 'Unauthorized'], 401); } return response()->json($user); } public function logout() { try { $token = JWTAuth::getToken(); if ($token) { JWTAuth::invalidate($token); } } catch (\Throwable $e) { // ignore invalid token } return response()->json(['message' => 'Logged out']); } protected function respondWithToken($token) { return [ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth()->factory()->getTTL() * 60, ]; } }