diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8640797..d024d09 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ variables: ROCKY_8_IMAGE_VERSION: 20241113_143521_update_php_82 ROCKY_9_IMAGE_VERSION: 20241114_161138_remove_redis - DEBIAN_12_IMAGE_VERSION: 20241112_113948_update_package_and_dependencies + DEBIAN_12_IMAGE_VERSION: 20241204_162237_update_download_linphone_org PHP_REDIS_REMI_VERSION: php-pecl-redis6-6.1.0-1 PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.16-2 PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-3 diff --git a/flexiapi/.env.example b/flexiapi/.env.example index 126bb44..eb45339 100644 --- a/flexiapi/.env.example +++ b/flexiapi/.env.example @@ -45,6 +45,7 @@ APP_PHONE_CHANGE_CODE_EXPIRATION_MINUTES=10 APP_RECOVERY_CODE_EXPIRATION_MINUTES=10 APP_PROVISIONING_TOKEN_EXPIRATION_MINUTES=0 APP_API_KEY_EXPIRATION_MINUTES=60 # Number of minutes the unused API Keys are valid +APP_RESET_PASSWORD_EMAIL_TOKEN_EXPIRATION_MINUTES=1440 # 24h # Account creation and authentication ACCOUNT_EMAIL_UNIQUE=false # Emails are unique between all the accounts diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index 6e0e6ab..a3b055e 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -256,6 +256,11 @@ class Account extends Authenticatable return $this->hasMany(AuthToken::class); } + public function resetPasswordEmailTokens() + { + return $this->hasMany(ResetPasswordEmailToken::class)->latest(); + } + /** * Attributes */ diff --git a/flexiapi/app/Consommable.php b/flexiapi/app/Consommable.php index d52d0b5..f54df88 100644 --- a/flexiapi/app/Consommable.php +++ b/flexiapi/app/Consommable.php @@ -27,6 +27,11 @@ abstract class Consommable extends Model $this->user_agent = $request->userAgent(); } + public function offed(): bool + { + return $this->consumed() || $this->expired(); + } + public function consumed(): bool { return $this->{$this->consommableAttribute} == null; diff --git a/flexiapi/app/Http/Controllers/Account/ResetPasswordEmailController.php b/flexiapi/app/Http/Controllers/Account/ResetPasswordEmailController.php new file mode 100644 index 0000000..bad748a --- /dev/null +++ b/flexiapi/app/Http/Controllers/Account/ResetPasswordEmailController.php @@ -0,0 +1,38 @@ +firstOrFail(); + + return view('account.password_reset', [ + 'token' => $token + ]); + } + + public function reset(Request $request) + { + $request->validate([ + 'token' => 'required|size:16', + 'password' => 'required|min:8|confirmed', + 'h-captcha-response' => captchaConfigured() ? 'required|HCaptcha' : '' + ]); + + $token = ResetPasswordEmailToken::where('token', $request->get('token'))->firstOrFail(); + + if ($token->offed()) abort(403); + + $token->account->updatePassword($request->get('password')); + $token->consume(); + + return view('account.password_changed'); + } +} diff --git a/flexiapi/app/Http/Controllers/Admin/ResetPasswordEmailController.php b/flexiapi/app/Http/Controllers/Admin/ResetPasswordEmailController.php new file mode 100644 index 0000000..ac56aba --- /dev/null +++ b/flexiapi/app/Http/Controllers/Admin/ResetPasswordEmailController.php @@ -0,0 +1,38 @@ + $account + ]); + } + + public function send(int $accountId) + { + $account = Account::findOrFail($accountId); + + $resetPasswordEmail = new ResetPasswordEmailToken; + $resetPasswordEmail->account_id = $account->id; + $resetPasswordEmail->token = Str::random(16); + $resetPasswordEmail->email = $account->email; + $resetPasswordEmail->save(); + + Mail::to($account)->send(new ResetPassword($resetPasswordEmail)); + + return redirect()->route('admin.account.activity.index', $account); + } +} diff --git a/flexiapi/app/Http/Kernel.php b/flexiapi/app/Http/Kernel.php index 87075b4..4f5c78a 100644 --- a/flexiapi/app/Http/Kernel.php +++ b/flexiapi/app/Http/Kernel.php @@ -35,8 +35,7 @@ class Kernel extends HttpKernel \App\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, - \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, - \App\Http\Middleware\Space::class + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class ]; /** @@ -59,8 +58,7 @@ class Kernel extends HttpKernel 'throttle:600,1', // move to 600 instead of 60 'bindings', 'validate_json', - 'localization', - 'space' + 'localization' ], ]; @@ -89,7 +87,6 @@ class Kernel extends HttpKernel 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, - 'space' => \App\Http\Middleware\Space::class, 'space.expired' => \App\Http\Middleware\IsSpaceExpired::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, diff --git a/flexiapi/app/Http/Middleware/IsSpaceExpired.php b/flexiapi/app/Http/Middleware/IsSpaceExpired.php index 446db97..9fae621 100644 --- a/flexiapi/app/Http/Middleware/IsSpaceExpired.php +++ b/flexiapi/app/Http/Middleware/IsSpaceExpired.php @@ -4,16 +4,34 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Config; use Symfony\Component\HttpFoundation\Response; class IsSpaceExpired { public function handle(Request $request, Closure $next): Response { - if ($request->user() && !$request->user()->superAdmin && $request->get('resolvedSpace')?->isExpired()) { - abort(403, 'The related Space has expired'); + if (empty(config('app.root_domain'))) { + return abort(503, 'APP_ROOT_DOMAIN is not configured'); } - return $next($request); + $space = \App\Space::where('host', $request->header('host'))->first(); + + if ($space) { + if (!str_ends_with($space->host, config('app.root_domain'))) { + return abort(503, 'The APP_ROOT_DOMAIN configured does not match with the current root domain'); + } + + Config::set('app.url', '://' . $space->host); + Config::set('app.sip_domain', $space->domain); + + if ($request->user() && !$request->user()->superAdmin && $space?->isExpired()) { + abort(403, 'The related Space has expired'); + } + + return $next($request); + } + + return abort(404, 'Host not configured'); } } diff --git a/flexiapi/app/Http/Middleware/Space.php b/flexiapi/app/Http/Middleware/Space.php deleted file mode 100644 index 1c77c7c..0000000 --- a/flexiapi/app/Http/Middleware/Space.php +++ /dev/null @@ -1,35 +0,0 @@ -header('host'))->first(); - - if ($space) { - if (!str_ends_with($space->host, config('app.root_domain'))) { - return abort(503, 'The APP_ROOT_DOMAIN configured does not match with the current root domain'); - } - - Config::set('app.url', '://' . $space->host); - Config::set('app.sip_domain', $space->domain); - - $request->request->set('resolvedSpace', $space); - - return $next($request); - } - - return abort(404, 'Host not configured'); - } -} diff --git a/flexiapi/app/Mail/ResetPassword.php b/flexiapi/app/Mail/ResetPassword.php new file mode 100644 index 0000000..c68110d --- /dev/null +++ b/flexiapi/app/Mail/ResetPassword.php @@ -0,0 +1,48 @@ +. +*/ + +namespace App\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; + +use App\Account; +use App\ResetPasswordEmailToken; + +class ResetPassword extends Mailable +{ + use Queueable, SerializesModels; + + private $token; + + public function __construct(ResetPasswordEmailToken $token) + { + $this->token = $token; + } + + public function build() + { + return $this->view('mails.reset_password') + ->text('mails.reset_password') + ->with([ + 'token' => $this->token + ]); + } +} diff --git a/flexiapi/app/ResetPasswordEmailToken.php b/flexiapi/app/ResetPasswordEmailToken.php new file mode 100644 index 0000000..10241d9 --- /dev/null +++ b/flexiapi/app/ResetPasswordEmailToken.php @@ -0,0 +1,28 @@ +belongsTo(Account::class); + } + + public function consume() + { + $this->used = true; + $this->save(); + } + + public function consumed(): bool + { + return $this->used == true; + } +} diff --git a/flexiapi/composer.lock b/flexiapi/composer.lock index a2f95b1..0e1e305 100644 --- a/flexiapi/composer.lock +++ b/flexiapi/composer.lock @@ -1325,16 +1325,16 @@ }, { "name": "giggsey/libphonenumber-for-php-lite", - "version": "8.13.50", + "version": "8.13.51", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php-lite.git", - "reference": "57bb2bfd8d4a9896ed961c584141247f2a35bc04" + "reference": "34e43f33e21a8cdeebc36e9de57157ae821ef56b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/57bb2bfd8d4a9896ed961c584141247f2a35bc04", - "reference": "57bb2bfd8d4a9896ed961c584141247f2a35bc04", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/34e43f33e21a8cdeebc36e9de57157ae821ef56b", + "reference": "34e43f33e21a8cdeebc36e9de57157ae821ef56b", "shasum": "" }, "require": { @@ -1404,7 +1404,7 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php-lite/issues", "source": "https://github.com/giggsey/libphonenumber-for-php-lite" }, - "time": "2024-11-18T09:58:30+00:00" + "time": "2024-12-02T09:22:48+00:00" }, { "name": "graham-campbell/result-type", @@ -4790,16 +4790,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.4", + "version": "v0.12.5", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + "reference": "36a03ff27986682c22985e56aabaf840dd173cb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/36a03ff27986682c22985e56aabaf840dd173cb5", + "reference": "36a03ff27986682c22985e56aabaf840dd173cb5", "shasum": "" }, "require": { @@ -4826,12 +4826,12 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, "bamarni-bin": { "bin-links": false, "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -4863,9 +4863,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.5" }, - "time": "2024-06-10T01:18:23+00:00" + "time": "2024-11-29T06:14:30+00:00" }, { "name": "ralouphie/getallheaders", @@ -5599,16 +5599,16 @@ }, { "name": "respect/validation", - "version": "2.3.8", + "version": "2.3.9", "source": { "type": "git", "url": "https://github.com/Respect/Validation.git", - "reference": "25ce44c7ee9613d260c7c0e44e27daa2131f383a" + "reference": "c96758eb27339c97486f311f25fbc797df2f6736" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Respect/Validation/zipball/25ce44c7ee9613d260c7c0e44e27daa2131f383a", - "reference": "25ce44c7ee9613d260c7c0e44e27daa2131f383a", + "url": "https://api.github.com/repos/Respect/Validation/zipball/c96758eb27339c97486f311f25fbc797df2f6736", + "reference": "c96758eb27339c97486f311f25fbc797df2f6736", "shasum": "" }, "require": { @@ -5661,9 +5661,9 @@ ], "support": { "issues": "https://github.com/Respect/Validation/issues", - "source": "https://github.com/Respect/Validation/tree/2.3.8" + "source": "https://github.com/Respect/Validation/tree/2.3.9" }, - "time": "2024-11-26T09:14:36+00:00" + "time": "2024-11-28T09:44:01+00:00" }, { "name": "sabre/uri", @@ -5922,12 +5922,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Scyllaly\\HCaptcha\\HCaptchaServiceProvider" - ], "aliases": { "HCaptcha": "Scyllaly\\HCaptcha\\Facades\\HCaptcha" - } + }, + "providers": [ + "Scyllaly\\HCaptcha\\HCaptchaServiceProvider" + ] } }, "autoload": { @@ -7034,16 +7034,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -7081,7 +7081,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -7097,7 +7097,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/error-handler", @@ -7256,16 +7256,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", "shasum": "" }, "require": { @@ -7312,7 +7312,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" }, "funding": [ { @@ -7328,7 +7328,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/finder", @@ -7396,16 +7396,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6" + "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", - "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/431771b7a6f662f1575b3cfc8fd7617aa9864d57", + "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57", "shasum": "" }, "require": { @@ -7453,7 +7453,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.15" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.16" }, "funding": [ { @@ -7469,20 +7469,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T16:09:24+00:00" + "time": "2024-11-13T18:58:10+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5" + "reference": "8838b5b21d807923b893ccbfc2cbeda0f1bc00f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b002a5b3947653c5aee3adac2a024ea615fd3ff5", - "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8838b5b21d807923b893ccbfc2cbeda0f1bc00f0", + "reference": "8838b5b21d807923b893ccbfc2cbeda0f1bc00f0", "shasum": "" }, "require": { @@ -7567,7 +7567,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.15" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.16" }, "funding": [ { @@ -7583,7 +7583,7 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:57:37+00:00" + "time": "2024-11-27T12:49:36+00:00" }, { "name": "symfony/mailer", @@ -8449,16 +8449,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.13", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" + "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", - "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", + "url": "https://api.github.com/repos/symfony/routing/zipball/91e02e606b4b705c2f4fb42f7e7708b7923a3220", + "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220", "shasum": "" }, "require": { @@ -8512,7 +8512,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.13" + "source": "https://github.com/symfony/routing/tree/v6.4.16" }, "funding": [ { @@ -8528,20 +8528,20 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:30:56+00:00" + "time": "2024-11-13T15:31:34+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -8595,7 +8595,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -8611,7 +8611,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", @@ -8796,16 +8796,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -8854,7 +8854,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -8870,7 +8870,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/uid", @@ -10101,16 +10101,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.4.15", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36" + "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/70ab1f65a4516ef741e519ea938e6aa465e6aa36", - "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7a379d8871f6a36f01559c14e11141cc02eb8dc8", + "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8", "shasum": "" }, "require": { @@ -10162,7 +10162,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.15" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.16" }, "funding": [ { @@ -10178,7 +10178,7 @@ "type": "tidelift" } ], - "time": "2024-11-09T06:56:25+00:00" + "time": "2024-11-25T14:52:46+00:00" }, { "name": "symfony/filesystem", diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php index 7a03c3b..4a79f0e 100644 --- a/flexiapi/config/app.php +++ b/flexiapi/config/app.php @@ -52,6 +52,7 @@ return [ 'phone_change_code_expiration_minutes' => env('APP_PHONE_CHANGE_CODE_EXPIRATION_MINUTES', 10), 'recovery_code_expiration_minutes' => env('APP_RECOVERY_CODE_EXPIRATION_MINUTES', 10), 'provisioning_token_expiration_minutes' => env('APP_PROVISIONING_TOKEN_EXPIRATION_MINUTES', 0), + 'reset_password_email_token_expiration_minutes' => env('APP_RESET_PASSWORD_EMAIL_TOKEN_EXPIRATION_MINUTES', 1440), /** * Amount of minutes before re-authorizing the generation of a new account creation token */ diff --git a/flexiapi/database/migrations/2024_12_02_142311_create_reset_password_email_tokens_table.php b/flexiapi/database/migrations/2024_12_02_142311_create_reset_password_email_tokens_table.php new file mode 100644 index 0000000..b3f5a75 --- /dev/null +++ b/flexiapi/database/migrations/2024_12_02_142311_create_reset_password_email_tokens_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('token', 16); + $table->boolean('used')->default(false); + $table->string('email'); + $table->integer('account_id')->unsigned(); + $table->string('ip')->nullable(); + $table->string('user_agent')->nullable(); + $table->timestamps(); + + $table->foreign('account_id')->references('id') + ->on('accounts')->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::dropIfExists('reset_password_email_tokens'); + } +}; diff --git a/flexiapi/public/css/style.css b/flexiapi/public/css/style.css index 9bb9446..d4fd183 100644 --- a/flexiapi/public/css/style.css +++ b/flexiapi/public/css/style.css @@ -619,6 +619,7 @@ table tr td a { table tr td, table tr th { + width: 20rem; padding: 1rem; font-size: 1.5rem; } diff --git a/flexiapi/resources/views/account/password_changed.blade.php b/flexiapi/resources/views/account/password_changed.blade.php new file mode 100644 index 0000000..21b743a --- /dev/null +++ b/flexiapi/resources/views/account/password_changed.blade.php @@ -0,0 +1,18 @@ +@extends('layouts.main', ['welcome' => true]) + +@section('content') +
+
+

