diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index e28687b..9eb056b 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -41,6 +41,7 @@ class Account extends Authenticatable protected $casts = [ 'activated' => 'boolean', ]; + protected $fillable = ['username', 'domain', 'email']; public static $dtmfProtocols = ['sipinfo' => 'SIPInfo', 'rfc2833' => 'RFC2833', 'sipmessage' => 'SIP Message']; diff --git a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php index ca08b03..c289b73 100644 --- a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php +++ b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php @@ -63,7 +63,7 @@ class AuthenticateController extends Controller } if (!$account) { - return redirect()->back()->withErrors(['authentication' => 'The account doesn\'t exists']); + return redirect()->back()->withErrors(['authentication' => 'Wrong username or password']); } // Try out the passwords diff --git a/flexiapi/app/Http/Controllers/Account/PasswordController.php b/flexiapi/app/Http/Controllers/Account/PasswordController.php index c1cbf5d..d00d40d 100644 --- a/flexiapi/app/Http/Controllers/Account/PasswordController.php +++ b/flexiapi/app/Http/Controllers/Account/PasswordController.php @@ -45,9 +45,7 @@ class PasswordController extends Controller $account->activated = true; $account->save(); - $algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5'; - - $account->updatePassword($request->get('password'), $algorithm); + $account->updatePassword($request->get('password'), 'SHA-256'); if ($account->passwords()->count() > 0) { Log::channel('events')->info('Web: Password changed', ['id' => $account->identifier]); diff --git a/flexiapi/app/Http/Controllers/Account/RecoveryController.php b/flexiapi/app/Http/Controllers/Account/RecoveryController.php index 1d94730..b7cf4b4 100644 --- a/flexiapi/app/Http/Controllers/Account/RecoveryController.php +++ b/flexiapi/app/Http/Controllers/Account/RecoveryController.php @@ -36,6 +36,8 @@ class RecoveryController extends Controller 'g-recaptcha-response' => captchaConfigured() ? 'required|captcha' : '', ]; + $account = null; + if ($request->get('email')) { if (config('app.account_email_unique') == false) { $rules['username'] = 'required'; @@ -94,12 +96,18 @@ class RecoveryController extends Controller { $request->validate([ 'account_id' => 'required', - 'code' => 'required|digits:4' + 'number_1' => 'required|digits:1', + 'number_2' => 'required|digits:1', + 'number_3' => 'required|digits:1', + 'number_4' => 'required|digits:1' ]); + $code = $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4'); + + $account = Account::where('id', Crypt::decryptString($request->get('account_id')))->firstOrFail(); - if ($account->recovery_code != $request->get('code')) { + if ($account->recovery_code != $code) { return redirect()->back()->withErrors([ 'code' => 'Wrong code' ]); diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php index 0ef7cec..b9319b6 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php @@ -97,6 +97,9 @@ class AccountController extends Controller $account->phone = $request->get('phone'); $account->fillPassword($request); + $account->refresh(); + $account->setRole($request->get('role')); + Log::channel('events')->info('Web Admin: Account created', ['id' => $account->identifier]); return redirect()->route('admin.account.edit', $account->id); diff --git a/flexiapi/app/Http/Controllers/Admin/AccountImportController.php b/flexiapi/app/Http/Controllers/Admin/AccountImportController.php new file mode 100644 index 0000000..4530d61 --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/AccountImportController.php @@ -0,0 +1,226 @@ +errors = collect(); + } + + public function create(Request $request) + { + return view('admin.account.import.create', [ + 'domains' => Account::select('domain')->distinct()->get()->pluck('domain') + ]); + } + + public function store(Request $request) + { + $request->validate([ + 'csv' => ['required', File::types(['csv', 'txt'])], + 'domain' => 'required|exists:accounts' + ]); + + $lines = $this->csvToCollection($request->file('csv')); + + /** + * Error checking + */ + + // Usernames + + $existingUsernames = Account::where('domain', $request->get('domain')) + ->whereIn('username', $lines->pluck('username')->all()) + ->pluck('username'); + + if ($existingUsernames->isNotEmpty()) { + $this->errors['Those usernames already exists'] = $existingUsernames->join(', ', ' and '); + } + + if ($duplicates = $lines->pluck('username')->duplicates()) { + if ($duplicates->isNotEmpty()) { + $this->errors['Those usernames are declared several times'] = $duplicates->join(', ', ' and '); + } + } + + if ($lines->pluck('username')->contains(function ($value) { + return strlen($value) < 2; + })) { + $this->errors['Some usernames are shorter than expected'] = ''; + } + + // Passwords + + if ($lines->pluck('password')->contains(function ($value) { + return strlen($value) < 6; + })) { + $this->errors['Some passwords are shorter than expected'] = ''; + } + + // Roles + + if ($lines->pluck('role')->contains(function ($value) { + return !in_array($value, ['admin', 'user']); + })) { + $this->errors['Some roles are not correct'] = ''; + } + + // Status + + if ($lines->pluck('status')->contains(function ($value) { + return !in_array($value, ['active', 'inactive']); + })) { + $this->errors['Some status are not correct'] = ''; + } + + // Phones + + if ($phones = $lines->pluck('phone')->filter(function ($value) { + return strlen($value) > 2 && substr($value, 0, 1) != '+'; + })) { + if ($phones->isNotEmpty()) { + $this->errors['Some phone numbers are not correct'] = $phones->join(', ', ' and '); + } + } + + $existingPhones = Alias::whereIn('alias', $lines->pluck('phone')->all()) + ->pluck('alias'); + + if ($existingPhones->isNotEmpty()) { + $this->errors['Those phones numbers already exists'] = $existingPhones->join(', ', ' and '); + } + + // Emails + + if ($emails = $lines->pluck('email')->filter(function ($value) { + return !filter_var($value, FILTER_VALIDATE_EMAIL); + })) { + if ($emails->isNotEmpty()) { + $this->errors['Some emails are not correct'] = $emails->join(', ', ' and '); + } + } + + $existingEmails = Account::whereIn('email', $lines->pluck('email')->all()) + ->pluck('email'); + + if ($existingEmails->isNotEmpty()) { + $this->errors['Those emails numbers already exists'] = $existingEmails->join(', ', ' and '); + } + + if ($emails = $lines->pluck('email')->duplicates()) { + if ($emails->isNotEmpty()) { + $this->errors['Those emails are declared several times'] = $emails->join(', ', ' and '); + } + } + + $filePath = $this->errors->isEmpty() + ? Storage::putFile($this->importDirectory, $request->file('csv')) + : null; + + return view('admin.account.import.check', [ + 'linesCount' => $lines->count(), + 'errors' => $this->errors, + 'domain' => $request->get('domain'), + 'filePath' => $filePath + ]); + } + + public function handle(Request $request) + { + $request->validate([ + 'file_path' => 'required', + 'domain' => 'required|exists:accounts' + ]); + + $lines = $this->csvToCollection(storage_path('app/' . $request->get('file_path'))); + + $accounts = []; + $now = \Carbon\Carbon::now(); + + $admins = $phones = []; + + foreach ($lines as $line) { + if ($line->role == 'admin') { + array_push($admins, $line->username); + } + + if (!empty($line->phone)) { + $phones[$line->username] = $line->phone; + } + + array_push($accounts, [ + 'username' => $line->username, + 'domain' => $request->get('domain'), + 'email' => $line->email, + 'activated' => $line->status == 'active', + 'ip_address' => '127.0.0.1', + 'user_agent' => 'CSV Import', + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + + Account::insert($accounts); + + // Set admins accounts + foreach ($admins as $username) { + $account = Account::where('username', $username) + ->where('domain', $request->get('domain')) + ->first(); + $account->admin = true; + } + + // Set admins accounts + foreach ($phones as $username => $phone) { + $account = Account::where('username', $username) + ->where('domain', $request->get('domain')) + ->first(); + $account->phone = $phone; + } + + return redirect()->route('admin.account.index'); + } + + private function csvToCollection($file): Collection + { + $lines = collect(); + $csv = fopen($file, 'r'); + + $i = 1; + while (!feof($csv)) { + if ($line = fgetcsv($csv, 1000, ',')) { + $lines->push((object)[ + 'line' => $i, + 'username' => $line[0], + 'password' => $line[1], + 'role' => $line[2], + 'status' => $line[3], + 'phone' => $line[4], + 'email' => $line[5], + ]); + + $i++; + } + } + + fclose($csv); + + $lines->shift(); + + return $lines; + } +} diff --git a/flexiapi/public/css/far.css b/flexiapi/public/css/far.css index 93b7237..f129338 100644 --- a/flexiapi/public/css/far.css +++ b/flexiapi/public/css/far.css @@ -106,6 +106,7 @@ body.show_menu { p, a, ul li, +ol li, pre { font-size: 1.5rem; color: var(--second-7); @@ -124,11 +125,16 @@ pre code { font-size: 1.3rem; } -ul li { +ul li, +ol li { margin-left: 2rem; list-style-type: disc; } +ol li { + list-style-type: decimal; +} + ul li ul li { list-style-type: circle; } @@ -279,20 +285,12 @@ header nav a#menu { header nav a#menu:after { display: block; - font-family: 'Material Icons'; + font-family: 'Material Icons Outlined'; content: "\e5d2"; font-size: 3rem; } -body.show_menu header nav a#menu:after { - content: "\e5cd"; -} - -header nav a#logo span { - margin-left: 1.5rem; -} - -@media screen and (max-width: 800px) { +body.show_menu header nav a#menu:after {font-family: 'Material Icons' header nav a#logo { position: absolute; left: calc(50% - 1.5rem); @@ -314,6 +312,7 @@ header nav a#logo::before { display: block; border-radius: 1rem; box-shadow: 0 0 2rem rgba(0, 0, 0, 0.2); + margin-right: 2rem; } header nav a.oppose { @@ -619,7 +618,7 @@ table tr.empty td { table tr.empty td:before { content: '\e5c9'; - font-family: 'Material Icons'; + font-family: 'Material Icons Outlined'; font-size: 8rem; color: var(--second-4); display: block; @@ -753,6 +752,7 @@ select.list_toggle { display: inline-block; font-size: 1.5rem; line-height: 2rem; + margin-left: 0; } .breadcrumb li a { @@ -769,4 +769,54 @@ select.list_toggle { font-size: 1rem; line-height: 2rem; display: inline-block; +} + +/* Steps */ + +ol.steps { + counter-reset: css-counter 0; + padding-top: 6.5rem; +} + +ol.steps li { + counter-increment: css-counter 1; + display: inline-block; + position: relative; + font-weight: 600; + color: var(--grey-5); +} + +ol.steps li + li { + margin-left: 9rem; +} + +ol.steps li + li:after { + display: block; + border-top: 1px solid var(--grey-3); + content: ""; + width: 7rem; + position: absolute; + top: -3rem; + left: -8rem; +} + +ol.steps li:before { + content: counter(css-counter); + display: block; + font-size: 2rem; + font-weight: 300; + border-radius: 3rem; + line-height: 5rem; + width: 5rem; + text-align: center; + border: 1px solid var(--grey-3); + position: absolute; + left: calc(50% - 2.5rem - 1px); + bottom: 3rem; +} + +ol.steps li.active:before { + background-color: var(--main-5); + border-color: var(--main-5); + color: white; } \ No newline at end of file diff --git a/flexiapi/public/css/form.css b/flexiapi/public/css/form.css index 397e2c3..951d5b4 100644 --- a/flexiapi/public/css/form.css +++ b/flexiapi/public/css/form.css @@ -65,16 +65,18 @@ p .btn { .btn.btn-tertiary { background-color: var(--main-1); - border-color: transparent; + border-color: var(--main-1); color: var(--main-5); } .btn.btn-tertiary:hover { background-color: var(--main-2); + border-color: var(--main-2); } .btn.btn-tertiary:active { background-color: var(--main-3); + border-color: var(--main-3); } form { @@ -129,7 +131,7 @@ form h2 { grid-column: 1/-1; } -form .disabled { +form .disabled:not(a) { opacity: 0.5; pointer-events: none; filter: blur(0.25rem); @@ -205,7 +207,7 @@ form div.checkbox { form div.search:after, form div.select:after { - font-family: 'Material Icons'; + font-family: 'Material Icons Outlined'; content: "\e5cf"; display: block; font-size: 2.5rem; diff --git a/flexiapi/resources/views/account/delete.blade.php b/flexiapi/resources/views/account/delete.blade.php index 3cf2e47..ab1ca4d 100644 --- a/flexiapi/resources/views/account/delete.blade.php +++ b/flexiapi/resources/views/account/delete.blade.php @@ -1,23 +1,36 @@ @extends('layouts.main') +@section('breadcrumb') +
Enter the pin code you received to recover your account.
Updated on {{ $account->updated_at->format('d/m/Y') }} - @include('parts.tabs', [ 'items' => [ route('admin.account.edit', $account->id, ['type' => 'messages']) => 'Information', @@ -42,7 +41,7 @@
{{ $title }} {{ $body }}
+ @endforeach + @endif +Use this existing (.csv) template or create your own csv file.
+ ++ This file MUST be in csv format and contain at least the following information: +
+