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:
48
app/Console/Commands/InitProjectCommand.php
Normal file
48
app/Console/Commands/InitProjectCommand.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
];
|
||||
|
||||
66
app/Http/Middleware/SelfHostedCredentialsMiddleware.php
Normal file
66
app/Http/Middleware/SelfHostedCredentialsMiddleware.php
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user