lock Reset password

+
+ +

Your password was updated properly.

+

+ Authenticate +

+
+ +
+ +
+@endsection diff --git a/flexiapi/resources/views/account/password_reset.blade.php b/flexiapi/resources/views/account/password_reset.blade.php new file mode 100644 index 0000000..9eb244a --- /dev/null +++ b/flexiapi/resources/views/account/password_reset.blade.php @@ -0,0 +1,39 @@ +@extends('layouts.main', ['welcome' => true]) + +@section('content') +
+
+

lock Reset password

+
+ + @if ($token->offed()) +

This link is not available anymore.

+ @else +
+ @csrf + + +
+ + + @include('parts.errors', ['name' => 'password']) +
+
+ + + @include('parts.errors', ['name' => 'password_confirmation']) +
+ + @include('parts.captcha') + +
+ +
+
+ @endif +
+ +
+ +
+@endsection diff --git a/flexiapi/resources/views/admin/account/activity/index.blade.php b/flexiapi/resources/views/admin/account/activity/index.blade.php index c3ab8cc..3217594 100644 --- a/flexiapi/resources/views/admin/account/activity/index.blade.php +++ b/flexiapi/resources/views/admin/account/activity/index.blade.php @@ -60,7 +60,7 @@ - accountCreationToken->consumed()) class="disabled crossed" @endif> + accountCreationToken->offed()) class="disabled crossed" @endif> **** {{ $account->accountCreationToken->created_at }} @@ -88,8 +88,8 @@ - @foreach ($account->recoveryCodes as $recoveryCode) - consumed()) class="disabled crossed" @endif> + @foreach ($account->recoveryCodes as $key => $recoveryCode) + offed() || $key > 0) class="disabled crossed" @endif> **** {{ $recoveryCode->created_at }} @@ -119,8 +119,8 @@ - @foreach ($account->phoneChangeCodes as $phoneChangeCode) - consumed()) class="disabled crossed" @endif> + @foreach ($account->phoneChangeCodes as $key => $phoneChangeCode) + offed() || $key > 0) class="disabled crossed" @endif> {{ $phoneChangeCode->phone }} {{ $phoneChangeCode->code ?? '-' }} @@ -151,8 +151,8 @@ - @foreach ($account->emailChangeCodes as $emailChangeCode) - consumed()) class="disabled crossed" @endif> + @foreach ($account->emailChangeCodes as $key => $emailChangeCode) + offed() || $key > 0) class="disabled crossed" @endif> {{ $emailChangeCode->email }} {{ $emailChangeCode->code ?? '-' }} @@ -182,8 +182,8 @@ - @foreach ($account->provisioningTokens as $provisioningToken) - consumed()) class="disabled crossed" @endif> + @foreach ($account->provisioningTokens as $key => $provisioningToken) + offed() || $key > 0) class="disabled crossed" @endif> {{ $provisioningToken->token }} {{ $provisioningToken->created_at }} @@ -200,4 +200,34 @@ @endif +@if ($account->resetPasswordEmailTokens->isNotEmpty()) +

