Fix 401 errors for Guest PWA API endpoints: Remove global tenant middleware from bootstrap/app.php and apply only to tenant routes; add throttle:100,1 to guest routes in api.php; enhance EventPublicController with published status validation for all methods to ensure secure public access without auth.
This commit is contained in:
@@ -47,11 +47,11 @@ class EventPublicController extends BaseController
|
|||||||
}
|
}
|
||||||
public function event(string $slug)
|
public function event(string $slug)
|
||||||
{
|
{
|
||||||
$event = DB::table('events')->where('slug', $slug)->first([
|
$event = DB::table('events')->where('slug', $slug)->where('status', 'published')->first([
|
||||||
'id', 'slug', 'name', 'default_locale', 'created_at', 'updated_at'
|
'id', 'slug', 'name', 'default_locale', 'created_at', 'updated_at'
|
||||||
]);
|
]);
|
||||||
if (! $event) {
|
if (! $event) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found or not public']], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$locale = request()->query('locale', 'de');
|
$locale = request()->query('locale', 'de');
|
||||||
@@ -88,9 +88,9 @@ class EventPublicController extends BaseController
|
|||||||
|
|
||||||
public function stats(string $slug)
|
public function stats(string $slug)
|
||||||
{
|
{
|
||||||
$event = DB::table('events')->where('slug', $slug)->first(['id']);
|
$event = DB::table('events')->where('slug', $slug)->where('status', 'published')->first(['id']);
|
||||||
if (! $event) {
|
if (! $event) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found or not public']], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$eventId = $event->id;
|
$eventId = $event->id;
|
||||||
@@ -127,9 +127,9 @@ class EventPublicController extends BaseController
|
|||||||
|
|
||||||
public function emotions(string $slug)
|
public function emotions(string $slug)
|
||||||
{
|
{
|
||||||
$event = DB::table('events')->where('slug', $slug)->first(['id']);
|
$event = DB::table('events')->where('slug', $slug)->where('status', 'published')->first(['id']);
|
||||||
if (! $event) {
|
if (! $event) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found or not public']], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = DB::table('emotions')
|
$rows = DB::table('emotions')
|
||||||
@@ -176,9 +176,9 @@ class EventPublicController extends BaseController
|
|||||||
|
|
||||||
public function tasks(string $slug, Request $request)
|
public function tasks(string $slug, Request $request)
|
||||||
{
|
{
|
||||||
$event = DB::table('events')->where('slug', $slug)->first(['id']);
|
$event = DB::table('events')->where('slug', $slug)->where('status', 'published')->first(['id']);
|
||||||
if (! $event) {
|
if (! $event) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found or not public']], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = DB::table('tasks')
|
$query = DB::table('tasks')
|
||||||
@@ -260,9 +260,9 @@ class EventPublicController extends BaseController
|
|||||||
|
|
||||||
public function photos(Request $request, string $slug)
|
public function photos(Request $request, string $slug)
|
||||||
{
|
{
|
||||||
$event = DB::table('events')->where('slug', $slug)->first(['id']);
|
$event = DB::table('events')->where('slug', $slug)->where('status', 'published')->first(['id']);
|
||||||
if (! $event) {
|
if (! $event) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found or not public']], 404);
|
||||||
}
|
}
|
||||||
$eventId = $event->id;
|
$eventId = $event->id;
|
||||||
|
|
||||||
@@ -327,6 +327,7 @@ class EventPublicController extends BaseController
|
|||||||
public function photo(int $id)
|
public function photo(int $id)
|
||||||
{
|
{
|
||||||
$row = DB::table('photos')
|
$row = DB::table('photos')
|
||||||
|
->join('events', 'photos.event_id', '=', 'events.id')
|
||||||
->leftJoin('tasks', 'photos.task_id', '=', 'tasks.id')
|
->leftJoin('tasks', 'photos.task_id', '=', 'tasks.id')
|
||||||
->select([
|
->select([
|
||||||
'photos.id',
|
'photos.id',
|
||||||
@@ -340,9 +341,10 @@ class EventPublicController extends BaseController
|
|||||||
'tasks.title as task_title'
|
'tasks.title as task_title'
|
||||||
])
|
])
|
||||||
->where('photos.id', $id)
|
->where('photos.id', $id)
|
||||||
|
->where('events.status', 'published')
|
||||||
->first();
|
->first();
|
||||||
if (! $row) {
|
if (! $row) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Photo not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Photo not found or event not public']], 404);
|
||||||
}
|
}
|
||||||
$row->file_path = $this->toPublicUrl((string)($row->file_path ?? ''));
|
$row->file_path = $this->toPublicUrl((string)($row->file_path ?? ''));
|
||||||
$row->thumbnail_path = $this->toPublicUrl((string)($row->thumbnail_path ?? ''));
|
$row->thumbnail_path = $this->toPublicUrl((string)($row->thumbnail_path ?? ''));
|
||||||
@@ -364,9 +366,13 @@ class EventPublicController extends BaseController
|
|||||||
$deviceId = 'anon';
|
$deviceId = 'anon';
|
||||||
}
|
}
|
||||||
|
|
||||||
$photo = DB::table('photos')->where('id', $id)->first(['id', 'event_id']);
|
$photo = DB::table('photos')
|
||||||
|
->join('events', 'photos.event_id', '=', 'events.id')
|
||||||
|
->where('photos.id', $id)
|
||||||
|
->where('events.status', 'published')
|
||||||
|
->first(['photos.id', 'photos.event_id']);
|
||||||
if (! $photo) {
|
if (! $photo) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Photo not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Photo not found or event not public']], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Idempotent like per device
|
// Idempotent like per device
|
||||||
@@ -401,9 +407,9 @@ class EventPublicController extends BaseController
|
|||||||
|
|
||||||
public function upload(Request $request, string $slug)
|
public function upload(Request $request, string $slug)
|
||||||
{
|
{
|
||||||
$event = DB::table('events')->where('slug', $slug)->first(['id']);
|
$event = DB::table('events')->where('slug', $slug)->where('status', 'published')->first(['id']);
|
||||||
if (! $event) {
|
if (! $event) {
|
||||||
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found']], 404);
|
return response()->json(['error' => ['code' => 'not_found', 'message' => 'Event not found or not public']], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deviceId = (string) $request->header('X-Device-Id', 'anon');
|
$deviceId = (string) $request->header('X-Device-Id', 'anon');
|
||||||
|
|||||||
@@ -25,11 +25,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
AddLinkHeadersForPreloadedAssets::class,
|
AddLinkHeadersForPreloadedAssets::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$middleware->api(append: [
|
$middleware->api(append: []);
|
||||||
\App\Http\Middleware\TenantTokenGuard::class,
|
|
||||||
\App\Http\Middleware\TenantIsolation::class,
|
|
||||||
\App\Http\Middleware\CreditCheckMiddleware::class,
|
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions) {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
//
|
//
|
||||||
|
|||||||
2823
package-lock.json
generated
2823
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
|||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-organize-imports": "^4.1.0",
|
"prettier-plugin-organize-imports": "^4.1.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
"shadcn": "^3.3.1",
|
||||||
"typescript-eslint": "^8.23.0"
|
"typescript-eslint": "^8.23.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -12,19 +12,20 @@ Route::prefix('v1')->name('api.v1.')->group(function () {
|
|||||||
Route::get('/oauth/authorize', [OAuthController::class, 'authorize'])->name('oauth.authorize');
|
Route::get('/oauth/authorize', [OAuthController::class, 'authorize'])->name('oauth.authorize');
|
||||||
Route::post('/oauth/token', [OAuthController::class, 'token'])->name('oauth.token');
|
Route::post('/oauth/token', [OAuthController::class, 'token'])->name('oauth.token');
|
||||||
|
|
||||||
// Guest PWA routes (public, no auth)
|
// Guest PWA routes (public, throttled, no tenant middleware)
|
||||||
|
Route::middleware('throttle:100,1')->group(function () {
|
||||||
Route::get('/events/{slug}', [EventPublicController::class, 'event'])->name('events.show');
|
Route::get('/events/{slug}', [EventPublicController::class, 'event'])->name('events.show');
|
||||||
Route::get('/events/{slug}/stats', [EventPublicController::class, 'stats'])->name('events.stats');
|
Route::get('/events/{slug}/stats', [EventPublicController::class, 'stats'])->name('events.stats');
|
||||||
Route::get('/events/{slug}/emotions', [EventPublicController::class, 'emotions'])->name('events.emotions');
|
Route::get('/events/{slug}/emotions', [EventPublicController::class, 'emotions'])->name('events.emotions');
|
||||||
Route::get('/events/{slug}/tasks', [EventPublicController::class, 'tasks'])->name('events.tasks');
|
Route::get('/events/{slug}/tasks', [EventPublicController::class, 'tasks'])->name('events.tasks');
|
||||||
Route::get('/events/{slug}/photos', [EventPublicController::class, 'photos'])->name('events.photos');
|
Route::get('/events/{slug}/photos', [EventPublicController::class, 'photos'])->name('events.photos');
|
||||||
Route::get('/photos/{id}', [EventPublicController::class, 'photo'])->name('photos.show');
|
Route::get('/photos/{id}', [EventPublicController::class, 'photo'])->name('photos.show');
|
||||||
|
|
||||||
Route::post('/photos/{id}/like', [EventPublicController::class, 'like'])->name('photos.like');
|
Route::post('/photos/{id}/like', [EventPublicController::class, 'like'])->name('photos.like');
|
||||||
Route::post('/events/{slug}/upload', [EventPublicController::class, 'upload'])->name('events.upload');
|
Route::post('/events/{slug}/upload', [EventPublicController::class, 'upload'])->name('events.upload');
|
||||||
|
});
|
||||||
|
|
||||||
// Protected tenant API routes (require auth:sanctum)
|
// Protected tenant API routes (require auth:sanctum + tenant middleware)
|
||||||
Route::middleware('auth:sanctum')->prefix('tenant')->group(function () {
|
Route::middleware(['auth:sanctum', \App\Http\Middleware\TenantTokenGuard::class, \App\Http\Middleware\TenantIsolation::class])->prefix('tenant')->group(function () {
|
||||||
Route::get('me', [OAuthController::class, 'me'])->name('tenant.me');
|
Route::get('me', [OAuthController::class, 'me'])->name('tenant.me');
|
||||||
|
|
||||||
// Events CRUD
|
// Events CRUD
|
||||||
|
|||||||
Reference in New Issue
Block a user