diff --git a/app/Console/Commands/MakeUser.php b/app/Console/Commands/MakeUser.php new file mode 100644 index 0000000..0041dfa --- /dev/null +++ b/app/Console/Commands/MakeUser.php @@ -0,0 +1,153 @@ +components->info("Creating a new Support user..."); + + $firstName = $this->askValid( + 'first_name', + 'First name', + 'Admin', + ['required', 'min:3', 'max:255'], + ); + $lastName = $this->askValid( + 'last_name', + 'Last name', + 'Saïtama', + ['required', 'min:3', 'max:255'], + ); + $email = $this->askValid( + 'email', + 'Email', + 'equipe.web@ldc.fr', + ['required', 'unique:users,email', 'email:rfc,strict,dns,spoof,filter'], + ); + $role = $this->askRole(); + $password = $this->askPassword(); + + $user = new User([ + 'first_name' => $firstName, + 'last_name' => $lastName, + 'email' => $email, + 'role' => $role->value, + 'published' => true, + 'published_at' => Date::now(), + ]); + $user->saveOrFail(); + + $user->userPassword()->create([ + 'password' => bcrypt($password), + ]); + + $this->components->success("User {$firstName} {$lastName} created successfully."); + } + + /** + * Ask for a value and validate it, re-prompting on failure. + * + * @param string $field + * @param string $label + * @param string $default + * @param array $rules + * @return string + */ + private function askValid(string $field, string $label, string $default, array $rules): string + { + try { + $value = $this->ask(sprintf("Type the wanted user %s", $label), $default); + $validator = Validator::make([$field => $value], [$field => $rules]); + $validator->validate(); + return $value; + } catch (ValidationException $e) { + $this->components->error( + collect($e->errors())->flatten()->first() + ); + return $this->askValid($field, $label, $default, $rules); + } + } + + /** + * Ask for a role and return the corresponding RoleEnum. + * + * @return object + */ + private function askRole(): object + { + $options = collect(UserRoleRegistry::all())->mapWithKeys(fn(object $role) => [ + Str::ucFirst($role->label) => $role + ]); + + $choice = $this->choice('Role', $options->keys()->all(), Str::ucFirst(UserRoleRegistry::get('client')->label)); + $choiceKey = is_string($choice) ? $choice : (string) $choice[0]; + return $options->get($choiceKey) ?? UserRoleRegistry::get('client'); + } + + /** + * Ask for a password and confirmation password. + * + * @return string + */ + private function askPassword(): string + { + $password = $this->secret('Type the wanted user Password'); + $passwordConfirmation = $this->secret('Confirm the password'); + + try { + Validator::make( + [ + 'password' => $password, + 'password_confirmation' => $passwordConfirmation, + ], + [ + 'password' => [ + 'required', + 'confirmed', + 'max:255', + Password::min(8)->letters()->mixedCase()->numbers()->symbols()->uncompromised(), + ], + ] + )->validate(); + return $password; + } catch (ValidationException $e) { + $this->components->error(collect($e->errors())->flatten()->first()); + return $this->askPassword(); + } + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php new file mode 100644 index 0000000..6079f06 --- /dev/null +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -0,0 +1,135 @@ +authenticate(); + $request->session()->regenerate(); + + return redirect()->intended(route(config('uwil-backend.routes.name') . 'dashboard.index', absolute: false)); + } + + /** + * Destroy an authenticated session. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function logout(Request $request): \Illuminate\Http\RedirectResponse + { + Auth::guard('backend')->logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return to_route(config('uwil-backend.routes.name') . 'login'); + } + + /** + * Display the forgotten password view. + * + * @return \Inertia\Response + */ + public function forgotPassword(): \Inertia\Response + { + return Inertia::render('back/Pages/Auth/ForgotPassword'); + } + + /** + * Handle an incoming forgotten password request. + * + * @param \App\Http\Requests\Auth\ForgotPasswordRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function postForgotPassword(ForgotPasswordRequest $request): \Illuminate\Http\RedirectResponse + { + $validatedData = $request->validated(); + $status = Password::broker('backend')->sendResetLink($validatedData); + + return $status === Password::RESET_LINK_SENT + ? to_route(config('uwil-backend.routes.name') . 'login') + : back(); + } + + /** + * Display the view. + * + * @param string $token + * @return \Inertia\Response + */ + public function resetPassword(string $token): \Inertia\Response + { + return Inertia::render('back/Pages/Auth/ResetPassword', [ + 'token' => $token, + 'email' => request()->query('email', ''), + ]); + } + + /** + * Handle an incoming resetting password request. + * + * @param \App\Http\Requests\Auth\ResetPasswordRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function postResetPassword(ResetPasswordRequest $request): \Illuminate\Http\RedirectResponse + { + $status = Password::broker('backend')->reset( + $request->validated(), + function (User $user, string $password): void { + $user->setRememberToken(Str::random(60)); + $user->save(); + + UserPassword::updateOrCreate( + ['user_id' => $user->getKey()], + ['password' => Hash::make($password)], + ); + + event(new PasswordReset($user)); + + Mail::send(new ResetPassword($user)); + } + ); + + return $status === Password::PASSWORD_RESET + ? to_route(config('uwil-backend.routes.name') . 'login') + : back(); + } +} diff --git a/app/Http/Middlewares/HandleInertiaRequests.php b/app/Http/Middlewares/HandleInertiaRequests.php index ba2ed54..6db131c 100644 --- a/app/Http/Middlewares/HandleInertiaRequests.php +++ b/app/Http/Middlewares/HandleInertiaRequests.php @@ -21,10 +21,9 @@ class HandleInertiaRequests extends Middleware ...InertiaHelper::errorProps(parent::share($request)), // More props here. ]; - return array_replace_recursive( $props, - InertiaSharedRegistry::resolve($request) + InertiaSharedRegistry::resolve($request), ); } } diff --git a/app/Http/Requests/Auth/ForgotPasswordRequest.php b/app/Http/Requests/Auth/ForgotPasswordRequest.php new file mode 100644 index 0000000..3475921 --- /dev/null +++ b/app/Http/Requests/Auth/ForgotPasswordRequest.php @@ -0,0 +1,35 @@ + + */ + public function rules(): array + { + return [ + 'email' => 'required|string|email:rfc,strict,dns,spoof,filter', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'email' => trans('validation.attributes.email'), + ]; + } +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php new file mode 100644 index 0000000..1e844cd --- /dev/null +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -0,0 +1,109 @@ + + */ + #[\Override] + public function rules(): array + { + return [ + 'email' => 'required|string|email:rfc,strict,dns,spoof,filter', + 'password' => [ + 'required', + 'max:255', + Password::min(8) + ->letters() + ->mixedCase() + ->numbers() + ->symbols() + ->uncompromised() + ], + 'remember' => 'boolean', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'email' => trans('validation.attributes.email'), + 'password' => trans('validation.attributes.password'), + ]; + } + + /** + * Attempt to authenticate the request's credentials. + * + * @return void + * @throws \Illuminate\Validation\ValidationException Wrong email. + */ + #[\Override] + public function authenticate(): void + { + $this->ensureIsNotRateLimited(); + + $user = \App\Models\User::query() + ->where('email', $this->input('email')) + ->first(); + + if ( + !$user + || !$user->userPassword + || !Hash::check($this->input('password'), $user->userPassword->password) + ) { + RateLimiter::hit($this->throttleKey()); + throw ValidationException::withMessages([ + 'email' => trans('auth.failed'), + ]); + } + + Auth::guard('backend')->login($user, $this->boolean('remember')); + RateLimiter::clear($this->throttleKey()); + } + + /** + * Ensure the login request is not rate limited. + * + * @return void + * @throws \Illuminate\Validation\ValidationException Wrong email. + */ + #[\Override] + public function ensureIsNotRateLimited(): void + { + if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { + return; + } + + event(new Lockout($this)); + + $seconds = RateLimiter::availableIn($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => trans('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => ceil($seconds / 60), + ]), + ]); + } +} diff --git a/app/Http/Requests/Auth/ResetPasswordRequest.php b/app/Http/Requests/Auth/ResetPasswordRequest.php new file mode 100644 index 0000000..005a2d9 --- /dev/null +++ b/app/Http/Requests/Auth/ResetPasswordRequest.php @@ -0,0 +1,47 @@ + + */ + public function rules(): array + { + $passwordRules = Password::min(8)->letters()->mixedCase()->numbers()->symbols()->uncompromised(); + return [ + 'token' => 'required|string', + 'email' => 'required|string|email:rfc,strict,dns,spoof,filter', + 'password' => [ + 'required', + 'required_with:password_confirmation', + 'confirmed', + 'max:255', + $passwordRules, + ], + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'token' => trans('validation.custom.token'), + 'email' => trans('validation.attributes.email'), + 'password' => trans('validation.attributes.password'), + ]; + } +} diff --git a/app/Mails/Auth/LinkResetPasswordMail.php b/app/Mails/Auth/LinkResetPasswordMail.php new file mode 100644 index 0000000..4123d2a --- /dev/null +++ b/app/Mails/Auth/LinkResetPasswordMail.php @@ -0,0 +1,51 @@ +user->getEmailForPasswordReset(), + subject: sprintf( + '%s - %s', + trans('auth.email_title_reset_password'), + config('app.name') + ), + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content(): \Illuminate\Mail\Mailables\Content + { + return new Content( + markdown: 'back.mails.link-reset-password', + with: [ + 'url' => $this->url, + 'user' => $this->user, + ], + ); + } +} diff --git a/app/Mails/Auth/ResetPassword.php b/app/Mails/Auth/ResetPassword.php new file mode 100644 index 0000000..0c6919a --- /dev/null +++ b/app/Mails/Auth/ResetPassword.php @@ -0,0 +1,60 @@ +user = clone $user; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope(): \Illuminate\Mail\Mailables\Envelope + { + return new Envelope( + to: $this->user->getEmailForPasswordReset(), + subject: sprintf( + '%s - %s', + trans('auth.email_title_reset_complete'), + config('app.name') + ), + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content(): \Illuminate\Mail\Mailables\Content + { + return new Content( + markdown: 'back.mails.reset-password', + with: [ + 'first_name' => $this->user->first_name, + 'last_name' => $this->user->last_name, + ], + ); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..ea412d2 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,61 @@ +userPassword()->value('password') ?? ''; + } + + /** + * Send the password reset notification to the user. + * + * @param string $token + * @return void + */ + // phpstan-ignore-next-line + // phpcs:ignore + #[\Override] + public function sendPasswordResetNotification($token): void + { + $url = sprintf( + "%s?email=%s", + url(route('back.reset-password', ['token' => $token], false)), + urlencode($this->getEmailForPasswordReset()) + ); + + Mail::send(new LinkResetPasswordMail($url, $this)); + } + + // * RELATIONSHIPS. + + /** + * Get the user's associated password. + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne<\App\Models\UserPassword> + */ + public function userPassword(): \Illuminate\Database\Eloquent\Relations\HasOne + { + return $this->hasOne(UserPassword::class); + } +} \ No newline at end of file diff --git a/app/Models/UserPassword.php b/app/Models/UserPassword.php new file mode 100644 index 0000000..fe50c68 --- /dev/null +++ b/app/Models/UserPassword.php @@ -0,0 +1,25 @@ + + */ + #[\Override] + protected $fillable = [ + 'user_id', + 'password', + ]; +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..2f3e788 --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,22 @@ +withRouting( health: '/up', + web: __DIR__ . '/../routes/auth.php', then: function () { Route::middleware(['web', 'throttle:5,2']) ->post('/csp-report', function () { @@ -55,13 +56,8 @@ return Application::configure(basePath: dirname(__DIR__)) $middleware->preventRequestForgery(except: [ '/csp-report', ]); - $middleware->group('saml', [ - \Uwil\LaravelBackend\Middlewares\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - ]); $middleware->alias([ - 'auth' => \Uwil\LaravelBackend\Middlewares\Authenticate::class, + 'auth' => \Uwil\LaravelBackend\Middlewares\Authenticate::class, 'guest' => \Uwil\LaravelBackend\Middlewares\RedirectIfAuthenticated::class, ]); }) @@ -89,4 +85,8 @@ return Application::configure(basePath: dirname(__DIR__)) ->withInput($safeInput) ->withErrors($e->errors()); }); - })->create(); + }) + ->withCommands([ + \App\Console\Commands\MakeUser::class, + ]) + ->create(); diff --git a/bootstrap/providers.php b/bootstrap/providers.php index b472c6e..b3aeaab 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -4,4 +4,5 @@ return [ Uwil\LaravelCommon\Providers\AppServiceProvider::class, Uwil\LaravelCommon\Providers\TranslationServiceProvider::class, Uwil\K8S\Laravel\K8SServiceProvider::class, + App\Providers\AppServiceProvider::class, ]; diff --git a/composer.json b/composer.json index 7a1d549..5d3ccfb 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,6 @@ "laravel/tinker": "^3.0", "monolog/monolog": "^3.0", "psr/log": "^3.0", - "scaler-tech/laravel-saml2": "^2.7", "tightenco/ziggy": "^2.5", "uwil/admin-secure-laravel": "^5.1", "uwil/laravel-backend": "^5.0", diff --git a/composer.lock b/composer.lock index 878f1b5..2f31974 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ced98c57935b85c05a2f58f91f5347de", + "content-hash": "c7008e1b25e55e3dd4f28ec944534e12", "packages": [ { "name": "brick/math", @@ -1468,16 +1468,16 @@ }, { "name": "laravel/framework", - "version": "v13.6.0", + "version": "v13.7.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "416a93ea9c53161e0d4b8a44045f447b65a7d2f1" + "reference": "f13b85b2cce7ef5e8f3bcdf2b6c6364bbdedae0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/416a93ea9c53161e0d4b8a44045f447b65a7d2f1", - "reference": "416a93ea9c53161e0d4b8a44045f447b65a7d2f1", + "url": "https://api.github.com/repos/laravel/framework/zipball/f13b85b2cce7ef5e8f3bcdf2b6c6364bbdedae0b", + "reference": "f13b85b2cce7ef5e8f3bcdf2b6c6364bbdedae0b", "shasum": "" }, "require": { @@ -1520,6 +1520,7 @@ "symfony/mime": "^7.4.0 || ^8.0.0", "symfony/polyfill-php84": "^1.33", "symfony/polyfill-php85": "^1.33", + "symfony/polyfill-php86": "^1.36", "symfony/process": "^7.4.5 || ^8.0.5", "symfony/routing": "^7.4.0 || ^8.0.0", "symfony/uid": "^7.4.0 || ^8.0.0", @@ -1687,7 +1688,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-04-21T13:32:11+00:00" + "time": "2026-04-28T17:18:25+00:00" }, { "name": "laravel/prompts", @@ -1806,16 +1807,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.12", + "version": "v2.0.13", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919" + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/a6abb4e54f6fcd3138120b9ad497f0bd146f9919", - "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce", "shasum": "" }, "require": { @@ -1863,7 +1864,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-04-14T13:33:34+00:00" + "time": "2026-04-16T14:03:50+00:00" }, { "name": "laravel/telescope", @@ -3446,16 +3447,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.53", + "version": "2.0.54", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3" + "reference": "a96a835067c39ee7a709329fe70869817da18081" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2d1a664b940b9b8f367185307dc010d11a2790f3", - "reference": "2d1a664b940b9b8f367185307dc010d11a2790f3", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/a96a835067c39ee7a709329fe70869817da18081", + "reference": "a96a835067c39ee7a709329fe70869817da18081", "shasum": "" }, "require": { @@ -3536,7 +3537,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.53" + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.54" }, "funding": [ { @@ -3552,7 +3553,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T01:30:02+00:00" + "time": "2026-04-27T06:59:24+00:00" }, { "name": "psr/clock", @@ -5663,20 +5664,20 @@ }, { "name": "symfony/options-resolver", - "version": "v7.4.8", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4" + "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", - "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8", + "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -5710,7 +5711,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.8" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.8" }, "funding": [ { @@ -5730,11 +5731,11 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5793,7 +5794,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -5817,16 +5818,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", - "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", "shasum": "" }, "require": { @@ -5875,7 +5876,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" }, "funding": [ { @@ -5895,11 +5896,11 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -5962,7 +5963,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" }, "funding": [ { @@ -5986,7 +5987,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6047,7 +6048,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" }, "funding": [ { @@ -6071,7 +6072,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -6132,7 +6133,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" }, "funding": [ { @@ -6156,7 +6157,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -6216,7 +6217,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -6240,7 +6241,7 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", @@ -6296,7 +6297,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" }, "funding": [ { @@ -6320,16 +6321,16 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "2c408a6bb0313e6001a83628dc5506100474254e" + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/2c408a6bb0313e6001a83628dc5506100474254e", - "reference": "2c408a6bb0313e6001a83628dc5506100474254e", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", "shasum": "" }, "require": { @@ -6376,7 +6377,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" }, "funding": [ { @@ -6396,11 +6397,91 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:50:15+00:00" + "time": "2026-04-26T13:10:57+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "33d8fc5a705481e21fe3a81212b26f9b1f61749c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/33d8fc5a705481e21fe3a81212b26f9b1f61749c", + "reference": "33d8fc5a705481e21fe3a81212b26f9b1f61749c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -6459,7 +6540,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -7357,16 +7438,16 @@ }, { "name": "uwil/admin-secure-laravel", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "git@lab.s-web.fr:ldc-web/projets/ldc-web-admin-secure-laravel.git", - "reference": "0299d8397111b5a80ea3200a9fd387b94c34f208" + "reference": "c54dad85ee051e103f50dc8f19d457703bc5ff03" }, "dist": { "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/20/packages/composer/archives/uwil/admin-secure-laravel.zip?sha=0299d8397111b5a80ea3200a9fd387b94c34f208", - "reference": "0299d8397111b5a80ea3200a9fd387b94c34f208", + "url": "https://lab.s-web.fr/api/v4/projects/20/packages/composer/archives/uwil/admin-secure-laravel.zip?sha=c54dad85ee051e103f50dc8f19d457703bc5ff03", + "reference": "c54dad85ee051e103f50dc8f19d457703bc5ff03", "shasum": "" }, "require": { @@ -7374,7 +7455,7 @@ "illuminate/support": "~11|~12|~13", "laravel/framework": "~11|~12|~13", "php": "^8.3", - "symfony/options-resolver": "^7.0" + "symfony/options-resolver": "^7.0|^8.0" }, "require-dev": { "larastan/larastan": "^2.9", @@ -7482,17 +7563,11 @@ }, { "name": "uwil/laravel-activity-logs", - "version": "6.0.0", - "source": { - "type": "git", - "url": "git@lab.s-web.fr:ldc-web/projets/laravel-activity-logs.git", - "reference": "765bf7aab42af11e79a64ce1b2245a09abae6fe1" - }, + "version": "6.0.1", "dist": { - "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/229/packages/composer/archives/uwil/laravel-activity-logs.zip?sha=765bf7aab42af11e79a64ce1b2245a09abae6fe1", - "reference": "765bf7aab42af11e79a64ce1b2245a09abae6fe1", - "shasum": "" + "type": "path", + "url": "packages/laravel-activity-logs", + "reference": "988e15d19ea29ad3f9b077057330f226dc963d29" }, "require": { "illuminate/support": "~13", @@ -7502,7 +7577,10 @@ "uwil/laravel-common": "^6.0" }, "require-dev": { + "driftingly/rector-laravel": "^2.3", "larastan/larastan": "^3.9", + "laravel/pint": "^1.29", + "orchestra/testbench": "^11.0", "slevomat/coding-standard": "^8.3", "squizlabs/php_codesniffer": "^3.7" }, @@ -7519,6 +7597,20 @@ "Uwil\\LaravelActivityLogs\\": "src/" } }, + "scripts": { + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" + ], + "phpcs": [ + "./vendor/bin/phpcs" + ], + "phpstan": [ + "./vendor/bin/phpstan" + ], + "rector": [ + "./vendor/bin/rector" + ] + }, "license": [ "MIT" ], @@ -7531,21 +7623,19 @@ "support": { "email": "digital@ldc.fr", "issues": "https://lab.s-web.fr/ldc-web/projets/laravel-activity-logs/-/issues" + }, + "transport-options": { + "symlink": true, + "relative": true } }, { "name": "uwil/laravel-backend", "version": "5.0.1", - "source": { - "type": "git", - "url": "git@lab.s-web.fr:ldc-web/projets/laravel-backend.git", - "reference": "9ec385e80de62ebcb0362911c56a9e7d917bcc80" - }, "dist": { - "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/228/packages/composer/archives/uwil/laravel-backend.zip?sha=9ec385e80de62ebcb0362911c56a9e7d917bcc80", - "reference": "9ec385e80de62ebcb0362911c56a9e7d917bcc80", - "shasum": "" + "type": "path", + "url": "packages/laravel-backend", + "reference": "cabc9caa919d4fdcb509558f631416fbdafcc7d2" }, "require": { "diglactic/laravel-breadcrumbs": "^10.0", @@ -7554,15 +7644,16 @@ "laravel/framework": "~13", "laravel/telescope": "^5.7", "php": "^8.5", + "scaler-tech/laravel-saml2": "^2.7", "sentry/sentry-laravel": "^4.10", "uwil/laravel-activity-logs": "^6.0", "uwil/laravel-common": "^6.0" }, "require-dev": { + "driftingly/rector-laravel": "^2.3", "larastan/larastan": "^3.9", - "orchestra/testbench": "^11.0", - "phpunit/php-code-coverage": "^11.0", - "phpunit/phpunit": "^11.0", + "laravel/pint": "^1.29", + "orchestra/testbench": "^11.1", "slevomat/coding-standard": "^8.3", "squizlabs/php_codesniffer": "^3.7", "uwil/admin-secure-laravel": "^5.1" @@ -7586,8 +7677,8 @@ } }, "scripts": { - "test": [ - "vendor/bin/phpunit" + "post-autoload-dump": [ + "@php ./vendor/bin/testbench package:discover --ansi" ], "phpcs": [ "./vendor/bin/phpcs" @@ -7595,8 +7686,8 @@ "phpstan": [ "./vendor/bin/phpstan" ], - "post-autoload-dump": [ - "@php ./vendor/bin/testbench package:discover --ansi" + "rector": [ + "./vendor/bin/rector" ] }, "license": [ @@ -7611,21 +7702,19 @@ "support": { "email": "digital@ldc.fr", "issues": "https://lab.s-web.fr/ldc-web/projets/laravel-backend/-/issues" + }, + "transport-options": { + "symlink": true, + "relative": true } }, { "name": "uwil/laravel-common", "version": "6.0.0", - "source": { - "type": "git", - "url": "git@lab.s-web.fr:ldc-web/projets/laravel-common.git", - "reference": "dbaeb3f980a1c7a17c92601c090d8687c6bae8e6" - }, "dist": { - "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/215/packages/composer/archives/uwil/laravel-common.zip?sha=dbaeb3f980a1c7a17c92601c090d8687c6bae8e6", - "reference": "dbaeb3f980a1c7a17c92601c090d8687c6bae8e6", - "shasum": "" + "type": "path", + "url": "packages/laravel-common", + "reference": "db3afe457554ee103cac4a92cf601dc6a39780b1" }, "require": { "diglactic/laravel-breadcrumbs": "^10.0", @@ -7639,10 +7728,10 @@ "uwil/laravel-i18n": "^3.0" }, "require-dev": { + "driftingly/rector-laravel": "^2.3", "larastan/larastan": "^3.9", - "orchestra/testbench": "^11.0", - "phpunit/php-code-coverage": "^11.0", - "phpunit/phpunit": "^11.0", + "laravel/pint": "^1.29", + "orchestra/testbench": "^11.1", "slevomat/coding-standard": "^8.3", "squizlabs/php_codesniffer": "^3.7" }, @@ -7655,12 +7744,12 @@ } }, "autoload": { - "files": [ - "src/Helpers/methods.php" - ], "psr-4": { "Uwil\\LaravelCommon\\": "src/" - } + }, + "files": [ + "src/Helpers/methods.php" + ] }, "autoload-dev": { "psr-4": { @@ -7668,11 +7757,17 @@ } }, "scripts": { - "test": [ - "vendor/bin/phpunit" - ], "post-autoload-dump": [ "@php ./vendor/bin/testbench package:discover --ansi" + ], + "phpcs": [ + "./vendor/bin/phpcs" + ], + "phpstan": [ + "./vendor/bin/phpstan" + ], + "rector": [ + "./vendor/bin/rector" ] }, "license": [ @@ -7687,21 +7782,19 @@ "support": { "email": "digital@ldc.fr", "issues": "https://lab.s-web.fr/ldc-web/projets/laravel-common/-/issues" + }, + "transport-options": { + "symlink": true, + "relative": true } }, { "name": "uwil/laravel-i18n", "version": "3.0.0", - "source": { - "type": "git", - "url": "git@lab.s-web.fr:ldc-web/projets/laravel-i18n.git", - "reference": "a87ec12228efd4b13c4a7298631c6dc0f601be47" - }, "dist": { - "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/252/packages/composer/archives/uwil/laravel-i18n.zip?sha=a87ec12228efd4b13c4a7298631c6dc0f601be47", - "reference": "a87ec12228efd4b13c4a7298631c6dc0f601be47", - "shasum": "" + "type": "path", + "url": "packages/laravel-i18n", + "reference": "1d14e78522ec29cf95897fbeb1bd0ffd5279c93a" }, "require": { "illuminate/support": "~13", @@ -7712,10 +7805,10 @@ "uwil/laravel-common": "^6.0" }, "require-dev": { + "driftingly/rector-laravel": "^2.3", "larastan/larastan": "^3.9", + "laravel/pint": "^1.29", "orchestra/testbench": "^11.0", - "phpunit/php-code-coverage": "^11.0", - "phpunit/phpunit": "^11.0", "slevomat/coding-standard": "^8.3", "squizlabs/php_codesniffer": "^3.7" }, @@ -7733,14 +7826,17 @@ } }, "scripts": { + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" + ], "phpcs": [ "./vendor/bin/phpcs" ], "phpstan": [ "./vendor/bin/phpstan" ], - "post-autoload-dump": [ - "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" + "rector": [ + "./vendor/bin/rector" ] }, "license": [ @@ -7755,6 +7851,10 @@ "support": { "email": "digital@ldc.fr", "issues": "https://lab.s-web.fr/ldc-web/projets/laravel-i18n/-/issues" + }, + "transport-options": { + "symlink": true, + "relative": true } }, { @@ -7843,16 +7943,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "d870a33f0f79d2b4579740b0620200221ee44aeb" + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/d870a33f0f79d2b4579740b0620200221ee44aeb", - "reference": "d870a33f0f79d2b4579740b0620200221ee44aeb", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", "shasum": "" }, "require": { @@ -7889,7 +7989,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.1.0" + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" }, "funding": [ { @@ -7913,7 +8013,7 @@ "type": "tidelift" } ], - "time": "2026-04-16T23:10:39+00:00" + "time": "2026-04-26T05:33:54+00:00" } ], "packages-dev": [ @@ -8944,11 +9044,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.51", + "version": "2.1.53", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", - "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ef67586798c003274797b288a68b221e4270dca7", + "reference": "ef67586798c003274797b288a68b221e4270dca7", "shasum": "" }, "require": { @@ -8993,7 +9093,7 @@ "type": "github" } ], - "time": "2026-04-21T18:22:01+00:00" + "time": "2026-04-28T16:09:00+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/config/auth.php b/config/auth.php index 5cb5e72..a27774f 100644 --- a/config/auth.php +++ b/config/auth.php @@ -40,11 +40,6 @@ return [ 'driver' => 'session', 'provider' => 'users', ], - - 'frontend' => [ - 'driver' => 'session', - 'provider' => 'customers', - ], ], /** @@ -67,7 +62,7 @@ return [ 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => env('AUTH_MODEL', Uwil\LaravelBackend\Models\User::class), + 'model' => env('AUTH_MODEL', App\Models\User::class), ], ], @@ -91,10 +86,10 @@ return [ */ 'passwords' => [ - 'users' => [ + 'backend' => [ 'provider' => 'users', - 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), - 'expire' => 60, + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, 'throttle' => 60, ], ], diff --git a/config/saml2.php b/config/saml2.php deleted file mode 100644 index f84b2e4..0000000 --- a/config/saml2.php +++ /dev/null @@ -1,397 +0,0 @@ - \Slides\Saml2\Models\Tenant::class, - - /** - *-------------------------------------------------------------------------- - * Use built-in routes - *-------------------------------------------------------------------------- - * - * If "useRoutes" set to true, the package defines five new routes: - * - * Method | URI | Name - * -------|---------------------------------|------------------ - * POST | {routesPrefix}/{uuid}/acs | saml.acs - * GET | {routesPrefix}/{uuid}/login | saml.login - * GET | {routesPrefix}/{uuid}/logout | saml.logout - * GET | {routesPrefix}/{uuid}/metadata | saml.metadata - * GET | {routesPrefix}/{uuid}/sls | saml.sls - * - */ - - 'useRoutes' => true, - - /** - *-------------------------------------------------------------------------- - * Built-in routes prefix - *-------------------------------------------------------------------------- - * - * Here you may define the prefix for built-in routes. - * - */ - - 'routesPrefix' => '/saml2', - - /** - *-------------------------------------------------------------------------- - * Middle groups to use for the SAML routes - *-------------------------------------------------------------------------- - * - * Note, Laravel 5.2 requires a group which includes StartSession - * - */ - - 'routesMiddleware' => ['saml'], - - /** - *-------------------------------------------------------------------------- - * Signature validation - *-------------------------------------------------------------------------- - * - * Set to true if you want to use parameters from $_SERVER to validate the signature. - * - */ - - 'retrieveParametersFromServer' => false, - - /** - *-------------------------------------------------------------------------- - * Login redirection URL. - *-------------------------------------------------------------------------- - * - * The redirection URL after successful login. - * - */ - - 'loginRoute' => '/admin/login', - - /** - *-------------------------------------------------------------------------- - * Logout redirection URL. - *-------------------------------------------------------------------------- - * - * The redirection URL after successful logout. - * - */ - - 'logoutRoute' => '/admin/login', - - /** - *-------------------------------------------------------------------------- - * Login error redirection URL. - *-------------------------------------------------------------------------- - * - * The redirection URL after login failing. - * - */ - - 'errorRoute' => '/admin/login', - - /** - *-------------------------------------------------------------------------- - * Strict mode. - *-------------------------------------------------------------------------- - * - * If 'strict' is True, then the PHP Toolkit will reject unsigned - * or unencrypted messages if it expects them signed or encrypted - * Also will reject the messages if not strictly follow the SAML - * standard: Destination, NameId, Conditions... are validated too. - * - */ - - 'strict' => true, - - /** - *-------------------------------------------------------------------------- - * Debug mode. - *-------------------------------------------------------------------------- - * - * When enabled, errors must be printed. - * - */ - - 'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)), - - /** - *-------------------------------------------------------------------------- - * Whether to use `X-Forwarded-*` headers to determine port/domain/protocol. - *-------------------------------------------------------------------------- - * - * If 'proxyVars' is True, then the Saml lib will trust proxy headers - * e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if - * your application is running behind a load balancer which terminates SSL. - * - */ - - 'proxyVars' => true, - - /** - *-------------------------------------------------------------------------- - * Service Provider configuration. - *-------------------------------------------------------------------------- - * - * General setting of the service provider. - * - */ - - 'sp' => [ - - /** - *-------------------------------------------------------------------------- - * NameID format. - *-------------------------------------------------------------------------- - * - * Specifies constraints on the name identifier to be used to - * represent the requested subject. - * - */ - - 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', - - /** - *-------------------------------------------------------------------------- - * SP Certificates. - *-------------------------------------------------------------------------- - * - * Usually x509cert and privateKey of the SP are provided by files placed at - * the certs folder. But we can also provide them with the following parameters. - * - */ - - 'x509cert' => env('SAML2_SP_CERT_x509', ''), - 'privateKey' => env('SAML2_SP_CERT_PRIVATEKEY', ''), - - /** - *-------------------------------------------------------------------------- - * Identifier (URI) of the SP entity. - *-------------------------------------------------------------------------- - * - * Leave blank to use the 'saml.metadata' route. - * - */ - - 'entityId' => env('SAML2_SP_ENTITYID', ''), - - /** - *-------------------------------------------------------------------------- - * The Assertion Consumer Service (ACS) URL. - *-------------------------------------------------------------------------- - * - * URL Location where the from the IdP will be returned, using HTTP-POST binding. - * Leave blank to use the 'saml.acs' route. - * - */ - - 'assertionConsumerService' => [ - 'url' => '', - ], - - /** - *-------------------------------------------------------------------------- - * The Single Logout Service URL. - *-------------------------------------------------------------------------- - * - * Specifies info about where and how the message MUST be - * returned to the requester, in this case our SP. - * - * URL Location where the from the IdP will be returned, using HTTP-Redirect binding. - * Leave blank to use the 'saml.sls' route. - * - */ - - 'singleLogoutService' => [ - 'url' => '' - ], - ], - - /** - *-------------------------------------------------------------------------- - * OneLogin security settings. - *-------------------------------------------------------------------------- - */ - - 'security' => [ - - 'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', - 'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256', - - /** - *-------------------------------------------------------------------------- - * NameId encryption - *-------------------------------------------------------------------------- - * - * Indicates that the nameID of the sent by this SP - * will be encrypted. - * - */ - - 'nameIdEncrypted' => true, - - /** - *-------------------------------------------------------------------------- - * AuthnRequest signage - *-------------------------------------------------------------------------- - * - * Indicates whether the messages sent by - * this SP will be signed. The Metadata of the SP will offer this info - * - */ - - 'authnRequestsSigned' => true, - - /** - *-------------------------------------------------------------------------- - * Logout request signage - *-------------------------------------------------------------------------- - * - * Indicates whether the messages sent by this SP - * will be signed. - * - */ - - 'logoutRequestSigned' => true, - - /** - *-------------------------------------------------------------------------- - * Logout response signage - *-------------------------------------------------------------------------- - * - * Indicates whether the messages sent by this SP - * will be signed. - * - */ - - 'logoutResponseSigned' => true, - - /** - *-------------------------------------------------------------------------- - * Whether need to sign metadata. - *-------------------------------------------------------------------------- - * - * The possible values: - * - false - * - true (use certs) - * - array: - * ``` - * [ - * 'keyFileName' => 'metadata.key', - * 'certFileName' => 'metadata.crt' - * ] - * ``` - * - */ - - 'signMetadata' => true, - - /** - *-------------------------------------------------------------------------- - * Requirement to sign messages. - *-------------------------------------------------------------------------- - * - * Indicates a requirement for the , and - * elements received by this SP to be signed. - * - */ - - 'wantMessagesSigned' => true, - - /** - *-------------------------------------------------------------------------- - * Requirement to sign assertion elements. - *-------------------------------------------------------------------------- - * - * Indicates a requirement for the elements received by - * this SP to be signed. - * - */ - - 'wantAssertionsSigned' => true, - - /** - *-------------------------------------------------------------------------- - * Requirement to encrypt NameID. - *-------------------------------------------------------------------------- - * - * Indicates a requirement for the NameID received by this SP to be encrypted. - * - */ - - 'wantNameIdEncrypted' => false, - - /** - *-------------------------------------------------------------------------- - * Authentication context. - *-------------------------------------------------------------------------- - * - * Set to false and no AuthContext will be sent in the AuthNRequest, - * - * Set true or don't present this parameter and you will get an - * AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' - * - * Set an array with the possible auth context values: - * ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'] - * - */ - - 'requestedAuthnContext' => true, - ], - - /** - *-------------------------------------------------------------------------- - * Contact information. - *-------------------------------------------------------------------------- - * - * It is recommended to supply a technical and support contacts. - * - */ - - 'contactPerson' => [ - 'technical' => [ - 'givenName' => env('SAML2_CONTACT_TECHNICAL_NAME', env('APP_NAME', 'Technical')), - 'emailAddress' => env('SAML2_CONTACT_TECHNICAL_EMAIL', 'no@reply.com') - ], - 'support' => [ - 'givenName' => env('SAML2_CONTACT_SUPPORT_NAME', env('APP_NAME', 'Support')), - 'emailAddress' => env('SAML2_CONTACT_SUPPORT_EMAIL', 'no@reply.com') - ], - ], - - /** - *-------------------------------------------------------------------------- - * Organization information. - *-------------------------------------------------------------------------- - * - * The info in en_US lang is recommended, add more if required. - * - */ - - 'organization' => [ - 'en-US' => [ - 'name' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')), - 'displayname' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')), - 'url' => env('SAML2_ORGANIZATION_URL', env('APP_URL', 'http://url')) - ], - ], - - /** - * Default tenant UUID. - */ - - 'default_tenant_uuid' => env('SAML2_DEFAULT_TENANT_UUID', null), - - /** - * Target domain. - */ - 'domain' => env('SAML2_DOMAIN', "") -]; diff --git a/config/telescope.php b/config/telescope.php index 3d4c080..aa056af 100644 --- a/config/telescope.php +++ b/config/telescope.php @@ -42,7 +42,7 @@ return [ * */ - 'path' => env('TELESCOPE_PATH', 'admin/telescope'), + 'path' => env('TELESCOPE_PATH', config('uwil-backend.routes.prefix') . '/telescope'), /** *-------------------------------------------------------------------------- diff --git a/config/uwil-backend.php b/config/uwil-backend.php new file mode 100644 index 0000000..2530a9f --- /dev/null +++ b/config/uwil-backend.php @@ -0,0 +1,32 @@ + [ + 'prefix' => '/', + 'name' => 'back.', + ], + + /** + *-------------------------------------------------------------------------- + * Saml + *-------------------------------------------------------------------------- + * + * This option may be used to active or not the saml connection. + * - enabled: boolean. + */ + + 'saml' => [ + 'enabled' => false, + ], +]; diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/database/migrations/2026_04_28_145141_add_password_users_table.php b/database/migrations/2026_04_28_145141_add_password_users_table.php new file mode 100644 index 0000000..cf07bc1 --- /dev/null +++ b/database/migrations/2026_04_28_145141_add_password_users_table.php @@ -0,0 +1,35 @@ +id()->comment('Id of the user password.');; + $table->foreignId('user_id')->unique()->comment('Associated user.') + ->constrained('users')->cascadeOnDelete(); + $table->string('password')->comment('Password of the user.'); + $table->timestamp('created_at')->nullable()->comment('Date the user password was created.'); + $table->timestamp('updated_at')->nullable()->comment('Date the user password was updated.'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('user_passwords'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_04_28_165712_add_password_reset_tokens_table.php b/database/migrations/2026_04_28_165712_add_password_reset_tokens_table.php new file mode 100644 index 0000000..a087a64 --- /dev/null +++ b/database/migrations/2026_04_28_165712_add_password_reset_tokens_table.php @@ -0,0 +1,32 @@ +string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('password_reset_tokens'); + } +}; \ No newline at end of file diff --git a/lang/fr/auth.php b/lang/fr/auth.php new file mode 100644 index 0000000..c00a51d --- /dev/null +++ b/lang/fr/auth.php @@ -0,0 +1,44 @@ + 'Ces identifiants ne correspondent pas à nos enregistrements.', + 'password' => 'Le mot de passe fourni est incorrect.', + 'throttle' => 'Tentatives de connexion trop nombreuses. Veuillez réessayer dans :seconds secondes.', + "login" => "Se connecter", + "remember" => "Se souvenir de moi", + "forgot_password" => "Mot de passe oublié", + "forgot_password_desc" => "Renseignez votre email pour recevoir un lien de réinitialisation.", + "description" => "Connectez-vous afin d'accéder à notre outil :appName !", + "connected" => "Connecté !", + "connected_desc" => "Vous êtes à présent authentifié au Support UWIL.", + "send_link" => "Envoyer le lien", + "mail_sent" => "Mail envoyé !", + "mail_sent_desc" => "Si un compte est associé à l'adresse :email, un e-mail de réinitialisation du mot de passe + vous sera envoyé dans quelques instants.", + "reset_password" => "Réinitialisation du mot de passe", + "reset_password_desc" => "Choisissez un nouveau mot de passe pour votre compte.", + "reset_your_password" => "Réinitialiser votre mot de passe", + "reset_sent" => "Réinitialisation du mot de passe effectué !", + "reset_sent_desc" => "Mot de passe mis à jour avec succès. Vous pouvez dès à présent vous connecter + avec vos nouveaux identifiants.", + 'email_hello' => 'Bonjour !', + 'email_get_reset_password' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation + du mot de passe de votre compte.', + 'email_reset_link_expire' => 'Ce lien expirera dans :count minutes.', + 'email_button_redirect' => 'Cliquez sur le bouton suivant afin de procéder au changement:', + 'email_reset_password' => 'Réinitialiser le mot de passe', + 'email_wrong_destination' => 'Si vous n\'avez pas demandé la réinitialisation de votre mot de passe, aucune + autre action n\'est requise.', + 'email_cant_visualise' => 'Si vous avez des difficultés à cliquer sur le bouton \':actionText\' ou si vous ne + le voyez pas, copiez ou cliquez sur le lien suivant:', + 'email_reset_complete' => 'La réinitialisation du mot de passe associé à votre compte a bien été + effectuée.', + 'email_new_connection' => 'Vous pouvez désormais vour connecter avec votre nouvel identifiant et + mot de passe en cliquant sur le bouton suivant:', + 'email_title_reset_password' => 'Réinitialisation du mot de passe', + 'email_title_reset_complete' => 'Mot de passe modifié', + 'reset_password' => 'Réinitialiser mot de passe', + 'reset_password_desc' => 'Formulaire de réinitialisation du mot de passe de votre compte au back office.', +]; diff --git a/lang/fr/back.php b/lang/fr/back.php index 7570e5e..4e67eb0 100644 --- a/lang/fr/back.php +++ b/lang/fr/back.php @@ -3,5 +3,5 @@ declare(strict_types=1); return [ - // "dashboard" => "Tableau de bord", + "your_field" => "Votre :field", ]; diff --git a/lang/fr/enums.php b/lang/fr/enums.php index e8aff62..d91d287 100644 --- a/lang/fr/enums.php +++ b/lang/fr/enums.php @@ -6,5 +6,6 @@ return [ "user-role" => [ "project-manager" => "Chef de projet", "developper" => "Développeur", + "client" => "Client", ], ]; diff --git a/lang/fr/front.php b/lang/fr/front.php deleted file mode 100644 index 13f78de..0000000 --- a/lang/fr/front.php +++ /dev/null @@ -1,6 +0,0 @@ - "Bonjour Monde", - "back_to_your_website" => "Revenir à la page d'accueil", -]; diff --git a/lang/fr/validation.php b/lang/fr/validation.php index b72159a..99d60a5 100644 --- a/lang/fr/validation.php +++ b/lang/fr/validation.php @@ -4,6 +4,7 @@ declare(strict_types=1); return [ "custom" => [ - // "published" => "publié", + "token" => "token", + "new_password" => "nouveau mot de passe", ], ]; \ No newline at end of file diff --git a/manifest.yaml b/manifest.yaml index 2eb7bba..fa51b22 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -38,11 +38,6 @@ data: SCOUT_QUEUE: "${APP_SCOUT_QUEUE}" MEILISEARCH_HOST: "meilisearch.common-services:7700" MEILISEARCH_KEY: "${APP_MEILISEARCH_KEY}" - SAML2_SP_CERT_x509: "${APP_SAML2_SP_CERT_x509}" - SAML2_SP_CERT_PRIVATEKEY: "${APP_SAML2_SP_CERT_PRIVATEKEY}" - SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}" - SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}" - SAML2_DEFAULT_TENANT_UUID: "${APP_SAML2_DEFAULT_TENANT_UUID}" UWIL_ADMIN_SECURE_CIDR_LIST: "${APP_GLOBAL_UWIL_ADMIN_SECURE_CIDR_LIST}" --- apiVersion: v1 diff --git a/resources/ts/back/Components/Inputs/SimpleSwitch.tsx b/resources/ts/back/Components/Inputs/SimpleSwitch.tsx index e10d164..c123e60 100644 --- a/resources/ts/back/Components/Inputs/SimpleSwitch.tsx +++ b/resources/ts/back/Components/Inputs/SimpleSwitch.tsx @@ -9,6 +9,7 @@ import InputError from "@/back/Components/InputError"; import { cn } from "@/lib/utils"; type SimpleSwitchProps = { + id?: string; name: string; checked?: boolean; label?: ReactNode | string; @@ -24,6 +25,7 @@ type SimpleSwitchProps = { export default function SimpleSwitch({ name, + id = name, checked, label, leftHelper, @@ -48,7 +50,7 @@ export default function SimpleSwitch({
{leftHelper && {leftHelper}} setData(name, value)} required={required} diff --git a/resources/ts/back/Components/Layout/AppFooter.tsx b/resources/ts/back/Components/Layout/AppFooter.tsx index 5037847..a406529 100644 --- a/resources/ts/back/Components/Layout/AppFooter.tsx +++ b/resources/ts/back/Components/Layout/AppFooter.tsx @@ -10,9 +10,9 @@ export default function AppFooter() { const appName = (usePage().props.appEnv as AppEnv).name; const year = new Date().getFullYear(); return ( -
+
-

+

uwil/{str(appName).slug().value()} - UWIL © {year}

diff --git a/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx b/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx index 76a317b..86ade70 100644 --- a/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx +++ b/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx @@ -46,8 +46,8 @@ import { SidebarRegistry } from "@/back/Components/Layout/Sidebar/SidebarRegistr import Views from "@/back/lib/actions"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { AppEnv, LaravelEnum } from "@/types/global"; -import { AuthProp } from "@/back/types/global"; +import { AppEnv } from "@/types/global"; +import { AuthProp, UserModel } from "@/back/types/global"; import { GetTranslation } from "@/lib/utils"; export type NavListLinksProps = { @@ -124,10 +124,11 @@ export default function AppSidebar() { const { appEnv, auth, enumUserRole } = usePage().props as unknown as { appEnv: AppEnv; auth: AuthProp; - enumUserRole: LaravelEnum; + enumUserRole: UserModel["role"][]; }; const { state } = useSidebar(); const prevStateRef = useRef(state); + const isConceptor = enumUserRole.find((role) => role.name === "conceptor"); const isCollapsed = state === "collapsed"; // Translations. @@ -165,7 +166,7 @@ export default function AppSidebar() { }, { title: str(transAdmin).plural().value(), - show: auth.user.role === enumUserRole.conceptor.value, + show: auth.user.role.value === isConceptor!.value, links: [ ...SidebarRegistry.getAll(auth, "admin"), { diff --git a/resources/ts/back/Layouts/AuthenticatedLayout.tsx b/resources/ts/back/Layouts/AuthenticatedLayout.tsx index df02bfc..5200e6e 100644 --- a/resources/ts/back/Layouts/AuthenticatedLayout.tsx +++ b/resources/ts/back/Layouts/AuthenticatedLayout.tsx @@ -51,7 +51,7 @@ function LayoutContent({ exit={{ opacity: 0 }} transition={{ duration: 0.3 }} style={{ maxWidth: `calc(100% - ${sidebarVar})` }} - className="relative flex flex-col justify-between transition-[max-width] duration-200 ease-linear w-full px-8 pb-4" + className="relative flex flex-col justify-between transition-[max-width] duration-200 ease-linear w-full px-8" >
diff --git a/resources/ts/back/Layouts/GuestLayout.tsx b/resources/ts/back/Layouts/GuestLayout.tsx index d5e1213..7b64049 100644 --- a/resources/ts/back/Layouts/GuestLayout.tsx +++ b/resources/ts/back/Layouts/GuestLayout.tsx @@ -2,20 +2,26 @@ import { ReactNode } from "react"; // * Components shadcn import { Card } from "@/components/ui/card"; +import { Toaster } from "@/components/ui/toaster"; // * Other import ApplicationLogo from "@/back/Components/ApplicationLogo"; import AppSettings from "@/back/Components/Layout/Settings/AppSettings"; import { ThemeProvider } from "@/back/providers/ThemeProvider"; +import AppFooter from "@/back/Components/Layout/AppFooter"; export default function GuestLayout({ children }: { children: ReactNode }) { return ( - -
- - - {children} - +
+ +
+ + + {children} + +
+ +
); diff --git a/resources/ts/back/Pages/Auth/ForgotPassword.tsx b/resources/ts/back/Pages/Auth/ForgotPassword.tsx new file mode 100644 index 0000000..bf83fec --- /dev/null +++ b/resources/ts/back/Pages/Auth/ForgotPassword.tsx @@ -0,0 +1,87 @@ +// * Packages. +import { Head, Link, useForm } from "@inertiajs/react"; +import { route } from "ziggy-js"; +import { LoaderCircleIcon, MoveLeftIcon, SendIcon } from "lucide-react"; +import { SubmitEvent } from "react"; +// * Components shadcn. +import { Button } from "@/components/ui/button"; +import { + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +// * Other. +import GuestLayout from "@/back/Layouts/GuestLayout"; +import SimpleString from "@/back/Components/Inputs/SimpleString"; +import useTrans from "@/hooks/use-translations"; +import { toast } from "@/hooks/use-toast"; + +export default function ForgotPassword() { + const { post, setData, data, processing } = useForm({ + email: "", + }); + + const mailSent = useTrans("auth.mail_sent"); + const mailSentDesc = useTrans("auth.mail_sent_desc", { + params: { email: data.email }, + }); + + const submit = (event: SubmitEvent) => { + event.preventDefault(); + post(route("back.forgot-password.post"), { + onSuccess: () => { + toast({ + title: mailSent, + description: mailSentDesc, + }); + }, + }); + }; + + return ( + + + + +

+ {useTrans("auth.forgot_password")} +

+
+ + {useTrans("auth.forgot_password_desc")} + +
+ +
+ +
+ + + {useTrans("auth.login")} + + +
+ +
+
+ ); +} diff --git a/resources/ts/back/Pages/Auth/Login.tsx b/resources/ts/back/Pages/Auth/Login.tsx index c82ef21..e0a549f 100644 --- a/resources/ts/back/Pages/Auth/Login.tsx +++ b/resources/ts/back/Pages/Auth/Login.tsx @@ -1,8 +1,9 @@ -// * Packages -import { Head, usePage } from "@inertiajs/react"; -import { SquareArrowOutUpRightIcon } from "lucide-react"; +// * Packages. +import { Head, Link, useForm, usePage } from "@inertiajs/react"; import { route } from "ziggy-js"; -// * Components shadcn +import { SubmitEvent } from "react"; +import { LoaderCircleIcon, LogInIcon, MoveRightIcon } from "lucide-react"; +// * Components shadcn. import { Button } from "@/components/ui/button"; import { CardContent, @@ -10,14 +11,40 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -// * Other +// * Other. import GuestLayout from "@/back/Layouts/GuestLayout"; -import InputError from "@/back/Components/InputError"; +import SimpleString from "@/back/Components/Inputs/SimpleString"; import useTrans from "@/hooks/use-translations"; import { AppEnv } from "@/types/global"; +import { toast } from "@/hooks/use-toast"; +import SimpleSwitch from "@/back/Components/Inputs/SimpleSwitch"; -export default function Login({ tenantUUID }: { tenantUUID: string }) { +export default function Login() { const appName = (usePage().props.appEnv as AppEnv).name; + + /** Translations. */ + const transConnected = useTrans("auth.connected"); + const transConnectedDesc = useTrans("auth.connected_desc"); + + /** Form section. */ + const { post, setData, data, processing } = useForm({ + email: null, + password: null, + remember: false, + }); + + const submit = (event: SubmitEvent) => { + event.preventDefault(); + post(route("back.login.post"), { + onSuccess: () => { + toast({ + title: transConnected, + description: transConnectedDesc, + }); + }, + }); + }; + return ( @@ -27,25 +54,72 @@ export default function Login({ tenantUUID }: { tenantUUID: string }) { {useTrans("uwil-backend::back.login_title")} - -

