diff --git a/backend/app/Http/Controllers/Auth/RegisteredUserController.php b/backend/app/Http/Controllers/Auth/RegisteredUserController.php index c19fb66..f551047 100644 --- a/backend/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/backend/app/Http/Controllers/Auth/RegisteredUserController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Mail\UserAccountActivated; use App\Mail\UserPasswordReset; use App\Mail\UserRegistrationCompleted; use App\Models\Company; @@ -25,6 +26,7 @@ class RegisteredUserController extends Controller public function store(Request $request): Response { $password = bin2hex(random_bytes(16)); + $activation_token = bin2hex(random_bytes(16)); $request->validate([ 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class], @@ -58,6 +60,7 @@ class RegisteredUserController extends Controller 'phone' => $request->phone, 'role' => $request->role, 'password' => Hash::make($password), + 'activation_token' => $activation_token ]); if ($user->role === "STUDENT") { @@ -83,12 +86,33 @@ class RegisteredUserController extends Controller throw $e; } - Mail::to($user)->sendNow(new UserRegistrationCompleted($user->name, $password)); + Mail::to($user)->sendNow(new UserRegistrationCompleted($user->name, $activation_token)); event(new Registered($user)); return response()->noContent(); } + public function activate(Request $request) { + $request->validate([ + 'token' => ['required', 'string', 'exists:users,activation_token'], + 'password' => ['required', 'string', 'min:8'], + ]); + + $user = User::where('activation_token', '=', $request->token)->first(); + + if (!$user) { + return response()->json(['message' => 'Invalid activation token'], 400); + } + + $user->active = true; + $user->activation_token = null; + $user->password = Hash::make($request->password); + $user->save(); + + Mail::to($user)->sendNow(new UserAccountActivated($user->name)); + return response()->noContent(); + } + public function reset_password(Request $request): Response { $request->validate([ diff --git a/backend/app/Http/Requests/Auth/LoginRequest.php b/backend/app/Http/Requests/Auth/LoginRequest.php index f5692bf..c99dde2 100644 --- a/backend/app/Http/Requests/Auth/LoginRequest.php +++ b/backend/app/Http/Requests/Auth/LoginRequest.php @@ -49,6 +49,15 @@ class LoginRequest extends FormRequest ]); } + // Check if the authenticated user's account is active + if (! Auth::user()->active) { + Auth::logout(); + + throw ValidationException::withMessages([ + 'email' => __('auth.inactive_account'), + ]); + } + RateLimiter::clear($this->throttleKey()); } diff --git a/backend/app/Mail/UserAccountActivated.php b/backend/app/Mail/UserAccountActivated.php new file mode 100644 index 0000000..a60ba9c --- /dev/null +++ b/backend/app/Mail/UserAccountActivated.php @@ -0,0 +1,58 @@ +name = $name; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'User Account Activated', + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'mail.activation.completed', + with: [ + "name" => $this->name, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/backend/app/Mail/UserRegistrationCompleted.php b/backend/app/Mail/UserRegistrationCompleted.php index f4c6a57..523343d 100644 --- a/backend/app/Mail/UserRegistrationCompleted.php +++ b/backend/app/Mail/UserRegistrationCompleted.php @@ -14,15 +14,15 @@ class UserRegistrationCompleted extends Mailable use Queueable, SerializesModels; private string $name; - private string $password; + private string $activation_token; /** * Create a new message instance. */ - public function __construct(string $name, string $password) + public function __construct(string $name, string $activation_token) { $this->name = $name; - $this->password = $password; + $this->activation_token = $activation_token; } /** @@ -44,7 +44,7 @@ class UserRegistrationCompleted extends Mailable view: 'mail.registration.completed', with: [ "name" => $this->name, - "password" => $this->password + "activation_token" => $this->activation_token ] ); } diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index de3bb49..c80bbdc 100644 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -25,6 +25,9 @@ class User extends Authenticatable 'email', 'role', 'password', + 'active', + 'needs_password_change', + 'activation_token', ]; /** @@ -35,6 +38,7 @@ class User extends Authenticatable protected $hidden = [ 'password', 'remember_token', + 'activation_token', ]; /** @@ -47,6 +51,8 @@ class User extends Authenticatable return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'active' => 'boolean', + 'needs_password_change' => 'boolean' ]; } diff --git a/backend/database/factories/UserFactory.php b/backend/database/factories/UserFactory.php index 39a6cef..fdbf6e3 100644 --- a/backend/database/factories/UserFactory.php +++ b/backend/database/factories/UserFactory.php @@ -36,6 +36,8 @@ class UserFactory extends Factory 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), 'remember_token' => Str::random(10), + 'active' => true, + 'needs_password_change' => false, ]; } diff --git a/backend/database/migrations/0001_01_01_000000_create_users_table.php b/backend/database/migrations/0001_01_01_000000_create_users_table.php index 05fb5d9..9004abd 100644 --- a/backend/database/migrations/0001_01_01_000000_create_users_table.php +++ b/backend/database/migrations/0001_01_01_000000_create_users_table.php @@ -17,6 +17,9 @@ return new class extends Migration $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); + $table->boolean('active')->default(false); + $table->boolean('needs_password_change')->default(false); + $table->string('activation_token')->nullable(); $table->rememberToken(); $table->timestamps(); }); diff --git a/backend/resources/views/mail/activation/completed.blade.php b/backend/resources/views/mail/activation/completed.blade.php new file mode 100644 index 0000000..d6212b4 --- /dev/null +++ b/backend/resources/views/mail/activation/completed.blade.php @@ -0,0 +1,8 @@ +@include("parts.header") +

