diff --git a/backend/.gitignore b/backend/.gitignore index 92c6761..f35e8ef 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -6,9 +6,8 @@ /storage/*.key /vendor /dist-frontend -.env -.env.backup -.env.production +.env* +!.env.example Homestead.json Homestead.yaml auth.json diff --git a/backend/app/Http/Controllers/AdminController.php b/backend/app/Http/Controllers/AdminController.php index 80e04eb..879201c 100644 --- a/backend/app/Http/Controllers/AdminController.php +++ b/backend/app/Http/Controllers/AdminController.php @@ -188,6 +188,13 @@ class AdminController extends Controller public function clearOptimization() { if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401); + + //Check if .env file exists. If not, abort the operation + if(!file_exists(base_path('.env'))) { + return response()->json([ + 'message' => 'WARNING!! Environment file not found' + ], 400); + } Artisan::call('optimize:clear'); @@ -205,6 +212,66 @@ class AdminController extends Controller 'message' => 'Cache cleared successfully' ]); } + + public function encryptEnvironment(Request $request) { + if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401); + $request->validate([ + 'key' => 'required|string|min:6' + ]); + + $key = "base64:".base64_encode(hash('sha256', $request->input('key'), true)); + + Artisan::call('env:encrypt', ['--force' => true, '--no-interaction' => true, '--key' => $key]); + //Check if "ERROR" is in the output + $output = Artisan::output(); + if(str_contains($output, 'ERROR')) { + return response()->json([ + 'message' => str_replace('ERROR ', '', $output) + ], 400); + } + + return response()->json([ + 'message' => 'Environment encrypted successfully' + ]); + } + + public function decryptEnvironment(Request $request) { + if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401); + $request->validate([ + 'key' => 'required|string|min:6' + ]); + + $key = "base64:".base64_encode(hash('sha256', $request->input('key'), true)); + + Artisan::call('env:decrypt', ['--force' => true, '--no-interaction' => true, '--key' => $key]); + //Check if "ERROR" is in the output + $output = Artisan::output(); + if(str_contains($output, 'ERROR')) { + return response()->json([ + 'message' => str_replace('ERROR ', '', $output) + ], 400); + } + + //Delete .env.encrypted file if exists + if(file_exists(base_path('.env.encrypted'))) unlink(base_path('.env.encrypted')); + + return response()->json([ + 'message' => 'Environment decrypted successfully' + ]); + } + + public function deleteEnvironment() { + if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401); + + if(!file_exists(base_path('bootstrap/cache/config.php'))) Artisan::call('config:cache'); + + //Delete .env file if exists + if(file_exists(base_path('.env'))) unlink(base_path('.env')); + + return response()->json([ + 'message' => 'Environment file deleted successfully' + ]); + } public function getTelegramBotDebugInfo() { if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401); diff --git a/backend/routes/api.php b/backend/routes/api.php index bf1e9b3..102b5c4 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -109,6 +109,10 @@ Route::middleware('auth:sanctum')->group( function () { Route::post('/admin/clearOptimization', [AdminController::class, 'clearOptimization']); Route::post('/admin/clearCache', [AdminController::class, 'clearCache']); + Route::post('/admin/envEncrypt', [AdminController::class, 'encryptEnvironment']); + Route::post('/admin/envDecrypt', [AdminController::class, 'decryptEnvironment']); + Route::post('/admin/envDelete', [AdminController::class, 'deleteEnvironment']); + Route::get('/admin/telegramBot/debug', [AdminController::class, 'getTelegramBotDebugInfo']); Route::post('/admin/telegramBot/setWebhook', [AdminController::class, 'setTelegramWebhook']); Route::post('/admin/telegramBot/unsetWebhook', [AdminController::class, 'unsetTelegramWebhook']); diff --git a/frontend/src/app/_components/modal-alert/modal-alert.component.ts b/frontend/src/app/_components/modal-alert/modal-alert.component.ts index 18b6b27..940e3bd 100644 --- a/frontend/src/app/_components/modal-alert/modal-alert.component.ts +++ b/frontend/src/app/_components/modal-alert/modal-alert.component.ts @@ -95,8 +95,8 @@ export class ModalAlertComponent implements OnInit, OnDestroy { this.translate.get([ 'alert.delete_confirm_title', 'alert.delete_confirm_text', - 'table.yes_remove', - 'table.cancel', + 'yes_remove', + 'cancel', 'alert.deleted_successfully', 'alert.delete_failed' ]).subscribe((res: any) => { @@ -108,8 +108,8 @@ export class ModalAlertComponent implements OnInit, OnDestroy { showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', - confirmButtonText: res['table.yes_remove'], - cancelButtonText: res['table.cancel'] + confirmButtonText: res['yes_remove'], + cancelButtonText: res['cancel'] }).then((result: any) => { if (result.isConfirmed) { this.api.patch(`alerts/${this.id}`, { diff --git a/frontend/src/app/_components/table/table.component.ts b/frontend/src/app/_components/table/table.component.ts index d9725b8..5231c35 100644 --- a/frontend/src/app/_components/table/table.component.ts +++ b/frontend/src/app/_components/table/table.component.ts @@ -242,7 +242,7 @@ export class TableComponent implements OnInit, OnDestroy { } deleteService(id: number) { - this.translate.get(['table.yes_remove', 'table.cancel', 'table.remove_service_confirm', 'table.remove_service_text']).subscribe((res: { [key: string]: string; }) => { + this.translate.get(['yes_remove', 'cancel', 'table.remove_service_confirm', 'table.remove_service_text']).subscribe((res: { [key: string]: string; }) => { Swal.fire({ title: res['table.remove_service_confirm'], text: res['table.remove_service_confirm_text'], @@ -250,8 +250,8 @@ export class TableComponent implements OnInit, OnDestroy { showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', - confirmButtonText: res['table.yes_remove'], - cancelButtonText: res['table.cancel'] + confirmButtonText: res['yes_remove'], + cancelButtonText: res['cancel'] }).then((result) => { if (result.isConfirmed) { this.api.delete(`services/${id}`).then((response) => { @@ -274,7 +274,7 @@ export class TableComponent implements OnInit, OnDestroy { } deleteTraining(id: number) { - this.translate.get(['table.yes_remove', 'table.cancel', 'table.remove_training_confirm', 'table.remove_training_text']).subscribe((res: { [key: string]: string; }) => { + this.translate.get(['yes_remove', 'cancel', 'table.remove_training_confirm', 'table.remove_training_text']).subscribe((res: { [key: string]: string; }) => { Swal.fire({ title: res['table.remove_training_confirm'], text: res['table.remove_training_confirm_text'], @@ -282,8 +282,8 @@ export class TableComponent implements OnInit, OnDestroy { showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', - confirmButtonText: res['table.yes_remove'], - cancelButtonText: res['table.cancel'] + confirmButtonText: res['yes_remove'], + cancelButtonText: res['cancel'] }).then((result) => { if (result.isConfirmed) { this.api.delete(`trainings/${id}`).then((response) => { diff --git a/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.html b/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.html index 25c8870..fcb997f 100644 --- a/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.html +++ b/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.html @@ -133,6 +133,13 @@ + +
+

{{ 'admin.env_operations'|translate|ftitlecase }}

+ + + +

diff --git a/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.ts b/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.ts index 58c3a1c..02cc8ec 100644 --- a/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.ts +++ b/frontend/src/app/_routes/admin/maintenance/admin-maintenance.component.ts @@ -202,7 +202,6 @@ export class AdminMaintenanceComponent implements OnInit { title: this.translateService.instant('success_title'), text: this.translateService.instant('admin.run_optimization_success') }); - this.getDB(); }).catch((err: any) => { Swal.fire({ icon: 'error', @@ -219,7 +218,6 @@ export class AdminMaintenanceComponent implements OnInit { title: this.translateService.instant('success_title'), text: this.translateService.instant('admin.clear_optimization_success') }); - this.getDB(); }).catch((err: any) => { Swal.fire({ icon: 'error', @@ -236,7 +234,6 @@ export class AdminMaintenanceComponent implements OnInit { title: this.translateService.instant('success_title'), text: this.translateService.instant('admin.clear_cache_success') }); - this.getDB(); }).catch((err: any) => { Swal.fire({ icon: 'error', @@ -246,6 +243,100 @@ export class AdminMaintenanceComponent implements OnInit { }); } + envEncrypt() { + Swal.fire({ + title: this.translateService.instant('admin.env_encrypt_title'), + text: this.translateService.instant('admin.env_encrypt_text'), + input: 'password', + inputAttributes: { + autocapitalize: 'off' + }, + showCancelButton: true, + confirmButtonText: this.translateService.instant('confirm'), + cancelButtonText: this.translateService.instant('cancel'), + showLoaderOnConfirm: true, + preConfirm: (key) => { + return this.api.post('admin/envEncrypt', { key }).then((res: any) => { + return res; + }).catch((err: any) => { + Swal.showValidationMessage( + err.error.message + ); + }); + }, + allowOutsideClick: () => !Swal.isLoading() + }).then((result) => { + if (result.isConfirmed) { + Swal.fire({ + icon: 'success', + title: this.translateService.instant('success_title'), + text: this.translateService.instant('admin.env_encrypt_success') + }); + } + }); + } + + envDecrypt() { + Swal.fire({ + title: this.translateService.instant('admin.env_decrypt_title'), + text: this.translateService.instant('admin.env_decrypt_text'), + input: 'password', + inputAttributes: { + autocapitalize: 'off' + }, + showCancelButton: true, + confirmButtonText: this.translateService.instant('confirm'), + cancelButtonText: this.translateService.instant('cancel'), + showLoaderOnConfirm: true, + preConfirm: (key) => { + return this.api.post('admin/envDecrypt', { key }).then((res: any) => { + return res; + }).catch((err: any) => { + Swal.showValidationMessage( + err.error.message + ); + }); + }, + allowOutsideClick: () => !Swal.isLoading() + }).then((result) => { + if (result.isConfirmed) { + Swal.fire({ + icon: 'success', + title: this.translateService.instant('success_title'), + text: this.translateService.instant('admin.env_decrypt_success') + }); + } + }); + } + + envDelete() { + //Require confirmation before proceeding + Swal.fire({ + title: this.translateService.instant('admin.env_delete_title'), + text: this.translateService.instant('admin.env_delete_text'), + icon: 'warning', + showCancelButton: true, + confirmButtonText: this.translateService.instant('yes'), + cancelButtonText: this.translateService.instant('no') + }).then((result) => { + if (result.isConfirmed) { + this.api.post('admin/envDelete').then((res: any) => { + Swal.fire({ + icon: 'success', + title: this.translateService.instant('success_title'), + text: this.translateService.instant('admin.env_delete_success') + }); + }).catch((err: any) => { + Swal.fire({ + icon: 'error', + title: this.translateService.instant('error_title'), + text: err.message + }); + }); + } + }); + } + setTelegramBotWebhook() { this.api.post('admin/telegramBot/setWebhook').then((res: any) => { Swal.fire({ diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index c1f72b3..a77c6fb 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -52,11 +52,22 @@ "run": "run", "run_confirm_title": "Are you sure you want to run this command?", "run_confirm_text": "This action cannot be undone.", - "run_success": "Command executed successfully" + "run_success": "Command executed successfully", + "env_operations": "environment variables operations", + "env_encrypt": "encrypt .env", + "env_encrypt_title": "Encrypt .env file", + "env_encrypt_confirm": "Insert the password to encrypt the .env file", + "env_encrypt_success": ".env encrypted successfully", + "env_decrypt": "decrypt .env", + "env_decrypt_title": "Decrypt .env file", + "env_decrypt_confirm": "Insert the password to decrypt the .env file", + "env_decrypt_success": ".env decrypted successfully", + "env_delete": "delete .env", + "env_delete_title": "Delete .env file", + "env_delete_confirm": "Are you sure you want to delete the .env file?", + "env_delete_success": ".env deleted successfully" }, - "table": { - "yes_remove": "Yes, remove", - "cancel": "Cancel", + "table": { "remove_service_confirm": "Are you sure you want to remove this service?", "remove_service_confirm_text": "This action cannot be undone.", "service_deleted_successfully": "Service deleted successfully", @@ -174,6 +185,9 @@ "file_too_big": "File too big", "password_min_length": "Password must be at least 6 characters long" }, + "yes_remove": "Yes, remove", + "confirm": "Confirm", + "cancel": "Cancel", "enable": "enable", "disable": "disable", "maintenance_mode": "maintenance mode", diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json index 0cf87b3..db5f1d1 100644 --- a/frontend/src/assets/i18n/it.json +++ b/frontend/src/assets/i18n/it.json @@ -52,11 +52,22 @@ "run": "esegui", "run_confirm_title": "Sei sicuro di voler eseguire questo comando?", "run_confirm_text": "Questa operazione non potrà essere annullata.", - "run_success": "Comando eseguito con successo" + "run_success": "Comando eseguito con successo", + "env_operations": "operazioni alle variabili d'ambiente", + "env_encrypt": "cripta .env", + "env_encrypt_title": "Cripta il file .env", + "env_encrypt_text": "Inserisci la password per criptare il file .env", + "env_encrypt_success": ".env criptato con successo", + "env_decrypt": "decripta .env", + "env_decrypt_title": "Decripta il file .env", + "env_decrypt_text": "Inserisci la password per decriptare il file .env", + "env_decrypt_success": ".env decriptato con successo", + "env_delete": "rimuovi .env", + "env_delete_title": "Rimuovi il file .env", + "env_delete_text": "Sei sicuro di voler rimuovere il file .env?", + "env_delete_success": ".env rimosso con successo" }, "table": { - "yes_remove": "Si, rimuovi", - "cancel": "Annulla", "remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?", "remove_service_confirm_text": "Questa operazione non può essere annullata.", "service_deleted_successfully": "Intervento rimosso con successo", @@ -174,6 +185,9 @@ "file_too_big": "File troppo grande", "password_min_length": "La password deve essere di almeno 6 caratteri" }, + "yes_remove": "Si, rimuovi", + "confirm": "Conferma", + "cancel": "Annulla", "enable": "attiva", "disable": "disattiva", "maintenance_mode": "modalità manutenzione",