- + - - +

+ + +
+ + +
+
+ + {useTrans("auth.forgot_password")} + + + +
+
); diff --git a/resources/ts/back/Pages/Auth/ResetPassword.tsx b/resources/ts/back/Pages/Auth/ResetPassword.tsx new file mode 100644 index 0000000..cab5f64 --- /dev/null +++ b/resources/ts/back/Pages/Auth/ResetPassword.tsx @@ -0,0 +1,114 @@ +// * Packages. +import { SubmitEvent } from "react"; +import { Head, useForm } from "@inertiajs/react"; +import { route } from "ziggy-js"; +import { LoaderCircleIcon, ShieldCheckIcon } from "lucide-react"; +// * Components shadcn. +import { Button } from "@/components/ui/button"; +import { + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +// * Other. +import GuestLayout from "@/back/Layouts/GuestLayout"; +import SimpleString from "@/back/Components/Inputs/SimpleString"; +import useTrans from "@/hooks/use-translations"; +import { toast } from "@/hooks/use-toast"; + +type ResetPasswordProps = { + token: string; + email: string; +}; + +export default function ResetPassword({ token, email }: ResetPasswordProps) { + const resetSent = useTrans("auth.reset_sent"); + const resetSentDesc = useTrans("auth.reset_sent_desc"); + + const { post, setData, processing } = useForm({ + token, + email, + password: "", + password_confirmation: "", + }); + + const submit = (event: SubmitEvent) => { + event.preventDefault(); + post(route("back.reset-password.post"), { + onSuccess: () => { + toast({ + title: resetSent, + description: resetSentDesc, + }); + }, + }); + }; + + return ( + + + + +