Set Password Emails

+ + + + + + + + + + + @foreach ($account->resetPasswordEmailTokens as $key => $resetPasswordEmailToken) + offed() || $key > 0) class="disabled crossed" @endif> + + + + + + @endforeach + +
TokenCreatedUsedEmail
{{ $resetPasswordEmailToken->token }} + {{ $resetPasswordEmailToken->created_at }} + + {{ $resetPasswordEmailToken->consumed() ? $resetPasswordEmailToken->updated_at : '-' }} + + {{ $resetPasswordEmailToken->email }} +
+@endif + @endsection \ No newline at end of file diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php index af16bf5..09e01d1 100644 --- a/flexiapi/resources/views/admin/account/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/create_edit.blade.php @@ -83,6 +83,14 @@ value="@if($account->id){{ $account->email }}@else{{ old('email') }}@endif"> @include('parts.errors', ['name' => 'email']) + + @if (!empty($account->email)) +

+ + Send an email to the user to reset the password + +

+ @endif
diff --git a/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php b/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php new file mode 100644 index 0000000..b5c0922 --- /dev/null +++ b/flexiapi/resources/views/admin/account/reset_password_email/create.blade.php @@ -0,0 +1,25 @@ +@extends('layouts.main') + +@section('breadcrumb') + @include('admin.account.parts.breadcrumb_accounts_index') + @include('admin.account.parts.breadcrumb_accounts_edit', ['account' => $account]) + +@endsection + +@section('content') + +
+

