Docker compose setup (#513)

* fix password reset bug

* self hosted mode middleware changes on  pages

* fix lint

* wip: self hosted changes

* wip: self hosted frontend changes

* wip self hosted mode changes

* typo correction

* remove commented logic

* fix env variable names

* fix lint issues

* fix minor updates

* #445 Switched from single monolithic docker image to a docker-compose
 orchestrated network of services

* Automatically configures shared secret

* Working through some issues

* Use local file storage

* Moved the dockerfiles

* Fixed some issues when building from clean

* Corrected workflow

* Hopefully schedules everything correctly now

* Prep storage for worker process as well

* .env files are required

* Pinned dependency versions

* Disable self hosted in the client as well

* Removed double defaulting logic

* Using regexs is more succinct

* Added FRONT_URL environment variable

* Merge 236e4-self-hosted-mode-changes

* Improve inital user setup

* Finalized the new docker-compose setup

* Fix back-end formatting issues

---------

Co-authored-by: Frank <csskfaves@gmail.com>
Co-authored-by: Don Benjamin <don@webhammer.co.uk>
This commit is contained in:
Julien Nahum
2024-08-05 12:06:20 +02:00
committed by GitHub
parent 6b13f95322
commit 3280e38ee1
49 changed files with 6152 additions and 3431 deletions

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class InitProjectCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:init-project';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Creates the default admin user';
/**
* Execute the console command.
*/
public function handle()
{
if (!config('app.self_hosted')) {
$this->error('This command can only be run in self-hosted mode.');
return;
}
// Check if there are any existing users or if the ID increment is not at 0
if (User::max('id') !== null) {
$this->error('Users already exist in the database or the User table is not empty. Aborting initialization.');
return;
}
User::create([
'name' => 'Admin',
'email' => 'admin@opnform.com',
'password' => bcrypt('password'),
]);
$this->info('Admin user created with default credentials: admin@opnform.com / password');
return 0;
}
}

View File

@@ -98,7 +98,7 @@ class RegisterController extends Controller
private function checkRegistrationAllowed(array $data)
{
if (config('app.self_hosted') && !array_key_exists('invite_token', $data)) {
if (config('app.self_hosted') && !array_key_exists('invite_token', $data) && (app()->environment() !== 'testing')) {
response()->json(['message' => 'Registration is not allowed in self host mode'], 400)->throwResponse();
}
}

View File

@@ -75,7 +75,13 @@ class PublicFormController extends Controller
]);
}
return redirect()->to(Storage::temporaryUrl($path, now()->addMinutes(5)));
$internal_url = Storage::temporaryUrl($path, now()->addMinutes(5));
foreach(config('filesystems.disks.s3.temporary_url_rewrites') as $from => $to) {
$internal_url = str_replace($from, $to, $internal_url);
}
return redirect()->to($internal_url);
}
public function answer(AnswerFormRequest $request)

View File

@@ -3,7 +3,9 @@
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Models\Workspace;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class ProfileController extends Controller
{
@@ -18,7 +20,7 @@ class ProfileController extends Controller
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users,email,'.$user->id,
'email' => 'required|email|unique:users,email,' . $user->id,
]);
return tap($user)->update([
@@ -26,4 +28,43 @@ class ProfileController extends Controller
'email' => strtolower($request->email),
]);
}
// For self-hosted mode, only admin can update their credentials
public function updateAdminCredentials(Request $request)
{
$request->validate([
'email' => 'required|email|not_in:admin@opnform.com',
'password' => 'required|min:6|confirmed|not_in:password',
], [
'email.not_in' => "Please provide email address other than 'admin@opnform.com'",
'password.not_in' => "Please another password other than 'password'."
]);
ray('in', $request->password);
$user = $request->user();
$user->update([
'email' => $request->email,
'password' => bcrypt($request->password),
]);
ray($user);
Cache::forget('initial_user_setup_complete');
Cache::forget('max_user_id');
$workspace = Workspace::create([
'name' => 'My Workspace',
'icon' => '🧪',
]);
$user->workspaces()->sync([
$workspace->id => [
'role' => 'admin',
],
], false);
return $this->success([
'message' => 'Congratulations, your account credentials have been updated successfully.',
'user' => $user,
]);
}
}

View File

@@ -15,24 +15,26 @@ class TemplateController extends Controller
$limit = (int) $request->get('limit', 0);
$onlyMy = (bool) $request->get('onlymy', false);
$templates = Template::when(Auth::check(), function ($query) use ($onlyMy) {
$query = Template::query();
if (Auth::check()) {
if ($onlyMy) {
$query->where('creator_id', Auth::id());
} else {
$query->where(function ($query) {
$query->where('publicly_listed', true)
->orWhere('creator_id', Auth::id());
$query->where(function ($q) {
$q->where('publicly_listed', true)
->orWhere('creator_id', Auth::id());
});
}
})
->when(! Auth::check(), function ($query) {
$query->where('publicly_listed', true);
})
->when($limit > 0, function ($query) use ($limit) {
$query->limit($limit);
})
->orderByDesc('created_at')
->get();
} else {
$query->where('publicly_listed', true);
}
if ($limit > 0) {
$query->limit($limit);
}
$templates = $query->orderByDesc('created_at')->get();
return FormTemplateResource::collection($templates);
}

View File

@@ -10,6 +10,7 @@ use App\Http\Middleware\IsAdmin;
use App\Http\Middleware\IsModerator;
use App\Http\Middleware\IsNotSubscribed;
use App\Http\Middleware\IsSubscribed;
use App\Http\Middleware\SelfHostedCredentialsMiddleware;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
@@ -60,6 +61,7 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
SelfHostedCredentialsMiddleware::class,
ImpersonationMiddleware::class,
],
];

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Cache;
use App\Models\User;
class SelfHostedCredentialsMiddleware
{
public const ALLOWED_ROUTES = [
'login',
'credentials.update',
'user.current',
'logout',
];
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (app()->environment('testing')) {
return $next($request);
}
if (in_array($request->route()->getName(), self::ALLOWED_ROUTES)) {
return $next($request);
}
if (
config('app.self_hosted') &&
$request->user() &&
!$this->isInitialSetupComplete()
) {
return response()->json([
'message' => 'You must change your credentials when in self-hosted mode',
'type' => 'error',
], Response::HTTP_FORBIDDEN);
}
return $next($request);
}
private function isInitialSetupComplete(): bool
{
return (bool) Cache::remember('initial_user_setup_complete', 60 * 60, function () {
$maxUserId = $this->getMaxUserId();
if ($maxUserId === 0) {
return false;
}
return !User::where('email', 'admin@opnform.com')->exists();
});
}
private function getMaxUserId(): int
{
return (int) Cache::remember('max_user_id', 60 * 60, function () {
return User::max('id') ?? 0;
});
}
}