+ {useTrans("auth.reset_password")} +

+
+ + {useTrans("auth.reset_password_desc")} + +
+ +
+ {}} + disabled={true} + /> + + +
+ +
+ +
+
+ ); +} diff --git a/resources/ts/back/Pages/Errors/Default.tsx b/resources/ts/back/Pages/Errors/Default.tsx index 7d99860..473ef2d 100644 --- a/resources/ts/back/Pages/Errors/Default.tsx +++ b/resources/ts/back/Pages/Errors/Default.tsx @@ -15,7 +15,7 @@ export default function Default({ status }: { status: number }) { -
+

@@ -30,9 +30,6 @@ export default function Default({ status }: { status: number }) { {useTrans("uwil-backend::back.dashboard")} -

diff --git a/resources/ts/back/Pages/Users/Form.tsx b/resources/ts/back/Pages/Users/Form.tsx index e6c4985..e94c2de 100644 --- a/resources/ts/back/Pages/Users/Form.tsx +++ b/resources/ts/back/Pages/Users/Form.tsx @@ -9,7 +9,6 @@ import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import InputError from "@/back/Components/InputError"; -import { LaravelEnum } from "@/types/global"; import { AuthProp, UserModel } from "@/back/types/global"; import SubmitButton from "@/back/Components/SubmitButton"; import useCrudSubmit from "@/back/hooks/use-crud-submit"; @@ -28,8 +27,8 @@ export default function Form({ method: number; }) { const userModel = (usePage().props.auth as AuthProp).user; - const enumUserRole = usePage().props.enumUserRole as LaravelEnum; - const isConceptor = userModel.role === enumUserRole.conceptor.value; + const enumUserRole = usePage().props.enumUserRole as UserModel["role"][]; + const isConceptor = enumUserRole.find((role) => role.name === "conceptor"); const filterRoles = (value: number) => { return ( Object.entries(enumUserRole) @@ -46,7 +45,7 @@ export default function Form({ email: model?.email, published: model?.published ?? false, published_at: model?.published_at, - role: model?.role, + role: model && model.role ? model.role.value : undefined, }); const submit = useCrudSubmit({ @@ -141,26 +140,26 @@ export default function Form({ .ucFirst() .value()} placeholder="ex: john.doe@gmail.com" - disabled={!isConceptor} - wordCounter={isConceptor} - value={!isConceptor ? model?.email : data.email} + disabled={!isConceptor!.value} + wordCounter={Boolean(isConceptor!.value)} + value={!isConceptor!.value ? model?.email : data.email} setData={setData} /> - {isConceptor && ( + {Boolean(isConceptor!.value) && ( setData(name, value?.value ?? value)} /> )} - {isConceptor && ( + {userModel.role.value === isConceptor!.value && ( { +const getColumns = () => { return [ { accessorKey: "first_name", @@ -80,19 +79,14 @@ const getColumns = (enumUserRole: LaravelEnum) => { field={column.id} /> ), - cell: ({ row }: { row: Row }) => ( - - {str( - String( - Object.values(enumUserRole).find( - (role) => role.value === row.getValue("role"), - )?.label, - ), - ) - .ucFirst() - .value()} - - ), + cell: ({ row }: { row: Row }) => { + const rowRole = row.getValue("role") as UserModel["role"]; + return ( + + {str(String(rowRole?.label ?? '')).ucFirst().value()} + + ); + }, }, { accessorKey: "published", @@ -160,9 +154,8 @@ export default function Index({ userModels: Resource; searchFields: string; }) { - const { auth, enumUserRole } = usePage().props as unknown as { + const { auth } = usePage().props as unknown as { auth: AuthProp; - enumUserRole: LaravelEnum; }; const { create } = auth.policies.users; const modelName = usePage().props.modelName as string; @@ -173,7 +166,7 @@ export default function Index({ Views.DELETION, ]; - const columns = getColumns(enumUserRole); + const columns = getColumns(); const transCrudAddEntry = useTrans("uwil-backend::back.crud_add_entry"); const afterNode = () => { return ( diff --git a/resources/ts/back/Pages/Users/Show.tsx b/resources/ts/back/Pages/Users/Show.tsx index 71662bc..0fb58ff 100644 --- a/resources/ts/back/Pages/Users/Show.tsx +++ b/resources/ts/back/Pages/Users/Show.tsx @@ -11,12 +11,11 @@ import ModelActions from "@/back/Components/ModelActions"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import Views from "@/back/lib/actions"; import { GetFormatedDate, GetTranslation } from "@/lib/utils"; -import { LaravelEnum } from "@/types/global"; import { AuthProp, SingleResource, UserModel } from "@/back/types/global"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -const getColumns = (enumUserRole: LaravelEnum) => { +const getColumns = () => { return [ { accessorKey: "first_name", @@ -54,16 +53,9 @@ const getColumns = (enumUserRole: LaravelEnum) => { header: str(GetTranslation(`uwil-backend::validation.attributes.role`)) .ucFirst() .value(), - cell: ({ value }: { value: number }) => ( + cell: ({ value }: { value: UserModel["role"] }) => ( - {str( - String( - Object.values(enumUserRole).find((role) => role.value === value) - ?.label, - ), - ) - .ucFirst() - .value()} + {str(String(value?.label ?? '')).ucFirst().value()} ), }, @@ -133,15 +125,14 @@ const getColumns = (enumUserRole: LaravelEnum) => { }; export default function Show({ data }: { data: SingleResource }) { - const { auth, enumUserRole } = usePage().props as unknown as { + const { auth } = usePage().props as unknown as { auth: AuthProp; - enumUserRole: LaravelEnum; }; const { viewAny } = auth.policies.users; const userModel = data.data as UserModel; const actionModels = [Views.EDITION, Views.DUPLICATION, Views.DELETION]; - const columns = getColumns(enumUserRole); + const columns = getColumns(); const transCrudBackList = useTrans("uwil-backend::back.crud_back_to_list"); const beforeNode = () => { diff --git a/resources/ts/back/types/global.d.ts b/resources/ts/back/types/global.d.ts index 48726a3..133ec52 100644 --- a/resources/ts/back/types/global.d.ts +++ b/resources/ts/back/types/global.d.ts @@ -25,7 +25,7 @@ type UserModel = { first_name: string; last_name: string; email: string; - role: number; + role: { value: number; label: string; name: string }; published: boolean; published_at: Date; created_at: Date; diff --git a/resources/views/back/mails/link-reset-password.blade.php b/resources/views/back/mails/link-reset-password.blade.php new file mode 100644 index 0000000..a961281 --- /dev/null +++ b/resources/views/back/mails/link-reset-password.blade.php @@ -0,0 +1,23 @@ +{{-- blade-formatter-disable --}} +@component('mail::message') + +# {{ __('auth.email_hello') }} + +{{ __('auth.email_get_reset_password') }} +{{ __('auth.email_reset_link_expire', ['count' => config('auth.passwords.users.expire')]) }} + +{{ __('auth.email_button_redirect') }} +@component('mail::button', ['url' => $url]) +{{ __('auth.email_reset_password') }} +@endcomponent + +{{ __('auth.email_wrong_destination') }} + +{{ config('app.name') }}. + +
+ +{{ __('auth.email_cant_visualise', ['actionText' => __('auth.email_reset_password')]) }} +{{ $url }} + +@endcomponent \ No newline at end of file diff --git a/resources/views/back/mails/reset-password.blade.php b/resources/views/back/mails/reset-password.blade.php new file mode 100644 index 0000000..51026b9 --- /dev/null +++ b/resources/views/back/mails/reset-password.blade.php @@ -0,0 +1,20 @@ +{{-- blade-formatter-disable --}} +@component('mail::message') + +# {{ __('auth.email_hello') }} + +{{ __('auth.email_reset_complete') }} + +{{ __('auth.email_new_connection') }} +@component('mail::button', ['url' => route('back.login')]) +{{ __('auth.login') }} +@endcomponent + +{{ config('app.name') }}. + +
+ +{{ __('auth.email_cant_visualise', ['actionText' => __('auth.login')]) }} +{{ route('back.login') }} + +@endcomponent \ No newline at end of file diff --git a/routes/auth.php b/routes/auth.php new file mode 100644 index 0000000..5541b5a --- /dev/null +++ b/routes/auth.php @@ -0,0 +1,30 @@ +name(config('uwil-backend.routes.name')) + ->middleware(['web', 'admin-secure']) + ->group(function (): void { + Route::middleware('guest:backend')->group(function (): void { + Route::get('login', [AuthenticatedSessionController::class, 'login']) + ->name('login'); + Route::post('login', [AuthenticatedSessionController::class, 'postLogin']) + ->name('login.post'); + + Route::get('forgot-password', [AuthenticatedSessionController::class, 'forgotPassword']) + ->name('forgot-password'); + Route::post('forgot-password', [AuthenticatedSessionController::class, 'postForgotPassword']) + ->name('forgot-password.post'); + + Route::get('reset-password/{token}', [AuthenticatedSessionController::class, 'resetPassword']) + ->name('reset-password'); + Route::post('reset-password', [AuthenticatedSessionController::class, 'postResetPassword']) + ->name('reset-password.post'); + }); + Route::middleware(['auth:backend', 'verified'])->group(function (): void { + Route::get('logout', [AuthenticatedSessionController::class, 'logout']) + ->name('logout'); + }); + });