Vážená/ý {{ $name }},

+

úspešne ste aktivovali váš účet!

+
+ +

s pozdravom

+

Systém ISOP UKF

+@include("parts.footer") \ No newline at end of file diff --git a/backend/resources/views/mail/registration/completed.blade.php b/backend/resources/views/mail/registration/completed.blade.php index a712bc7..b35e6f6 100644 --- a/backend/resources/views/mail/registration/completed.blade.php +++ b/backend/resources/views/mail/registration/completed.blade.php @@ -3,7 +3,12 @@

vaša registrácia do systému ISOP UKF prebehla úspešne!


-

Vaše heslo je: {{ $password }}

+

Aktivujte účet pomocou nasledujúceho linku:

+
+

+ {{ config('app.frontend_url') }}/account/activation/{{ $activation_token }} +


diff --git a/backend/routes/api.php b/backend/routes/api.php index 802c184..486c72c 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -22,6 +22,10 @@ Route::middleware(['auth:sanctum'])->get('/user', function (Request $request) { return $user; }); +Route::prefix('/account')->group(function () { + Route::post("/activate", [RegisteredUserController::class, 'activate']); +}); + Route::middleware(['auth:sanctum'])->prefix('/students')->group(function () { Route::post('/change-password', [RegisteredUserController::class, 'change_password']); Route::get('/', [StudentDataController::class, 'all']); diff --git a/frontend/app/pages/account/activation/[token].vue b/frontend/app/pages/account/activation/[token].vue new file mode 100644 index 0000000..b7e09c5 --- /dev/null +++ b/frontend/app/pages/account/activation/[token].vue @@ -0,0 +1,138 @@ + + + + + diff --git a/frontend/app/pages/account/index.vue b/frontend/app/pages/account/index.vue new file mode 100644 index 0000000..1da7911 --- /dev/null +++ b/frontend/app/pages/account/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/frontend/app/pages/dashboard/admin/index.vue b/frontend/app/pages/dashboard/admin/index.vue index 1fbfcc2..f8019d0 100644 --- a/frontend/app/pages/dashboard/admin/index.vue +++ b/frontend/app/pages/dashboard/admin/index.vue @@ -32,6 +32,9 @@ const user = useSanctumUser(); Praxe + + Môj profil +
diff --git a/frontend/app/pages/dashboard/company/index.vue b/frontend/app/pages/dashboard/company/index.vue index ee77806..cb064d3 100644 --- a/frontend/app/pages/dashboard/company/index.vue +++ b/frontend/app/pages/dashboard/company/index.vue @@ -24,12 +24,12 @@ const user = useSanctumUser();
- - Môj profil - Praxe + + Môj profil +
diff --git a/frontend/app/pages/dashboard/company/profile/index.vue b/frontend/app/pages/dashboard/company/profile/index.vue new file mode 100644 index 0000000..8277f17 --- /dev/null +++ b/frontend/app/pages/dashboard/company/profile/index.vue @@ -0,0 +1,130 @@ + + + + +