Fix: Resolve ENV parsing issue and implement Cloudflare Turnstile

This commit is contained in:
dyzcdn
2025-12-23 08:38:51 +07:00
parent 01d4a925ae
commit 12ae6fe45d
11 changed files with 92 additions and 48 deletions

View File

@@ -3,24 +3,20 @@ APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost:8000 APP_URL=http://localhost:8000
APP_LOCALE=en APP_LOCALE=en
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# --- Logging & Performance ---
PHP_CLI_SERVER_WORKERS=4 PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12 BCRYPT_ROUNDS=12
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_STACK=single LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
# Database Configuration # --- Database ---
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
@@ -28,34 +24,41 @@ DB_DATABASE=dy_app_db
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD= DB_PASSWORD=
# Session Configuration # --- Session & Cache ---
SESSION_DRIVER=database SESSION_DRIVER=database
SESSION_LIFETIME=120 SESSION_LIFETIME=120
SESSION_ENCRYPT=false SESSION_ENCRYPT=false
SESSION_PATH=/ SESSION_PATH=/
SESSION_DOMAIN=null SESSION_DOMAIN=null
CACHE_STORE=database
# Broadcasting & Filesystem
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local FILESYSTEM_DISK=local
QUEUE_CONNECTION=database QUEUE_CONNECTION=database
# Cache Configuration # --- Memcached & Redis ---
CACHE_STORE=database
# CACHE_PREFIX=
# Memcached Configuration
MEMCACHED_HOST=127.0.0.1 MEMCACHED_HOST=127.0.0.1
# Redis Configuration
REDIS_CLIENT=phpredis REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1 REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
# Mail Configuration # --- Broadcasting (Reverb) ---
BROADCAST_CONNECTION=log
REVERB_APP_ID=
REVERB_APP_KEY=
REVERB_APP_SECRET=
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
# --- Vite ---
VITE_APP_NAME="${APP_NAME}"
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
# --- Mail Configuration ---
MAIL_MAILER=smtp MAIL_MAILER=smtp
# MAIL_SCHEME=null
MAIL_HOST=127.0.0.1 MAIL_HOST=127.0.0.1
MAIL_PORT=587 MAIL_PORT=587
MAIL_USERNAME=null MAIL_USERNAME=null
@@ -64,7 +67,7 @@ MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="hello@example.com" MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"
# Support Mailer (Secondary) # --- Support Mailer ---
MAIL_SUPPORT_MAILER=smtp MAIL_SUPPORT_MAILER=smtp
MAIL_SUPPORT_HOST=127.0.0.1 MAIL_SUPPORT_HOST=127.0.0.1
MAIL_SUPPORT_PORT=587 MAIL_SUPPORT_PORT=587
@@ -74,58 +77,43 @@ MAIL_SUPPORT_ENCRYPTION=tls
MAIL_SUPPORT_FROM_ADDRESS="support@example.com" MAIL_SUPPORT_FROM_ADDRESS="support@example.com"
MAIL_SUPPORT_FROM_NAME="DyDev Support" MAIL_SUPPORT_FROM_NAME="DyDev Support"
# AWS Configuration # --- Services (AWS) ---
AWS_ACCESS_KEY_ID= AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY= AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1 AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET= AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false AWS_USE_PATH_STYLE_ENDPOINT=false
# VITE Configuration # --- Social Authentication ---
VITE_APP_NAME="${APP_NAME}"
# Social Authentication (OAuth) Configuration
GITHUB_CLIENT_ID= GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET= GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT_URI=${APP_URL}/auth/github/callback GITHUB_REDIRECT_URI="${APP_URL}/auth/github/callback"
GOOGLE_CLIENT_ID= GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET= GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=${APP_URL}/auth/google/callback GOOGLE_REDIRECT_URI="${APP_URL}/auth/google/callback"
# CA ROOT # --- Certificate Authority (CA) ---
CA_ROOT_COUNTRY_NAME="ID" CA_ROOT_COUNTRY_NAME="ID"
CA_ROOT_ORGANIZATION_NAME="DyDev TrustLab OpenSource" CA_ROOT_ORGANIZATION_NAME="DyDev TrustLab OpenSource"
CA_ROOT_ORGANIZATIONAL_UNIT_NAME="www.dyzulk.com" CA_ROOT_ORGANIZATIONAL_UNIT_NAME="www.dyzulk.com"
CA_ROOT_COMMON_NAME="DyzulkDev" CA_ROOT_COMMON_NAME="DyzulkDev"
# CA INTERMEDIATE 4096
CA_4096_COUNTRY_NAME="ID" CA_4096_COUNTRY_NAME="ID"
CA_4096_ORGANIZATION_NAME="DyDev TrustLab" CA_4096_ORGANIZATION_NAME="DyDev TrustLab"
CA_4096_ORGANIZATIONAL_UNIT_NAME="www.dyzulk.com" CA_4096_ORGANIZATIONAL_UNIT_NAME="www.dyzulk.com"
CA_4096_COMMON_NAME="DyDev Infinity CA1" CA_4096_COMMON_NAME="DyDev Infinity CA1"
# CA INTERMEDIATE 2048
CA_2048_COUNTRY_NAME="ID" CA_2048_COUNTRY_NAME="ID"
CA_2048_ORGANIZATION_NAME="Twinpath TrustLab" CA_2048_ORGANIZATION_NAME="Twinpath TrustLab"
CA_2048_ORGANIZATIONAL_UNIT_NAME="www.dyzulk.com" CA_2048_ORGANIZATIONAL_UNIT_NAME="www.dyzulk.com"
CA_2048_COMMON_NAME="Twinpath Infinity CA2" CA_2048_COMMON_NAME="Twinpath Infinity CA2"
# CA LEAF DEFAULT
CA_LEAF_DEFAULT_COUNTRY_NAME="ID" CA_LEAF_DEFAULT_COUNTRY_NAME="ID"
CA_LEAF_DEFAULT_LOCALITY="Jakarta" CA_LEAF_DEFAULT_LOCALITY="Jakarta"
CA_LEAF_DEFAULT_STATE="DKI Jakarta" CA_LEAF_DEFAULT_STATE="DKI Jakarta"
CA_LEAF_DEFAULT_ORGANIZATION_NAME="MyLab Secured" CA_LEAF_DEFAULT_ORGANIZATION_NAME="MyLab Secured"
# Reverb (WebSockets) # --- Security (Turnstile) ---
REVERB_APP_ID= TURNSTILE_SITE_KEY=
REVERB_APP_KEY= TURNSTILE_SECRET_KEY=
REVERB_APP_SECRET=
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

View File

@@ -22,7 +22,10 @@ class ForgotPasswordController extends Controller
*/ */
public function sendResetLinkEmail(Request $request) public function sendResetLinkEmail(Request $request)
{ {
$request->validate(['email' => 'required|email']); $request->validate([
'email' => 'required|email',
'cf-turnstile-response' => ['required', new \App\Rules\Turnstile],
]);
// We will send the password reset link to this user. Once we have attempted // We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we // to send the link, we will examine the response then see the message we

View File

@@ -29,6 +29,7 @@ class AuthController extends Controller
$credentials = $request->validate([ $credentials = $request->validate([
'email' => ['required', 'email'], 'email' => ['required', 'email'],
'password' => ['required'], 'password' => ['required'],
'cf-turnstile-response' => ['required', new \App\Rules\Turnstile],
]); ]);
// Find user by email // Find user by email
@@ -72,6 +73,7 @@ class AuthController extends Controller
'email' => 'required|string|email|max:255|unique:users', 'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8', 'password' => 'required|string|min:8',
'terms' => 'required|accepted', 'terms' => 'required|accepted',
'cf-turnstile-response' => ['required', new \App\Rules\Turnstile],
]); ]);
$roleInfo = \App\Models\Role::where('name', 'customer')->first(); $roleInfo = \App\Models\Role::where('name', 'customer')->first();

