From b977eb9239030042aad5469d5ab390e5a6cf9a8f Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 16:37:35 +0200 Subject: [PATCH 01/48] Add IsRemote to ScheduleItemRequest for Student --- .../DTOs/Requests/CreateScheduleItemRequestRequest.cs | 1 + .../Models/DTOs/Responses/AvailableScheduleForAdResponse.cs | 1 + TutorLizard.BusinessLogic/Services/StudentService.cs | 3 ++- .../Shared/Components/AvailableScheduleForAd/Default.cshtml | 6 +++++- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs b/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs index 39784bc7..443fb9cd 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs @@ -10,5 +10,6 @@ public class CreateScheduleItemRequestRequest { public int StudentId { get; set; } public int ScheduleItemId { get; set; } + public bool IsRemote { get; set; } } } diff --git a/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs b/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs index dc3ae1e9..f82d3b26 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs @@ -10,6 +10,7 @@ public class AvailableScheduleForAdResponse { public List Items { get; set; } = new(); public bool IsAccepted { get; set; } = true; + public bool IsRemote { get; set; } public int AdId { get; set; } } } diff --git a/TutorLizard.BusinessLogic/Services/StudentService.cs b/TutorLizard.BusinessLogic/Services/StudentService.cs index 9a817b31..f8bc7e60 100644 --- a/TutorLizard.BusinessLogic/Services/StudentService.cs +++ b/TutorLizard.BusinessLogic/Services/StudentService.cs @@ -48,7 +48,8 @@ public async Task CreateScheduleItemRequest(C ScheduleItemId = scheduleItemId, DateCreated = DateTime.UtcNow, StudentId = studentId, - IsAccepted = false + IsAccepted = false, + IsRemote = request.IsRemote }; await _scheduleItemRequestRepository.Create(scheduleItemRequest); diff --git a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml index 7bb8378f..306fabfb 100644 --- a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml +++ b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml @@ -31,7 +31,11 @@
@if (item.Status == ScheduleItemRequestStatus.RequestNotSent) { - +
+ +
From 380ba09d428a0d9a5fe26c6f37584ef136586423 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 20:17:16 +0200 Subject: [PATCH 02/48] Add xUnit tests to StudentService --- .../StudentServiceScheduleItemRequestTests.cs | 70 +++++++++++++++++++ .../Student/StudentServiceTestBase.cs | 59 ++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs create mode 100644 Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs new file mode 100644 index 00000000..dc010133 --- /dev/null +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -0,0 +1,70 @@ +using Moq; +using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; +using TutorLizard.BusinessLogic.Models; +using TutorLizard.BusinessLogic.Models.DTOs.Requests; +using TutorLizard.BusinessLogic.Services; + +namespace TutorLizard.BusinessLogic.Tests.Services.Student +{ + public class StudentServiceScheduleItemRequestTests : StudentServiceTestBase + { + [Fact] + public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemoteToTrue() + { + // Arrange + var scheduleItem = new ScheduleItem { Id = 1 }; + var studentId = 1; + var isRemote = true; + + SetupMockGetScheduleItemById(scheduleItem); + + var request = new CreateScheduleItemRequestRequest + { + StudentId = studentId, + ScheduleItemId = scheduleItem.Id, + IsRemote = isRemote + }; + + // Act + var response = await StudentService.CreateScheduleItemRequest(request); + + // Assert + Assert.True(response.Success); + Assert.NotNull(response.CreatedScheduleItemRequestId); + + var createdScheduleItemRequest = DbContext.ScheduleItemRequests.FirstOrDefault(); + Assert.NotNull(createdScheduleItemRequest); + Assert.True(createdScheduleItemRequest.IsRemote); + } + + [Fact] + public async Task CreateScheduleItemRequest_WhenRequestSent_ShouldReturnSuccess() + { + // Arrange + var scheduleItem = new ScheduleItem { Id = 1 }; + var studentId = 1; + var isRemote = true; + + SetupMockGetScheduleItemById(scheduleItem); + + var request = new CreateScheduleItemRequestRequest + { + StudentId = studentId, + ScheduleItemId = scheduleItem.Id, + IsRemote = isRemote + }; + + // Act + var response = await StudentService.CreateScheduleItemRequest(request); + + // Assert + Assert.True(response.Success); + Assert.NotNull(response.CreatedScheduleItemRequestId); + } + + + + + + } +} diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs new file mode 100644 index 00000000..7258aab7 --- /dev/null +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs @@ -0,0 +1,59 @@ +using AutoFixture; +using Microsoft.EntityFrameworkCore; +using Moq; +using TutorLizard.BusinessLogic.Data; +using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; +using TutorLizard.BusinessLogic.Models; +using TutorLizard.BusinessLogic.Services; + +namespace TutorLizard.BusinessLogic.Tests.Services.Student +{ + public class StudentServiceTestBase : TestsWithInMemoryDbBase + { + protected StudentService StudentService; + protected Fixture Fixture = new(); + protected Mock> MockAdRepository = new(); + protected Mock> MockAdRequestRepository = new(); + protected Mock> MockScheduleItemRepository = new(); + protected Mock> MockScheduleItemRequestRepository = new(); + + + protected StudentServiceTestBase() : base() + { + StudentService = new StudentService(MockAdRepository.Object, + MockAdRequestRepository.Object, + MockScheduleItemRepository.Object, + MockScheduleItemRequestRepository.Object); + } + + protected void SetupMockGetScheduleItemById(ScheduleItem? scheduleItem) + { + MockScheduleItemRepository + .Setup(x => x.GetById(It.IsAny())) + .Returns(Task.FromResult(scheduleItem)); + } + + protected void SetupMockGetAllScheduleItems(List scheduleItems) + { + var scheduleItemsInDb = AddEntitiesToInMemoryDb(scheduleItems); + MockScheduleItemRepository + .Setup(x => x.GetAll()) + .Returns(scheduleItemsInDb); + } + + + protected IQueryable AddEntitiesToInMemoryDb(List entities) + where TEntity : class + { + DbContext + .Set() + .AddRange(entities); + DbContext.SaveChanges(); + + return DbContext + .Set() + .AsQueryable(); + } + } +} + \ No newline at end of file From d6bf216bc498c9bb28b6868924cce4163ff38f38 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 20:17:34 +0200 Subject: [PATCH 03/48] Add migration --- ...Add-IsRemote-In-StudentService.Designer.cs | 336 ++++++++++++++++++ ...02175824_Add-IsRemote-In-StudentService.cs | 22 ++ 2 files changed, 358 insertions(+) create mode 100644 TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs create mode 100644 TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs diff --git a/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs new file mode 100644 index 00000000..b2e7a1bd --- /dev/null +++ b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs @@ -0,0 +1,336 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TutorLizard.BusinessLogic.Data; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + [DbContext(typeof(JaszczurContext))] + [Migration("20240602175824_Add-IsRemote-In-StudentService")] + partial class AddIsRemoteInStudentService + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Price") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TutorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("TutorId"); + + b.ToTable("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReplyMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.HasIndex("StudentId"); + + b.ToTable("AdRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("ScheduleItemId") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.HasIndex("StudentId"); + + b.ToTable("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserType") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Category", "Category") + .WithMany("Ads") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("Ads") + .HasForeignKey("TutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("AdRequests") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("AdRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ad"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("ScheduleItems") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ad"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.ScheduleItem", "ScheduleItem") + .WithMany("ScheduleItemRequests") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("ScheduleItemRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ScheduleItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Navigation("AdRequests"); + + b.Navigation("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Navigation("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Navigation("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Navigation("AdRequests"); + + b.Navigation("Ads"); + + b.Navigation("ScheduleItemRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs new file mode 100644 index 00000000..a9856426 --- /dev/null +++ b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + /// + public partial class AddIsRemoteInStudentService : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} From b6c4b3756c641616cbdd4e8169c732c4101c1720 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 20:18:44 +0200 Subject: [PATCH 04/48] Add isRemote to StudentService --- .../Services/StudentService.cs | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/TutorLizard.BusinessLogic/Services/StudentService.cs b/TutorLizard.BusinessLogic/Services/StudentService.cs index f8bc7e60..749230ce 100644 --- a/TutorLizard.BusinessLogic/Services/StudentService.cs +++ b/TutorLizard.BusinessLogic/Services/StudentService.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using System.Runtime.InteropServices; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Models; @@ -25,6 +24,7 @@ public StudentService(IDbRepository adRepository, _scheduleItemRequestRepository = scheduleItemRequestRepository; } + #region Schedule public async Task CreateScheduleItemRequest(CreateScheduleItemRequestRequest request) { int studentId = request.StudentId; @@ -54,13 +54,50 @@ public async Task CreateScheduleItemRequest(C await _scheduleItemRequestRepository.Create(scheduleItemRequest); - return new CreateScheduleItemRequestResponse - { + return new CreateScheduleItemRequestResponse + { Success = true, CreatedScheduleItemRequestId = scheduleItemRequest.Id, }; } + public async Task GetAvailableScheduleForAd(AvailableScheduleForAdRequest request) + { + List items = await _scheduleItemRepository.GetAll() + .Where(si => si.Ad.AdRequests.Any(ar => ar.StudentId == request.StudentId && ar.IsAccepted)) + .Select(si => new ScheduleItemDto() + { + AdId = si.AdId, + DateTime = si.DateTime, + Id = si.Id, + Status = si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId && sir.IsAccepted) ? ScheduleItemDto.ScheduleItemRequestStatus.Accepted + : si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId) ? ScheduleItemDto.ScheduleItemRequestStatus.Pending + : ScheduleItemDto.ScheduleItemRequestStatus.RequestNotSent + }) + .ToListAsync(); + + bool isAccepted = await _adRequestRepository.GetAll() + .Where(ar => ar.AdId == request.AdId) + .AnyAsync(ar => ar.StudentId == request.StudentId && ar.IsAccepted); + + bool isRemote = await _adRequestRepository.GetAll() + .Where(ad => ad.Id == request.AdId) + .Select(ad => ad.IsRemote) + .FirstOrDefaultAsync(); + + AvailableScheduleForAdResponse response = new() + { + AdId = request.AdId, + IsAccepted = isAccepted, + IsRemote = isRemote, + Items = items + }; + + return response; + } + + #endregion + #region Ads public async Task ViewAcceptedAds(StudentsAcceptedAdsRequest request) { var studentId = request.StudentId; @@ -143,7 +180,7 @@ public async Task ViewAdRequestStatus(AdRequestStatusRe .FirstOrDefaultAsync(); if (adRequestDetails is null) - return new AdRequestStatusResponse() { IsSuccessful = false }; + return new AdRequestStatusResponse() { IsSuccessful = false }; AdRequestStatusResponse response = new AdRequestStatusResponse() { @@ -234,32 +271,5 @@ public async Task CreateAdRequest(CreateAdRequestReques }; } - public async Task GetAvailableScheduleForAd(AvailableScheduleForAdRequest request) - { - List items = await _scheduleItemRepository.GetAll() - .Where(si => si.Ad.AdRequests.Any(ar => ar.StudentId == request.StudentId && ar.IsAccepted)) - .Select(si => new ScheduleItemDto() - { - AdId = si.AdId, - DateTime = si.DateTime, - Id = si.Id, - Status = si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId && sir.IsAccepted) ? ScheduleItemDto.ScheduleItemRequestStatus.Accepted - : si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId) ? ScheduleItemDto.ScheduleItemRequestStatus.Pending - : ScheduleItemDto.ScheduleItemRequestStatus.RequestNotSent - }) - .ToListAsync(); - - bool isAccepted = await _adRequestRepository.GetAll() - .Where(ar => ar.AdId == request.AdId) - .AnyAsync(ar => ar.StudentId == request.StudentId && ar.IsAccepted); - - AvailableScheduleForAdResponse response = new() - { - AdId = request.AdId, - IsAccepted = isAccepted, - Items = items - }; - - return response; - } + #endregion } From 2940f020f2cbea04310616149b5dc66c02fc2ea3 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 21:21:37 +0200 Subject: [PATCH 05/48] Add IsRemote to StudentController in CreateScheduleItemRequest --- TutorLizard.Web/Controllers/StudentController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TutorLizard.Web/Controllers/StudentController.cs b/TutorLizard.Web/Controllers/StudentController.cs index 8c3d17a0..6c894537 100644 --- a/TutorLizard.Web/Controllers/StudentController.cs +++ b/TutorLizard.Web/Controllers/StudentController.cs @@ -73,7 +73,7 @@ public async Task AdRequests(IFormCollection buttonAction) [HttpPost] [ValidateAntiForgeryToken] - public async Task CreateScheduleItemRequest(int scheduleItemId, int adId) + public async Task CreateScheduleItemRequest(int scheduleItemId, int adId, bool isRemote) { int? studentId = _userAuthenticationService.GetLoggedInUserId(); @@ -85,7 +85,8 @@ public async Task CreateScheduleItemRequest(int scheduleItemId, i CreateScheduleItemRequestRequest request = new() { StudentId = (int)studentId, - ScheduleItemId = scheduleItemId + ScheduleItemId = scheduleItemId, + IsRemote = isRemote }; CreateScheduleItemRequestResponse response = await _studentService.CreateScheduleItemRequest(request); From 5ca0dda65bde906ee4dd7ec1664268f001c91b8a Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 23:31:58 +0200 Subject: [PATCH 06/48] Fix IsRemote in CreateScheduleItemRequest Fixed problem in saving data in Database --- .../Responses/CreateScheduleItemRequestResponse.cs | 1 + TutorLizard.BusinessLogic/Services/StudentService.cs | 3 ++- .../Components/AvailableScheduleForAd/Default.cshtml | 12 ++++++------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs b/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs index c66eeda4..060abe0c 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs @@ -10,5 +10,6 @@ public class CreateScheduleItemRequestResponse { public bool Success { get; set; } public int CreatedScheduleItemRequestId { get; set; } + public bool IsRemote { get; set; } } } diff --git a/TutorLizard.BusinessLogic/Services/StudentService.cs b/TutorLizard.BusinessLogic/Services/StudentService.cs index 749230ce..1aba912b 100644 --- a/TutorLizard.BusinessLogic/Services/StudentService.cs +++ b/TutorLizard.BusinessLogic/Services/StudentService.cs @@ -54,10 +54,11 @@ public async Task CreateScheduleItemRequest(C await _scheduleItemRequestRepository.Create(scheduleItemRequest); + return new CreateScheduleItemRequestResponse { Success = true, - CreatedScheduleItemRequestId = scheduleItemRequest.Id, + CreatedScheduleItemRequestId = scheduleItemRequest.Id }; } diff --git a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml index 306fabfb..460b32b0 100644 --- a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml +++ b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml @@ -31,15 +31,15 @@
@if (item.Status == ScheduleItemRequestStatus.RequestNotSent) { -
- -
+
+ +
- + }
From b45c2bb20c746005917d61d02c7fd2e185707578 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 2 Jun 2024 23:51:03 +0200 Subject: [PATCH 07/48] Fix tests of IsRemote of CreateScheduleItemRequest --- .../StudentServiceScheduleItemRequestTests.cs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs index dc010133..34ad479f 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -12,29 +12,43 @@ public class StudentServiceScheduleItemRequestTests : StudentServiceTestBase public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemoteToTrue() { // Arrange - var scheduleItem = new ScheduleItem { Id = 1 }; - var studentId = 1; - var isRemote = true; - + var scheduleItem = new ScheduleItem { Id = 1, Ad = new Ad { TutorId = 2 } }; SetupMockGetScheduleItemById(scheduleItem); var request = new CreateScheduleItemRequestRequest { - StudentId = studentId, - ScheduleItemId = scheduleItem.Id, - IsRemote = isRemote + StudentId = 2, + ScheduleItemId = 1, + IsRemote = true }; // Act - var response = await StudentService.CreateScheduleItemRequest(request); + await StudentService.CreateScheduleItemRequest(request); // Assert - Assert.True(response.Success); - Assert.NotNull(response.CreatedScheduleItemRequestId); + MockScheduleItemRequestRepository.Verify(repo => repo.Create(It.Is(req => req.IsRemote == true)), Times.Once); + + } - var createdScheduleItemRequest = DbContext.ScheduleItemRequests.FirstOrDefault(); - Assert.NotNull(createdScheduleItemRequest); - Assert.True(createdScheduleItemRequest.IsRemote); + [Fact] + public async Task CreateScheduleItemRequest_WhenIsRemoteIsFalse_ShouldSetIsRemoteToFalse() + { + // Arrange + var scheduleItem = new ScheduleItem { Id = 1, Ad = new Ad { TutorId = 2 } }; + SetupMockGetScheduleItemById(scheduleItem); + + var request = new CreateScheduleItemRequestRequest + { + StudentId = 2, + ScheduleItemId = 1, + IsRemote = false + }; + + // Act + await StudentService.CreateScheduleItemRequest(request); + + // Assert + MockScheduleItemRequestRepository.Verify(repo => repo.Create(It.Is(req => req.IsRemote == false)), Times.Once); } [Fact] From 22086c9ed5ac539f1f723bfdd669241452174c53 Mon Sep 17 00:00:00 2001 From: skrawus Date: Thu, 6 Jun 2024 15:36:00 +0200 Subject: [PATCH 08/48] Create SendActivationEmail method --- .../Controllers/AccountController.cs | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 6390d7eb..2233542c 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Net; +using System.Net.Mail; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.Web.Interfaces.Services; @@ -81,7 +83,10 @@ public async Task Register(RegisterUserModel model) if (ModelState.IsValid && await _userAuthenticationService.RegisterUser(model.UserName, UserType.Regular, model.Email, model.Password)) { - _uiMessagesService.ShowSuccessMessage("Użytkownik zarejestrowany."); + string activationCode = GenerateActivationCode(); + SendActivationEmail(model.Email, activationCode); + + _uiMessagesService.ShowSuccessMessage("Wysłano mail aktywacyjny."); return LocalRedirect("/Home/Index"); } } @@ -94,7 +99,40 @@ public async Task Register(RegisterUserModel model) return LocalRedirect("/Home/Index"); } - public IActionResult AccessDenied() + private string GenerateActivationCode() + { + return Guid.NewGuid().ToString(); + } + + public void SendActivationEmail(string userEmail, string activationCode) +{ + var fromAddress = new MailAddress("lizardtutoring@gmail.com", "Tutor Lizard"); + var toAddress = new MailAddress(userEmail); + const string fromPassword = "pvez johg nzwc enjg"; + string subject = "Aktywacja konta"; + string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/activation/{activationCode}"; + + var smtp = new SmtpClient + { + Host = "smtp.gmail.com", + Port = 587, + EnableSsl = true, + DeliveryMethod = SmtpDeliveryMethod.Network, + UseDefaultCredentials = false, + Credentials = new NetworkCredential(fromAddress.Address, fromPassword) + }; + using (var message = new MailMessage(fromAddress, toAddress) + { + Subject = subject, + Body = body + }) + { + smtp.Send(message); + } +} + + +public IActionResult AccessDenied() { return View(); } From 0a71588e64a631f854e93491cff6593d5e26ba88 Mon Sep 17 00:00:00 2001 From: skrawus Date: Thu, 6 Jun 2024 21:37:46 +0200 Subject: [PATCH 09/48] Create Activation emails --- .../Data/JaszczurContext.cs | 5 + .../Interfaces/Services/IUserService.cs | 2 +- TutorLizard.BusinessLogic/Models/User.cs | 2 + .../Services/UserService.cs | 6 +- .../Controllers/AccountController.cs | 75 ++-- .../Services/IUserAuthenticationService.cs | 5 +- ...06152246_Add-Activation-Emails.Designer.cs | 345 ++++++++++++++++++ .../20240606152246_Add-Activation-Emails.cs | 40 ++ .../JaszczurContextModelSnapshot.cs | 9 + TutorLizard.Web/Program.cs | 12 + .../Services/UserAuthenticationService.cs | 67 +++- .../Views/Account/ActivateAccount.cshtml | 7 + 12 files changed, 534 insertions(+), 41 deletions(-) create mode 100644 TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.Designer.cs create mode 100644 TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.cs create mode 100644 TutorLizard.Web/Views/Account/ActivateAccount.cshtml diff --git a/TutorLizard.BusinessLogic/Data/JaszczurContext.cs b/TutorLizard.BusinessLogic/Data/JaszczurContext.cs index b8de0174..284b26c9 100644 --- a/TutorLizard.BusinessLogic/Data/JaszczurContext.cs +++ b/TutorLizard.BusinessLogic/Data/JaszczurContext.cs @@ -20,6 +20,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Property(user => user.Id) .ValueGeneratedOnAdd(); + modelBuilder.Entity() + .Property(user => user.IsActive) + .HasDefaultValue(false) + .IsRequired(); + modelBuilder.Entity() .Property(user => user.UserType) .HasConversion(); diff --git a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs index 9c1bfa0f..75d14132 100644 --- a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs +++ b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs @@ -6,5 +6,5 @@ namespace TutorLizard.BusinessLogic.Interfaces.Services; public interface IUserService { public Task LogIn(string username, string password); - public Task RegisterUser(string userName, UserType type, string email, string password); + public Task RegisterUser(string userName, UserType type, string email, string password, string activationCode); } diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index 59ac7641..0193e4d0 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -6,6 +6,7 @@ namespace TutorLizard.BusinessLogic.Models; public class User { public int Id { get; set; } + public bool? IsActive { get; set; } [Required] [MinLength(5)] @@ -43,4 +44,5 @@ public User() public ICollection Ads { get; set; } = new List(); public ICollection AdRequests { get; set; } = new List(); public ICollection ScheduleItemRequests { get; set; } = new List(); + public string ActivationCode { get; set; } } diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 4110ccb5..0c362818 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -39,7 +39,7 @@ public UserService(IDbRepository userRepository) return null; } - public async Task RegisterUser(string userName, UserType type, string email, string password) + public async Task RegisterUser(string userName, UserType type, string email, string password, string activationCode) { if (await _userRepository.GetAll().AnyAsync(user => user.Name == userName)) return false; @@ -49,7 +49,9 @@ public async Task RegisterUser(string userName, UserType type, string emai Name = userName, UserType = type, Email = email, - PasswordHash = password + PasswordHash = password, + ActivationCode = activationCode, + IsActive = false }; user.PasswordHash = _passwordHasher.HashPassword(user, password); diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 2233542c..4a1e8251 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -48,12 +48,20 @@ public async Task Login(LoginModel model) { if (ModelState.IsValid && await _userAuthenticationService.LogInAsync(model.UserName, model.Password)) { - _uiMessagesService.ShowSuccessMessage("Jesteś zalogowany/a."); - if (string.IsNullOrEmpty(returnUrl)) + if (await _userAuthenticationService.IsUserActive(model.UserName)) { - return RedirectToAction("Index", "Home"); + _uiMessagesService.ShowSuccessMessage("Jesteś zalogowany/a."); + if (string.IsNullOrEmpty(returnUrl)) + { + return RedirectToAction("Index", "Home"); + } + return Redirect(returnUrl); + } + else + { + _uiMessagesService.ShowFailureMessage("Logowanie nieudane"); + return LocalRedirect("/Home/Index"); } - return Redirect(returnUrl); } } catch @@ -64,6 +72,7 @@ public async Task Login(LoginModel model) _uiMessagesService.ShowFailureMessage("Logowanie nieudane."); return RedirectToAction(nameof(Login), new { returnUrl = returnUrl }); } + [Authorize] public async Task Logout() { @@ -74,18 +83,30 @@ public IActionResult Register() { return View(); } + [HttpPost] [ValidateAntiForgeryToken] public async Task Register(RegisterUserModel model) { try { - if (ModelState.IsValid - && await _userAuthenticationService.RegisterUser(model.UserName, UserType.Regular, model.Email, model.Password)) + if (!ModelState.IsValid) { - string activationCode = GenerateActivationCode(); - SendActivationEmail(model.Email, activationCode); + var errors = ModelState.Values.SelectMany(v => v.Errors); + foreach (var error in errors) + { + Console.WriteLine(error.ErrorMessage); + } + return View(model); + } + + string activationCode = GenerateActivationCode(); + bool registrationResult = await _userAuthenticationService.RegisterUser( + model.UserName, UserType.Regular, model.Email, model.Password, activationCode); + if (registrationResult) + { + _userAuthenticationService.SendActivationEmail(model.Email, activationCode); _uiMessagesService.ShowSuccessMessage("Wysłano mail aktywacyjny."); return LocalRedirect("/Home/Index"); } @@ -104,35 +125,21 @@ private string GenerateActivationCode() return Guid.NewGuid().ToString(); } - public void SendActivationEmail(string userEmail, string activationCode) -{ - var fromAddress = new MailAddress("lizardtutoring@gmail.com", "Tutor Lizard"); - var toAddress = new MailAddress(userEmail); - const string fromPassword = "pvez johg nzwc enjg"; - string subject = "Aktywacja konta"; - string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/activation/{activationCode}"; - - var smtp = new SmtpClient + [HttpGet] + public IActionResult ActivateAccount(string activationCode) { - Host = "smtp.gmail.com", - Port = 587, - EnableSsl = true, - DeliveryMethod = SmtpDeliveryMethod.Network, - UseDefaultCredentials = false, - Credentials = new NetworkCredential(fromAddress.Address, fromPassword) - }; - using (var message = new MailMessage(fromAddress, toAddress) - { - Subject = subject, - Body = body - }) - { - smtp.Send(message); + if (_userAuthenticationService.ActivateUser(activationCode)) + { + return View("Account/ActivateAccount"); + } + else + { + _uiMessagesService.ShowFailureMessage("Wystąpił błąd. Rejestracja nieudana."); + return LocalRedirect("/Home/Index"); + } } -} - -public IActionResult AccessDenied() + public IActionResult AccessDenied() { return View(); } diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index 73b079b0..08f8a823 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -8,5 +8,8 @@ public interface IUserAuthenticationService int? GetLoggedInUserId(); public Task LogInAsync(string username, string password); public Task LogOutAsync(); - public Task RegisterUser(string username, UserType type, string email, string password); + public Task RegisterUser(string username, UserType type, string email, string password, string activationCode); + bool ActivateUser(string activationCode); + Task IsUserActive(string userName); + void SendActivationEmail(string email, string activationCode); } diff --git a/TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.Designer.cs b/TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.Designer.cs new file mode 100644 index 00000000..69e4f217 --- /dev/null +++ b/TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.Designer.cs @@ -0,0 +1,345 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TutorLizard.BusinessLogic.Data; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + [DbContext(typeof(JaszczurContext))] + [Migration("20240606152246_Add-Activation-Emails")] + partial class AddActivationEmails + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Price") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TutorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("TutorId"); + + b.ToTable("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReplyMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.HasIndex("StudentId"); + + b.ToTable("AdRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("ScheduleItemId") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.HasIndex("StudentId"); + + b.ToTable("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivationCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserType") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Category", "Category") + .WithMany("Ads") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("Ads") + .HasForeignKey("TutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("AdRequests") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("AdRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ad"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("ScheduleItems") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ad"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.ScheduleItem", "ScheduleItem") + .WithMany("ScheduleItemRequests") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("ScheduleItemRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ScheduleItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Navigation("AdRequests"); + + b.Navigation("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Navigation("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Navigation("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Navigation("AdRequests"); + + b.Navigation("Ads"); + + b.Navigation("ScheduleItemRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.cs b/TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.cs new file mode 100644 index 00000000..14a7d81c --- /dev/null +++ b/TutorLizard.Web/Migrations/20240606152246_Add-Activation-Emails.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + /// + public partial class AddActivationEmails : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ActivationCode", + table: "Users", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "IsActive", + table: "Users", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ActivationCode", + table: "Users"); + + migrationBuilder.DropColumn( + name: "IsActive", + table: "Users"); + } + } +} diff --git a/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs b/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs index 146d001c..b113965c 100644 --- a/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs +++ b/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs @@ -208,6 +208,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + b.Property("ActivationCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("DateCreated") .HasColumnType("datetime2"); @@ -216,6 +220,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("Name") .IsRequired() .HasMaxLength(40) diff --git a/TutorLizard.Web/Program.cs b/TutorLizard.Web/Program.cs index c5f558c6..23306a16 100644 --- a/TutorLizard.Web/Program.cs +++ b/TutorLizard.Web/Program.cs @@ -67,6 +67,18 @@ app.UseAuthorization(); +app.UseEndpoints(endpoints => +{ + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + endpoints.MapControllerRoute( + name: "ActivateAccount", + pattern: "{controller=Account}/{action=ActivateAccount}/{activationCode?}"); +}); + + + app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index c1c0519a..1543d979 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; +using System.Net.Mail; +using System.Net; using System.Security.Claims; +using TutorLizard.BusinessLogic.Data; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; @@ -10,11 +13,13 @@ public class UserAuthenticationService : IUserAuthenticationService { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; + private readonly JaszczurContext _dbContext; - public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService) + public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, JaszczurContext dbContext) { _httpContextAccessor = httpContextAccessor; _userService = userService; + _dbContext = dbContext; } public async Task LogInAsync(string username, string password) { @@ -61,9 +66,9 @@ public async Task LogOutAsync() await _httpContextAccessor.HttpContext.SignOutAsync("CookieAuth"); } - public Task RegisterUser(string username, UserType type, string email, string password) + public Task RegisterUser(string username, UserType type, string email, string password, string activationCode) { - return _userService.RegisterUser(username, type, email, password); + return _userService.RegisterUser(username, type, email, password, activationCode); } public int? GetLoggedInUserId() @@ -84,4 +89,60 @@ public Task RegisterUser(string username, UserType type, string email, str } return userId; } + + public void SendActivationEmail(string userEmail, string activationCode) + { + var fromAddress = new MailAddress("lizardtutoring@gmail.com", "Tutor Lizard"); + var toAddress = new MailAddress(userEmail); + const string fromPassword = "pvez johg nzwc enjg"; + string subject = "Aktywacja konta"; + string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/Account/ActivateAccount/{activationCode}"; + + var smtp = new SmtpClient + { + Host = "smtp.gmail.com", + Port = 587, + EnableSsl = true, + DeliveryMethod = SmtpDeliveryMethod.Network, + UseDefaultCredentials = false, + Credentials = new NetworkCredential(fromAddress.Address, fromPassword) + }; + using (var message = new MailMessage(fromAddress, toAddress) + { + Subject = subject, + Body = body + }) + { + smtp.Send(message); + } + } + + public bool ActivateUser(string activationCode) + { + var user = _dbContext.Users.FirstOrDefault(u => u.ActivationCode == activationCode); + + if (user != null) + { + user.IsActive = true; + user.ActivationCode = null; + _dbContext.SaveChanges(); + return true; + } + return false; + } + + public async Task IsUserActive(string userName) + { + int? userId = GetLoggedInUserId(); + + if (userId.HasValue) + { + var user = await _dbContext.Users.FindAsync(userId.Value); + if (user != null && user.Name == userName) + { + return (bool)user.IsActive; + } + } + return false; + } } diff --git a/TutorLizard.Web/Views/Account/ActivateAccount.cshtml b/TutorLizard.Web/Views/Account/ActivateAccount.cshtml new file mode 100644 index 00000000..b1cdea67 --- /dev/null +++ b/TutorLizard.Web/Views/Account/ActivateAccount.cshtml @@ -0,0 +1,7 @@ +@{ + ViewData["Title"] = "Aktywacja maila"; +} +

@ViewData["Title"]

+ +

Aktywacja konta

+

Twoje konto zostało aktywowane.

\ No newline at end of file From c3b9ba268f45b5bd3263d820ead62fb8e61f8491 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 21:13:06 +0200 Subject: [PATCH 10/48] Make ActivationCode nullable --- TutorLizard.BusinessLogic/Models/User.cs | 2 +- TutorLizard.Web/Services/UserAuthenticationService.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index 0193e4d0..5633c046 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -44,5 +44,5 @@ public User() public ICollection Ads { get; set; } = new List(); public ICollection AdRequests { get; set; } = new List(); public ICollection ScheduleItemRequests { get; set; } = new List(); - public string ActivationCode { get; set; } + public string? ActivationCode { get; set; } } diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 1543d979..45107cb3 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -6,6 +6,7 @@ using TutorLizard.BusinessLogic.Data; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; +using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; namespace TutorLizard.BusinessLogic.Services; @@ -21,6 +22,7 @@ public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUser _userService = userService; _dbContext = dbContext; } + public async Task LogInAsync(string username, string password) { var user = await _userService.LogIn(username, password); From cf19d581cef1ed531796e7bd85b65e1767c13013 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 22:05:10 +0200 Subject: [PATCH 11/48] Make ActivateUser method async --- TutorLizard.Web/Controllers/AccountController.cs | 7 +++++-- .../Interfaces/Services/IUserAuthenticationService.cs | 2 +- TutorLizard.Web/Services/UserAuthenticationService.cs | 9 ++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 4a1e8251..61a38379 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -126,9 +126,11 @@ private string GenerateActivationCode() } [HttpGet] - public IActionResult ActivateAccount(string activationCode) + public async Task ActivateAccount(string activationCode) { - if (_userAuthenticationService.ActivateUser(activationCode)) + bool isActivated = await _userAuthenticationService.ActivateUserAsync(activationCode); + + if (isActivated) { return View("Account/ActivateAccount"); } @@ -139,6 +141,7 @@ public IActionResult ActivateAccount(string activationCode) } } + public IActionResult AccessDenied() { return View(); diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index 08f8a823..a224ccae 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -9,7 +9,7 @@ public interface IUserAuthenticationService public Task LogInAsync(string username, string password); public Task LogOutAsync(); public Task RegisterUser(string username, UserType type, string email, string password, string activationCode); - bool ActivateUser(string activationCode); + Task ActivateUserAsync(string activationCode); Task IsUserActive(string userName); void SendActivationEmail(string email, string activationCode); } diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 45107cb3..e585fcac 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -7,6 +7,7 @@ using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; +using Microsoft.EntityFrameworkCore; namespace TutorLizard.BusinessLogic.Services; @@ -119,20 +120,22 @@ public void SendActivationEmail(string userEmail, string activationCode) } } - public bool ActivateUser(string activationCode) + public async Task ActivateUserAsync(string activationCode) { - var user = _dbContext.Users.FirstOrDefault(u => u.ActivationCode == activationCode); + var user = await _dbContext.Users + .FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false); if (user != null) { user.IsActive = true; user.ActivationCode = null; - _dbContext.SaveChanges(); + await _dbContext.SaveChangesAsync(); return true; } return false; } + public async Task IsUserActive(string userName) { int? userId = GetLoggedInUserId(); From 9c69231032c344b5cf507db81bc1a561d5bdad5b Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 23:02:34 +0200 Subject: [PATCH 12/48] Correct return view and messages in ActivateAccount method --- TutorLizard.Web/Controllers/AccountController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 61a38379..045bb123 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -132,11 +132,12 @@ public async Task ActivateAccount(string activationCode) if (isActivated) { - return View("Account/ActivateAccount"); + _uiMessagesService.ShowSuccessMessage("Atywacja udana."); + return View("ActivateAccount"); } else { - _uiMessagesService.ShowFailureMessage("Wystąpił błąd. Rejestracja nieudana."); + _uiMessagesService.ShowFailureMessage("Aktywacja konta nie powiodła się."); return LocalRedirect("/Home/Index"); } } From ce4d98dc44c3db7ca0b0845cbc56ba57659d572b Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 23:04:02 +0200 Subject: [PATCH 13/48] Correct generating URL and change ActivationCode after activation --- TutorLizard.Web/Services/UserAuthenticationService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index e585fcac..9f82bc31 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -99,7 +99,7 @@ public void SendActivationEmail(string userEmail, string activationCode) var toAddress = new MailAddress(userEmail); const string fromPassword = "pvez johg nzwc enjg"; string subject = "Aktywacja konta"; - string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/Account/ActivateAccount/{activationCode}"; + string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/Account/ActivateAccount?activationCode={activationCode}"; var smtp = new SmtpClient { @@ -128,7 +128,7 @@ public async Task ActivateUserAsync(string activationCode) if (user != null) { user.IsActive = true; - user.ActivationCode = null; + user.ActivationCode = "DEACTIVATED"; await _dbContext.SaveChangesAsync(); return true; } From 21e66fbe7d59d4490373d6912ca3031aa9c59568 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 23:34:51 +0200 Subject: [PATCH 14/48] Add IsActive to UserDto and edit LogIn It now allows only active users to login --- TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs | 1 + TutorLizard.BusinessLogic/Services/UserService.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs b/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs index 3002b166..d04a7d69 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs @@ -5,6 +5,7 @@ namespace TutorLizard.BusinessLogic.Models.DTOs; public class UserDto { public int Id { get; set; } + public bool? IsActive { get; set; } [Required(ErrorMessage = "Podaj nazwę użytkownika.")] [Display(Name = "Nazwa użytkownika")] diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 0c362818..714c9d5a 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -23,7 +23,7 @@ public UserService(IDbRepository userRepository) var user = await _userRepository.GetAll() .FirstOrDefaultAsync(user => user.Name == username); - if (user == null) + if (user == null || user.IsActive == false) { return null; } From 6913c4c7ac62aca5c9ea44380e476958ba353893 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 23:44:35 +0200 Subject: [PATCH 15/48] Change Login method so it shows correct messages --- TutorLizard.Web/Controllers/AccountController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 045bb123..3c451b20 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -59,18 +59,21 @@ public async Task Login(LoginModel model) } else { - _uiMessagesService.ShowFailureMessage("Logowanie nieudane"); + _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Konto nie jest aktywne."); return LocalRedirect("/Home/Index"); } } + else + { + _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Nieprawidłowa nazwa użytkownika lub hasło."); + return RedirectToAction(nameof(Login), new { returnUrl = returnUrl }); + } } catch { _uiMessagesService.ShowFailureMessage("Logowanie nieudane."); return LocalRedirect("/Home/Index"); } - _uiMessagesService.ShowFailureMessage("Logowanie nieudane."); - return RedirectToAction(nameof(Login), new { returnUrl = returnUrl }); } [Authorize] From c76eaa56ede692235f7f3febfe8ef84497b197bf Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 7 Jun 2024 23:47:12 +0200 Subject: [PATCH 16/48] Fix IsUserActive method --- TutorLizard.Web/Services/UserAuthenticationService.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 9f82bc31..0ef2d4c1 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -138,16 +138,13 @@ public async Task ActivateUserAsync(string activationCode) public async Task IsUserActive(string userName) { - int? userId = GetLoggedInUserId(); + var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Name == userName); - if (userId.HasValue) + if (user != null) { - var user = await _dbContext.Users.FindAsync(userId.Value); - if (user != null && user.Name == userName) - { - return (bool)user.IsActive; - } + return (bool)user.IsActive; } return false; } + } From 2b23d126d079a8954e1cccd5780e1f3ef66ab469 Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Sat, 8 Jun 2024 13:08:19 +0200 Subject: [PATCH 17/48] Add Oauth --- TutorLizard.BusinessLogic/Models/User.cs | 5 +- .../TutorLizard.BusinessLogic.csproj | 1 + .../Controllers/AccountController.cs | 86 ++++++++++++++++++- TutorLizard.Web/Models/LoginModel.cs | 8 +- TutorLizard.Web/Program.cs | 11 +++ TutorLizard.Web/TutorLizard.Web.csproj | 1 + TutorLizard.Web/Views/Account/Login.cshtml | 26 ++++++ TutorLizard.Web/appsettings.json | 6 ++ 8 files changed, 136 insertions(+), 8 deletions(-) diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index 59ac7641..e6f94d58 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -1,9 +1,10 @@ -using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Identity.EntityFramework; +using System.ComponentModel.DataAnnotations; using TutorLizard.BusinessLogic.Enums; namespace TutorLizard.BusinessLogic.Models; -public class User +public class User : IdentityUser { public int Id { get; set; } diff --git a/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj b/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj index 294a3d5d..70475976 100644 --- a/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj +++ b/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj @@ -8,6 +8,7 @@ + diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 9ef053ad..905fdc50 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -1,18 +1,24 @@ using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; -using TutorLizard.BusinessLogic.Services; +using TutorLizard.BusinessLogic.Models; using TutorLizard.Web.Models; namespace TutorLizard.Web.Controllers; public class AccountController : Controller { private readonly IUserAuthenticationService _userAuthenticationService; + private SignInManager _signInManager; + private UserManager _userManager; - public AccountController(IUserAuthenticationService userAuthenticationService) + public AccountController(IUserAuthenticationService userAuthenticationService, SignInManager signInManager, UserManager userManager) { _userAuthenticationService = userAuthenticationService; + _signInManager = signInManager; + _userManager = userManager; } public IActionResult Index() @@ -20,13 +26,19 @@ public IActionResult Index() return View(); } - public IActionResult Login([FromQuery] string? returnUrl) + public async Task Login([FromQuery] string? returnUrl) { if (returnUrl is not null) { TempData["returnUrl"] = returnUrl; } - return View(); + + var loginViewModel = new LoginModel() + { + AuthenticationSchemes = await _signInManager.GetExternalAuthenticationSchemesAsync() + }; + + return View(loginViewModel); } [HttpPost] @@ -93,4 +105,70 @@ public IActionResult AccessDenied() { return View(); } + + public IActionResult ExternalLogin(string provider, string returnUrl = "") + { + var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); + + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + + return new ChallengeResult(provider, properties); + } + + public async Task ExternalLoginCallback(string returnUrl = "", string remoteError = "") + { + var loginViewModel = new LoginModel() + { + AuthenticationSchemes = await _signInManager.GetExternalAuthenticationSchemesAsync() + }; + + if (!string.IsNullOrEmpty(remoteError)) + { + ModelState.AddModelError("", $"Error from external login provider: {remoteError}"); + return View("Login", loginViewModel); + } + + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info is null) + { + ModelState.AddModelError("", $"Error from external login provider: {remoteError}"); + return View("Login", loginViewModel); + } + + var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, + info.ProviderKey, + isPersistent: false, + bypassTwoFactor: true); + + if (signInResult.Succeeded) + { + return RedirectToAction("Index", "Home"); + } + else + { + var userEmail = info.Principal.FindFirstValue(ClaimTypes.Email); + + if(!string.IsNullOrEmpty(userEmail)) + { + var user = await _userManager.FindByEmailAsync(userEmail); + + if (user is null) + { + user = new User() + { + Name = userEmail, + Email = userEmail, + UserType = UserType.Regular + }; + + await _userManager.CreateAsync(user); + } + + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction("Index", "Home"); + } + } + ModelState.AddModelError("", $"Something went wrong"); + return View("Login", loginViewModel); + } } diff --git a/TutorLizard.Web/Models/LoginModel.cs b/TutorLizard.Web/Models/LoginModel.cs index 01f686c3..87819d4b 100644 --- a/TutorLizard.Web/Models/LoginModel.cs +++ b/TutorLizard.Web/Models/LoginModel.cs @@ -1,8 +1,9 @@ -using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authentication; +using System.ComponentModel.DataAnnotations; namespace TutorLizard.Web.Models; -public class LoginModel +public class LoginModel { [Required] public string UserName { get; set; } @@ -10,4 +11,7 @@ public class LoginModel [Required] [DataType(DataType.Password)] public string Password { get; set; } + + // Third-Party Login Providers + public IEnumerable AuthenticationSchemes { get; set; } } diff --git a/TutorLizard.Web/Program.cs b/TutorLizard.Web/Program.cs index 8f964bc6..48eab41a 100644 --- a/TutorLizard.Web/Program.cs +++ b/TutorLizard.Web/Program.cs @@ -1,7 +1,10 @@ +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using System; using TutorLizard.BusinessLogic.Data; using TutorLizard.BusinessLogic.Extensions; using TutorLizard.BusinessLogic.Interfaces.Services; +using TutorLizard.BusinessLogic.Models; using TutorLizard.BusinessLogic.Options; using TutorLizard.BusinessLogic.Services; @@ -31,6 +34,12 @@ options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; }); +builder.Services.AddAuthentication() + .AddGoogle(options => + { + options.ClientId = builder.Configuration["Auth:Google:ClientId"]; + options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"]; + }); builder.Services.AddDbContext(configuration => { @@ -42,6 +51,8 @@ builder.Services.AddTutorLizardDbRepositories(); + + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/TutorLizard.Web/TutorLizard.Web.csproj b/TutorLizard.Web/TutorLizard.Web.csproj index 29f278f9..c20ec6a3 100644 --- a/TutorLizard.Web/TutorLizard.Web.csproj +++ b/TutorLizard.Web/TutorLizard.Web.csproj @@ -8,6 +8,7 @@ + diff --git a/TutorLizard.Web/Views/Account/Login.cshtml b/TutorLizard.Web/Views/Account/Login.cshtml index 432986fd..e57752ec 100644 --- a/TutorLizard.Web/Views/Account/Login.cshtml +++ b/TutorLizard.Web/Views/Account/Login.cshtml @@ -26,6 +26,32 @@
+
+
+
External Login
+ + @if(Model.AuthenticationSchemes.Count() == 0) + { +
No external login provides available
+ } else + { +
+
+ @foreach(var provider in Model.AuthenticationSchemes) + { + + } +
+
+ } +
+
diff --git a/TutorLizard.Web/appsettings.json b/TutorLizard.Web/appsettings.json index 805e22b9..0b20546f 100644 --- a/TutorLizard.Web/appsettings.json +++ b/TutorLizard.Web/appsettings.json @@ -16,5 +16,11 @@ }, "ConnectionStrings": { "Default": "" + }, + "Auth": { + "Google": { + "ClientId": "434224565561-jkpk0aeuvknvr9ffipo5cn2njq69077o.apps.googleusercontent.com", + "ClientSecret": "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" + } } } From 8f38cdeebc0923269a831ac6b1789a3b6374662a Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Mon, 10 Jun 2024 13:19:21 +0200 Subject: [PATCH 18/48] change the feature to use authenticationservice instead of identity --- TutorLizard.BusinessLogic/Models/User.cs | 5 +- .../Controllers/AccountController.cs | 118 ++++++------------ TutorLizard.Web/Program.cs | 28 +++-- TutorLizard.Web/Views/Account/Login.cshtml | 32 ++--- 4 files changed, 65 insertions(+), 118 deletions(-) diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index e6f94d58..245726c0 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -1,10 +1,9 @@ -using Microsoft.AspNet.Identity.EntityFramework; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using TutorLizard.BusinessLogic.Enums; namespace TutorLizard.BusinessLogic.Models; -public class User : IdentityUser +public class User { public int Id { get; set; } diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 905fdc50..b01040ad 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -1,24 +1,21 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Google; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; -using TutorLizard.BusinessLogic.Models; using TutorLizard.Web.Models; namespace TutorLizard.Web.Controllers; + public class AccountController : Controller { private readonly IUserAuthenticationService _userAuthenticationService; - private SignInManager _signInManager; - private UserManager _userManager; - public AccountController(IUserAuthenticationService userAuthenticationService, SignInManager signInManager, UserManager userManager) + public AccountController(IUserAuthenticationService userAuthenticationService) { _userAuthenticationService = userAuthenticationService; - _signInManager = signInManager; - _userManager = userManager; } public IActionResult Index() @@ -33,14 +30,43 @@ public async Task Login([FromQuery] string? returnUrl) TempData["returnUrl"] = returnUrl; } - var loginViewModel = new LoginModel() - { - AuthenticationSchemes = await _signInManager.GetExternalAuthenticationSchemesAsync() - }; + return View(); + } - return View(loginViewModel); + public async Task GoogleLogin() + { + await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme, + new AuthenticationProperties + { + RedirectUri = Url.Action("GoogleResponse") + }); } + public async Task GoogleResponse() + { + var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); + + if (result?.Succeeded != true) + { + return RedirectToAction("Login"); + } + + var claims = result.Principal.Identities.FirstOrDefault()?.Claims.ToList(); + + var claimNameIdentifier = claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + var claimEmail = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; + + + var loggedIn = await _userAuthenticationService.LogInAsync(claimNameIdentifier, claimEmail); + + if (!loggedIn) + { + return RedirectToAction("Login"); + } + + return RedirectToAction("Index", "Home"); + } + [HttpPost] [ValidateAntiForgeryToken] public async Task Login(LoginModel model) @@ -105,70 +131,4 @@ public IActionResult AccessDenied() { return View(); } - - public IActionResult ExternalLogin(string provider, string returnUrl = "") - { - var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); - - var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); - - return new ChallengeResult(provider, properties); - } - - public async Task ExternalLoginCallback(string returnUrl = "", string remoteError = "") - { - var loginViewModel = new LoginModel() - { - AuthenticationSchemes = await _signInManager.GetExternalAuthenticationSchemesAsync() - }; - - if (!string.IsNullOrEmpty(remoteError)) - { - ModelState.AddModelError("", $"Error from external login provider: {remoteError}"); - return View("Login", loginViewModel); - } - - var info = await _signInManager.GetExternalLoginInfoAsync(); - if (info is null) - { - ModelState.AddModelError("", $"Error from external login provider: {remoteError}"); - return View("Login", loginViewModel); - } - - var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, - info.ProviderKey, - isPersistent: false, - bypassTwoFactor: true); - - if (signInResult.Succeeded) - { - return RedirectToAction("Index", "Home"); - } - else - { - var userEmail = info.Principal.FindFirstValue(ClaimTypes.Email); - - if(!string.IsNullOrEmpty(userEmail)) - { - var user = await _userManager.FindByEmailAsync(userEmail); - - if (user is null) - { - user = new User() - { - Name = userEmail, - Email = userEmail, - UserType = UserType.Regular - }; - - await _userManager.CreateAsync(user); - } - - await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectToAction("Index", "Home"); - } - } - ModelState.AddModelError("", $"Something went wrong"); - return View("Login", loginViewModel); - } } diff --git a/TutorLizard.Web/Program.cs b/TutorLizard.Web/Program.cs index 48eab41a..920c0ea7 100644 --- a/TutorLizard.Web/Program.cs +++ b/TutorLizard.Web/Program.cs @@ -1,10 +1,8 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Authentication.Google; using Microsoft.EntityFrameworkCore; -using System; using TutorLizard.BusinessLogic.Data; using TutorLizard.BusinessLogic.Extensions; using TutorLizard.BusinessLogic.Interfaces.Services; -using TutorLizard.BusinessLogic.Models; using TutorLizard.BusinessLogic.Options; using TutorLizard.BusinessLogic.Services; @@ -24,7 +22,17 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddAuthentication("CookieAuth") +builder.Services.AddAuthentication(options => + { + options.DefaultScheme = "CookieAuth"; + options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; + }) + .AddGoogle(options => + { + options.ClientId = builder.Configuration["Auth:Google:ClientId"]; + options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"]; + options.SaveTokens = true; + }) .AddCookie("CookieAuth", options => { options.ExpireTimeSpan = TimeSpan.FromDays(1); @@ -34,12 +42,7 @@ options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; }); -builder.Services.AddAuthentication() - .AddGoogle(options => - { - options.ClientId = builder.Configuration["Auth:Google:ClientId"]; - options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"]; - }); + builder.Services.AddDbContext(configuration => { @@ -68,12 +71,15 @@ app.UseCookiePolicy(new CookiePolicyOptions { - MinimumSameSitePolicy = SameSiteMode.Strict + MinimumSameSitePolicy = SameSiteMode.None, + Secure = CookieSecurePolicy.Always }); app.UseRouting(); +app.UseAuthentication(); app.UseAuthorization(); +app.UseHttpsRedirection(); app.MapControllerRoute( name: "default", diff --git a/TutorLizard.Web/Views/Account/Login.cshtml b/TutorLizard.Web/Views/Account/Login.cshtml index e57752ec..528aa7a0 100644 --- a/TutorLizard.Web/Views/Account/Login.cshtml +++ b/TutorLizard.Web/Views/Account/Login.cshtml @@ -26,31 +26,13 @@ -
-
-
External Login
- - @if(Model.AuthenticationSchemes.Count() == 0) - { -
No external login provides available
- } else - { -
-
- @foreach(var provider in Model.AuthenticationSchemes) - { - - } -
-
- } -
+
+
+
+

Login using Google

+
+
From 7c1695d4b6a714ecd3065d302ebb1e8761f4c749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Mon, 10 Jun 2024 13:32:55 +0200 Subject: [PATCH 19/48] hotfix to the login view --- TutorLizard.Web/Views/Account/Login.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TutorLizard.Web/Views/Account/Login.cshtml b/TutorLizard.Web/Views/Account/Login.cshtml index 528aa7a0..8d831391 100644 --- a/TutorLizard.Web/Views/Account/Login.cshtml +++ b/TutorLizard.Web/Views/Account/Login.cshtml @@ -28,10 +28,10 @@
-
+

Login using Google

- From 7b48646c4aa5f15498dc36e747f5ea63d6dc5f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Mon, 10 Jun 2024 13:48:15 +0200 Subject: [PATCH 20/48] add select account option to the googlelogin --- TutorLizard.Web/Controllers/AccountController.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index b01040ad..ffff9fad 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -38,7 +38,11 @@ public async Task GoogleLogin() await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme, new AuthenticationProperties { - RedirectUri = Url.Action("GoogleResponse") + RedirectUri = Url.Action("GoogleResponse"), + Items = + { + { "prompt", "select_account" } + } }); } @@ -99,6 +103,8 @@ public async Task Login(LoginModel model) public async Task Logout() { await _userAuthenticationService.LogOutAsync(); + await HttpContext.SignOutAsync(); + return RedirectToAction("Index", "Home"); } public IActionResult Register() From a8d362ef6bc7a885e5d0a9ccdb501ff591453df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Mon, 10 Jun 2024 14:18:45 +0200 Subject: [PATCH 21/48] hotfix --- TutorLizard.Web/Controllers/AccountController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 62dd4fee..ff26dfa5 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -109,7 +109,6 @@ public async Task Login(LoginModel model) public async Task Logout() { await _userAuthenticationService.LogOutAsync(); - await HttpContext.SignOutAsync(); return RedirectToAction("Index", "Home"); } From 6dfb8c94ae7144d2822d081401eb6c09035d1a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Mon, 10 Jun 2024 14:20:08 +0200 Subject: [PATCH 22/48] remove unneccessary property --- TutorLizard.Web/Models/LoginModel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/TutorLizard.Web/Models/LoginModel.cs b/TutorLizard.Web/Models/LoginModel.cs index 87819d4b..3fca4cf6 100644 --- a/TutorLizard.Web/Models/LoginModel.cs +++ b/TutorLizard.Web/Models/LoginModel.cs @@ -11,7 +11,4 @@ public class LoginModel [Required] [DataType(DataType.Password)] public string Password { get; set; } - - // Third-Party Login Providers - public IEnumerable AuthenticationSchemes { get; set; } } From cf121db941295bbe72238a67aaba792176845149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Mon, 10 Jun 2024 14:20:58 +0200 Subject: [PATCH 23/48] remove unneccessary nuget package --- TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj b/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj index 70475976..294a3d5d 100644 --- a/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj +++ b/TutorLizard.BusinessLogic/TutorLizard.BusinessLogic.csproj @@ -8,7 +8,6 @@ - From 1affbf5d1f2ae56f8697eebe6272c1de0e887e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Mon, 10 Jun 2024 19:10:14 +0200 Subject: [PATCH 24/48] Update TutorLizard.Web.csproj --- TutorLizard.Web/TutorLizard.Web.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/TutorLizard.Web/TutorLizard.Web.csproj b/TutorLizard.Web/TutorLizard.Web.csproj index b46ea2df..7b34b90f 100644 --- a/TutorLizard.Web/TutorLizard.Web.csproj +++ b/TutorLizard.Web/TutorLizard.Web.csproj @@ -8,6 +8,7 @@ + From 4b783aff484e4d58753eb76f5ca8f726d5c7ba4f Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Thu, 13 Jun 2024 17:46:02 +0200 Subject: [PATCH 25/48] Add exception handler for AuthenticationFailureException when user cancels the authentication process --- .../Controllers/AccountController.cs | 34 ++++++++++++------- TutorLizard.Web/Program.cs | 12 +++++-- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index ff26dfa5..39992206 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Security.Authentication; using System.Security.Claims; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; @@ -52,27 +53,34 @@ await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme, public async Task GoogleResponse() { - var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); - - if (result?.Succeeded != true) + try { - return RedirectToAction("Login"); - } + var result = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); + + if (result?.Succeeded != true) + { + return RedirectToAction("Login"); + } - var claims = result.Principal.Identities.FirstOrDefault()?.Claims.ToList(); + var claims = result.Principal.Identities.FirstOrDefault()?.Claims.ToList(); - var claimNameIdentifier = claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; - var claimEmail = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; + var claimNameIdentifier = claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + var claimEmail = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; - - var loggedIn = await _userAuthenticationService.LogInAsync(claimNameIdentifier, claimEmail); + var loggedIn = await _userAuthenticationService.LogInAsync(claimNameIdentifier, claimEmail); - if (!loggedIn) + if (!loggedIn) + { + return RedirectToAction("Login"); + } + + return RedirectToAction("Index", "Home"); + } + catch (Exception ex) { + _uiMessagesService.ShowFailureMessage("Logowanie nieudane."); return RedirectToAction("Login"); } - - return RedirectToAction("Index", "Home"); } [HttpPost] diff --git a/TutorLizard.Web/Program.cs b/TutorLizard.Web/Program.cs index ee9dda4d..73c66df3 100644 --- a/TutorLizard.Web/Program.cs +++ b/TutorLizard.Web/Program.cs @@ -31,16 +31,24 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddAuthentication(options => +builder.Services.AddAuthentication(options => { options.DefaultScheme = "CookieAuth"; - options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; }) .AddGoogle(options => { options.ClientId = builder.Configuration["Auth:Google:ClientId"]; options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"]; options.SaveTokens = true; + options.Events = new Microsoft.AspNetCore.Authentication.OAuth.OAuthEvents + { + OnRemoteFailure = context => + { + context.HandleResponse(); + context.Response.Redirect("/Account/Login"); + return Task.FromResult(0); + } + }; }) .AddCookie("CookieAuth", options => { From c90389f5157fc8b1126d30d9655da66d24ad2d8e Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Fri, 14 Jun 2024 15:17:04 +0200 Subject: [PATCH 26/48] Add registering and loging with google --- .../Repositories/Json/UserJsonRepository.cs | 4 +- .../Data/Repositories/IUserRepository.cs | 2 +- .../Interfaces/Services/IUserService.cs | 3 + .../Models/DTOs/UserDto.cs | 2 + TutorLizard.BusinessLogic/Models/User.cs | 5 +- .../Services/UserService.cs | 43 +++ .../Controllers/AccountController.cs | 18 +- .../Services/IUserAuthenticationService.cs | 3 + ...240614131623_AddGoogleIdToUser.Designer.cs | 339 ++++++++++++++++++ .../20240614131623_AddGoogleIdToUser.cs | 28 ++ .../JaszczurContextModelSnapshot.cs | 5 +- .../Services/UserAuthenticationService.cs | 48 +++ 12 files changed, 494 insertions(+), 6 deletions(-) create mode 100644 TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.Designer.cs create mode 100644 TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.cs diff --git a/TutorLizard.BusinessLogic/Data/Repositories/Json/UserJsonRepository.cs b/TutorLizard.BusinessLogic/Data/Repositories/Json/UserJsonRepository.cs index f46d22b2..075b440b 100644 --- a/TutorLizard.BusinessLogic/Data/Repositories/Json/UserJsonRepository.cs +++ b/TutorLizard.BusinessLogic/Data/Repositories/Json/UserJsonRepository.cs @@ -15,9 +15,9 @@ public UserJsonRepository(IOptions options) : base(options.Va } - public User CreateUser(string name, UserType type, string email, string passwordHash) + public User CreateUser(string name, UserType type, string email, string passwordHash, string googleid) { - User newUser = new(GetNewId(), name, type, email, passwordHash); + User newUser = new(GetNewId(), name, type, email, passwordHash, googleid); Data.Add(newUser); SaveToJson(); diff --git a/TutorLizard.BusinessLogic/Interfaces/Data/Repositories/IUserRepository.cs b/TutorLizard.BusinessLogic/Interfaces/Data/Repositories/IUserRepository.cs index 55018a8f..b855e000 100644 --- a/TutorLizard.BusinessLogic/Interfaces/Data/Repositories/IUserRepository.cs +++ b/TutorLizard.BusinessLogic/Interfaces/Data/Repositories/IUserRepository.cs @@ -5,7 +5,7 @@ namespace TutorLizard.BusinessLogic.Interfaces.Data.Repositories; public interface IUserRepository { - User CreateUser(string name, UserType type, string email, string passwordHash); + User CreateUser(string name, UserType type, string email, string passwordHash, string googleId); void DeleteUserById(int id); List GetAllUsers(); User? GetUserById(int id); diff --git a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs index 9c1bfa0f..584a8e0b 100644 --- a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs +++ b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs @@ -7,4 +7,7 @@ public interface IUserService { public Task LogIn(string username, string password); public Task RegisterUser(string userName, UserType type, string email, string password); + public Task RegisterUserWithGoogle(string username, string email, string googleId); + public Task IsTheGoogleUserRegistered(string googleId); + public Task LogInWithGoogle(string username, string googleId); } diff --git a/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs b/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs index 3002b166..ad8f4eea 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/UserDto.cs @@ -22,6 +22,7 @@ public class UserDto [Display(Name = "Data rejestracji")] public DateTime DateCreated { get; set; } = DateTime.Now; + public string? GoogleId { get; set; } public UserDto(User user) { @@ -30,5 +31,6 @@ public UserDto(User user) UserType = user.UserType; Email = user.Email; DateCreated = user.DateCreated; + GoogleId = user.GoogleId; } } diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index 245726c0..9f5e1ce5 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -27,13 +27,16 @@ public class User public DateTime DateCreated { get; set; } = DateTime.Now; - public User(int id, string name, UserType userType, string email, string passwordHash) + public string? GoogleId { get; set; } + + public User(int id, string name, UserType userType, string email, string passwordHash, string? googleId) { Id = id; Name = name; UserType = userType; Email = email; PasswordHash = passwordHash; + GoogleId = googleId; } public User() { diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 4110ccb5..d2fc99f2 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -36,9 +36,26 @@ public UserService(IDbRepository userRepository) if (result == PasswordVerificationResult.Success) return user.ToDto(); + return null; + } + + public async Task LogInWithGoogle(string username, string googleId) + { + var user = await _userRepository.GetAll() + .FirstOrDefaultAsync(user => user.Name == username); + + if (user == null) + { + return null; + } + + if (user.GoogleId == googleId) + return user.ToDto(); + return null; } + public async Task RegisterUser(string userName, UserType type, string email, string password) { if (await _userRepository.GetAll().AnyAsync(user => user.Name == userName)) @@ -58,4 +75,30 @@ public async Task RegisterUser(string userName, UserType type, string emai return true; } + + public async Task RegisterUserWithGoogle(string username, string email, string googleId) + { + if (await _userRepository.GetAll().AnyAsync(user => user.Name == username)) + return false; + + User user = new() + { + Name = username, + UserType = UserType.Regular, + Email = email, + GoogleId = googleId + }; + + await _userRepository.Create(user); + + return true; + } + + public async Task IsTheGoogleUserRegistered(string googleId) + { + if (await _userRepository.GetAll().AnyAsync(user => user.GoogleId == googleId)) + return true; + + return false; + } } \ No newline at end of file diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 39992206..a29ab7c0 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -65,12 +66,27 @@ public async Task GoogleResponse() var claims = result.Principal.Identities.FirstOrDefault()?.Claims.ToList(); var claimNameIdentifier = claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; + var claimName = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; var claimEmail = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; - var loggedIn = await _userAuthenticationService.LogInAsync(claimNameIdentifier, claimEmail); + if(await _userAuthenticationService.IsGoogleUserRegistered(claimNameIdentifier)) + { + try + { + await _userAuthenticationService.RegisterUserWithGoogle(claimName, claimEmail, claimNameIdentifier); + } + catch (Exception ex) + { + _uiMessagesService.ShowFailureMessage("Rejestracja użytkownika za pomocą konta google się nie powiodła"); + return RedirectToAction("Login"); + } + } + + var loggedIn = await _userAuthenticationService.LogInWithGoogleAsync(claimName,claimNameIdentifier); if (!loggedIn) { + _uiMessagesService.ShowFailureMessage("Logowanie nieudane."); return RedirectToAction("Login"); } diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index 73b079b0..e05ee514 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -9,4 +9,7 @@ public interface IUserAuthenticationService public Task LogInAsync(string username, string password); public Task LogOutAsync(); public Task RegisterUser(string username, UserType type, string email, string password); + public Task IsGoogleUserRegistered(string googleid); + public Task RegisterUserWithGoogle(string username, string email, string googleId); + public Task LogInWithGoogleAsync(string username, string googleId); } diff --git a/TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.Designer.cs b/TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.Designer.cs new file mode 100644 index 00000000..1c53d805 --- /dev/null +++ b/TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.Designer.cs @@ -0,0 +1,339 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TutorLizard.BusinessLogic.Data; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + [DbContext(typeof(JaszczurContext))] + [Migration("20240614131623_AddGoogleIdToUser")] + partial class AddGoogleIdToUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Price") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TutorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("TutorId"); + + b.ToTable("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReplyMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.HasIndex("StudentId"); + + b.ToTable("AdRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("ScheduleItemId") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.HasIndex("StudentId"); + + b.ToTable("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("GoogleId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserType") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Category", "Category") + .WithMany("Ads") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("Ads") + .HasForeignKey("TutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("AdRequests") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("AdRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ad"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("ScheduleItems") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ad"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.ScheduleItem", "ScheduleItem") + .WithMany("ScheduleItemRequests") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("ScheduleItemRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ScheduleItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Navigation("AdRequests"); + + b.Navigation("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Navigation("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Navigation("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Navigation("AdRequests"); + + b.Navigation("Ads"); + + b.Navigation("ScheduleItemRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.cs b/TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.cs new file mode 100644 index 00000000..7d4bf0cf --- /dev/null +++ b/TutorLizard.Web/Migrations/20240614131623_AddGoogleIdToUser.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + /// + public partial class AddGoogleIdToUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GoogleId", + table: "Users", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "GoogleId", + table: "Users"); + } + } +} diff --git a/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs b/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs index 146d001c..0f74d005 100644 --- a/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs +++ b/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "8.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -216,6 +216,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("GoogleId") + .HasColumnType("nvarchar(max)"); + b.Property("Name") .IsRequired() .HasMaxLength(40) diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index c1c0519a..8b48fffb 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -52,6 +52,44 @@ await _httpContextAccessor.HttpContext.SignInAsync("CookieAuth", return true; } + + public async Task LogInWithGoogleAsync(string username, string googleId) + { + var user = await _userService.LogInWithGoogle(username, googleId); + + if (user is null) + { + return false; + } + + var claims = new List + { + new Claim(ClaimTypes.Email, user.Email), + new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.NameIdentifier, user.GoogleId), + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Role, user.UserType.ToString()) + }; + + var claimsIdentity = new ClaimsIdentity( + claims, CookieAuthenticationDefaults.AuthenticationScheme); + + var authProperties = new AuthenticationProperties + { + AllowRefresh = true, + ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10), + IsPersistent = true, + }; + + if (_httpContextAccessor.HttpContext is null) + return false; + + await _httpContextAccessor.HttpContext.SignInAsync("CookieAuth", + new ClaimsPrincipal(claimsIdentity), + authProperties); + + return true; + } public async Task LogOutAsync() { @@ -66,6 +104,16 @@ public Task RegisterUser(string username, UserType type, string email, str return _userService.RegisterUser(username, type, email, password); } + public Task RegisterUserWithGoogle(string username, string email, string googleId) + { + return _userService.RegisterUserWithGoogle(username, email, googleId); + } + + public async Task IsGoogleUserRegistered(string googleid) + { + return await _userService.IsTheGoogleUserRegistered(googleid); + } + public int? GetLoggedInUserId() { if (_httpContextAccessor.HttpContext is null) From b708ad677e011eeabb849ad75d0f585153589eae Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 15:32:15 +0200 Subject: [PATCH 27/48] Move sensitive data to userSecrets --- TutorLizard.Web/Controllers/AccountController.cs | 1 - TutorLizard.Web/Models/EmailSettings.cs | 13 +++++++++++++ TutorLizard.Web/Program.cs | 3 +++ .../Services/UserAuthenticationService.cs | 12 +++++++++--- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 TutorLizard.Web/Models/EmailSettings.cs diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 3c451b20..17930a80 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -145,7 +145,6 @@ public async Task ActivateAccount(string activationCode) } } - public IActionResult AccessDenied() { return View(); diff --git a/TutorLizard.Web/Models/EmailSettings.cs b/TutorLizard.Web/Models/EmailSettings.cs new file mode 100644 index 00000000..79c4d3fe --- /dev/null +++ b/TutorLizard.Web/Models/EmailSettings.cs @@ -0,0 +1,13 @@ +namespace TutorLizard.Web.Models +{ + public class EmailSettings + { + public string MailAddress { get; set; } + public string Password { get; set; } + public string SmtpHost { get; set; } + public int SmtpPort { get; set; } + public string FromAddress { get; set; } + public string FromPassword { get; set; } + } + +} diff --git a/TutorLizard.Web/Program.cs b/TutorLizard.Web/Program.cs index 23306a16..072ffb23 100644 --- a/TutorLizard.Web/Program.cs +++ b/TutorLizard.Web/Program.cs @@ -5,6 +5,7 @@ using TutorLizard.BusinessLogic.Options; using TutorLizard.BusinessLogic.Services; using TutorLizard.Web.Interfaces.Services; +using TutorLizard.Web.Models; using TutorLizard.Web.Services; var builder = WebApplication.CreateBuilder(args); @@ -43,7 +44,9 @@ .LogTo(Console.WriteLine, LogLevel.Information); }); + builder.Services.AddTutorLizardDbRepositories(); +builder.Services.Configure(builder.Configuration.GetSection("EmailSettings")); var app = builder.Build(); diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 0ef2d4c1..a5a1bdf8 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -8,6 +8,9 @@ using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using TutorLizard.Web.Models; +using Microsoft.Extensions.Options; namespace TutorLizard.BusinessLogic.Services; @@ -16,12 +19,14 @@ public class UserAuthenticationService : IUserAuthenticationService private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; private readonly JaszczurContext _dbContext; + private readonly EmailSettings _emailSettings; - public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, JaszczurContext dbContext) + public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, JaszczurContext dbContext, IOptions emailSettings) { _httpContextAccessor = httpContextAccessor; _userService = userService; _dbContext = dbContext; + _emailSettings = emailSettings.Value; } public async Task LogInAsync(string username, string password) @@ -95,9 +100,10 @@ public Task RegisterUser(string username, UserType type, string email, str public void SendActivationEmail(string userEmail, string activationCode) { - var fromAddress = new MailAddress("lizardtutoring@gmail.com", "Tutor Lizard"); + var fromAddress = new MailAddress(_emailSettings.FromAddress, "Tutor Lizard"); var toAddress = new MailAddress(userEmail); - const string fromPassword = "pvez johg nzwc enjg"; + var fromPassword = _emailSettings.FromPassword; + string subject = "Aktywacja konta"; string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/Account/ActivateAccount?activationCode={activationCode}"; From 026eb475a29cfb7d9fd5261def04b307edf8c4f2 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 15:43:41 +0200 Subject: [PATCH 28/48] Add ActivationResult model --- TutorLizard.Web/Controllers/AccountController.cs | 4 ++-- .../Services/IUserAuthenticationService.cs | 3 ++- TutorLizard.Web/Models/ActivationResult.cs | 8 ++++++++ .../Services/UserAuthenticationService.cs | 14 +++++++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 TutorLizard.Web/Models/ActivationResult.cs diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 17930a80..9f1a56c9 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -131,9 +131,9 @@ private string GenerateActivationCode() [HttpGet] public async Task ActivateAccount(string activationCode) { - bool isActivated = await _userAuthenticationService.ActivateUserAsync(activationCode); + var result = await _userAuthenticationService.ActivateUserAsync(activationCode); - if (isActivated) + if (result.IsActivated) { _uiMessagesService.ShowSuccessMessage("Atywacja udana."); return View("ActivateAccount"); diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index a224ccae..6ab53fe4 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -1,4 +1,5 @@ using TutorLizard.BusinessLogic.Enums; +using TutorLizard.Web.Models; namespace TutorLizard.BusinessLogic.Interfaces.Services; @@ -9,7 +10,7 @@ public interface IUserAuthenticationService public Task LogInAsync(string username, string password); public Task LogOutAsync(); public Task RegisterUser(string username, UserType type, string email, string password, string activationCode); - Task ActivateUserAsync(string activationCode); + Task ActivateUserAsync(string activationCode); Task IsUserActive(string userName); void SendActivationEmail(string email, string activationCode); } diff --git a/TutorLizard.Web/Models/ActivationResult.cs b/TutorLizard.Web/Models/ActivationResult.cs new file mode 100644 index 00000000..8569ff2f --- /dev/null +++ b/TutorLizard.Web/Models/ActivationResult.cs @@ -0,0 +1,8 @@ +namespace TutorLizard.Web.Models +{ + public class ActivationResult + { + public bool IsActivated { get; set; } + public string ActivationCode { get; set; } + } +} diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index a5a1bdf8..bdc1b652 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -126,7 +126,7 @@ public void SendActivationEmail(string userEmail, string activationCode) } } - public async Task ActivateUserAsync(string activationCode) + public async Task ActivateUserAsync(string activationCode) { var user = await _dbContext.Users .FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false); @@ -136,9 +136,17 @@ public async Task ActivateUserAsync(string activationCode) user.IsActive = true; user.ActivationCode = "DEACTIVATED"; await _dbContext.SaveChangesAsync(); - return true; + return new ActivationResult + { + IsActivated = true, + ActivationCode = activationCode + }; } - return false; + return new ActivationResult + { + IsActivated = false, + ActivationCode = activationCode + }; } From e03154f9f072e7cc9979cbaaf4cd66c38ff0a130 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 16:04:16 +0200 Subject: [PATCH 29/48] Add GetAll in the test CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemoteToTrue --- .../StudentServiceScheduleItemRequestTests.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs index 34ad479f..b4b1a12c 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -12,12 +12,23 @@ public class StudentServiceScheduleItemRequestTests : StudentServiceTestBase public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemoteToTrue() { // Arrange - var scheduleItem = new ScheduleItem { Id = 1, Ad = new Ad { TutorId = 2 } }; + var ad = new Ad + { + Description = "Test Description", + Location = "Test Location", + Subject = "Test Subject", + Title = "Test Title", + TutorId = 2 + }; + var scheduleItem = new ScheduleItem { Id = 1, Ad = ad }; SetupMockGetScheduleItemById(scheduleItem); + var scheduleItems = new List { scheduleItem }; + SetupMockGetAllScheduleItems(scheduleItems); + var request = new CreateScheduleItemRequestRequest { - StudentId = 2, + StudentId = 3, ScheduleItemId = 1, IsRemote = true }; From c41bb69aef76b15e1f06158c2c3751a58a97282f Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 16:08:18 +0200 Subject: [PATCH 30/48] Add MockScheduleItemRequestRepository In Arrange of WhenIsRemoteIsTrue CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemoteToTrue --- .../StudentServiceScheduleItemRequestTests.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs index b4b1a12c..17620562 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -1,11 +1,10 @@ using Moq; -using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Models; using TutorLizard.BusinessLogic.Models.DTOs.Requests; using TutorLizard.BusinessLogic.Services; namespace TutorLizard.BusinessLogic.Tests.Services.Student -{ +{ public class StudentServiceScheduleItemRequestTests : StudentServiceTestBase { [Fact] @@ -33,12 +32,17 @@ public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemote IsRemote = true }; + MockScheduleItemRequestRepository + .Setup(x => x.Create(It.Is(req => req.IsRemote == true))) + .Returns((ScheduleItemRequest req) => Task.FromResult(req)) + .Verifiable(Times.Once); + // Act await StudentService.CreateScheduleItemRequest(request); // Assert - MockScheduleItemRequestRepository.Verify(repo => repo.Create(It.Is(req => req.IsRemote == true)), Times.Once); - + MockScheduleItemRequestRepository.VerifyAll(); + } [Fact] From 589f9b5db863e9328f4d51ba10a484b6a807b93f Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 16:12:15 +0200 Subject: [PATCH 31/48] Delete unnecessary check in test CreateScheduleItemRequest_WhenRequestSent_ShouldReturnSuccess --- .../Services/Student/StudentServiceScheduleItemRequestTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs index 17620562..97c3721d 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -88,7 +88,6 @@ public async Task CreateScheduleItemRequest_WhenRequestSent_ShouldReturnSuccess( // Assert Assert.True(response.Success); - Assert.NotNull(response.CreatedScheduleItemRequestId); } From b1a35127af9605869b36a30bf726c2fc4daa1167 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 16:23:00 +0200 Subject: [PATCH 32/48] Move link to secrets --- TutorLizard.Web/Models/EmailSettings.cs | 1 + TutorLizard.Web/Services/UserAuthenticationService.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TutorLizard.Web/Models/EmailSettings.cs b/TutorLizard.Web/Models/EmailSettings.cs index 79c4d3fe..dc5bd751 100644 --- a/TutorLizard.Web/Models/EmailSettings.cs +++ b/TutorLizard.Web/Models/EmailSettings.cs @@ -8,6 +8,7 @@ public class EmailSettings public int SmtpPort { get; set; } public string FromAddress { get; set; } public string FromPassword { get; set; } + public string ActivationLink { get; set; } } } diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index bdc1b652..b0960f00 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -105,7 +105,7 @@ public void SendActivationEmail(string userEmail, string activationCode) var fromPassword = _emailSettings.FromPassword; string subject = "Aktywacja konta"; - string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: \nhttp://localhost:7092/Account/ActivateAccount?activationCode={activationCode}"; + string body = $"Cześć tu zespół Tutor Lizard, \naby aktywować swoje konto, kliknij poniższy link: {_emailSettings.ActivationLink}{activationCode}"; var smtp = new SmtpClient { From 5d8d35eae82f2ea88cef4718fd2b84e45b37babd Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 16:56:59 +0200 Subject: [PATCH 33/48] Move activationCode generation to Service --- TutorLizard.Web/Controllers/AccountController.cs | 10 ++-------- .../Services/IUserAuthenticationService.cs | 2 +- .../Services/UserAuthenticationService.cs | 12 ++++++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 9f1a56c9..f7e308c9 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -103,9 +103,8 @@ public async Task Register(RegisterUserModel model) return View(model); } - string activationCode = GenerateActivationCode(); - bool registrationResult = await _userAuthenticationService.RegisterUser( - model.UserName, UserType.Regular, model.Email, model.Password, activationCode); + var (registrationResult, activationCode) = await _userAuthenticationService.RegisterUser( + model.UserName, UserType.Regular, model.Email, model.Password); if (registrationResult) { @@ -123,11 +122,6 @@ public async Task Register(RegisterUserModel model) return LocalRedirect("/Home/Index"); } - private string GenerateActivationCode() - { - return Guid.NewGuid().ToString(); - } - [HttpGet] public async Task ActivateAccount(string activationCode) { diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index 6ab53fe4..e17a6c19 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -9,7 +9,7 @@ public interface IUserAuthenticationService int? GetLoggedInUserId(); public Task LogInAsync(string username, string password); public Task LogOutAsync(); - public Task RegisterUser(string username, UserType type, string email, string password, string activationCode); + Task<(bool, string)> RegisterUser(string username, UserType type, string email, string password); Task ActivateUserAsync(string activationCode); Task IsUserActive(string userName); void SendActivationEmail(string email, string activationCode); diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index b0960f00..33fa0cff 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -74,9 +74,17 @@ public async Task LogOutAsync() await _httpContextAccessor.HttpContext.SignOutAsync("CookieAuth"); } - public Task RegisterUser(string username, UserType type, string email, string password, string activationCode) + public async Task<(bool, string)> RegisterUser(string username, UserType type, string email, string password) { - return _userService.RegisterUser(username, type, email, password, activationCode); + string activationCode = GenerateActivationCode(); + bool result = await _userService.RegisterUser(username, type, email, password, activationCode); + + return (result, activationCode); + } + + private string GenerateActivationCode() + { + return Guid.NewGuid().ToString(); } public int? GetLoggedInUserId() From b02f9e255ab45cf54834a9976907eac57976be98 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 16:57:22 +0200 Subject: [PATCH 34/48] Change Activation page view --- TutorLizard.Web/Views/Account/ActivateAccount.cshtml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TutorLizard.Web/Views/Account/ActivateAccount.cshtml b/TutorLizard.Web/Views/Account/ActivateAccount.cshtml index b1cdea67..c11389f5 100644 --- a/TutorLizard.Web/Views/Account/ActivateAccount.cshtml +++ b/TutorLizard.Web/Views/Account/ActivateAccount.cshtml @@ -1,7 +1,6 @@ @{ - ViewData["Title"] = "Aktywacja maila"; + ViewData["Title"] = "Aktywacja konta"; }

@ViewData["Title"]

-

Aktywacja konta

-

Twoje konto zostało aktywowane.

\ No newline at end of file +

Twoje konto zostało aktywowane.

\ No newline at end of file From af3d37fd4fddc10ed1af729155d1bd2b131b482e Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 18:56:28 +0200 Subject: [PATCH 35/48] Fix logging in so only active user can log in --- .../Interfaces/Services/IUserService.cs | 2 +- .../Models/DTOs/LogInResult.cs | 16 +++++ .../Services/UserService.cs | 35 ++++++++--- .../Controllers/AccountController.cs | 40 +++++++----- .../Services/IUserAuthenticationService.cs | 3 +- TutorLizard.Web/Models/LogInResult.cs | 18 ++++++ .../Services/UserAuthenticationService.cs | 61 ++++++++----------- 7 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 TutorLizard.BusinessLogic/Models/DTOs/LogInResult.cs create mode 100644 TutorLizard.Web/Models/LogInResult.cs diff --git a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs index 75d14132..71517f0d 100644 --- a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs +++ b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs @@ -5,6 +5,6 @@ namespace TutorLizard.BusinessLogic.Interfaces.Services; public interface IUserService { - public Task LogIn(string username, string password); + public Task LogIn(string username, string password); public Task RegisterUser(string userName, UserType type, string email, string password, string activationCode); } diff --git a/TutorLizard.BusinessLogic/Models/DTOs/LogInResult.cs b/TutorLizard.BusinessLogic/Models/DTOs/LogInResult.cs new file mode 100644 index 00000000..d59490a7 --- /dev/null +++ b/TutorLizard.BusinessLogic/Models/DTOs/LogInResult.cs @@ -0,0 +1,16 @@ +namespace TutorLizard.BusinessLogic.Models.DTOs +{ + public class LogInResult + { + public LogInResultCode ResultCode { get; set; } + public UserDto? User { get; set; } + } + + public enum LogInResultCode + { + Success, + UserNotFound, + InactiveAccount, + InvalidPassword + } +} diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 714c9d5a..99bb4024 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -18,25 +18,42 @@ public UserService(IDbRepository userRepository) { _userRepository = userRepository; } - public async Task LogIn(string username, string password) + public async Task LogIn(string username, string password) { var user = await _userRepository.GetAll() .FirstOrDefaultAsync(user => user.Name == username); - if (user == null || user.IsActive == false) + if (user == null) { - return null; + return new LogInResult + { + ResultCode = LogInResultCode.UserNotFound + }; } - var result = _passwordHasher - .VerifyHashedPassword(user, - user.PasswordHash, - password); + if (!user.IsActive.HasValue || !user.IsActive.Value) + { + return new LogInResult + { + ResultCode = LogInResultCode.InactiveAccount + }; + } + + var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); if (result == PasswordVerificationResult.Success) - return user.ToDto(); + { + return new LogInResult + { + ResultCode = LogInResultCode.Success, + User = user.ToDto() + }; + } - return null; + return new LogInResult + { + ResultCode = LogInResultCode.InvalidPassword + }; } public async Task RegisterUser(string userName, UserType type, string email, string password, string activationCode) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index f7e308c9..3d0b1f44 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -4,9 +4,11 @@ using System.Net.Mail; using TutorLizard.BusinessLogic.Enums; using TutorLizard.BusinessLogic.Interfaces.Services; +using TutorLizard.BusinessLogic.Models.DTOs; using TutorLizard.Web.Interfaces.Services; using TutorLizard.Web.Models; + namespace TutorLizard.Web.Controllers; public class AccountController : Controller { @@ -46,26 +48,36 @@ public async Task Login(LoginModel model) try { - if (ModelState.IsValid && await _userAuthenticationService.LogInAsync(model.UserName, model.Password)) + if (ModelState.IsValid) { - if (await _userAuthenticationService.IsUserActive(model.UserName)) - { - _uiMessagesService.ShowSuccessMessage("Jesteś zalogowany/a."); - if (string.IsNullOrEmpty(returnUrl)) - { - return RedirectToAction("Index", "Home"); - } - return Redirect(returnUrl); - } - else + var logInResult = await _userAuthenticationService.LogInAsync(model.UserName, model.Password); + + switch (logInResult.ResultCode) { - _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Konto nie jest aktywne."); - return LocalRedirect("/Home/Index"); + case BusinessLogic.Models.DTOs.LogInResultCode.Success: + _uiMessagesService.ShowSuccessMessage("Jesteś zalogowany/a."); + if (string.IsNullOrEmpty(returnUrl)) + { + return RedirectToAction("Index", "Home"); + } + return Redirect(returnUrl); + + case BusinessLogic.Models.DTOs.LogInResultCode.UserNotFound: + case BusinessLogic.Models.DTOs.LogInResultCode.InvalidPassword: + _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Nieprawidłowa nazwa użytkownika lub hasło."); + return RedirectToAction(nameof(Login), new { returnUrl = returnUrl }); + + case BusinessLogic.Models.DTOs.LogInResultCode.InactiveAccount: + _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Konto nie jest aktywne."); + return LocalRedirect("/Home/Index"); + + default: + throw new ArgumentOutOfRangeException(); } } else { - _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Nieprawidłowa nazwa użytkownika lub hasło."); + _uiMessagesService.ShowFailureMessage("Logowanie nieudane. Proszę wypełnić poprawnie formularz."); return RedirectToAction(nameof(Login), new { returnUrl = returnUrl }); } } diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index e17a6c19..a000b8ec 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -7,10 +7,9 @@ public interface IUserAuthenticationService { int? GetLoggedInUserId(); - public Task LogInAsync(string username, string password); + Task LogInAsync(string username, string password); public Task LogOutAsync(); Task<(bool, string)> RegisterUser(string username, UserType type, string email, string password); Task ActivateUserAsync(string activationCode); - Task IsUserActive(string userName); void SendActivationEmail(string email, string activationCode); } diff --git a/TutorLizard.Web/Models/LogInResult.cs b/TutorLizard.Web/Models/LogInResult.cs new file mode 100644 index 00000000..f1ce5bec --- /dev/null +++ b/TutorLizard.Web/Models/LogInResult.cs @@ -0,0 +1,18 @@ +using TutorLizard.BusinessLogic.Models.DTOs; + +namespace TutorLizard.Web.Models +{ + public class LogInResult + { + public LogInResultCode ResultCode { get; set; } + public UserDto? User { get; set; } + } + + public enum LogInResultCode + { + Success, + UserNotFound, + InactiveAccount, + InvalidPassword + } +} diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 33fa0cff..d2dc2eea 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using TutorLizard.Web.Models; using Microsoft.Extensions.Options; +using TutorLizard.BusinessLogic.Models.DTOs; namespace TutorLizard.BusinessLogic.Services; @@ -29,41 +30,39 @@ public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUser _emailSettings = emailSettings.Value; } - public async Task LogInAsync(string username, string password) + public async Task LogInAsync(string username, string password) { - var user = await _userService.LogIn(username, password); + var logInResult = await _userService.LogIn(username, password); - if (user is null) + if (logInResult.ResultCode == Models.DTOs.LogInResultCode.Success && logInResult.User != null) { - return false; - } - - var claims = new List + var claims = new List { - new Claim(ClaimTypes.Email, user.Email), - new Claim(ClaimTypes.Name, user.Name), - new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), - new Claim(ClaimTypes.Role, user.UserType.ToString()) + new Claim(ClaimTypes.Email, logInResult.User.Email), + new Claim(ClaimTypes.Name, logInResult.User.Name), + new Claim(ClaimTypes.NameIdentifier, logInResult.User.Id.ToString()), + new Claim(ClaimTypes.Role, logInResult.User.UserType.ToString()) }; - var claimsIdentity = new ClaimsIdentity( - claims, CookieAuthenticationDefaults.AuthenticationScheme); - - var authProperties = new AuthenticationProperties - { - AllowRefresh = true, - ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10), - IsPersistent = true, - }; + var claimsIdentity = new ClaimsIdentity( + claims, CookieAuthenticationDefaults.AuthenticationScheme); - if (_httpContextAccessor.HttpContext is null) - return false; + var authProperties = new AuthenticationProperties + { + AllowRefresh = true, + ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10), + IsPersistent = true, + }; - await _httpContextAccessor.HttpContext.SignInAsync("CookieAuth", - new ClaimsPrincipal(claimsIdentity), - authProperties); + if (_httpContextAccessor.HttpContext != null) + { + await _httpContextAccessor.HttpContext.SignInAsync("CookieAuth", + new ClaimsPrincipal(claimsIdentity), + authProperties); + } + } - return true; + return logInResult; } public async Task LogOutAsync() @@ -158,15 +157,5 @@ public async Task ActivateUserAsync(string activationCode) } - public async Task IsUserActive(string userName) - { - var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Name == userName); - - if (user != null) - { - return (bool)user.IsActive; - } - return false; - } } From 76dab34b0c90821a6227add12f73536c6b80bde4 Mon Sep 17 00:00:00 2001 From: skrawus Date: Fri, 14 Jun 2024 19:14:40 +0200 Subject: [PATCH 36/48] Change repository --- .../Services/UserAuthenticationService.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index d2dc2eea..c794727c 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -1,17 +1,16 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; -using System.Net.Mail; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; using System.Net; +using System.Net.Mail; using System.Security.Claims; using TutorLizard.BusinessLogic.Data; using TutorLizard.BusinessLogic.Enums; -using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; +using TutorLizard.BusinessLogic.Interfaces.Services; +using TutorLizard.BusinessLogic.Models; using TutorLizard.Web.Models; -using Microsoft.Extensions.Options; -using TutorLizard.BusinessLogic.Models.DTOs; namespace TutorLizard.BusinessLogic.Services; @@ -21,13 +20,15 @@ public class UserAuthenticationService : IUserAuthenticationService private readonly IUserService _userService; private readonly JaszczurContext _dbContext; private readonly EmailSettings _emailSettings; + private readonly IDbRepository _userRepository; - public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, JaszczurContext dbContext, IOptions emailSettings) + public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, JaszczurContext dbContext, IOptions emailSettings, IDbRepository userRepository) { _httpContextAccessor = httpContextAccessor; _userService = userService; _dbContext = dbContext; _emailSettings = emailSettings.Value; + _userRepository = userRepository; } public async Task LogInAsync(string username, string password) @@ -135,7 +136,8 @@ public void SendActivationEmail(string userEmail, string activationCode) public async Task ActivateUserAsync(string activationCode) { - var user = await _dbContext.Users + + var user = await _userRepository.GetAll() .FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false); if (user != null) From c88c3a4b7fbadfc7280d432f7513192f3d1d0d93 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sat, 15 Jun 2024 10:26:20 +0200 Subject: [PATCH 37/48] Make LogIn method in IUserService not-nullable --- TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs index 71517f0d..b902a7dc 100644 --- a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs +++ b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs @@ -5,6 +5,6 @@ namespace TutorLizard.BusinessLogic.Interfaces.Services; public interface IUserService { - public Task LogIn(string username, string password); + public Task LogIn(string username, string password); public Task RegisterUser(string userName, UserType type, string email, string password, string activationCode); } From b919b95684f1093c318def7da4f565f6c7a52666 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sat, 15 Jun 2024 10:29:08 +0200 Subject: [PATCH 38/48] Change Exception in AccountController --- TutorLizard.Web/Controllers/AccountController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index 3d0b1f44..beda58b9 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.ComponentModel; using System.Net; using System.Net.Mail; using TutorLizard.BusinessLogic.Enums; @@ -72,7 +73,9 @@ public async Task Login(LoginModel model) return LocalRedirect("/Home/Index"); default: - throw new ArgumentOutOfRangeException(); + throw new InvalidEnumArgumentException(argumentName: nameof(BusinessLogic.Models.DTOs.LogInResult.ResultCode), + invalidValue: (int)logInResult.ResultCode, + enumClass: typeof(BusinessLogic.Models.DTOs.LogInResult)); } } else From 5642e2f2c11e41fe4ff3c69705aed4e494c041fb Mon Sep 17 00:00:00 2001 From: skrawus Date: Sat, 15 Jun 2024 10:32:34 +0200 Subject: [PATCH 39/48] Delete duplicated class --- TutorLizard.Web/Models/LogInResult.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 TutorLizard.Web/Models/LogInResult.cs diff --git a/TutorLizard.Web/Models/LogInResult.cs b/TutorLizard.Web/Models/LogInResult.cs deleted file mode 100644 index f1ce5bec..00000000 --- a/TutorLizard.Web/Models/LogInResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -using TutorLizard.BusinessLogic.Models.DTOs; - -namespace TutorLizard.Web.Models -{ - public class LogInResult - { - public LogInResultCode ResultCode { get; set; } - public UserDto? User { get; set; } - } - - public enum LogInResultCode - { - Success, - UserNotFound, - InactiveAccount, - InvalidPassword - } -} From f53ed17c84efaff3e374b829a950f99ec15cb8a2 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sat, 15 Jun 2024 10:35:51 +0200 Subject: [PATCH 40/48] Make IsActive not nullable & update LogIn method --- TutorLizard.BusinessLogic/Models/User.cs | 2 +- TutorLizard.BusinessLogic/Services/UserService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index 5633c046..1331a883 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -6,7 +6,7 @@ namespace TutorLizard.BusinessLogic.Models; public class User { public int Id { get; set; } - public bool? IsActive { get; set; } + public bool IsActive { get; set; } [Required] [MinLength(5)] diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 99bb4024..435e71bc 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -31,7 +31,7 @@ public async Task LogIn(string username, string password) }; } - if (!user.IsActive.HasValue || !user.IsActive.Value) + if (!user.IsActive) { return new LogInResult { From b7ed9bbccceefb5882bd4ecddb342f75ca27376e Mon Sep 17 00:00:00 2001 From: skrawus Date: Sat, 15 Jun 2024 11:39:23 +0200 Subject: [PATCH 41/48] Correct IsRemote checkbox --- .../Services/StudentService.cs | 2 +- .../AvailableScheduleForAd/Default.cshtml | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/TutorLizard.BusinessLogic/Services/StudentService.cs b/TutorLizard.BusinessLogic/Services/StudentService.cs index 1aba912b..104f728d 100644 --- a/TutorLizard.BusinessLogic/Services/StudentService.cs +++ b/TutorLizard.BusinessLogic/Services/StudentService.cs @@ -81,7 +81,7 @@ public async Task GetAvailableScheduleForAd(Avai .Where(ar => ar.AdId == request.AdId) .AnyAsync(ar => ar.StudentId == request.StudentId && ar.IsAccepted); - bool isRemote = await _adRequestRepository.GetAll() + bool isRemote = await _adRepository.GetAll() .Where(ad => ad.Id == request.AdId) .Select(ad => ad.IsRemote) .FirstOrDefaultAsync(); diff --git a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml index 460b32b0..9ea08090 100644 --- a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml +++ b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml @@ -29,19 +29,21 @@ { Nie wysłano
- @if (item.Status == ScheduleItemRequestStatus.RequestNotSent) - {
-
- -
+ + @if (Model.IsRemote) + { +
+ +
+ } +
- }
}
From de7c7ba355cb3794dd65d371c7290260cef6453c Mon Sep 17 00:00:00 2001 From: skrawus Date: Sat, 15 Jun 2024 12:25:44 +0200 Subject: [PATCH 42/48] Change tests of IsRemote - make it one test CreateScheduleItemRequest_ShouldSetIsRemoteCorrectly --- .../StudentServiceScheduleItemRequestTests.cs | 37 ++++--------------- .../Student/StudentServiceTestBase.cs | 6 +-- 2 files changed, 9 insertions(+), 34 deletions(-) diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs index 97c3721d..2de8a0bc 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -7,8 +7,10 @@ namespace TutorLizard.BusinessLogic.Tests.Services.Student { public class StudentServiceScheduleItemRequestTests : StudentServiceTestBase { - [Fact] - public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemoteToTrue() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateScheduleItemRequest_ShouldSetIsRemoteCorrectly(bool isRemote) { // Arrange var ad = new Ad @@ -20,20 +22,19 @@ public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemote TutorId = 2 }; var scheduleItem = new ScheduleItem { Id = 1, Ad = ad }; - SetupMockGetScheduleItemById(scheduleItem); - var scheduleItems = new List { scheduleItem }; - SetupMockGetAllScheduleItems(scheduleItems); + SetupMockGetScheduleItemById(scheduleItem); + SetupMockGetAllScheduleItems(new List { scheduleItem }); var request = new CreateScheduleItemRequestRequest { StudentId = 3, ScheduleItemId = 1, - IsRemote = true + IsRemote = isRemote }; MockScheduleItemRequestRepository - .Setup(x => x.Create(It.Is(req => req.IsRemote == true))) + .Setup(x => x.Create(It.Is(req => req.IsRemote == isRemote))) .Returns((ScheduleItemRequest req) => Task.FromResult(req)) .Verifiable(Times.Once); @@ -42,28 +43,6 @@ public async Task CreateScheduleItemRequest_WhenIsRemoteIsTrue_ShouldSetIsRemote // Assert MockScheduleItemRequestRepository.VerifyAll(); - - } - - [Fact] - public async Task CreateScheduleItemRequest_WhenIsRemoteIsFalse_ShouldSetIsRemoteToFalse() - { - // Arrange - var scheduleItem = new ScheduleItem { Id = 1, Ad = new Ad { TutorId = 2 } }; - SetupMockGetScheduleItemById(scheduleItem); - - var request = new CreateScheduleItemRequestRequest - { - StudentId = 2, - ScheduleItemId = 1, - IsRemote = false - }; - - // Act - await StudentService.CreateScheduleItemRequest(request); - - // Assert - MockScheduleItemRequestRepository.Verify(repo => repo.Create(It.Is(req => req.IsRemote == false)), Times.Once); } [Fact] diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs index 7258aab7..98408524 100644 --- a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs @@ -1,7 +1,5 @@ using AutoFixture; -using Microsoft.EntityFrameworkCore; using Moq; -using TutorLizard.BusinessLogic.Data; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Models; using TutorLizard.BusinessLogic.Services; @@ -16,7 +14,7 @@ public class StudentServiceTestBase : TestsWithInMemoryDbBase protected Mock> MockAdRequestRepository = new(); protected Mock> MockScheduleItemRepository = new(); protected Mock> MockScheduleItemRequestRepository = new(); - + protected StudentServiceTestBase() : base() { @@ -41,7 +39,6 @@ protected void SetupMockGetAllScheduleItems(List scheduleItems) .Returns(scheduleItemsInDb); } - protected IQueryable AddEntitiesToInMemoryDb(List entities) where TEntity : class { @@ -56,4 +53,3 @@ protected IQueryable AddEntitiesToInMemoryDb(List ent } } } - \ No newline at end of file From d9dae9f295b61a59dd0cf87952124736f515b161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Sun, 16 Jun 2024 18:40:10 +0200 Subject: [PATCH 43/48] oauth fixes --- TutorLizard.BusinessLogic/Services/UserService.cs | 4 ++-- TutorLizard.Web/Controllers/AccountController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index d2fc99f2..8e7b2e19 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -42,7 +42,7 @@ public UserService(IDbRepository userRepository) public async Task LogInWithGoogle(string username, string googleId) { var user = await _userRepository.GetAll() - .FirstOrDefaultAsync(user => user.Name == username); + .FirstOrDefaultAsync(user => user.GoogleId == googleId); if (user == null) { @@ -96,7 +96,7 @@ public async Task RegisterUserWithGoogle(string username, string email, st public async Task IsTheGoogleUserRegistered(string googleId) { - if (await _userRepository.GetAll().AnyAsync(user => user.GoogleId == googleId)) + if (await _userRepository.GetAll().AnyAsync(user => user.GoogleId == googleId)) return true; return false; diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index a29ab7c0..2c01abde 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -69,7 +69,7 @@ public async Task GoogleResponse() var claimName = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; var claimEmail = claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; - if(await _userAuthenticationService.IsGoogleUserRegistered(claimNameIdentifier)) + if(!(await _userAuthenticationService.IsGoogleUserRegistered(claimNameIdentifier))) { try { From dfe0c00435d43bedad7383e8024451a34b1f1151 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 16 Jun 2024 19:03:18 +0200 Subject: [PATCH 44/48] Change the way of saving changes in ActivateUserAsync method --- .../Services/UserAuthenticationService.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index c794727c..526d563b 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -18,15 +18,13 @@ public class UserAuthenticationService : IUserAuthenticationService { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IUserService _userService; - private readonly JaszczurContext _dbContext; private readonly EmailSettings _emailSettings; private readonly IDbRepository _userRepository; - public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, JaszczurContext dbContext, IOptions emailSettings, IDbRepository userRepository) + public UserAuthenticationService(IHttpContextAccessor httpContextAccessor, IUserService userService, IOptions emailSettings, IDbRepository userRepository) { _httpContextAccessor = httpContextAccessor; _userService = userService; - _dbContext = dbContext; _emailSettings = emailSettings.Value; _userRepository = userRepository; } @@ -144,7 +142,13 @@ public async Task ActivateUserAsync(string activationCode) { user.IsActive = true; user.ActivationCode = "DEACTIVATED"; - await _dbContext.SaveChangesAsync(); + + await _userRepository.Update(user.Id, u => + { + u.IsActive = user.IsActive; + u.ActivationCode = user.ActivationCode; + }); + return new ActivationResult { IsActivated = true, From 52b2e4257eb1f4474422ada302e68ef1440c062a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Sun, 16 Jun 2024 19:12:13 +0200 Subject: [PATCH 45/48] functional oauth --- TutorLizard.BusinessLogic/Models/User.cs | 3 +- ...4_ChangePasswordHashToNullable.Designer.cs | 339 ++++++++++++++++++ ...0616164044_ChangePasswordHashToNullable.cs | 22 ++ ...6164645_PasswordHashToNullable.Designer.cs | 338 +++++++++++++++++ .../20240616164645_PasswordHashToNullable.cs | 40 +++ .../JaszczurContextModelSnapshot.cs | 1 - .../Services/UserAuthenticationService.cs | 2 +- TutorLizard.Web/Views/Account/Register.cshtml | 8 + 8 files changed, 749 insertions(+), 4 deletions(-) create mode 100644 TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.Designer.cs create mode 100644 TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.cs create mode 100644 TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.Designer.cs create mode 100644 TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.cs diff --git a/TutorLizard.BusinessLogic/Models/User.cs b/TutorLizard.BusinessLogic/Models/User.cs index 9f5e1ce5..de152ac3 100644 --- a/TutorLizard.BusinessLogic/Models/User.cs +++ b/TutorLizard.BusinessLogic/Models/User.cs @@ -19,11 +19,10 @@ public class User [MaxLength(100)] public string Email { get; set; } - [Required] [DataType(DataType.Password)] [MinLength(8)] [MaxLength(100)] - public string PasswordHash { get; set; } + public string? PasswordHash { get; set; } public DateTime DateCreated { get; set; } = DateTime.Now; diff --git a/TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.Designer.cs b/TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.Designer.cs new file mode 100644 index 00000000..002c703e --- /dev/null +++ b/TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.Designer.cs @@ -0,0 +1,339 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TutorLizard.BusinessLogic.Data; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + [DbContext(typeof(JaszczurContext))] + [Migration("20240616164044_ChangePasswordHashToNullable")] + partial class ChangePasswordHashToNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Price") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TutorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("TutorId"); + + b.ToTable("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReplyMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.HasIndex("StudentId"); + + b.ToTable("AdRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("ScheduleItemId") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.HasIndex("StudentId"); + + b.ToTable("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("GoogleId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserType") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Category", "Category") + .WithMany("Ads") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("Ads") + .HasForeignKey("TutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("AdRequests") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("AdRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ad"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("ScheduleItems") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ad"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.ScheduleItem", "ScheduleItem") + .WithMany("ScheduleItemRequests") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("ScheduleItemRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ScheduleItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Navigation("AdRequests"); + + b.Navigation("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Navigation("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Navigation("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Navigation("AdRequests"); + + b.Navigation("Ads"); + + b.Navigation("ScheduleItemRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.cs b/TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.cs new file mode 100644 index 00000000..fdd6bff9 --- /dev/null +++ b/TutorLizard.Web/Migrations/20240616164044_ChangePasswordHashToNullable.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + /// + public partial class ChangePasswordHashToNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.Designer.cs b/TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.Designer.cs new file mode 100644 index 00000000..a0b51d4c --- /dev/null +++ b/TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.Designer.cs @@ -0,0 +1,338 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TutorLizard.BusinessLogic.Data; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + [DbContext(typeof(JaszczurContext))] + [Migration("20240616164645_PasswordHashToNullable")] + partial class PasswordHashToNullable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Price") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TutorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("TutorId"); + + b.ToTable("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReplyMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.HasIndex("StudentId"); + + b.ToTable("AdRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("ScheduleItemId") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.HasIndex("StudentId"); + + b.ToTable("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("GoogleId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("PasswordHash") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserType") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Category", "Category") + .WithMany("Ads") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("Ads") + .HasForeignKey("TutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("AdRequests") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("AdRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ad"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("ScheduleItems") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ad"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.ScheduleItem", "ScheduleItem") + .WithMany("ScheduleItemRequests") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("ScheduleItemRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ScheduleItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Navigation("AdRequests"); + + b.Navigation("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Navigation("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Navigation("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Navigation("AdRequests"); + + b.Navigation("Ads"); + + b.Navigation("ScheduleItemRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.cs b/TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.cs new file mode 100644 index 00000000..5073e0fd --- /dev/null +++ b/TutorLizard.Web/Migrations/20240616164645_PasswordHashToNullable.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + /// + public partial class PasswordHashToNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PasswordHash", + table: "Users", + type: "nvarchar(100)", + maxLength: 100, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PasswordHash", + table: "Users", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100, + oldNullable: true); + } + } +} diff --git a/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs b/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs index 0f74d005..bcaf54e5 100644 --- a/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs +++ b/TutorLizard.Web/Migrations/JaszczurContextModelSnapshot.cs @@ -225,7 +225,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("nvarchar(40)"); b.Property("PasswordHash") - .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 8b48fffb..65c5ea38 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -66,7 +66,6 @@ public async Task LogInWithGoogleAsync(string username, string googleId) { new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.Name, user.Name), - new Claim(ClaimTypes.NameIdentifier, user.GoogleId), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Role, user.UserType.ToString()) }; @@ -84,6 +83,7 @@ public async Task LogInWithGoogleAsync(string username, string googleId) if (_httpContextAccessor.HttpContext is null) return false; + await _httpContextAccessor.HttpContext.SignOutAsync(); await _httpContextAccessor.HttpContext.SignInAsync("CookieAuth", new ClaimsPrincipal(claimsIdentity), authProperties); diff --git a/TutorLizard.Web/Views/Account/Register.cshtml b/TutorLizard.Web/Views/Account/Register.cshtml index 778877b6..16a771bb 100644 --- a/TutorLizard.Web/Views/Account/Register.cshtml +++ b/TutorLizard.Web/Views/Account/Register.cshtml @@ -32,6 +32,14 @@
+
+
+

Register using Google

+
+ +
From 10a2a95dcf29e0b7d5907862cccc893c08328cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kmieci=C5=84ski?= Date: Sun, 16 Jun 2024 20:05:30 +0200 Subject: [PATCH 46/48] remove secrets --- TutorLizard.Web/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TutorLizard.Web/appsettings.json b/TutorLizard.Web/appsettings.json index 67075667..816d75ab 100644 --- a/TutorLizard.Web/appsettings.json +++ b/TutorLizard.Web/appsettings.json @@ -31,8 +31,8 @@ }, "Auth": { "Google": { - "ClientId": "434224565561-jkpk0aeuvknvr9ffipo5cn2njq69077o.apps.googleusercontent.com", - "ClientSecret": "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" + "ClientId": "placeholder", + "ClientSecret": "placeholder" } } } From df071c998f64ef67286bc5e063125873b589bfeb Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 23 Jun 2024 13:04:59 +0200 Subject: [PATCH 47/48] Move ActivateUserAsync method to UserService From UserAuthenticationService --- .../Interfaces/Services/IUserService.cs | 2 ++ .../Models/ActivationResult.cs | 14 ++++++++ .../Services/UserService.cs | 30 ++++++++++++++++ .../Services/IUserAuthenticationService.cs | 2 -- TutorLizard.Web/Models/ActivationResult.cs | 8 ----- .../Services/UserAuthenticationService.cs | 36 ++----------------- 6 files changed, 49 insertions(+), 43 deletions(-) create mode 100644 TutorLizard.BusinessLogic/Models/ActivationResult.cs delete mode 100644 TutorLizard.Web/Models/ActivationResult.cs diff --git a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs index b902a7dc..81e07622 100644 --- a/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs +++ b/TutorLizard.BusinessLogic/Interfaces/Services/IUserService.cs @@ -1,4 +1,5 @@ using TutorLizard.BusinessLogic.Enums; +using TutorLizard.BusinessLogic.Models; using TutorLizard.BusinessLogic.Models.DTOs; namespace TutorLizard.BusinessLogic.Interfaces.Services; @@ -7,4 +8,5 @@ public interface IUserService { public Task LogIn(string username, string password); public Task RegisterUser(string userName, UserType type, string email, string password, string activationCode); + Task ActivateUserAsync(string activationCode); } diff --git a/TutorLizard.BusinessLogic/Models/ActivationResult.cs b/TutorLizard.BusinessLogic/Models/ActivationResult.cs new file mode 100644 index 00000000..62a197bb --- /dev/null +++ b/TutorLizard.BusinessLogic/Models/ActivationResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TutorLizard.BusinessLogic.Models +{ + public class ActivationResult + { + public bool IsActivated { get; set; } + public string ActivationCode { get; set; } + } +} diff --git a/TutorLizard.BusinessLogic/Services/UserService.cs b/TutorLizard.BusinessLogic/Services/UserService.cs index 435e71bc..90c0b4d0 100644 --- a/TutorLizard.BusinessLogic/Services/UserService.cs +++ b/TutorLizard.BusinessLogic/Services/UserService.cs @@ -77,4 +77,34 @@ public async Task RegisterUser(string userName, UserType type, string emai return true; } + + public async Task ActivateUserAsync(string activationCode) + { + + var user = await _userRepository.GetAll() + .FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false); + + if (user != null) + { + user.IsActive = true; + user.ActivationCode = "ACTIVATED"; + + await _userRepository.Update(user.Id, u => + { + u.IsActive = user.IsActive; + u.ActivationCode = user.ActivationCode; + }); + + return new ActivationResult + { + IsActivated = true, + ActivationCode = activationCode + }; + } + return new ActivationResult + { + IsActivated = false, + ActivationCode = activationCode + }; + } } \ No newline at end of file diff --git a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs index a000b8ec..08a52b89 100644 --- a/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs +++ b/TutorLizard.Web/Interfaces/Services/IUserAuthenticationService.cs @@ -1,5 +1,4 @@ using TutorLizard.BusinessLogic.Enums; -using TutorLizard.Web.Models; namespace TutorLizard.BusinessLogic.Interfaces.Services; @@ -10,6 +9,5 @@ public interface IUserAuthenticationService Task LogInAsync(string username, string password); public Task LogOutAsync(); Task<(bool, string)> RegisterUser(string username, UserType type, string email, string password); - Task ActivateUserAsync(string activationCode); void SendActivationEmail(string email, string activationCode); } diff --git a/TutorLizard.Web/Models/ActivationResult.cs b/TutorLizard.Web/Models/ActivationResult.cs deleted file mode 100644 index 8569ff2f..00000000 --- a/TutorLizard.Web/Models/ActivationResult.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace TutorLizard.Web.Models -{ - public class ActivationResult - { - public bool IsActivated { get; set; } - public string ActivationCode { get; set; } - } -} diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 526d563b..08af83c4 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -115,9 +115,11 @@ public void SendActivationEmail(string userEmail, string activationCode) var smtp = new SmtpClient { + // TODO przenieś do appsettingsów / secretsów Host = "smtp.gmail.com", Port = 587, EnableSsl = true, + // koniec - przenieś do appsettingsów / secretsów DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromAddress.Address, fromPassword) @@ -131,37 +133,5 @@ public void SendActivationEmail(string userEmail, string activationCode) smtp.Send(message); } } - - public async Task ActivateUserAsync(string activationCode) - { - - var user = await _userRepository.GetAll() - .FirstOrDefaultAsync(u => u.ActivationCode == activationCode && u.IsActive == false); - - if (user != null) - { - user.IsActive = true; - user.ActivationCode = "DEACTIVATED"; - - await _userRepository.Update(user.Id, u => - { - u.IsActive = user.IsActive; - u.ActivationCode = user.ActivationCode; - }); - - return new ActivationResult - { - IsActivated = true, - ActivationCode = activationCode - }; - } - return new ActivationResult - { - IsActivated = false, - ActivationCode = activationCode - }; - } - - - + } From c51b51054ff773a29c2698f82bc4db863fad4821 Mon Sep 17 00:00:00 2001 From: skrawus Date: Sun, 23 Jun 2024 15:51:22 +0200 Subject: [PATCH 48/48] Move data to secrets --- TutorLizard.Web/Controllers/AccountController.cs | 7 +++++-- TutorLizard.Web/Models/EmailSettings.cs | 7 +++---- TutorLizard.Web/Services/UserAuthenticationService.cs | 8 +++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/TutorLizard.Web/Controllers/AccountController.cs b/TutorLizard.Web/Controllers/AccountController.cs index beda58b9..97b60604 100644 --- a/TutorLizard.Web/Controllers/AccountController.cs +++ b/TutorLizard.Web/Controllers/AccountController.cs @@ -15,12 +15,15 @@ public class AccountController : Controller { private readonly IUserAuthenticationService _userAuthenticationService; private readonly IUiMessagesService _uiMessagesService; + private readonly IUserService _userService; public AccountController(IUserAuthenticationService userAuthenticationService, - IUiMessagesService uiMessagesService) + IUiMessagesService uiMessagesService, + IUserService userService) { _userAuthenticationService = userAuthenticationService; _uiMessagesService = uiMessagesService; + _userService = userService; } public IActionResult Index() @@ -140,7 +143,7 @@ public async Task Register(RegisterUserModel model) [HttpGet] public async Task ActivateAccount(string activationCode) { - var result = await _userAuthenticationService.ActivateUserAsync(activationCode); + var result = await _userService.ActivateUserAsync(activationCode); if (result.IsActivated) { diff --git a/TutorLizard.Web/Models/EmailSettings.cs b/TutorLizard.Web/Models/EmailSettings.cs index dc5bd751..481bb807 100644 --- a/TutorLizard.Web/Models/EmailSettings.cs +++ b/TutorLizard.Web/Models/EmailSettings.cs @@ -2,13 +2,12 @@ { public class EmailSettings { - public string MailAddress { get; set; } - public string Password { get; set; } - public string SmtpHost { get; set; } - public int SmtpPort { get; set; } public string FromAddress { get; set; } public string FromPassword { get; set; } public string ActivationLink { get; set; } + public string Host { get; set; } + public int Port { get; set; } + public bool EnableSsl { get; set; } } } diff --git a/TutorLizard.Web/Services/UserAuthenticationService.cs b/TutorLizard.Web/Services/UserAuthenticationService.cs index 08af83c4..8371bcfc 100644 --- a/TutorLizard.Web/Services/UserAuthenticationService.cs +++ b/TutorLizard.Web/Services/UserAuthenticationService.cs @@ -115,11 +115,9 @@ public void SendActivationEmail(string userEmail, string activationCode) var smtp = new SmtpClient { - // TODO przenieś do appsettingsów / secretsów - Host = "smtp.gmail.com", - Port = 587, - EnableSsl = true, - // koniec - przenieś do appsettingsów / secretsów + Host = _emailSettings.Host, + Port = _emailSettings.Port, + EnableSsl = _emailSettings.EnableSsl, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromAddress.Address, fromPassword)