envelope Send a Reset Password email

+
+ +

An email will be sent to {{ $account->email }} with a unique link allowing the user to reset its password.

+ +

This link will be available for {{ config('app.reset_password_email_token_expiration_minutes')/60 }} hours.

+ +

+ + paper-plane-right Send + +

+ +@endsection \ No newline at end of file diff --git a/flexiapi/resources/views/mails/reset_password.blade.php b/flexiapi/resources/views/mails/reset_password.blade.php new file mode 100644 index 0000000..6a2780a --- /dev/null +++ b/flexiapi/resources/views/mails/reset_password.blade.php @@ -0,0 +1,21 @@ + + + Reset your password on {{ config('app.name') }} + + +

Hello,

+

+ You are invited to reset your {{ $token->account->identifier }} account password on {{ config('app.name') }} via your email account. +

+

The following link will be valid for {{ config('app.reset_password_email_token_expiration_minutes')/60 }} hours.

+

+ + {{ route('account.reset_password_email.change', $token->token) }} + +

+

+ Regards,
+ {{ config('mail.signature') }} +

+ + diff --git a/flexiapi/resources/views/mails/reset_password_text.blade.php b/flexiapi/resources/views/mails/reset_password_text.blade.php new file mode 100644 index 0000000..72d0e48 --- /dev/null +++ b/flexiapi/resources/views/mails/reset_password_text.blade.php @@ -0,0 +1,10 @@ +Hello, + +You are invited to reset your {{ $token->account->identifier }} account password on {{ config('app.name') }} via your email account. + +The following link will be valid for {{ config('app.reset_password_email_token_expiration_minutes')/60 }} hours. + +{{ route('account.reset_password_email.change', $token->token) }} + +Regards, +{{ config('mail.signature') }} \ No newline at end of file diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index 01c2ca0..94f40ae 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -39,6 +39,7 @@ use App\Http\Controllers\Admin\AccountStatisticsController; use App\Http\Controllers\Admin\ContactsListController; use App\Http\Controllers\Admin\ContactsListContactController; use App\Http\Controllers\Admin\PhoneCountryController; +use App\Http\Controllers\Admin\ResetPasswordEmailController; use App\Http\Controllers\Admin\SpaceController; use App\Http\Controllers\Admin\StatisticsController; use Illuminate\Support\Facades\Route; @@ -47,10 +48,14 @@ Route::redirect('/', 'login')->name('account.home'); Route::get('documentation', 'Account\AccountController@documentation')->name('account.documentation'); Route::get('about', 'AboutController@about')->name('about'); -Route::group(['middleware' => 'web_panel_enabled'], function () { +Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::get('login', 'Account\AuthenticateController@login')->name('account.login'); Route::post('authenticate', 'Account\AuthenticateController@authenticate')->name('account.authenticate'); Route::get('authenticate/qrcode/{token?}', 'Account\AuthenticateController@loginAuthToken')->name('account.authenticate.auth_token'); + Route::get('logout', 'Account\AuthenticateController@logout')->name('account.logout'); + + Route::get('reset_password/{token}', 'Account\ResetPasswordEmailController@change')->name('account.reset_password_email.change'); + Route::post('reset_password', 'Account\ResetPasswordEmailController@reset')->name('account.reset_password_email.reset'); // Deprecated Route::get('authenticate/email/{code}', 'Account\AuthenticateController@validateEmail')->name('account.authenticate.email_confirm'); @@ -100,10 +105,6 @@ Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::post('confirm', 'confirm')->name('account.recovery.confirm'); }); - Route::middleware(['auth'])->group(function () { - Route::get('logout', 'Account\AuthenticateController@logout')->name('account.logout'); - }); - Route::name('account.')->middleware(['auth', 'auth.check_blocked'])->group(function () { Route::get('blocked', 'Account\AccountController@blocked')->name('blocked'); @@ -201,6 +202,11 @@ Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::post('{account_id}/contacts_lists', 'contactsListAdd')->name('contacts_lists.attach'); }); + Route::name('reset_password_email.')->controller(ResetPasswordEmailController::class)->prefix('{account_id}/reset_password_emails')->group(function () { + Route::get('create', 'create')->name('create'); + Route::get('send', 'send')->name('send'); + }); + Route::name('import.')->prefix('import')->controller(AccountImportController::class)->group(function () { Route::get('/', 'create')->name('create'); Route::post('/', 'store')->name('store'); diff --git a/flexiapi/tests/TestCase.php b/flexiapi/tests/TestCase.php index daf3f7e..e7cc0c2 100644 --- a/flexiapi/tests/TestCase.php +++ b/flexiapi/tests/TestCase.php @@ -20,7 +20,7 @@ namespace Tests; use App\PhoneCountry; -use App\Http\Middleware\Space; +use App\Http\Middleware\IsSpaceExpired; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -37,7 +37,7 @@ abstract class TestCase extends BaseTestCase { parent::setUp(); - $this->withoutMiddleware([Space::class]); + $this->withoutMiddleware([IsSpaceExpired::class]); PhoneCountry::truncate(); PhoneCountry::factory()->france()->activated()->create(); diff --git a/flexiapi/tests/TestCaseWithSpaceMiddleware.php b/flexiapi/tests/TestCaseWithSpaceMiddleware.php index b0f0ae0..10ac440 100644 --- a/flexiapi/tests/TestCaseWithSpaceMiddleware.php +++ b/flexiapi/tests/TestCaseWithSpaceMiddleware.php @@ -19,7 +19,6 @@ namespace Tests; -use App\Http\Middleware\Space; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Foundation\Testing\RefreshDatabase;