resolveGallery($request); if ($this->requestedGalleryMissing($request, $gallery)) { return $this->missingGalleryResponse($request); } if ($this->isGalleryExpired($gallery, $settings)) { return $this->deny($request, __('api.gallery.expired'), $gallery); } $hasValidAccess = $this->hasValidAccess($request, $gallery, $settings); $hasExistingGrant = (bool) ($this->sessionGrantedAt($request, $gallery) ?? $this->cookieGrantedAt($request, $gallery)); if ($hasValidAccess) { return $next($request); } if ($this->accessDuration($gallery, $settings) && $hasExistingGrant) { return $this->deny($request, __('api.gallery.expired'), $gallery); } if (! $this->requiresPassword($gallery, $settings) || ! $this->passwordHash($gallery, $settings)) { if ($this->accessDuration($gallery, $settings) && ! $hasExistingGrant) { self::grantForGallery($request, $gallery, $settings); } return $next($request); } return $this->deny($request, __('api.gallery.password_required'), $gallery); } public static function grantForGallery(Request $request, ?Gallery $gallery, GeneralSettings $settings, ?Carbon $grantedAt = null): void { $grantedAt = $grantedAt ?: Carbon::now(); $suffix = self::keySuffix($gallery); if ($request->hasSession()) { $request->session()->put(self::SESSION_GRANTED_BASE.'_'.$suffix, true); $request->session()->put(self::SESSION_GRANTED_AT_BASE.'_'.$suffix, $grantedAt->toIso8601String()); } $cookieMinutes = self::calculateCookieLifetime($gallery, $settings, $grantedAt); cookie()->queue( cookie( self::COOKIE_GRANTED_AT_BASE.'_'.$suffix, $grantedAt->toIso8601String(), $cookieMinutes, path: '/', secure: config('session.secure', false), httpOnly: true, raw: false, sameSite: config('session.same_site', 'lax') ) ); } private function hasValidAccess(Request $request, ?Gallery $gallery, GeneralSettings $settings): bool { if ($this->sessionAccessValid($request, $gallery, $settings)) { return true; } $grantedAt = $this->cookieGrantedAt($request, $gallery); if (! $grantedAt) { return false; } $duration = $this->accessDuration($gallery, $settings); if ($duration) { return ! $grantedAt->copy()->addMinutes($duration)->isPast(); } return (bool) $grantedAt; } private function sessionAccessValid(Request $request, ?Gallery $gallery, GeneralSettings $settings): bool { if (! $request->hasSession()) { return false; } $suffix = self::keySuffix($gallery); if (! $request->session()->get(self::SESSION_GRANTED_BASE.'_'.$suffix, false)) { return false; } $grantedAt = $this->sessionGrantedAt($request, $gallery); if (! $grantedAt) { return true; } $duration = $this->accessDuration($gallery, $settings); if ($duration) { $expiresAt = $grantedAt->copy()->addMinutes($duration); if ($expiresAt->isPast()) { $request->session()->forget([ self::SESSION_GRANTED_BASE.'_'.$suffix, self::SESSION_GRANTED_AT_BASE.'_'.$suffix, ]); return false; } } return true; } private function sessionGrantedAt(Request $request, ?Gallery $gallery): ?Carbon { if (! $request->hasSession()) { return null; } $value = $request->session()->get(self::SESSION_GRANTED_AT_BASE.'_'.self::keySuffix($gallery)); if (! $value) { return null; } try { return Carbon::parse($value); } catch (\Exception) { return null; } } private function cookieGrantedAt(Request $request, ?Gallery $gallery): ?Carbon { $value = $request->cookie(self::COOKIE_GRANTED_AT_BASE.'_'.self::keySuffix($gallery)); if (! $value) { return null; } try { $value = Crypt::decryptString($value); } catch (\Exception) { // Cookie might already be decrypted by web middleware. } try { return Carbon::parse($value); } catch (\Exception) { return null; } } private function isGalleryExpired(?Gallery $gallery, GeneralSettings $settings): bool { $expiresAt = $this->expiresAt($gallery, $settings); return $expiresAt !== null && Carbon::now()->greaterThanOrEqualTo($expiresAt); } private function deny(Request $request, string $message, ?Gallery $gallery = null): Response { if ($request->expectsJson()) { return response()->json(['message' => $message], Response::HTTP_FORBIDDEN); } $routeName = $gallery ? 'gallery.access.show' : 'gallery.access.default'; return redirect() ->route($routeName, $gallery) ->with('gallery_access_message', $message); } private static function calculateCookieLifetime(?Gallery $gallery, GeneralSettings $settings, Carbon $grantedAt): int { $expiresAt = $gallery?->expires_at ?? $settings->gallery_expires_at; if ($expiresAt) { $expiresAt = Carbon::parse($expiresAt); return max(1, $grantedAt->diffInMinutes($expiresAt)); } $duration = $gallery?->access_duration_minutes ?? $settings->gallery_access_duration_minutes; if ($duration) { return max(1, $duration); } return 24 * 60; } private function resolveGallery(Request $request): ?Gallery { $routeGallery = $request->route('gallery'); if ($routeGallery instanceof Gallery) { return $routeGallery; } $slug = is_string($routeGallery) ? $routeGallery : $request->query('gallery'); if (! $slug) { return Gallery::first(); } return Gallery::where('slug', $slug)->first(); } private function requestedGalleryMissing(Request $request, ?Gallery $gallery): bool { if ($gallery) { return false; } return (bool) ($request->route('gallery') || $request->query('gallery')); } private function missingGalleryResponse(Request $request): Response { if ($request->expectsJson()) { return response()->json(['message' => 'Gallery not found.'], Response::HTTP_NOT_FOUND); } abort(404, 'Gallery not found'); } private static function keySuffix(?Gallery $gallery): string { return $gallery ? 'gallery_'.$gallery->id : 'global'; } private function expiresAt(?Gallery $gallery, GeneralSettings $settings): ?Carbon { $value = $gallery?->expires_at ?? $settings->gallery_expires_at; if (! $value) { return null; } return Carbon::parse($value); } private function accessDuration(?Gallery $gallery, GeneralSettings $settings): ?int { return $gallery?->access_duration_minutes ?? $settings->gallery_access_duration_minutes; } private function requiresPassword(?Gallery $gallery, GeneralSettings $settings): bool { return (bool) ($gallery?->require_password ?? $settings->require_gallery_password); } private function passwordHash(?Gallery $gallery, GeneralSettings $settings): ?string { return $gallery?->password_hash ?? $settings->gallery_password_hash; } }