refactor: move /v1/* API routes exclusively to Website/Api module
Production stack has honeypot that null-routes API payloads sent to the web domain. API routes now only register via Website/Api module (api.lthn.io). Mod modules stripped to web-only routes. Frontend JS fetch calls use configurable API_URL for cross-origin API access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6b2032c687
commit
d83c9094cd
13 changed files with 45 additions and 63 deletions
|
|
@ -9,4 +9,5 @@ return [
|
|||
'network' => env('CHAIN_NETWORK', 'testnet'), // testnet or mainnet
|
||||
'lns_url' => env('LNS_URL', 'http://127.0.0.1:5553'),
|
||||
'api_token' => env('API_TOKEN', ''),
|
||||
'api_url' => env('API_URL', ''), // empty = same origin, set to https://api.lthn.io for production
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||
namespace Mod\Explorer;
|
||||
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class Boot
|
||||
|
|
@ -17,11 +16,6 @@ class Boot
|
|||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$event->views('explorer', __DIR__ . '/Views');
|
||||
$event->routes(function () {
|
||||
Route::prefix('explorer')->group(__DIR__ . '/Routes/web.php');
|
||||
Route::prefix('v1/explorer')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php');
|
||||
});
|
||||
$event->routes(fn () => Route::prefix('explorer')->group(__DIR__ . '/Routes/web.php'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
}
|
||||
|
||||
function poll() {
|
||||
fetch('/v1/explorer/info', {headers: {'Accept': 'application/json'}})
|
||||
fetch((window.LTHN_API || '') + '/v1/explorer/info', {headers: {'Accept': 'application/json'}})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var h = data.height || 0;
|
||||
|
|
|
|||
|
|
@ -5,25 +5,15 @@ declare(strict_types=1);
|
|||
namespace Mod\Gateway;
|
||||
|
||||
use Core\Events\FrameworkBooted;
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Mod\Gateway\Services\GatewayRegistry;
|
||||
|
||||
class Boot
|
||||
{
|
||||
// API routes registered by Website/Api — this module only provides services
|
||||
public static array $listens = [
|
||||
WebRoutesRegistering::class => 'onWebRoutes',
|
||||
FrameworkBooted::class => 'onFrameworkBooted',
|
||||
];
|
||||
|
||||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$event->routes(fn () => Route::prefix('v1/gateway')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php'));
|
||||
}
|
||||
|
||||
public function onFrameworkBooted(FrameworkBooted $event): void
|
||||
{
|
||||
app()->singleton(GatewayRegistry::class);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ namespace Mod\Names;
|
|||
|
||||
use Core\Events\ConsoleBooting;
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Mod\Names\Commands\RetryDnsTickets;
|
||||
|
||||
|
|
@ -20,12 +19,7 @@ class Boot
|
|||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$event->views('names', __DIR__ . '/Views');
|
||||
$event->routes(function () {
|
||||
Route::prefix('names')->group(__DIR__ . '/Routes/web.php');
|
||||
Route::prefix('v1/names')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php');
|
||||
});
|
||||
$event->routes(fn () => Route::prefix('names')->group(__DIR__ . '/Routes/web.php'));
|
||||
}
|
||||
|
||||
public function onConsole(ConsoleBooting $event): void
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
result.appendChild(el('p', {color: '#9ca3af', fontSize: '0.9rem'}, 'Checking...'));
|
||||
btn.disabled = true;
|
||||
|
||||
fetch('/v1/names/available/' + encodeURIComponent(name), {headers: {'Accept': 'application/json'}})
|
||||
fetch((window.LTHN_API || '') + '/v1/names/available/' + encodeURIComponent(name), {headers: {'Accept': 'application/json'}})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
btn.disabled = false;
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
if (!email) { return; }
|
||||
claimBtn.disabled = true;
|
||||
claimBtn.textContent = 'Submitting...';
|
||||
fetch('/v1/names/claim', {
|
||||
fetch((window.LTHN_API || '') + '/v1/names/claim', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
|
||||
body: JSON.stringify({name: name, email: email})
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ namespace Mod\Pool;
|
|||
|
||||
use Core\Events\FrameworkBooted;
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Mod\Pool\Services\PoolClient;
|
||||
|
||||
|
|
@ -19,12 +18,7 @@ class Boot
|
|||
|
||||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$event->routes(function () {
|
||||
Route::prefix('pool')->group(__DIR__ . '/Routes/web.php');
|
||||
Route::prefix('v1/pool')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php');
|
||||
});
|
||||
$event->routes(fn () => Route::prefix('pool')->group(__DIR__ . '/Routes/web.php'));
|
||||
}
|
||||
|
||||
public function onFrameworkBooted(FrameworkBooted $event): void
|
||||
|
|
|
|||
|
|
@ -4,20 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Mod\Proxy;
|
||||
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class Boot
|
||||
{
|
||||
public static array $listens = [
|
||||
WebRoutesRegistering::class => 'onWebRoutes',
|
||||
];
|
||||
|
||||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$event->routes(fn () => Route::prefix('v1/proxy')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php'));
|
||||
}
|
||||
// API-only module — routes registered by Website/Api
|
||||
public static array $listens = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||
namespace Mod\Trade;
|
||||
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class Boot
|
||||
|
|
@ -16,11 +15,6 @@ class Boot
|
|||
|
||||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$event->routes(function () {
|
||||
Route::prefix('trade')->group(__DIR__ . '/Routes/web.php');
|
||||
Route::prefix('v1/trade')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php');
|
||||
});
|
||||
$event->routes(fn () => Route::prefix('trade')->group(__DIR__ . '/Routes/web.php'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,21 @@ namespace Website\Api;
|
|||
|
||||
use Core\Events\DomainResolving;
|
||||
use Core\Events\WebRoutesRegistering;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* api.lthn.io — REST API homepage.
|
||||
* api.lthn.io — REST API.
|
||||
*
|
||||
* When api.lthn.io is the resolved domain, serves an API reference
|
||||
* landing page at /. API endpoints themselves are registered by Mod
|
||||
* modules via ApiRoutesRegistering.
|
||||
* Registers all /v1/* API routes. In production, Traefik routes
|
||||
* api.lthn.io traffic here. The honeypot null-routes API payloads
|
||||
* sent to the web domain, so these routes MUST NOT be on lthn.io.
|
||||
*
|
||||
* DomainResolving handles the homepage (/) — only on api.lthn.io.
|
||||
* API routes register unconditionally since Blesta connects via
|
||||
* host.docker.internal in homelab.
|
||||
*/
|
||||
class Boot extends ServiceProvider
|
||||
{
|
||||
|
|
@ -53,11 +58,16 @@ class Boot extends ServiceProvider
|
|||
|
||||
public function onWebRoutes(WebRoutesRegistering $event): void
|
||||
{
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
// API routes — no CSRF, no session needed
|
||||
$event->routes(fn () => Route::prefix('v1')
|
||||
->withoutMiddleware(ValidateCsrfToken::class)
|
||||
->group(__DIR__ . '/Routes/api.php'));
|
||||
|
||||
// Homepage only on API domain
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
foreach (static::$domains as $pattern) {
|
||||
if (preg_match($pattern, $host)) {
|
||||
$event->routes(fn () => Route::group([], __DIR__ . '/Routes/web.php'));
|
||||
$event->routes(fn () => Route::group([], __DIR__ . '/Routes/home.php'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
app/Website/Api/Routes/api.php
Normal file
16
app/Website/Api/Routes/api.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// All v1 API routes — served exclusively on api.lthn.io
|
||||
// Production: Traefik routes api.lthn.io here, honeypot blocks /v1 on lthn.io
|
||||
// Homelab: both domains hit same app, Blesta uses host.docker.internal
|
||||
|
||||
Route::prefix('names')->group(base_path('app/Mod/Names/Routes/api.php'));
|
||||
Route::prefix('explorer')->group(base_path('app/Mod/Explorer/Routes/api.php'));
|
||||
Route::prefix('proxy')->group(base_path('app/Mod/Proxy/Routes/api.php'));
|
||||
Route::prefix('gateway')->group(base_path('app/Mod/Gateway/Routes/api.php'));
|
||||
Route::prefix('pool')->group(base_path('app/Mod/Pool/Routes/api.php'));
|
||||
Route::prefix('trade')->group(base_path('app/Mod/Trade/Routes/api.php'));
|
||||
|
|
@ -5,5 +5,5 @@ declare(strict_types=1);
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use Website\Api\Controllers\ApiDocsController;
|
||||
|
||||
// Homepage — API reference (only serves on api.lthn.io domain)
|
||||
// API homepage — only registered on api.lthn.io domain
|
||||
Route::get('/', [ApiDocsController::class, 'index'])->name('api.index');
|
||||
|
|
@ -96,6 +96,7 @@
|
|||
|
||||
code { background: var(--surface); padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.85em; }
|
||||
</style>
|
||||
<script>window.LTHN_API = '{{ config('chain.api_url', '') }}';</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue