29 - Security hardening: registration gate, input validation, nginx headers, env defaults, user model
This commit is contained in:
parent
27f0ac8568
commit
b1d0ab793c
9 changed files with 39 additions and 24 deletions
10
.env.example
10
.env.example
|
|
@ -1,7 +1,7 @@
|
||||||
APP_NAME=Laravel
|
APP_NAME=Laravel
|
||||||
APP_ENV=local
|
APP_ENV=production
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=false
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
|
|
@ -18,18 +18,18 @@ 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=error
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DB_DATABASE=incr
|
DB_DATABASE=incr
|
||||||
DB_USERNAME=incr_user
|
DB_USERNAME=incr_user
|
||||||
DB_PASSWORD=incr_password
|
DB_PASSWORD=change_me_in_production
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
SESSION_ENCRYPT=false
|
SESSION_ENCRYPT=true
|
||||||
SESSION_PATH=/
|
SESSION_PATH=/
|
||||||
SESSION_DOMAIN=null
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ class RegisteredUserController extends Controller
|
||||||
*/
|
*/
|
||||||
public function create(): Response
|
public function create(): Response
|
||||||
{
|
{
|
||||||
|
if (User::exists()) {
|
||||||
|
abort(403, 'Registration is disabled.');
|
||||||
|
}
|
||||||
|
|
||||||
return Inertia::render('auth/register');
|
return Inertia::render('auth/register');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,16 +35,20 @@ public function create(): Response
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
if (User::exists()) {
|
||||||
|
abort(403, 'Registration is disabled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = User::create([
|
$user = User::forceCreate([
|
||||||
'name' => $request->name,
|
'name' => $validated['name'],
|
||||||
'email' => $request->email,
|
'email' => $validated['email'],
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($validated['password']),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
event(new Registered($user));
|
event(new Registered($user));
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,12 @@ class MilestoneController extends Controller
|
||||||
{
|
{
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$validated = $request->validate([
|
||||||
'target' => 'required|integer|min:1',
|
'target' => 'required|integer|min:1',
|
||||||
'description' => 'required|string|max:255',
|
'description' => 'required|string|max:255',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Milestone::create([
|
Milestone::create($validated);
|
||||||
'target' => $request->target,
|
|
||||||
'description' => $request->description,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return back()->with('success', 'Milestone created successfully');
|
return back()->with('success', 'Milestone created successfully');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,15 @@ public function update(Request $request)
|
||||||
|
|
||||||
public function history(Request $request): JsonResponse
|
public function history(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$limit = $request->get('limit', 30);
|
$limit = min(max(1, $request->integer('limit', 30)), 365);
|
||||||
|
|
||||||
return response()->json(AssetPrice::history($this->user->asset_id, $limit));
|
return response()->json(AssetPrice::history($this->user->asset_id, $limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function forDate(Request $request, string $date): JsonResponse
|
public function forDate(Request $request, string $date): JsonResponse
|
||||||
{
|
{
|
||||||
|
validator(['date' => $date], ['date' => 'required|date_format:Y-m-d'])->validate();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
'price' => AssetPrice::forDate($date, $this->user->asset_id),
|
'price' => AssetPrice::forDate($date, $this->user->asset_id),
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ public function share(Request $request): array
|
||||||
'name' => config('app.name'),
|
'name' => config('app.name'),
|
||||||
'quote' => ['message' => trim($message), 'author' => trim($author)],
|
'quote' => ['message' => trim($message), 'author' => trim($author)],
|
||||||
'auth' => [
|
'auth' => [
|
||||||
'user' => $request->user(),
|
'user' => $request->user()?->only(['id', 'name', 'email']),
|
||||||
],
|
],
|
||||||
'ziggy' => fn (): array => [
|
'ziggy' => fn (): array => [
|
||||||
...(new Ziggy)->toArray(),
|
...(new Ziggy)->toArray(),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int|null $asset_id
|
* @property int|null $asset_id
|
||||||
|
|
@ -26,7 +27,6 @@ class User extends Authenticatable
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
|
||||||
'asset_id',
|
'asset_id',
|
||||||
'price_tracking_enabled',
|
'price_tracking_enabled',
|
||||||
];
|
];
|
||||||
|
|
@ -57,10 +57,12 @@ public function asset(): BelongsTo
|
||||||
|
|
||||||
public static function default(): self
|
public static function default(): self
|
||||||
{
|
{
|
||||||
return self::firstOrCreate(
|
return self::firstWhere('email', 'user@incr.local')
|
||||||
['email' => 'user@incr.local'],
|
?? self::forceCreate([
|
||||||
['name' => 'Default User', 'password' => 'password']
|
'email' => 'user@incr.local',
|
||||||
);
|
'name' => 'Default User',
|
||||||
|
'password' => bcrypt(Str::random(32)),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasCompletedOnboarding(): bool
|
public function hasCompletedOnboarding(): bool
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@ server {
|
||||||
root /var/www/html/public;
|
root /var/www/html/public;
|
||||||
index index.php index.html;
|
index index.php index.html;
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.php?$query_string;
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ fi
|
||||||
|
|
||||||
# Wait for database to be ready
|
# Wait for database to be ready
|
||||||
echo "Waiting for database..."
|
echo "Waiting for database..."
|
||||||
until php artisan tinker --execute="DB::connection()->getPdo();" 2>/dev/null; do
|
until mysql -h"${DB_HOST:-db}" -u"${DB_USERNAME:-incr_user}" -p"${DB_PASSWORD}" -e "SELECT 1" >/dev/null 2>&1; do
|
||||||
echo "Database not ready, waiting..."
|
echo "Database not ready, waiting..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[supervisord]
|
[supervisord]
|
||||||
nodaemon=true
|
nodaemon=true
|
||||||
user=root
|
user=www-data
|
||||||
|
|
||||||
[program:nginx]
|
[program:nginx]
|
||||||
command=nginx -g "daemon off;"
|
command=nginx -g "daemon off;"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue