Skip to content

Commit

Permalink
cmt
Browse files Browse the repository at this point in the history
  • Loading branch information
Gianguyen1234 committed Nov 22, 2024
1 parent 2ecbbef commit f6e60e4
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 36 deletions.
12 changes: 12 additions & 0 deletions app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use Illuminate\Http\Exceptions\ThrottleRequestsException;

class Handler extends ExceptionHandler
{
Expand All @@ -27,4 +28,15 @@ public function register(): void
//
});
}
public function render($request, Throwable $exception)
{
if ($exception instanceof ThrottleRequestsException) {
return response()->json([
'message' => 'You have made too many requests. Please try again later.',
'error_code' => 429
], 429);
}

return parent::render($request, $exception);
}
}
43 changes: 40 additions & 3 deletions app/Http/Controllers/Auth/AuthenticatedSessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;


class AuthenticatedSessionController extends Controller
{
Expand All @@ -25,11 +28,45 @@ public function create(): View
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
// Validate form inputs and reCAPTCHA response
$request->validate([
'email' => 'required|email',
'password' => 'required',
'g-recaptcha-response' => 'required',
]);

// Verify reCAPTCHA response with Google's API
$recaptchaResponse = $request->input('g-recaptcha-response');
$recaptchaSecret = env('RECAPTCHA_SECRET_KEY');

// Log the reCAPTCHA response for debugging purposes
Log::info('reCAPTCHA Response:', ['g-recaptcha-response' => $recaptchaResponse]);

// Sending request to Google reCAPTCHA API for verification
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => $recaptchaSecret,
'response' => $recaptchaResponse,
'remoteip' => $request->ip(),
]);

// Log the reCAPTCHA verification response
Log::info('reCAPTCHA Verification Response:', $response->json());

// Check if the verification was successful
if (!$response->json('success')) {
return back()->withErrors(['g-recaptcha-response' => 'Captcha verification failed.']);
}

$request->session()->regenerate();
// Attempt to authenticate the user
if (Auth::attempt($request->only('email', 'password'))) {
$request->session()->regenerate(); // Regenerate session for security purposes
return redirect()->intended(RouteServiceProvider::HOME); // Redirect to the intended page after login
}

return redirect()->intended(RouteServiceProvider::HOME);
// If authentication fails
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
}

