diff --git a/.deploy/artwork-evolution-release/.env.example b/.deploy/artwork-evolution-release/.env.example
index fea5f577..e88813b6 100644
--- a/.deploy/artwork-evolution-release/.env.example
+++ b/.deploy/artwork-evolution-release/.env.example
@@ -41,6 +41,14 @@ SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
+# Skinbase Nova conditional public sessions
+SKINBASE_CONDITIONAL_SESSIONS_ENABLED=true
+SKINBASE_SKIP_ANONYMOUS_PUBLIC_GET_SESSIONS=true
+SKINBASE_SKIP_BOT_PUBLIC_GET_SESSIONS=true
+
+# Debug only; do not enable permanently in production
+SKINBASE_SESSION_DEBUG_HEADER=false
+
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=local
QUEUE_CONNECTION=redis
diff --git a/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalShareErrorsFromSession.php b/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalShareErrorsFromSession.php
new file mode 100644
index 00000000..1666082d
--- /dev/null
+++ b/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalShareErrorsFromSession.php
@@ -0,0 +1,25 @@
+attributes->get('skinbase.session_skipped') === true || ! $request->hasSession()) {
+ return $next($request);
+ }
+
+ return parent::handle($request, $next);
+ }
+}
\ No newline at end of file
diff --git a/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalStartSession.php b/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalStartSession.php
new file mode 100644
index 00000000..d1a9e3c0
--- /dev/null
+++ b/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalStartSession.php
@@ -0,0 +1,122 @@
+shouldSkipSession($request)) {
+ $request->attributes->set('skinbase.session_skipped', true);
+
+ $response = $next($request);
+
+ if ($response instanceof Response && config('skinbase-sessions.debug_header', false)) {
+ $response->headers->set('X-Skinbase-Session', 'skipped');
+ }
+
+ return $response;
+ }
+
+ $request->attributes->set('skinbase.session_skipped', false);
+
+ $response = parent::handle($request, $next);
+
+ if ($response instanceof Response && config('skinbase-sessions.debug_header', false)) {
+ $response->headers->set('X-Skinbase-Session', 'started');
+ }
+
+ return $response;
+ }
+
+ protected function shouldSkipSession(Request $request): bool
+ {
+ if (! $this->isSafeReadMethod($request)) {
+ return false;
+ }
+
+ if ($this->hasExistingSessionCookie($request)) {
+ return false;
+ }
+
+ if ($this->matchesAnyPath($request, config('skinbase-sessions.always_session_paths', []))) {
+ return false;
+ }
+
+ if (! $this->matchesAnyPath($request, config('skinbase-sessions.public_paths', []))) {
+ return false;
+ }
+
+ if (config('skinbase-sessions.skip_anonymous_public_get', true)) {
+ return true;
+ }
+
+ return config('skinbase-sessions.skip_known_crawlers_on_public_get', true)
+ && $this->isKnownCrawler($request);
+ }
+
+ protected function isSafeReadMethod(Request $request): bool
+ {
+ return in_array($request->getMethod(), ['GET', 'HEAD'], true);
+ }
+
+ protected function hasExistingSessionCookie(Request $request): bool
+ {
+ $cookieName = config('session.cookie');
+
+ return is_string($cookieName)
+ && $cookieName !== ''
+ && $request->cookies->has($cookieName);
+ }
+
+ protected function matchesAnyPath(Request $request, array $patterns): bool
+ {
+ foreach ($patterns as $pattern) {
+ if (! is_string($pattern) || $pattern === '') {
+ continue;
+ }
+
+ if ($pattern === '/' && $request->path() === '/') {
+ return true;
+ }
+
+ $normalizedPattern = trim($pattern, '/');
+
+ if ($normalizedPattern !== '' && $request->is($normalizedPattern)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function isKnownCrawler(Request $request): bool
+ {
+ $userAgent = strtolower((string) $request->userAgent());
+
+ if ($userAgent === '') {
+ return false;
+ }
+
+ foreach (config('skinbase-sessions.bot_user_agent_keywords', []) as $keyword) {
+ $normalizedKeyword = strtolower((string) $keyword);
+
+ if ($normalizedKeyword !== '' && str_contains($userAgent, $normalizedKeyword)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalValidateCsrfToken.php b/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalValidateCsrfToken.php
new file mode 100644
index 00000000..0112ae5e
--- /dev/null
+++ b/.deploy/artwork-evolution-release/app/Http/Middleware/ConditionalValidateCsrfToken.php
@@ -0,0 +1,21 @@
+attributes->get('skinbase.session_skipped') === true) {
+ return $next($request);
+ }
+
+ return parent::handle($request, $next);
+ }
+}
\ No newline at end of file
diff --git a/.deploy/artwork-evolution-release/app/Http/Middleware/HandleInertiaRequests.php b/.deploy/artwork-evolution-release/app/Http/Middleware/HandleInertiaRequests.php
index 86f3f3ea..dd870222 100644
--- a/.deploy/artwork-evolution-release/app/Http/Middleware/HandleInertiaRequests.php
+++ b/.deploy/artwork-evolution-release/app/Http/Middleware/HandleInertiaRequests.php
@@ -12,6 +12,15 @@ final class HandleInertiaRequests extends Middleware
{
protected $rootView = 'upload';
+ protected function canReadSessionAuth(Request $request): bool
+ {
+ if ($request->attributes->get('skinbase.session_skipped') === true) {
+ return false;
+ }
+
+ return $request->hasSession();
+ }
+
/**
* Select the root Blade view based on route prefix.
*/
@@ -58,13 +67,16 @@ final class HandleInertiaRequests extends Middleware
public function share(Request $request): array
{
+ $canReadSessionAuth = $this->canReadSessionAuth($request);
+ $user = $canReadSessionAuth ? $request->user() : null;
+
return array_merge(parent::share($request), [
'auth' => [
- 'user' => $request->user() ? [
- 'id' => $request->user()->id,
- 'name' => $request->user()->name,
- 'is_admin' => $request->user()->isAdmin(),
- 'is_moderator' => $request->user()->isModerator(),
+ 'user' => $user ? [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'is_admin' => $user->isAdmin(),
+ 'is_moderator' => $user->isModerator(),
] : null,
],
'cdn' => [
@@ -84,8 +96,8 @@ final class HandleInertiaRequests extends Middleware
'group_assets' => (bool) config('features.group_assets', true),
'group_activity_feed' => (bool) config('features.group_activity_feed', true),
],
- 'studio_groups' => $request->user()
- ? app(GroupService::class)->studioOptionsForUser($request->user())
+ 'studio_groups' => $user
+ ? app(GroupService::class)->studioOptionsForUser($user)
: [],
]);
}
diff --git a/.deploy/artwork-evolution-release/app/Providers/AppServiceProvider.php b/.deploy/artwork-evolution-release/app/Providers/AppServiceProvider.php
index 173ffa50..7c232247 100644
--- a/.deploy/artwork-evolution-release/app/Providers/AppServiceProvider.php
+++ b/.deploy/artwork-evolution-release/app/Providers/AppServiceProvider.php
@@ -151,6 +151,11 @@ class AppServiceProvider extends ServiceProvider
$displayName = null;
$userId = null;
$toolbarContentTypes = collect();
+ $request = request();
+ $canReadSessionAuth = $request instanceof \Illuminate\Http\Request
+ && $request->hasSession()
+ && $request->attributes->get('skinbase.session_skipped') !== true;
+ $authUser = $canReadSessionAuth ? Auth::user() : null;
try {
$toolbarContentTypes = $this->app
@@ -162,8 +167,9 @@ class AppServiceProvider extends ServiceProvider
$toolbarContentTypes = collect();
}
- if (Auth::check()) {
- $userId = Auth::id();
+ if ($authUser) {
+ $authUser->loadMissing('profile');
+ $userId = (int) $authUser->id;
try {
$uploadCount = DB::table('artworks')->where('user_id', $userId)->count();
} catch (\Throwable $e) {
@@ -200,19 +206,18 @@ class AppServiceProvider extends ServiceProvider
try {
$receivedCommentsCount = $this->app->make(ReceivedCommentsInboxService::class)
- ->unreadCountForUser(Auth::user());
+ ->unreadCountForUser($authUser);
} catch (\Throwable $e) {
$receivedCommentsCount = 0;
}
try {
- $profile = DB::table('user_profiles')->where('user_id', $userId)->first();
- $avatarHash = $profile->avatar_hash ?? null;
+ $avatarHash = $authUser->profile?->avatar_hash;
} catch (\Throwable $e) {
$avatarHash = null;
}
- $displayName = Auth::user()->name ?: (Auth::user()->username ?? '');
+ $displayName = $authUser->name ?: ($authUser->username ?? '');
}
$view->with(compact('userId','uploadCount', 'favCount', 'msgCount', 'noticeCount', 'receivedCommentsCount', 'avatarHash', 'displayName', 'toolbarContentTypes'));
diff --git a/.deploy/artwork-evolution-release/bootstrap/app.php b/.deploy/artwork-evolution-release/bootstrap/app.php
index 1e203fb6..f108a625 100644
--- a/.deploy/artwork-evolution-release/bootstrap/app.php
+++ b/.deploy/artwork-evolution-release/bootstrap/app.php
@@ -1,8 +1,14 @@
withRouting(
@@ -13,6 +19,12 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
+ $middleware->web(replace: [
+ StartSession::class => ConditionalStartSession::class,
+ ShareErrorsFromSession::class => ConditionalShareErrorsFromSession::class,
+ ValidateCsrfToken::class => ConditionalValidateCsrfToken::class,
+ ]);
+
$middleware->validateCsrfTokens(except: [
'chat_post',
'chat_post/*',
diff --git a/.deploy/artwork-evolution-release/config/skinbase-sessions.php b/.deploy/artwork-evolution-release/config/skinbase-sessions.php
new file mode 100644
index 00000000..f1c5e9eb
--- /dev/null
+++ b/.deploy/artwork-evolution-release/config/skinbase-sessions.php
@@ -0,0 +1,124 @@
+ env('SKINBASE_CONDITIONAL_SESSIONS_ENABLED', true),
+
+ 'skip_anonymous_public_get' => env('SKINBASE_SKIP_ANONYMOUS_PUBLIC_GET_SESSIONS', true),
+
+ 'skip_known_crawlers_on_public_get' => env('SKINBASE_SKIP_BOT_PUBLIC_GET_SESSIONS', true),
+
+ 'debug_header' => env('SKINBASE_SESSION_DEBUG_HEADER', false),
+
+ 'public_paths' => [
+ '/',
+ 'featured',
+ 'uploads/latest',
+ 'uploads/daily',
+ 'members/photos',
+ 'downloads/today',
+ 'comments/monthly',
+ 'discover',
+ 'discover/*',
+ 'explore',
+ 'explore/*',
+ 'blog',
+ 'blog/*',
+ 'pages/*',
+ 'about',
+ 'help',
+ 'help/*',
+ 'contact',
+ 'faq',
+ 'rules-and-guidelines',
+ 'privacy-policy',
+ 'terms-of-service',
+ 'staff',
+ 'bug-report',
+ 'rss-feeds',
+ 'rss',
+ 'rss/*',
+ 'news',
+ 'news/*',
+ 'worlds',
+ 'worlds/*',
+ 'creators',
+ 'creators/*',
+ 'stories',
+ 'stories/*',
+ 'tags',
+ 'tags/*',
+ 'categories',
+ 'leaderboard',
+ 'art',
+ 'art/*',
+ 'sitemap.xml',
+ 'sitemaps/*',
+ 'robots.txt',
+ ],
+
+ 'always_session_paths' => [
+ 'login',
+ 'logout',
+ 'register',
+ 'register/*',
+ 'auth/*',
+ 'forgot-password',
+ 'reset-password',
+ 'reset-password/*',
+ 'confirm-password',
+ 'email/verification-notification',
+ 'verify-email',
+ 'verify-email/*',
+ 'setup/*',
+
+ 'dashboard',
+ 'dashboard/*',
+ 'manage',
+ 'studio',
+ 'studio/*',
+ 'upload',
+ 'upload/*',
+ 'settings',
+ 'settings/*',
+ 'messages',
+ 'messages/*',
+ 'worlds/create',
+
+ 'cp',
+ 'cp/*',
+ 'admin',
+ 'admin/*',
+
+ 'api/me',
+ 'api/auth/*',
+ ],
+
+ 'bot_user_agent_keywords' => [
+ 'googlebot',
+ 'bingbot',
+ 'slurp',
+ 'duckduckbot',
+ 'baiduspider',
+ 'yandexbot',
+ 'sogou',
+ 'exabot',
+ 'facebot',
+ 'facebookexternalhit',
+ 'ia_archiver',
+ 'semrushbot',
+ 'ahrefsbot',
+ 'mj12bot',
+ 'dotbot',
+ 'petalbot',
+ 'applebot',
+ 'twitterbot',
+ 'linkedinbot',
+ 'discordbot',
+ 'telegrambot',
+ 'whatsapp',
+ 'crawler',
+ 'spider',
+ 'bot',
+ ],
+];
\ No newline at end of file
diff --git a/.deploy/artwork-evolution-release/resources/views/artworks/show.blade.php b/.deploy/artwork-evolution-release/resources/views/artworks/show.blade.php
index 2e1f8e04..e1ed3c3c 100644
--- a/.deploy/artwork-evolution-release/resources/views/artworks/show.blade.php
+++ b/.deploy/artwork-evolution-release/resources/views/artworks/show.blade.php
@@ -21,6 +21,7 @@
$comments = $comments ?? [];
$groupSummary = $groupSummary ?? null;
$useUnifiedSeo = true;
+ $canReadSessionAuth = request()->hasSession() && ! request()->attributes->get('skinbase.session_skipped');
@endphp
@push('head')
@@ -49,7 +50,7 @@
data-canonical='@json($meta["canonical"])'
data-comments='@json($comments)'
data-group-summary='@json($groupSummary)'
- data-is-authenticated='@json(auth()->check())'>
+ data-is-authenticated='@json($canReadSessionAuth && auth()->check())'>
@vite(['resources/js/Pages/ArtworkPage.jsx'])
diff --git a/.deploy/artwork-evolution-release/resources/views/collections.blade.php b/.deploy/artwork-evolution-release/resources/views/collections.blade.php
index 672e42ec..31de4271 100644
--- a/.deploy/artwork-evolution-release/resources/views/collections.blade.php
+++ b/.deploy/artwork-evolution-release/resources/views/collections.blade.php
@@ -1,7 +1,9 @@
@extends('layouts.nova')
@push('head')
+ @if(request()->hasSession() && ! request()->attributes->get('skinbase.session_skipped'))
+ @endif
@vite(['resources/js/collections.jsx'])