diff --git a/app/Http/Controllers/Auth/GoogleController.php b/app/Http/Controllers/Auth/GoogleController.php index c2a334d..3bb85d0 100644 --- a/app/Http/Controllers/Auth/GoogleController.php +++ b/app/Http/Controllers/Auth/GoogleController.php @@ -7,38 +7,29 @@ use Illuminate\Http\Request; use Illuminate\Support\Str; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Auth; // Added the Auth facade here +use Illuminate\Support\Facades\Auth; use Laravel\Socialite\Facades\Socialite; class GoogleController extends Controller { - /** - * Redirect the user to Google specifically for Logging In. - */ public function login() { session(['google_auth_intent' => 'login']); return Socialite::driver('google')->redirect(); } - /** - * Redirect the user to Google specifically for Registering. - */ public function register() { session(['google_auth_intent' => 'register']); return Socialite::driver('google')->redirect(); } - /** - * Handle the secure callback from Google. - */ public function callback(Request $request) { try { $googleUser = Socialite::driver('google')->user(); } catch (\Exception $e) { - return redirect('/dashboard/login')->withErrors([ + return redirect()->route('login')->withErrors([ 'email' => 'Google authentication was cancelled or failed. Please try again.', ]); } @@ -49,27 +40,29 @@ public function callback(Request $request) ->orWhere('email', $googleUser->getEmail()) ->first(); - // SCENARIO 1: USER IS TRYING TO LOG IN - if ($intent === 'login') { - if (!$existingUser) { - return redirect('/dashboard/register')->withErrors([ - 'email' => 'No account found with this Google account. Please register first.', + if ($intent === 'register') { + if ($existingUser) { + return redirect()->route('login')->withErrors([ + 'email' => 'An account with this email already exists. Please log in instead.', ]); } - $existingUser->update([ + $newUser = User::create([ + 'name' => $googleUser->getName(), + 'email' => $googleUser->getEmail(), 'google_id' => $googleUser->getId(), 'avatar' => $googleUser->getAvatar(), + 'password' => Hash::make(Str::random(32)), ]); - Auth::login($existingUser, true); // Fixed red line here - return redirect('/dashboard'); + Auth::login($newUser, true); + + return redirect()->route('dashboard'); } - // SCENARIO 2: USER IS TRYING TO REGISTER if ($intent === 'register') { if ($existingUser) { - return redirect('/dashboard/login')->withErrors([ + return redirect()->route('login')->withErrors([ 'email' => 'An account with this email already exists. Please log in instead.', ]); } @@ -83,10 +76,11 @@ public function callback(Request $request) 'api_token' => Str::random(60), ]); - Auth::login($newUser, true); // Fixed red line here - return redirect('/dashboard'); + Auth::login($newUser, true); + + return redirect()->route('dashboard'); } - return redirect('/dashboard/login'); + return redirect()->route('login'); } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 7d9045f..6bc22f8 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,44 +5,29 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { - use HasFactory, Notifiable; + use HasApiTokens, HasFactory, Notifiable; - /** - * The attributes that are mass assignable. - * - * @var list - */ protected $fillable = [ 'name', 'email', 'password', - 'google_id', // Required for Google OAuth - 'avatar', // Required for Google OAuth - 'api_token', // Allowed based on migration + 'google_id', + 'avatar', ]; - /** - * The attributes that should be hidden for serialization. - * - * @var list - */ protected $hidden = [ 'password', 'remember_token', - 'api_token', // Good practice: Hide API tokens from array/JSON serialization ]; - /** - * Get the attributes that should be cast. - * - * @return array - */ protected function casts(): array { return [ + 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } diff --git a/app/Support/Csp/CustomPolicy.php b/app/Support/Csp/CustomPolicy.php new file mode 100644 index 0000000..d77c4b3 --- /dev/null +++ b/app/Support/Csp/CustomPolicy.php @@ -0,0 +1,37 @@ +add(Directive::IMG, [ + Keyword::SELF, + 'res.cloudinary.com', + '*.googleusercontent.com', + 'data:', + ]) + ->add(Directive::STYLE, [ + Keyword::SELF, + Keyword::UNSAFE_INLINE, + 'fonts.googleapis.com', + ]) + ->add(Directive::FONT, [ + Keyword::SELF, + 'fonts.gstatic.com', + 'data:', + ]) + ->add(Directive::SCRIPT, [ + Keyword::SELF, + Keyword::UNSAFE_INLINE, + Keyword::UNSAFE_EVAL, + ]); + } +} \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index 619c984..4d19aac 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,8 +11,8 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware) { - // This line is CRITICAL for Render to allow file uploads $middleware->trustProxies(at: '*'); + $middleware->append(\Spatie\Csp\AddCspHeaders::class); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/composer.json b/composer.json index c3e7793..013d631 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "livewire/livewire": "^4.2", "resend/resend-laravel": "^1.3", "smalot/pdfparser": "^2.12", + "spatie/laravel-csp": "^3.23", "spatie/laravel-sitemap": "^8.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 96ddddf..3f95168 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ae974665f22a831a16151b7f312fed95", + "content-hash": "1c57a617758a125f18ea0afcfa55999d", "packages": [ { "name": "blade-ui-kit/blade-heroicons", @@ -4478,6 +4478,91 @@ ], "time": "2026-03-20T08:54:01+00:00" }, + { + "name": "spatie/laravel-csp", + "version": "3.23.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-csp.git", + "reference": "1c04d35fddead75d594046bdcab48653556f940f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-csp/zipball/1c04d35fddead75d594046bdcab48653556f940f", + "reference": "1c04d35fddead75d594046bdcab48653556f940f", + "shasum": "" + }, + "require": { + "illuminate/http": "^11.36.1|^12.0|^13.0", + "illuminate/support": "^11.36.1|^12.0|^13.0", + "php": "^8.3", + "spatie/laravel-package-tools": "^1.17" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.9|^10.0|^11.0", + "pestphp/pest": "^3.0|^4.0", + "roave/security-advisories": "dev-master" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Csp\\CspServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Csp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Thomas Verhelst", + "email": "tvke91@gmail.com", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Add CSP headers to the responses of a Laravel app", + "homepage": "https://github.com/spatie/laravel-csp", + "keywords": [ + "content-security-policy", + "csp", + "headers", + "laravel", + "laravel-csp", + "security", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-csp/tree/3.23.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2026-02-21T14:39:15+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.93.0", diff --git a/config/csp.php b/config/csp.php new file mode 100644 index 0000000..6239a3d --- /dev/null +++ b/config/csp.php @@ -0,0 +1,64 @@ + [ + Spatie\Csp\Presets\Basic::class, + App\Support\Csp\CustomPolicy::class, + ], + + /** + * Register additional global CSP directives here. + */ + 'directives' => [ + // [Directive::SCRIPT, [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE]], + ], + + /* + * These presets which will be put in a report-only policy. This is great for testing out + * a new policy or changes to existing CSP policy without breaking anything. + */ + 'report_only_presets' => [ + // + ], + + /** + * Register additional global report-only CSP directives here. + */ + 'report_only_directives' => [ + // [Directive::SCRIPT, [Keyword::UNSAFE_EVAL, Keyword::UNSAFE_INLINE]], + ], + + /* + * All violations against a policy will be reported to this url. + * A great service you could use for this is https://report-uri.com/ + */ + 'report_uri' => env('CSP_REPORT_URI', ''), + + /* + * Headers will only be added if this setting is set to true. + */ + 'enabled' => env('CSP_ENABLED', true), + + /** + * Headers will be added when Vite is hot reloading. + */ + 'enabled_while_hot_reloading' => env('CSP_ENABLED_WHILE_HOT_RELOADING', false), + + /* + * The class responsible for generating the nonces used in inline tags and headers. + */ + 'nonce_generator' => Spatie\Csp\Nonce\RandomString::class, + + /* + * Set false to disable automatic nonce generation and handling. + * This is useful when you want to use 'unsafe-inline' for scripts/styles + * and cannot add inline nonces. + * Note that this will make your CSP policy less secure. + */ + 'nonce_enabled' => env('CSP_NONCE_ENABLED', true), +]; diff --git a/config/session.php b/config/session.php index f574482..086809b 100644 --- a/config/session.php +++ b/config/session.php @@ -169,7 +169,7 @@ | */ - 'secure' => env('SESSION_SECURE_COOKIE'), + 'secure' => env('SESSION_SECURE_COOKIE', env('APP_ENV') !== 'local'), /* |-------------------------------------------------------------------------- @@ -230,4 +230,4 @@ 'serialization' => 'json', -]; +]; \ No newline at end of file diff --git a/database/migrations/2026_04_07_051047_drop_api_token_from_users_table.php b/database/migrations/2026_04_07_051047_drop_api_token_from_users_table.php new file mode 100644 index 0000000..b225507 --- /dev/null +++ b/database/migrations/2026_04_07_051047_drop_api_token_from_users_table.php @@ -0,0 +1,24 @@ +dropUnique(['api_token']); + $table->dropColumn('api_token'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->string('api_token', 80)->unique()->nullable()->default(null); + }); + } +}; \ No newline at end of file diff --git a/routes/auth.php b/routes/auth.php index 6f509d0..61ccebe 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -16,7 +16,8 @@ ->name('google.register'); Route::get('auth/google/callback', [GoogleController::class, 'callback']) - ->name('google.callback'); + ->name('google.callback') + ->middleware('throttle:6,1'); Route::get('register', [RegisteredUserController::class, 'create']) ->name('register'); @@ -39,4 +40,4 @@ Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) ->name('logout'); -}); +}); \ No newline at end of file