Add Passkey Support & Pint

This commit is contained in:
2026-04-21 15:56:46 +02:00
parent 8ae9eaaadb
commit 05d4ef1bdb
57 changed files with 2151 additions and 716 deletions

View File

@@ -4,14 +4,16 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Alert; use App\Models\Alert;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class AlertController extends Controller class AlertController extends Controller
{ {
/** /**
* Display alert index page * Display alert index page
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('admin.alert.index'); return view('admin.alert.index');
} }
@@ -19,7 +21,7 @@ class AlertController extends Controller
/** /**
* Create Alert. * Create Alert.
*/ */
public function store(Request $request): \Illuminate\Http\RedirectResponse public function store(Request $request): RedirectResponse
{ {
$validated = $request->validate([ $validated = $request->validate([
'message' => 'required|string|max:255', 'message' => 'required|string|max:255',
@@ -39,7 +41,7 @@ class AlertController extends Controller
/** /**
* Delete Alert. * Delete Alert.
*/ */
public function delete(int $alert_id): \Illuminate\Http\RedirectResponse public function delete(int $alert_id): RedirectResponse
{ {
Alert::where('id', $alert_id)->delete(); Alert::where('id', $alert_id)->delete();

View File

@@ -3,13 +3,14 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\View\View;
class CommentsController extends Controller class CommentsController extends Controller
{ {
/** /**
* Display Comments Page. * Display Comments Page.
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('admin.comments.index'); return view('admin.comments.index');
} }

View File

@@ -4,13 +4,15 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Contact; use App\Models\Contact;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class ContactController extends Controller class ContactController extends Controller
{ {
/** /**
* Display Contact Page. * Display Contact Page.
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
$contacts = Contact::orderBy('created_at', 'DESC')->get(); $contacts = Contact::orderBy('created_at', 'DESC')->get();
@@ -22,7 +24,7 @@ class ContactController extends Controller
/** /**
* Delete Contact. * Delete Contact.
*/ */
public function delete(int $contact_id): \Illuminate\Http\RedirectResponse public function delete(int $contact_id): RedirectResponse
{ {
Contact::where('id', $contact_id)->delete(); Contact::where('id', $contact_id)->delete();

View File

@@ -8,6 +8,7 @@ use App\Models\Episode;
use App\Services\DownloadService; use App\Services\DownloadService;
use App\Services\EpisodeService; use App\Services\EpisodeService;
use App\Services\GalleryService; use App\Services\GalleryService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class EpisodeController extends Controller class EpisodeController extends Controller
@@ -31,7 +32,7 @@ class EpisodeController extends Controller
/** /**
* Add Episode to existing series * Add Episode to existing series
*/ */
public function store(Request $request): \Illuminate\Http\RedirectResponse public function store(Request $request): RedirectResponse
{ {
$referenceEpisode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail(); $referenceEpisode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
$episodeNumber = $referenceEpisode->hentai->episodes()->count() + 1; $episodeNumber = $referenceEpisode->hentai->episodes()->count() + 1;
@@ -59,7 +60,7 @@ class EpisodeController extends Controller
/** /**
* Edit Episode * Edit Episode
*/ */
public function update(Request $request): \Illuminate\Http\RedirectResponse public function update(Request $request): RedirectResponse
{ {
$episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail(); $episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
$studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value); $studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value);

View File

@@ -8,7 +8,9 @@ use App\Models\Hentai;
use App\Services\DownloadService; use App\Services\DownloadService;
use App\Services\EpisodeService; use App\Services\EpisodeService;
use App\Services\GalleryService; use App\Services\GalleryService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class ReleaseController extends Controller class ReleaseController extends Controller
{ {
@@ -31,7 +33,7 @@ class ReleaseController extends Controller
/** /**
* Display release page * Display release page
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('admin.release.create'); return view('admin.release.create');
} }
@@ -39,7 +41,7 @@ class ReleaseController extends Controller
/** /**
* Upload New Hentai with One or Multipe Episodes * Upload New Hentai with One or Multipe Episodes
*/ */
public function store(Request $request): \Illuminate\Http\RedirectResponse public function store(Request $request): RedirectResponse
{ {
// Create new Hentai or find existing one // Create new Hentai or find existing one
$slug = $this->episodeService->generateSlug($request->input('title')); $slug = $this->episodeService->generateSlug($request->input('title'));

View File

@@ -4,10 +4,12 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\SiteBackground; use App\Models\SiteBackground;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Intervention\Image\Encoders\WebpEncoder; use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image; use Intervention\Image\Laravel\Facades\Image;
@@ -16,7 +18,7 @@ class SiteBackgroundController extends Controller
/** /**
* Display admin index page * Display admin index page
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('admin.background.index', [ return view('admin.background.index', [
'images' => SiteBackground::all(), 'images' => SiteBackground::all(),
@@ -26,7 +28,7 @@ class SiteBackgroundController extends Controller
/** /**
* Create new site backgrounds * Create new site backgrounds
*/ */
public function create(Request $request): \Illuminate\Http\RedirectResponse public function create(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
'images' => 'required', 'images' => 'required',
@@ -73,7 +75,7 @@ class SiteBackgroundController extends Controller
return redirect()->back(); return redirect()->back();
} }
public function update(Request $request): \Illuminate\Http\RedirectResponse public function update(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
'id' => 'required|exists:site_backgrounds,id', 'id' => 'required|exists:site_backgrounds,id',
@@ -96,7 +98,7 @@ class SiteBackgroundController extends Controller
/** /**
* Delete backround * Delete backround
*/ */
public function delete(Request $request): \Illuminate\Http\RedirectResponse public function delete(Request $request): RedirectResponse
{ {
$id = $request->input('id'); $id = $request->input('id');

View File

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Episode; use App\Models\Episode;
use App\Models\EpisodeSubtitle; use App\Models\EpisodeSubtitle;
use App\Models\Subtitle; use App\Models\Subtitle;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class SubtitleController extends Controller class SubtitleController extends Controller
@@ -13,7 +14,7 @@ class SubtitleController extends Controller
/** /**
* Add new Subtitle. * Add new Subtitle.
*/ */
public function store(Request $request): \Illuminate\Http\RedirectResponse public function store(Request $request): RedirectResponse
{ {
$subtitle = Subtitle::create([ $subtitle = Subtitle::create([
'name' => $request->name, 'name' => $request->name,
@@ -32,7 +33,7 @@ class SubtitleController extends Controller
/** /**
* Update Episode Subtitles. * Update Episode Subtitles.
*/ */
public function update(Request $request): \Illuminate\Http\RedirectResponse public function update(Request $request): RedirectResponse
{ {
$episode = Episode::where('id', $request->input('episode_id'))->firstOrFail(); $episode = Episode::where('id', $request->input('episode_id'))->firstOrFail();

View File

@@ -6,13 +6,14 @@ use App\Enums\UserRole;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller class UserController extends Controller
{ {
/** /**
* Display Users Page. * Display Users Page.
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('admin.users.index'); return view('admin.users.index');
} }

View File

@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\Rules; use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View; use Illuminate\View\View;
class NewPasswordController extends Controller class NewPasswordController extends Controller
@@ -26,7 +27,7 @@ class NewPasswordController extends Controller
/** /**
* Handle an incoming new password request. * Handle an incoming new password request.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {

View File

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password; use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View; use Illuminate\View\View;
class PasswordResetLinkController extends Controller class PasswordResetLinkController extends Controller
@@ -21,7 +22,7 @@ class PasswordResetLinkController extends Controller
/** /**
* Handle an incoming password reset link request. * Handle an incoming password reset link request.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {

View File

@@ -11,13 +11,14 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules; use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
class RegisteredUserController extends Controller class RegisteredUserController extends Controller
{ {
/** /**
* Handle an incoming registration request. * Handle an incoming registration request.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {

View File

@@ -4,14 +4,16 @@ namespace App\Http\Controllers;
use App\Models\Contact; use App\Models\Contact;
use App\Rules\ValidCaptcha; use App\Rules\ValidCaptcha;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class ContactController extends Controller class ContactController extends Controller
{ {
/** /**
* Display Contact Page. * Display Contact Page.
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('contact.form'); return view('contact.form');
} }
@@ -19,7 +21,7 @@ class ContactController extends Controller
/** /**
* Store Contact Submission. * Store Contact Submission.
*/ */
public function store(Request $request): \Illuminate\Http\RedirectResponse public function store(Request $request): RedirectResponse
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|max:30', 'name' => 'required|max:30',

View File

@@ -4,15 +4,17 @@ namespace App\Http\Controllers;
use App\Helpers\CacheHelper; use App\Helpers\CacheHelper;
use App\Models\Episode; use App\Models\Episode;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class HomeController extends Controller class HomeController extends Controller
{ {
/** /**
* Display Home Page. * Display Home Page.
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
$guest = Auth::guest(); $guest = Auth::guest();
@@ -45,7 +47,7 @@ class HomeController extends Controller
/** /**
* Display Banned Page. * Display Banned Page.
*/ */
public function banned(): \Illuminate\View\View public function banned(): View
{ {
return view('auth.banned'); return view('auth.banned');
} }
@@ -54,7 +56,7 @@ class HomeController extends Controller
* Redirects to a random Hentai episode * Redirects to a random Hentai episode
* Done due to performance reasons * Done due to performance reasons
*/ */
public function random(): \Illuminate\Http\RedirectResponse public function random(): RedirectResponse
{ {
$random = Episode::inRandomOrder() $random = Episode::inRandomOrder()
->limit(1) ->limit(1)
@@ -69,7 +71,7 @@ class HomeController extends Controller
/** /**
* Display Search Page. * Display Search Page.
*/ */
public function search(): \Illuminate\View\View public function search(): View
{ {
return view('search.index'); return view('search.index');
} }
@@ -77,7 +79,7 @@ class HomeController extends Controller
/** /**
* Display Download Search Page. * Display Download Search Page.
*/ */
public function downloadSearch(): \Illuminate\View\View public function downloadSearch(): View
{ {
return view('search.download'); return view('search.download');
} }
@@ -85,7 +87,7 @@ class HomeController extends Controller
/** /**
* Redirect POST Data to GET with Query String. * Redirect POST Data to GET with Query String.
*/ */
public function searchRedirect(Request $request): \Illuminate\Http\RedirectResponse public function searchRedirect(Request $request): RedirectResponse
{ {
return redirect()->route('hentai.search', [ return redirect()->route('hentai.search', [
'search' => $request->input('live-search'), 'search' => $request->input('live-search'),
@@ -95,7 +97,7 @@ class HomeController extends Controller
/** /**
* Display Stats Page. * Display Stats Page.
*/ */
public function stats(): \Illuminate\View\View public function stats(): View
{ {
return view('home.stats', [ return view('home.stats', [
'viewCount' => CacheHelper::getTotalViewCount(), 'viewCount' => CacheHelper::getTotalViewCount(),
@@ -107,7 +109,7 @@ class HomeController extends Controller
/** /**
* Manually set website language * Manually set website language
*/ */
public function updateLanguage(Request $request): \Illuminate\Http\RedirectResponse public function updateLanguage(Request $request): RedirectResponse
{ {
abort_unless(in_array($request->language, config('app.supported_locales'), true), 404); abort_unless(in_array($request->language, config('app.supported_locales'), true), 404);

View File

@@ -5,13 +5,14 @@ namespace App\Http\Controllers;
use App\Http\Requests\MatrixRegisterRequest; use App\Http\Requests\MatrixRegisterRequest;
use App\Services\MatrixRegistrationService; use App\Services\MatrixRegistrationService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class MatrixController extends Controller class MatrixController extends Controller
{ {
/** /**
* Display the user page. * Display the user page.
*/ */
public function index(Request $request): \Illuminate\View\View public function index(Request $request): View
{ {
$rooms = [ $rooms = [
['name' => '🏠 General', 'description' => 'Our main chat.', 'alias' => 'https://matrix.to/#/#general:hstream.moe'], ['name' => '🏠 General', 'description' => 'Our main chat.', 'alias' => 'https://matrix.to/#/#general:hstream.moe'],

View File

@@ -2,14 +2,16 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class NotificationController extends Controller class NotificationController extends Controller
{ {
/** /**
* Display the user's notification page. * Display the user's notification page.
*/ */
public function index(Request $request): \Illuminate\View\View public function index(Request $request): View
{ {
return view('profile.notifications', [ return view('profile.notifications', [
'user' => $request->user(), 'user' => $request->user(),
@@ -20,7 +22,7 @@ class NotificationController extends Controller
/** /**
* Delete Notifcation * Delete Notifcation
*/ */
public function delete(Request $request): \Illuminate\Http\RedirectResponse public function delete(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
'id' => 'required|exists:notifications,id', 'id' => 'required|exists:notifications,id',

View File

@@ -6,7 +6,10 @@ use App\Models\Episode;
use App\Models\Playlist; use App\Models\Playlist;
use App\Models\PlaylistEpisode; use App\Models\PlaylistEpisode;
use App\Services\PlaylistService; use App\Services\PlaylistService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View;
class PlaylistController extends Controller class PlaylistController extends Controller
{ {
@@ -20,7 +23,7 @@ class PlaylistController extends Controller
/** /**
* Display the public playlists page. * Display the public playlists page.
*/ */
public function index(): \Illuminate\View\View public function index(): View
{ {
return view('playlist.index'); return view('playlist.index');
} }
@@ -28,7 +31,7 @@ class PlaylistController extends Controller
/** /**
* Display public playlist. * Display public playlist.
*/ */
public function show($playlist_id): \Illuminate\View\View public function show($playlist_id): View
{ {
if (! is_numeric($playlist_id)) { if (! is_numeric($playlist_id)) {
abort(404); abort(404);
@@ -44,7 +47,7 @@ class PlaylistController extends Controller
/** /**
* Display the user's playlists page. * Display the user's playlists page.
*/ */
public function playlists(Request $request): \Illuminate\View\View public function playlists(Request $request): View
{ {
$title = 'Delete Playlist!'; $title = 'Delete Playlist!';
$text = 'Are you sure you want to delete?'; $text = 'Are you sure you want to delete?';
@@ -59,7 +62,7 @@ class PlaylistController extends Controller
/** /**
* Display user's playlist. * Display user's playlist.
*/ */
public function showPlaylist(Request $request, $playlist_id): \Illuminate\View\View public function showPlaylist(Request $request, $playlist_id): View
{ {
if (! is_numeric($playlist_id)) { if (! is_numeric($playlist_id)) {
abort(404); abort(404);
@@ -77,7 +80,7 @@ class PlaylistController extends Controller
/** /**
* Create user playlist (Form). * Create user playlist (Form).
*/ */
public function createPlaylist(Request $request): \Illuminate\Http\RedirectResponse public function createPlaylist(Request $request): RedirectResponse
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|max:30', 'name' => 'required|max:30',
@@ -95,7 +98,7 @@ class PlaylistController extends Controller
/** /**
* Delete user playlist. * Delete user playlist.
*/ */
public function deletePlaylist(Request $request, $playlist_id): \Illuminate\Http\RedirectResponse public function deletePlaylist(Request $request, $playlist_id): RedirectResponse
{ {
if (! is_numeric($playlist_id)) { if (! is_numeric($playlist_id)) {
abort(404); abort(404);
@@ -115,7 +118,7 @@ class PlaylistController extends Controller
/** /**
* Delete episode from playlist. * Delete episode from playlist.
*/ */
public function deleteEpisodeFromPlaylist(Request $request): \Illuminate\Http\JsonResponse public function deleteEpisodeFromPlaylist(Request $request): JsonResponse
{ {
if (! is_numeric($request->input('playlist')) || ! is_numeric($request->input('episode'))) { if (! is_numeric($request->input('playlist')) || ! is_numeric($request->input('episode'))) {
return response()->json([ return response()->json([
@@ -143,7 +146,7 @@ class PlaylistController extends Controller
/** /**
* Add to user playlist (API). * Add to user playlist (API).
*/ */
public function addPlaylistApi(Request $request): \Illuminate\Http\JsonResponse public function addPlaylistApi(Request $request): JsonResponse
{ {
$user = $request->user(); $user = $request->user();
@@ -180,7 +183,7 @@ class PlaylistController extends Controller
/** /**
* Create user playlist (API). * Create user playlist (API).
*/ */
public function createPlaylistApi(Request $request): \Illuminate\Http\JsonResponse public function createPlaylistApi(Request $request): JsonResponse
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|max:30', 'name' => 'required|max:30',

View File

@@ -8,6 +8,7 @@ use App\Models\User;
use Conner\Tagging\Model\Tag; use Conner\Tagging\Model\Tag;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Redirect;
@@ -138,7 +139,7 @@ class ProfileController extends Controller
/** /**
* Delete the user's account. * Delete the user's account.
*/ */
public function destroy(Request $request): \Illuminate\Http\RedirectResponse public function destroy(Request $request): RedirectResponse
{ {
$user = $request->user(); $user = $request->user();
@@ -173,7 +174,7 @@ class ProfileController extends Controller
/** /**
* Store custom user avatar. * Store custom user avatar.
*/ */
protected function storeAvatar(\Illuminate\Http\UploadedFile $file, User $user): void protected function storeAvatar(UploadedFile $file, User $user): void
{ {
// Create Folder for Image Upload // Create Folder for Image Upload
if (! Storage::disk('public')->exists('/images/avatars')) { if (! Storage::disk('public')->exists('/images/avatars')) {

View File

@@ -13,13 +13,14 @@ use hisorange\BrowserDetect\Facade as Browser;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class StreamController extends Controller class StreamController extends Controller
{ {
/** /**
* Display Stream Page. * Display Stream Page.
*/ */
public function index(Request $request, string $title): \Illuminate\View\View public function index(Request $request, string $title): View
{ {
$titleParts = explode('-', $title); $titleParts = explode('-', $title);
if (! is_numeric($titleParts[array_key_last($titleParts)])) { if (! is_numeric($titleParts[array_key_last($titleParts)])) {

View File

@@ -2,7 +2,34 @@
namespace App\Http; namespace App\Http;
use App\Http\Middleware\Authenticate;
use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\IsAdmin;
use App\Http\Middleware\IsBanned;
use App\Http\Middleware\IsModerator;
use App\Http\Middleware\PreventRequestsDuringMaintenance;
use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\SetLocale;
use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\ValidateSignature;
use App\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
use Illuminate\Auth\Middleware\RequirePassword;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Http\Middleware\HandleCors;
use Illuminate\Http\Middleware\SetCacheHeaders;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class Kernel extends HttpKernel class Kernel extends HttpKernel
{ {
@@ -15,12 +42,12 @@ class Kernel extends HttpKernel
*/ */
protected $middleware = [ protected $middleware = [
// \App\Http\Middleware\TrustHosts::class, // \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class, TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class, HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class, PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class, TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ConvertEmptyStringsToNull::class,
]; ];
/** /**
@@ -30,20 +57,20 @@ class Kernel extends HttpKernel
*/ */
protected $middlewareGroups = [ protected $middlewareGroups = [
'web' => [ 'web' => [
\App\Http\Middleware\EncryptCookies::class, EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class, ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, SubstituteBindings::class,
\App\Http\Middleware\IsBanned::class, IsBanned::class,
\App\Http\Middleware\SetLocale::class, SetLocale::class,
], ],
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api', ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, SubstituteBindings::class,
], ],
]; ];
@@ -55,18 +82,18 @@ class Kernel extends HttpKernel
* @var array<string, class-string|string> * @var array<string, class-string|string>
*/ */
protected $middlewareAliases = [ protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class, 'auth' => Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 'auth.session' => AuthenticateSession::class,
'auth.admin' => \App\Http\Middleware\IsAdmin::class, 'auth.admin' => IsAdmin::class,
'auth.moderator' => \App\Http\Middleware\IsModerator::class, 'auth.moderator' => IsModerator::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class, 'can' => Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'guest' => RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'password.confirm' => RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, 'precognitive' => HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class, 'signed' => ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'verified' => EnsureEmailIsVerified::class,
]; ];
} }

View File

@@ -13,7 +13,7 @@ class IsModerator
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next * @param Closure(Request): (Response) $next
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {

View File

@@ -13,7 +13,7 @@ class RedirectIfAuthenticated
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next * @param Closure(Request): (Response) $next
*/ */
public function handle(Request $request, Closure $next, string ...$guards): Response public function handle(Request $request, Closure $next, string ...$guards): Response
{ {

View File

@@ -13,7 +13,7 @@ class SetLocale
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next * @param Closure(Request): (Response) $next
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {

View File

@@ -4,6 +4,7 @@ namespace App\Http\Requests\Auth;
use App\Rules\ValidCaptcha; use App\Rules\ValidCaptcha;
use Illuminate\Auth\Events\Lockout; use Illuminate\Auth\Events\Lockout;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
@@ -23,7 +24,7 @@ class LoginRequest extends FormRequest
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> * @return array<string, ValidationRule|array<mixed>|string>
*/ */
public function rules(): array public function rules(): array
{ {
@@ -37,7 +38,7 @@ class LoginRequest extends FormRequest
/** /**
* Attempt to authenticate the request's credentials. * Attempt to authenticate the request's credentials.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function authenticate(): void public function authenticate(): void
{ {
@@ -57,7 +58,7 @@ class LoginRequest extends FormRequest
/** /**
* Ensure the login request is not rate limited. * Ensure the login request is not rate limited.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function ensureIsNotRateLimited(): void public function ensureIsNotRateLimited(): void
{ {

View File

@@ -2,6 +2,7 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class MatrixRegisterRequest extends FormRequest class MatrixRegisterRequest extends FormRequest
@@ -20,7 +21,7 @@ class MatrixRegisterRequest extends FormRequest
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> * @return array<string, ValidationRule|array<mixed>|string>
*/ */
public function rules(): array public function rules(): array
{ {

View File

@@ -3,6 +3,7 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@@ -11,7 +12,7 @@ class ProfileUpdateRequest extends FormRequest
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> * @return array<string, ValidationRule|array<mixed>|string>
*/ */
public function rules(): array public function rules(): array
{ {

View File

@@ -2,6 +2,7 @@
namespace App\Livewire; namespace App\Livewire;
use App\Enums\UserRole;
use App\Models\Downloads; use App\Models\Downloads;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
@@ -74,9 +75,9 @@ class DownloadsSearch extends Component
$types[] = 'FHD'; $types[] = 'FHD';
} elseif ($label === 'FHD 48fps') { } elseif ($label === 'FHD 48fps') {
$types[] = 'FHDi'; $types[] = 'FHDi';
} elseif ($label === 'UHD' && auth()->user()->hasRole(\App\Enums\UserRole::SUPPORTER)) { } elseif ($label === 'UHD' && auth()->user()->hasRole(UserRole::SUPPORTER)) {
$types[] = 'UHD'; $types[] = 'UHD';
} elseif ($label === 'UHD 48fps' && auth()->user()->hasRole(\App\Enums\UserRole::SUPPORTER)) { } elseif ($label === 'UHD 48fps' && auth()->user()->hasRole(UserRole::SUPPORTER)) {
$types[] = 'UHDi'; $types[] = 'UHDi';
} }
} }
@@ -99,7 +100,7 @@ class DownloadsSearch extends Component
public function mount() public function mount()
{ {
if (! auth()->user()->hasRole(\App\Enums\UserRole::SUPPORTER)) { if (! auth()->user()->hasRole(UserRole::SUPPORTER)) {
return; return;
} }

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
class SiteBackground extends Model class SiteBackground extends Model
{ {
@@ -21,7 +22,7 @@ class SiteBackground extends Model
/** /**
* Returns the current IDs of active wallpaper * Returns the current IDs of active wallpaper
*/ */
public function getImages(): ?\Illuminate\Support\Collection public function getImages(): ?Collection
{ {
$now = Carbon::now(); $now = Carbon::now();

View File

@@ -11,10 +11,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys;
use Spatie\LaravelPasskeys\Models\Concerns\InteractsWithPasskeys;
class User extends Authenticatable class User extends Authenticatable implements HasPasskeys
{ {
use HasFactory, Notifiable; use HasFactory, InteractsWithPasskeys, Notifiable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.

View File

@@ -4,6 +4,8 @@ namespace App\Providers;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use SocialiteProviders\Discord\Provider;
use SocialiteProviders\Manager\SocialiteWasCalled;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@@ -20,8 +22,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { Event::listen(function (SocialiteWasCalled $event) {
$event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class); $event->extendSocialite('discord', Provider::class);
}); });
} }
} }

View File

@@ -11,6 +11,7 @@ use AltchaOrg\Altcha\Solution;
use AltchaOrg\Altcha\VerifySolutionOptions; use AltchaOrg\Altcha\VerifySolutionOptions;
use Closure; use Closure;
use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;
/** /**
* Validation rule to verify captcha solution. * Validation rule to verify captcha solution.
@@ -97,7 +98,7 @@ class ValidCaptcha implements ValidationRule
/** /**
* Run the validation rule. * Run the validation rule.
* *
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail * @param Closure(string, ?string=): PotentiallyTranslatedString $fail
*/ */
public function validate(string $attribute, mixed $value, Closure $fail): void public function validate(string $attribute, mixed $value, Closure $fail): void
{ {

View File

@@ -1,5 +1,10 @@
<?php <?php
use App\Exceptions\Handler;
use App\Http\Kernel;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Foundation\Application;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Create The Application | Create The Application
@@ -11,7 +16,7 @@
| |
*/ */
$app = new Illuminate\Foundation\Application( $app = new Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
); );
@@ -28,7 +33,7 @@ $app = new Illuminate\Foundation\Application(
$app->singleton( $app->singleton(
Illuminate\Contracts\Http\Kernel::class, Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class Kernel::class
); );
$app->singleton( $app->singleton(
@@ -37,8 +42,8 @@ $app->singleton(
); );
$app->singleton( $app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class, ExceptionHandler::class,
App\Exceptions\Handler::class Handler::class
); );
/* /*

View File

@@ -28,6 +28,7 @@
"rtconner/laravel-tagging": "^5.0", "rtconner/laravel-tagging": "^5.0",
"socialiteproviders/discord": "^4.2", "socialiteproviders/discord": "^4.2",
"spatie/laravel-discord-alerts": "^1.8", "spatie/laravel-discord-alerts": "^1.8",
"spatie/laravel-passkeys": "^1.7",
"spatie/laravel-sitemap": "^7.3" "spatie/laravel-sitemap": "^7.3"
}, },
"require-dev": { "require-dev": {

2325
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,12 @@
<?php <?php
use App\Providers\AppServiceProvider;
use App\Providers\AuthServiceProvider;
use App\Providers\EventServiceProvider;
use App\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Mews\Captcha\Facades\Captcha;
return [ return [
@@ -175,11 +180,11 @@ return [
/* /*
* Application Service Providers... * Application Service Providers...
*/ */
App\Providers\AppServiceProvider::class, AppServiceProvider::class,
App\Providers\AuthServiceProvider::class, AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class, // App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class, EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, RouteServiceProvider::class,
])->toArray(), ])->toArray(),
/* /*
@@ -195,7 +200,7 @@ return [
'aliases' => Facade::defaultAliases()->merge([ 'aliases' => Facade::defaultAliases()->merge([
// 'Example' => App\Facades\Example::class, // 'Example' => App\Facades\Example::class,
'Captcha' => Mews\Captcha\Facades\Captcha::class, 'Captcha' => Captcha::class,
])->toArray(), ])->toArray(),
]; ];

View File

@@ -1,5 +1,7 @@
<?php <?php
use App\Models\User;
return [ return [
/* /*
@@ -62,7 +64,7 @@ return [
'providers' => [ 'providers' => [
'users' => [ 'users' => [
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => App\Models\User::class, 'model' => User::class,
], ],
// 'users' => [ // 'users' => [

View File

@@ -1,5 +1,7 @@
<?php <?php
use Spatie\DiscordAlerts\Jobs\SendToDiscordChannelJob;
return [ return [
/* /*
* The webhook URLs that we'll use to send a message to Discord. * The webhook URLs that we'll use to send a message to Discord.
@@ -14,5 +16,5 @@ return [
* This job will send the message to Discord. You can extend this * This job will send the message to Discord. You can extend this
* job to set timeouts, retries, etc... * job to set timeouts, retries, etc...
*/ */
'job' => Spatie\DiscordAlerts\Jobs\SendToDiscordChannelJob::class, 'job' => SendToDiscordChannelJob::class,
]; ];

View File

@@ -1,5 +1,7 @@
<?php <?php
use Intervention\Image\Drivers\Gd\Driver;
return [ return [
/* /*
@@ -16,7 +18,7 @@ return [
| |
*/ */
'driver' => \Intervention\Image\Drivers\Gd\Driver::class, 'driver' => Driver::class,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

49
config/passkeys.php Normal file
View File

@@ -0,0 +1,49 @@
<?php
use App\Models\User;
use Spatie\LaravelPasskeys\Actions\ConfigureCeremonyStepManagerFactoryAction;
use Spatie\LaravelPasskeys\Actions\FindPasskeyToAuthenticateAction;
use Spatie\LaravelPasskeys\Actions\GeneratePasskeyAuthenticationOptionsAction;
use Spatie\LaravelPasskeys\Actions\GeneratePasskeyRegisterOptionsAction;
use Spatie\LaravelPasskeys\Actions\StorePasskeyAction;
use Spatie\LaravelPasskeys\Models\Passkey;
return [
/*
* After a successful authentication attempt using a passkey
* we'll redirect to this URL.
*/
'redirect_to_after_login' => '/',
/*
* These class are responsible for performing core tasks regarding passkeys.
* You can customize them by creating a class that extends the default, and
* by specifying your custom class name here.
*/
'actions' => [
'generate_passkey_register_options' => GeneratePasskeyRegisterOptionsAction::class,
'store_passkey' => StorePasskeyAction::class,
'generate_passkey_authentication_options' => GeneratePasskeyAuthenticationOptionsAction::class,
'find_passkey' => FindPasskeyToAuthenticateAction::class,
'configure_ceremony_step_manager_factory' => ConfigureCeremonyStepManagerFactoryAction::class,
],
/*
* These properties will be used to generate the passkey.
*/
'relying_party' => [
'name' => config('app.name'),
'id' => parse_url(config('app.url'), PHP_URL_HOST),
'icon' => null,
],
/*
* The models used by the package.
*
* You can override this by specifying your own models
*/
'models' => [
'passkey' => Passkey::class,
'authenticatable' => env('AUTH_MODEL', User::class),
],
];

View File

@@ -1,5 +1,8 @@
<?php <?php
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
use Laravel\Sanctum\Sanctum; use Laravel\Sanctum\Sanctum;
return [ return [
@@ -60,9 +63,9 @@ return [
*/ */
'middleware' => [ 'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 'authenticate_session' => AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, 'encrypt_cookies' => EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, 'validate_csrf_token' => ValidateCsrfToken::class,
], ],
]; ];

View File

@@ -2,10 +2,11 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\Episode;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Episode> * @extends Factory<Episode>
*/ */
class EpisodeFactory extends Factory class EpisodeFactory extends Factory
{ {

View File

@@ -2,10 +2,11 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\Hentai;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Hentai> * @extends Factory<Hentai>
*/ */
class HentaiFactory extends Factory class HentaiFactory extends Factory
{ {

View File

@@ -2,10 +2,11 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\Studios;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Studios> * @extends Factory<Studios>
*/ */
class StudiosFactory extends Factory class StudiosFactory extends Factory
{ {

View File

@@ -2,11 +2,12 @@
namespace Database\Factories; namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> * @extends Factory<User>
*/ */
class UserFactory extends Factory class UserFactory extends Factory
{ {

View File

@@ -50,7 +50,7 @@ return new class extends Migration
$alreadyexists = Episode::where('slug', $episode->slug)->first(); $alreadyexists = Episode::where('slug', $episode->slug)->first();
if ($alreadyexists) { if ($alreadyexists) {
throw new \RuntimeException('Migration stopped! Slug already exists: '.$episode->slug); throw new RuntimeException('Migration stopped! Slug already exists: '.$episode->slug);
} }
$episode->save(); $episode->save();

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Spatie\LaravelPasskeys\Support\Config;
return new class extends Migration
{
public function up()
{
$authenticatableClass = Config::getAuthenticatableModel();
$authenticatableTableName = (new $authenticatableClass)->getTable();
Schema::create('passkeys', function (Blueprint $table) use ($authenticatableTableName, $authenticatableClass) {
$table->id();
$table
->foreignIdFor($authenticatableClass, 'authenticatable_id')
->constrained(table: $authenticatableTableName, indexName: 'passkeys_authenticatable_fk')
->cascadeOnDelete();
$table->text('name');
$table->text('credential_id');
$table->json('data');
$table->timestamp('last_used_at')->nullable();
$table->timestamps();
});
}
};

7
package-lock.json generated
View File

@@ -7,6 +7,7 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@jellyfin/libass-wasm": "^4.1.1", "@jellyfin/libass-wasm": "^4.1.1",
"@simplewebauthn/browser": "^13.3.0",
"@yaireo/tagify": "^4.21.2", "@yaireo/tagify": "^4.21.2",
"altcha": "^3.0.0", "altcha": "^3.0.0",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
@@ -998,6 +999,12 @@
"win32" "win32"
] ]
}, },
"node_modules/@simplewebauthn/browser": {
"version": "13.3.0",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz",
"integrity": "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==",
"license": "MIT"
},
"node_modules/@svta/cml-608": { "node_modules/@svta/cml-608": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@svta/cml-608/-/cml-608-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@svta/cml-608/-/cml-608-1.0.1.tgz",

View File

@@ -19,6 +19,7 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@jellyfin/libass-wasm": "^4.1.1", "@jellyfin/libass-wasm": "^4.1.1",
"@simplewebauthn/browser": "^13.3.0",
"@yaireo/tagify": "^4.21.2", "@yaireo/tagify": "^4.21.2",
"altcha": "^3.0.0", "altcha": "^3.0.0",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",

View File

@@ -23,3 +23,16 @@ import "altcha/themes/cupcake.css";
// Alpine.start(); // Alpine.start();
initTE({ Collapse, Carousel, Clipboard, Modal, Tab, Lightbox, Tooltip, Ripple }); initTE({ Collapse, Carousel, Clipboard, Modal, Tab, Lightbox, Tooltip, Ripple });
/**
* Passkey Support
*/
import {
browserSupportsWebAuthn,
startAuthentication,
startRegistration,
} from '@simplewebauthn/browser'
window.browserSupportsWebAuthn = browserSupportsWebAuthn;
window.startAuthentication = startAuthentication;
window.startRegistration = startRegistration;

View File

@@ -31,7 +31,18 @@
<!-- Or --> <!-- Or -->
<div class="grid grid-cols-3"> <div class="grid grid-cols-3">
<hr class="self-center border-neutral-600"> <hr class="self-center border-neutral-600">
<p>OR</p> <p class="text-neutral-800 dark:text-neutral-400">OR</p>
<hr class="self-center border-neutral-600">
</div>
</div>
<!-- Passkey Login -->
<div class="w-full text-center text-white mb-3">
<x-authenticate-passkey />
<div class="grid grid-cols-3 pt-3">
<hr class="self-center border-neutral-600">
<p class="text-neutral-800 dark:text-neutral-400">OR</p>
<hr class="self-center border-neutral-600"> <hr class="self-center border-neutral-600">
</div> </div>
</div> </div>

View File

@@ -15,6 +15,9 @@
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg"> <div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
@include('profile.partials.update-profile-information-form') @include('profile.partials.update-profile-information-form')
</div> </div>
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
<livewire:passkeys />
</div>
<div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg"> <div class="p-4 sm:p-8 bg-white/40 dark:bg-neutral-950/40 backdrop-blur shadow sm:rounded-lg">
@include('profile.partials.update-password-form') @include('profile.partials.update-password-form')
</div> </div>

View File

@@ -0,0 +1,23 @@
<div>
@include('passkeys::components.partials.authenticateScript')
<form id="passkey-login-form" method="POST" action="{{ route('passkeys.login') }}">
@csrf
</form>
@if($message = session()->get('authenticatePasskey::message'))
<div class="bg-red-100 text-red-700 p-4 border border-red-400 rounded">
{{ $message }}
</div>
@endif
<div onclick="authenticateWithPasskey()">
@if ($slot->isEmpty())
<div class="bg-zinc-700 hover:bg-zinc-600 text-white font-bold px-4 h-10 rounded text-center p-[10px] cursor-pointer">
<i class="fa-solid fa-key"></i> {{ __('passkeys::passkeys.authenticate_using_passkey') }}
</div>
@else
{{ $slot }}
@endif
</div>
</div>

View File

@@ -0,0 +1,18 @@
<script>
async function authenticateWithPasskey(remember = false) {
const response = await fetch('{{ route('passkeys.authentication_options') }}')
const options = await response.json();
const startAuthenticationResponse = await startAuthentication({ optionsJSON: options, });
const form = document.getElementById('passkey-login-form');
form.addEventListener('formdata', ({formData}) => {
formData.set('remember', remember);
formData.set('start_authentication_response', JSON.stringify(startAuthenticationResponse));
});
form.submit();
}
</script>

View File

@@ -0,0 +1,11 @@
@script
<script>
Livewire.on('passkeyPropertiesValidated', async function (eventData) {
const passkeyOptions = eventData[0].passkeyOptions;
const passkey = await startRegistration({ optionsJSON: passkeyOptions });
@this.call('storePasskey', JSON.stringify(passkey));
});
</script>
@endscript

View File

@@ -0,0 +1,44 @@
<section>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('passkeys::passkeys.passkeys') }}
</h2>
<div class="mt-2">
<label for="name" class="block mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __('passkeys::passkeys.name') }}
</label>
<form id="passkeyForm" wire:submit="validatePasskeyProperties" class="flex items-center space-x-2 mt-2">
<div>
<input autocomplete="off" type="text" wire:model="name" class="border-gray-300 dark:border-gray-700 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600 rounded-md shadow-sm">
@error('name')
<span class="text-red-500 text-sm">{{ $message }}</span>
@enderror
</div>
<button type="submit" class="inline-flex items-center px-4 py-2 bg-rose-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-rose-700 active:bg-rose-900 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150">
{{ __('passkeys::passkeys.create') }}
</button>
</form>
</div>
<div class="mt-6">
<ul class="space-y-4">
@foreach($passkeys as $passkey)
<li class="flex justify-between items-center p-4 bg-white border-neutral-200 dark:bg-neutral-900 dark:border-neutral-700 border rounded-lg shadow-sm">
<div class="text-gray-900 dark:text-gray-100">
{{ $passkey->name }}
</div>
<div class="ml-2 text-gray-900 dark:text-gray-100">
{{ __('passkeys::passkeys.last_used') }}: {{ $passkey->last_used_at?->diffForHumans() ?? __('passkeys::passkeys.not_used_yet') }}
</div>
<div>
<button wire:click="deletePasskey({{ $passkey->id }})" class="inline-flex items-center px-4 py-2 bg-rose-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-rose-700 active:bg-rose-900 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150">
{{ __('passkeys::passkeys.delete') }}
</button>
</div>
</li>
@endforeach
</ul>
</div>
</section>
@include('passkeys::livewire.partials.createScript')

View File

@@ -2,6 +2,8 @@
use App\Http\Controllers\Api\UserApiController; use App\Http\Controllers\Api\UserApiController;
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use App\Http\Controllers\MatrixController;
use App\Http\Controllers\NotificationController;
use App\Http\Controllers\PlaylistController; use App\Http\Controllers\PlaylistController;
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -13,11 +15,11 @@ use Illuminate\Support\Facades\Route;
*/ */
// Matrix // Matrix
Route::get('/join-matrix', [App\Http\Controllers\MatrixController::class, 'index'])->name('join.matrix'); Route::get('/join-matrix', [MatrixController::class, 'index'])->name('join.matrix');
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {
// Matrix // Matrix
Route::post('/join-matrix', [App\Http\Controllers\MatrixController::class, 'store'])->name('join.matrix.create'); Route::post('/join-matrix', [MatrixController::class, 'store'])->name('join.matrix.create');
Route::get('/user/profile', [ProfileController::class, 'index'])->name('profile.show'); Route::get('/user/profile', [ProfileController::class, 'index'])->name('profile.show');
Route::get('/user/comments', [ProfileController::class, 'comments'])->name('profile.comments'); Route::get('/user/comments', [ProfileController::class, 'comments'])->name('profile.comments');
@@ -25,8 +27,8 @@ Route::middleware('auth')->group(function () {
Route::get('/user/watched', [ProfileController::class, 'watched'])->name('user.watched'); Route::get('/user/watched', [ProfileController::class, 'watched'])->name('user.watched');
// Notifications // Notifications
Route::get('/user/notifications', [App\Http\Controllers\NotificationController::class, 'index'])->name('profile.notifications'); Route::get('/user/notifications', [NotificationController::class, 'index'])->name('profile.notifications');
Route::delete('/user/notifications', [App\Http\Controllers\NotificationController::class, 'delete'])->name('profile.notifications.delete'); Route::delete('/user/notifications', [NotificationController::class, 'delete'])->name('profile.notifications.delete');
// User Profile Actions // User Profile Actions
Route::get('/user/settings', [ProfileController::class, 'settings'])->name('profile.settings'); Route::get('/user/settings', [ProfileController::class, 'settings'])->name('profile.settings');

View File

@@ -45,6 +45,11 @@ Route::post('/get-download', [DownloadApiController::class, 'getDownload']);
Route::post('/update-language', [HomeController::class, 'updateLanguage'])->name('update.language'); Route::post('/update-language', [HomeController::class, 'updateLanguage'])->name('update.language');
/**
* Passkey Routes
*/
Route::passkeys();
require __DIR__.'/user.php'; require __DIR__.'/user.php';
require __DIR__.'/admin.php'; require __DIR__.'/admin.php';
require __DIR__.'/auth.php'; require __DIR__.'/auth.php';

View File

@@ -8,6 +8,7 @@ use App\Models\Hentai;
use App\Models\Studios; use App\Models\Studios;
use App\Services\GalleryService; use App\Services\GalleryService;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Tests\TestCase; use Tests\TestCase;
@@ -38,7 +39,7 @@ class GalleryServiceTest extends TestCase
$file = UploadedFile::fake()->image('test_image.jpg'); $file = UploadedFile::fake()->image('test_image.jpg');
// Create a request with the fake file // Create a request with the fake file
$request = new \Illuminate\Http\Request; $request = new Request;
$request->files->add(['episodegallery1' => [$file]]); $request->files->add(['episodegallery1' => [$file]]);
// Call the method to test // Call the method to test