/**
Expand Down
49 changes: 40 additions & 9 deletions app/Http/Controllers/CommentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use App\Models\Post;
use App\Models\Comment;
use Mews\Purifier\Facades\Purifier;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\RateLimiter;

class CommentController extends Controller
{
Expand Down Expand Up @@ -36,29 +38,58 @@ public function show($slug, $id)

public function store(Request $request, $slug)
{
// Define a unique rate limit key (use user ID if logged in, otherwise fallback to IP)
$key = $request->user()
? 'comment|user:' . $request->user()->id
: 'comment|ip:' . $request->ip();

// Check if the user/IP is rate-limited
if (!RateLimiter::attempt($key, 1, function () {
return true;
}, 300)) { // 300 seconds = 5 minutes
return redirect()->back()->withErrors(['error' => 'You can only post one comment every 5 minutes. Please wait and try again.']);
}

// Fetch the post by its slug
$post = Post::where('slug', $slug)->firstOrFail();

// Validate the request
// Validate the incoming request
$request->validate([
'author_name' => 'required|string|max:255',
'content' => 'required|string|max:1000',
'parent_id' => 'nullable|exists:comments,id', // Validate the parent comment (if any)
'parent_id' => 'nullable|exists:comments,id', // Validate parent comment
'g-recaptcha-response' => 'required|string', // reCAPTCHA validation
]);

// Sanitize the content using Mew Purifier
// Verify the reCAPTCHA response with Google's API
$recaptchaResponse = $request->input('g-recaptcha-response');
$recaptchaSecret = env('RECAPTCHA_SECRET_KEY');

$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => $recaptchaSecret,
'response' => $recaptchaResponse,
'remoteip' => $request->ip(),
]);

if (!$response->json('success')) {
return redirect()->back()->withErrors(['g-recaptcha-response' => 'Captcha verification failed.'])->withInput();
}

// Sanitize the comment content using HTML Purifier
$sanitizedContent = Purifier::clean($request->content);
// Check if sanitized content is empty

if (empty(trim($sanitizedContent))) {
return redirect()->back()->withErrors(['content' => 'Your comment does not contain valid content.']);
return redirect()->back()->withErrors(['content' => 'Your comment does not contain valid content.'])->withInput();
}

// Create the comment or reply
// Create the comment or reply (escape author name and assign sanitized content)
$post->comments()->create([
'author_name' => htmlspecialchars($request->author_name, ENT_QUOTES, 'UTF-8'), // Escaping special characters
'content' => $sanitizedContent, // Use sanitized content
'parent_id' => $request->parent_id, // Set parent_id if it's a reply
'author_name' => htmlspecialchars($request->author_name, ENT_QUOTES, 'UTF-8'), // Escape author name
'content' => $sanitizedContent, // Store sanitized content
'parent_id' => $request->parent_id, // Assign parent_id if it's a reply
]);

// Redirect back to the post page with a success message
return redirect()->route('posts.show', $post->slug)->with('success', 'Comment posted successfully!');
}

Expand Down
10 changes: 10 additions & 0 deletions app/Providers/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ public function boot(): void
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

// Define rate limit for comment submission: 5 per minute per IP
RateLimiter::for('comment', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});

// Define rate limit for post submission: 1 per 30 minutes per user
RateLimiter::for('post', function (Request $request) {
return Limit::perMinutes(30, 1)->by($request->user()->id);
});

$this->routes(function () {
Route::middleware('api')
->prefix('api')
Expand Down
49 changes: 29 additions & 20 deletions resources/views/auth/login.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class="img-fluid" alt="Login Image">
<form method="POST" action="{{ route('login') }}">
@csrf
<h2>Other choices to login:</h2>

<!-- Social login buttons -->
<div class="d-flex flex-row align-items-center justify-content-center mb-4">
<button type="button" class="btn btn-floating btn-primary mx-2">
Expand All @@ -39,8 +38,6 @@ class="img-fluid" alt="Login Image">
<label for="email" class="form-label-lg">Email address</label> <!-- Increased label size -->
<input id="email" type="email" class="form-control form-control-lg @error('email') is-invalid @enderror"
name="email" value="{{ old('email') }}" required autofocus placeholder="Enter your email">


@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
Expand All @@ -52,14 +49,23 @@ class="img-fluid" alt="Login Image">
<label for="password" class="form-label-lg">Password</label> <!-- Larger label for password -->
<input id="password" type="password" class="form-control form-control-lg @error('password') is-invalid @enderror"
name="password" required placeholder="Enter your password">

@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>

<!-- reCAPTCHA -->
<div class="form-group mt-4">
<div class="g-recaptcha" data-sitekey="{{ env('RECAPTCHA_SITE_KEY') }}"></div>
@error('g-recaptcha-response')
<span class="invalid-feedback d-block" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>

<div class="d-flex justify-content-between align-items-center">
<!-- Checkbox -->
<div class="form-check mb-0">
Expand Down Expand Up @@ -136,22 +142,25 @@ class="link-danger">Register</a></p>
}
.divider {
text-align: center;
border-bottom: 1px solid #e9ecef; /* Solid border for the divider */
margin: 1.5rem 0;
position: relative;
}
.divider p {
margin-bottom: 0;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #fff; /* Background color to make the text stand out */
padding: 0 1rem; /* Padding around the text */
font-weight: bold;
}
text-align: center;
border-bottom: 1px solid #e9ecef;
/* Solid border for the divider */
margin: 1.5rem 0;
position: relative;
}
.divider p {
margin-bottom: 0;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
/* Background color to make the text stand out */
padding: 0 1rem;
/* Padding around the text */
font-weight: bold;
}
/* Button animations */
.btn-lg {
Expand Down
2 changes: 1 addition & 1 deletion resources/views/layout.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.0/lazysizes.min.js" async></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.6/markdown-it.min.js"></script>

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-lazyload/17.1.3/lazyload.min.js"></script>

Expand Down
8 changes: 6 additions & 2 deletions resources/views/partials/comments.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<label for="content" class="form-label">Comment</label>
<textarea class="form-control" name="content" rows="3" placeholder="Your comment..." required></textarea>
</div>
<div class="g-recaptcha" data-sitekey="{{ env('RECAPTCHA_SITE_KEY') }}"></div>
<button type="submit" class="btn btn-success">Post Reply</button>
</form>
</div>
Expand Down Expand Up @@ -67,8 +68,11 @@
<label for="content" class="form-label">Comment</label>
<textarea class="form-control" name="content" rows="3" placeholder="Your comment..." required></textarea>
</div>
<!-- Add reCAPTCHA -->
<div class="g-recaptcha" data-sitekey="{{ env('RECAPTCHA_SITE_KEY') }}"></div>
<button type="submit" class="btn btn-success">Post Comment</button>
</form>
</div>

</div>
</div>


6 changes: 5 additions & 1 deletion routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@
Route::get('/posts/{slug}/comments', [CommentController::class, 'index'])->name('comments.index'); // List all comments for a specific post
Route::get('/posts/{slug}/comments/create', [CommentController::class, 'create'])->name('comments.create'); // Create a new comment form (if needed)
Route::get('/posts/{slug}/comments/{id}', [CommentController::class, 'show'])->name('comments.show'); // Show a specific comment related to the post
Route::post('/posts/{slug}/comments', [CommentController::class, 'store'])->name('comments.store');
// Limit comment submission to 5 per minute per IP
Route::middleware('throttle:comment')
->post('/posts/{slug}/comments', [CommentController::class, 'store'])
->name('comments.store');


//upvote comment
Route::post('comments/{id}/upvote', [CommentController::class, 'upvote'])->name('comments.upvote');
Expand Down

0 comments on commit f6e60e4

Please sign in to comment.