View File

@@ -20,6 +20,7 @@ class ContactController extends Controller
'category' => 'required|string|in:Legal Inquiry,Technical Support,Partnership,Other', 'category' => 'required|string|in:Legal Inquiry,Technical Support,Partnership,Other',
'subject' => 'required|string|max:255', 'subject' => 'required|string|max:255',
'message' => 'required|string|max:2000', 'message' => 'required|string|max:2000',
'cf-turnstile-response' => ['required', new \App\Rules\Turnstile],
]); ]);
ContactSubmission::create([ ContactSubmission::create([

View File

@@ -33,7 +33,10 @@ class SearchController extends Controller
if ($user->isAdmin()) { if ($user->isAdmin()) {
// 2. Admin: User Search // 2. Admin: User Search
$users = User::where('name', 'like', "%$query%") $users = User::where(function($q) use ($query) {
$q->where('first_name', 'like', "%$query%")
->orWhere('last_name', 'like', "%$query%");
})
->orWhere('email', 'like', "%$query%") ->orWhere('email', 'like', "%$query%")
->limit(5) ->limit(5)
->get() ->get()

28
app/Rules/Turnstile.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
class Turnstile implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$response = Http::asForm()->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'secret' => env('TURNSTILE_SECRET_KEY'),
'response' => $value,
'remoteip' => request()->ip(),
]);
if (! $response->json('success')) {
$fail('The :attribute verification failed. Please try again.');
}
}
}

View File

@@ -0,0 +1,11 @@
@props(['theme' => 'auto', 'size' => 'normal', 'tabindex' => 0])
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile"
data-sitekey="{{ env('TURNSTILE_SITE_KEY') }}"
data-theme="{{ $theme }}"
data-size="{{ $size }}"
data-tabindex="{{ $tabindex }}"
{{ $attributes }}
></div>

View File

@@ -52,6 +52,8 @@
@enderror @enderror
</div> </div>
<!-- Turnstile -->
<x-turnstile class="mb-5" />
<!-- Button --> <!-- Button -->
<div> <div>
<button type="submit" <button type="submit"

View File

@@ -125,6 +125,8 @@
Forgot password? Forgot password?
</a> </a>
</div> </div>
<!-- Turnstile -->
<x-turnstile class="mb-5" />
<!-- Button --> <!-- Button -->
<div> <div>
<button type="submit" <button type="submit"

View File

@@ -146,6 +146,8 @@
</label> </label>
</div> </div>
</div> </div>
<!-- Turnstile -->
<x-turnstile class="mb-5" />
<!-- Button --> <!-- Button -->
<div> <div>
<button type="submit" <button type="submit"

View File

@@ -73,6 +73,8 @@
class="w-full rounded-2xl border-gray-200 bg-gray-50/50 px-5 py-4 text-sm text-gray-900 transition focus:ring-brand-500 focus:border-brand-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white resize-none">{{ old('message') }}</textarea> class="w-full rounded-2xl border-gray-200 bg-gray-50/50 px-5 py-4 text-sm text-gray-900 transition focus:ring-brand-500 focus:border-brand-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white resize-none">{{ old('message') }}</textarea>
</div> </div>
<!-- Turnstile -->
<x-turnstile class="mb-5" />
<div> <div>
<button type="submit" <button type="submit"
class="flex w-full justify-center rounded-2xl bg-brand-500 px-4 py-5 text-sm font-bold text-white shadow-xl shadow-brand-500/30 hover:bg-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-500 transition-all active:scale-[0.98]"> class="flex w-full justify-center rounded-2xl bg-brand-500 px-4 py-5 text-sm font-bold text-white shadow-xl shadow-brand-500/30 hover:bg-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-500 transition-all active:scale-[0.98]">