-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: convert to frontend-only architecture with API authentication #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f194a70
5d504ad
39d2841
e0703f3
8bda247
9cd098d
8e84f31
037f4cb
9d34001
8c213b4
5257341
cdb0f3e
4022b3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,6 @@ | |
| use Illuminate\Http\Request; | ||
| use Illuminate\Http\JsonResponse; | ||
| use Illuminate\Support\Facades\Http; | ||
| use App\Models\Setting; | ||
|
|
||
| class ApiProxyController extends Controller | ||
| { | ||
|
|
@@ -15,38 +14,46 @@ class ApiProxyController extends Controller | |
| public function proxy(Request $request, string $endpoint): JsonResponse | ||
| { | ||
| try { | ||
| // Get user's API settings | ||
| $apiUrl = Setting::getValue('api_url', 'http://host.docker.internal:8007/api/v1'); | ||
| $apiToken = Setting::getValue('auth_token'); | ||
| // Get API URL from configuration | ||
| $apiUrl = config('api.url'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Proxy now hits container-localhost and can't reach host API Prompt for AI agents |
||
|
|
||
| if (!$apiToken) { | ||
| // Get token from session | ||
| $apiToken = $request->session()->get('api_token'); | ||
|
|
||
| // Define endpoints that don't require authentication | ||
| $publicEndpoints = ['register', 'session']; | ||
| $isPublicEndpoint = in_array(trim($endpoint, '/'), $publicEndpoints); | ||
|
|
||
| // Check authentication for protected endpoints | ||
| if (!$isPublicEndpoint && !$apiToken) { | ||
| return response()->json([ | ||
| 'error' => 'API token not configured', | ||
| 'message' => 'Please configure your Telegram Bot API token in settings' | ||
| 'error' => 'Authentication required', | ||
| 'message' => 'Please login to access this resource' | ||
| ], 401); | ||
| } | ||
|
|
||
| // Replace localhost with host.docker.internal for Docker environment | ||
| $apiUrl = str_replace('http://localhost:', 'http://host.docker.internal:', $apiUrl); | ||
|
|
||
| // Build the external URL | ||
| $externalUrl = rtrim($apiUrl, '/') . '/' . ltrim($endpoint, '/'); | ||
|
|
||
| // Prepare request headers | ||
| $headers = [ | ||
| 'Accept' => 'application/json', | ||
| 'Content-Type' => 'application/json', | ||
| 'Authorization' => "Bearer {$apiToken}", | ||
| ]; | ||
|
|
||
| // Add authorization header only if we have a token | ||
| if ($apiToken) { | ||
| $headers['Authorization'] = "Bearer {$apiToken}"; | ||
| } | ||
|
|
||
| // Add any custom headers from the original request | ||
| if ($request->header('X-Requested-With')) { | ||
| $headers['X-Requested-With'] = $request->header('X-Requested-With'); | ||
| } | ||
|
|
||
| // Make the external request | ||
| $response = Http::withHeaders($headers) | ||
| ->timeout(30) | ||
| ->timeout(config('api.timeout')) | ||
| ->send( | ||
| $request->method(), | ||
| $externalUrl, | ||
|
|
@@ -67,7 +74,6 @@ public function proxy(Request $request, string $endpoint): JsonResponse | |
| \Log::error('API Proxy Error: ' . $e->getMessage(), [ | ||
| 'endpoint' => $endpoint, | ||
| 'method' => $request->method(), | ||
| 'user_id' => auth()->id(), | ||
| ]); | ||
|
|
||
| return response()->json([ | ||
|
|
@@ -84,19 +90,18 @@ public function proxy(Request $request, string $endpoint): JsonResponse | |
| public function proxyUpload(Request $request, string $endpoint): JsonResponse | ||
| { | ||
| try { | ||
| // Get user's API settings | ||
| $apiUrl = Setting::getValue('api_url', 'http://host.docker.internal:8007/api/v1'); | ||
| $apiToken = Setting::getValue('auth_token'); | ||
| // Get API URL from configuration | ||
| $apiUrl = config('api.url'); | ||
|
|
||
| // Get token from session | ||
| $apiToken = $request->session()->get('api_token'); | ||
|
|
||
| if (!$apiToken) { | ||
| return response()->json([ | ||
| 'error' => 'API token not configured' | ||
| 'error' => 'Authentication required' | ||
| ], 401); | ||
| } | ||
|
|
||
| // Replace localhost with host.docker.internal for Docker environment | ||
| $apiUrl = str_replace('http://localhost:', 'http://host.docker.internal:', $apiUrl); | ||
|
|
||
| $externalUrl = rtrim($apiUrl, '/') . '/' . ltrim($endpoint, '/'); | ||
|
|
||
| // Prepare headers | ||
|
|
@@ -142,7 +147,6 @@ public function proxyUpload(Request $request, string $endpoint): JsonResponse | |
| } catch (\Exception $e) { | ||
| \Log::error('API Proxy Upload Error: ' . $e->getMessage(), [ | ||
| 'endpoint' => $endpoint, | ||
| 'user_id' => auth()->id(), | ||
| ]); | ||
|
|
||
| return response()->json([ | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |
| use Illuminate\Auth\Events\Lockout; | ||
| use Illuminate\Http\RedirectResponse; | ||
| use Illuminate\Http\Request; | ||
| use Illuminate\Support\Facades\Auth; | ||
| use Illuminate\Support\Facades\Http; | ||
| use Illuminate\Support\Facades\RateLimiter; | ||
| use Illuminate\Support\Str; | ||
| use Illuminate\Validation\ValidationException; | ||
|
|
@@ -19,36 +19,63 @@ public function create(): View | |
| return view('auth.login'); | ||
| } | ||
|
|
||
| public function store(Request $request): RedirectResponse | ||
| public function store(Request $request): RedirectResponse|\Illuminate\Http\JsonResponse | ||
| { | ||
| // Frontend has already called the API and is just storing token/user in session | ||
| $request->validate([ | ||
| 'email' => ['required', 'string', 'email'], | ||
| 'password' => ['required', 'string'], | ||
| 'token' => ['required', 'string'], | ||
| 'user' => ['required', 'array'], | ||
| ]); | ||
|
|
||
| $this->ensureIsNotRateLimited($request); | ||
|
|
||
| if (! Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) { | ||
| RateLimiter::hit($this->throttleKey($request)); | ||
| try { | ||
| // Store authentication data in session | ||
| $request->session()->regenerate(); | ||
| $request->session()->put('api_token', $request->input('token')); | ||
| $request->session()->put('user', $request->input('user')); | ||
| $request->session()->put('authenticated', true); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Prompt for AI agentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The login handler trusts the client-provided token and user payload and immediately sets the session as authenticated. Because api.auth middleware only checks for these session values, a malicious request can POST arbitrary data to /login and bypass authentication entirely. Please verify the token server-side (e.g., by validating it with the external API) before marking the session as authenticated. Prompt for AI agentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Client can become authenticated by supplying any token/user payload; backend never verifies the token before setting the session. Prompt for AI agents |
||
|
|
||
| if ($request->expectsJson()) { | ||
| return response()->json([ | ||
| 'success' => true, | ||
| 'message' => 'Session created successfully', | ||
| 'redirect' => route('dashboard', absolute: false) | ||
| ]); | ||
| } | ||
|
|
||
| return redirect()->intended(route('dashboard', absolute: false)); | ||
|
|
||
| } catch (\Exception $e) { | ||
| if ($request->expectsJson()) { | ||
| return response()->json([ | ||
| 'message' => 'Session creation failed', | ||
| 'errors' => ['token' => ['Session creation failed']] | ||
| ], 500); | ||
| } | ||
|
|
||
| throw ValidationException::withMessages([ | ||
| 'email' => trans('auth.failed'), | ||
| 'token' => 'Session creation failed', | ||
| ]); | ||
| } | ||
|
|
||
| RateLimiter::clear($this->throttleKey($request)); | ||
|
|
||
| $request->session()->regenerate(); | ||
|
|
||
| return redirect()->intended(route('dashboard', absolute: false)); | ||
| } | ||
|
|
||
| public function destroy(Request $request): RedirectResponse | ||
| { | ||
| Auth::guard('web')->logout(); | ||
| try { | ||
| // Call API logout if we have a token | ||
| $token = $request->session()->get('api_token'); | ||
| if ($token) { | ||
| $apiUrl = config('api.url'); | ||
| Http::timeout(config('api.timeout')) | ||
| ->withToken($token) | ||
| ->delete("{$apiUrl}/session"); | ||
| } | ||
| } catch (\Exception $e) { | ||
| // Log error but continue with local logout | ||
| \Log::warning('API logout failed: ' . $e->getMessage()); | ||
| } | ||
|
|
||
| // Clear session | ||
| $request->session()->invalidate(); | ||
|
|
||
| $request->session()->regenerateToken(); | ||
|
|
||
| return redirect('/'); | ||
|
|
||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proxy now points to localhost from inside Docker, breaking all external API calls.
Prompt for AI agents