diff --git a/Weifer.Database.EF/Entitys/Customer.cs b/Weifer.Database.EF/Entitys/Customer.cs index e36418c..fda9b33 100644 --- a/Weifer.Database.EF/Entitys/Customer.cs +++ b/Weifer.Database.EF/Entitys/Customer.cs @@ -13,6 +13,7 @@ namespace Weifer.Database.EF.Entitys public string LastName { get; set; } public string Email { get; set; } public string PasswordHash { get; set; } + public string SessionToken { get; set; } public DateTime CreatedOn { get; set; } public ICollection ShoppingLists { get; set; } } diff --git a/Weifer.Database.EF/Migrations/20240304193908_added_sessiontoken.Designer.cs b/Weifer.Database.EF/Migrations/20240304193908_added_sessiontoken.Designer.cs new file mode 100644 index 0000000..0fe891d --- /dev/null +++ b/Weifer.Database.EF/Migrations/20240304193908_added_sessiontoken.Designer.cs @@ -0,0 +1,138 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Weifer.Database.EF; + +#nullable disable + +namespace Weifer.Database.EF.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240304193908_added_sessiontoken")] + partial class added_sessiontoken + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SessionToken") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("CustomerId"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingItem", b => + { + b.Property("ShoppingItemId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("Purchased") + .HasColumnType("bit"); + + b.Property("ShoppingListId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("ShoppingItemId"); + + b.HasIndex("ShoppingListId"); + + b.ToTable("ShoppingItems"); + }); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingList", b => + { + b.Property("ShoppingListId") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("ShoppingListName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ShoppingListId"); + + b.HasIndex("CustomerId"); + + b.ToTable("ShoppingLists"); + }); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingItem", b => + { + b.HasOne("Weifer.Database.EF.Entitys.ShoppingList", null) + .WithMany("ShoppingItems") + .HasForeignKey("ShoppingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingList", b => + { + b.HasOne("Weifer.Database.EF.Entitys.Customer", null) + .WithMany("ShoppingLists") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.Customer", b => + { + b.Navigation("ShoppingLists"); + }); + + modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingList", b => + { + b.Navigation("ShoppingItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Weifer.Database.EF/Migrations/20240304193908_added_sessiontoken.cs b/Weifer.Database.EF/Migrations/20240304193908_added_sessiontoken.cs new file mode 100644 index 0000000..0b12f3e --- /dev/null +++ b/Weifer.Database.EF/Migrations/20240304193908_added_sessiontoken.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Weifer.Database.EF.Migrations +{ + /// + public partial class added_sessiontoken : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SessionToken", + table: "Customers", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SessionToken", + table: "Customers"); + } + } +} diff --git a/Weifer.Database.EF/Migrations/DatabaseContextModelSnapshot.cs b/Weifer.Database.EF/Migrations/DatabaseContextModelSnapshot.cs index 560d3f4..1ec21fb 100644 --- a/Weifer.Database.EF/Migrations/DatabaseContextModelSnapshot.cs +++ b/Weifer.Database.EF/Migrations/DatabaseContextModelSnapshot.cs @@ -47,6 +47,10 @@ namespace Weifer.Database.EF.Migrations .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("SessionToken") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.HasKey("CustomerId"); b.ToTable("Customers"); diff --git a/Weifer.Database.EF/Weifer.Database.EF.csproj b/Weifer.Database.EF/Weifer.Database.EF.csproj index 605c691..0c73042 100644 --- a/Weifer.Database.EF/Weifer.Database.EF.csproj +++ b/Weifer.Database.EF/Weifer.Database.EF.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + True diff --git a/Weifer.ShoppingApp.API/RestApi/AuthenticationApiController.cs b/Weifer.ShoppingApp.API/RestApi/AuthenticationApiController.cs index 1ef5f6e..bc32979 100644 --- a/Weifer.ShoppingApp.API/RestApi/AuthenticationApiController.cs +++ b/Weifer.ShoppingApp.API/RestApi/AuthenticationApiController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Primitives; using Weifer.Database.EF; using Weifer.ShoppingApp.API.Controllers; using Weifer.ShoppingApp.API.Models; @@ -39,19 +40,39 @@ public class AuthenticationApiController : ControllerBase { var customer = await dbContext.Customers.Where(cu => cu.Email == credentials.Email).FirstOrDefaultAsync(); var token = authenticationController.GenerateJwtToken(); + if (customer != null) + { + customer.SessionToken = token; + await dbContext.SaveChangesAsync(); + } return Ok(new { token = token, // Token Information customer = new CustomerDto - { // Kundeninformationen + { CustomerId = customer.CustomerId, FirstName = customer.FirstName, LastName = customer.LastName, Email = credentials.Email + } }); } return Unauthorized(); } + [HttpGet("validateToken")] + public async Task ValidateToken() + { + + var token = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); + + var user = await dbContext.Customers.FirstOrDefaultAsync(x => x.SessionToken == token); + + if (user != null) + { + return Ok(); + } + return Unauthorized(); + } } diff --git a/Weifer.ShoppingApp.API/Weifer.ShoppingApp.API.csproj b/Weifer.ShoppingApp.API/Weifer.ShoppingApp.API.csproj index 9ed2ad3..4c0930b 100644 --- a/Weifer.ShoppingApp.API/Weifer.ShoppingApp.API.csproj +++ b/Weifer.ShoppingApp.API/Weifer.ShoppingApp.API.csproj @@ -10,10 +10,7 @@ - - - - + diff --git a/Weifer.ShoppingApp.API/libman.json b/Weifer.ShoppingApp.API/libman.json new file mode 100644 index 0000000..ceee271 --- /dev/null +++ b/Weifer.ShoppingApp.API/libman.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "defaultProvider": "cdnjs", + "libraries": [] +} \ No newline at end of file diff --git a/Weifer.ShoppingApp.Frontend_/src/app/app.component.ts b/Weifer.ShoppingApp.Frontend_/src/app/app.component.ts index 70367ee..bb0d69a 100644 --- a/Weifer.ShoppingApp.Frontend_/src/app/app.component.ts +++ b/Weifer.ShoppingApp.Frontend_/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, HostBinding } from '@angular/core'; +import { Component, HostBinding, OnInit } from '@angular/core'; import { AuthService, ScreenService, AppInfoService } from './shared/services'; @Component({ @@ -6,12 +6,15 @@ import { AuthService, ScreenService, AppInfoService } from './shared/services'; templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent { +export class AppComponent implements OnInit { @HostBinding('class') get getClass() { return Object.keys(this.screen.sizes).filter(cl => this.screen.sizes[cl]).join(' '); } constructor(private authService: AuthService, private screen: ScreenService, public appInfo: AppInfoService) { } + ngOnInit(): void { + this.authService.checkAuthenticationStatus(); + } isAuthenticated() { return this.authService.loggedIn; diff --git a/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.html b/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.html index 161dc49..610fa19 100644 --- a/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.html +++ b/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.html @@ -15,7 +15,7 @@ + [editorOptions]="{ text: 'Remember me', elementAttr: { class: 'form-text' }, value: remember_me, onValueChanged: rememberMeChanged }"> diff --git a/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.ts b/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.ts index 8d7e338..3b811c4 100644 --- a/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.ts +++ b/Weifer.ShoppingApp.Frontend_/src/app/shared/components/login-form/login-form.component.ts @@ -15,6 +15,7 @@ import { AuthService } from '../../services'; export class LoginFormComponent { loading = false; loginCredentials: any = {}; + remember_me: boolean = false; constructor(private authService: AuthService, private router: Router) { } @@ -24,7 +25,7 @@ export class LoginFormComponent { this.loading = true; try { - const result = await this.authService.logIn(email, password); + const result = await this.authService.logIn(email, password, this.remember_me); this.loading = false; var message = "Success" notify({ @@ -49,6 +50,11 @@ export class LoginFormComponent { } } + rememberMeChanged = (e: any) => { + + this.remember_me = e.value; + console.log(this.remember_me) + } onCreateAccountClick = () => { this.router.navigate(['/create-account']); } diff --git a/Weifer.ShoppingApp.Frontend_/src/app/shared/services/auth.service.ts b/Weifer.ShoppingApp.Frontend_/src/app/shared/services/auth.service.ts index 8cc2b85..25bfc9a 100644 --- a/Weifer.ShoppingApp.Frontend_/src/app/shared/services/auth.service.ts +++ b/Weifer.ShoppingApp.Frontend_/src/app/shared/services/auth.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router'; import { Observable, firstValueFrom, tap } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { environment } from '../../../environment/environment'; import { CustomerDto } from '../../types/CustomerDto'; @@ -29,9 +29,9 @@ export class AuthService { this._lastAuthenticatedPath = value; } - constructor(private router: Router, private http: HttpClient) { } + constructor(private router: Router, private http: HttpClient) {} - async logIn(email: string, password: string): Promise { + async logIn(email: string, password: string, rememberMe: boolean = false): Promise { const result = await firstValueFrom(this.http.post(`${API_URL}/authenticationapi/login`, { email, password })); if (result.token) { @@ -42,12 +42,41 @@ export class AuthService { lastName: result.customer.lastName, avatarUrl: "https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/07.png" }; - + localStorage.setItem('access_token', result.token); + // localStorage.setItem('user_data', JSON.stringify(this.user)); } - localStorage.setItem('access_token', result.token); + if (rememberMe) { + localStorage.setItem('remember_me', 'true'); + } + return result; } + checkAuthenticationStatus(): void { + const token = localStorage.getItem('access_token'); + const rememberMe = localStorage.getItem('remember_me') === 'true'; + //const userData = localStorage.getItem('user_data'); + //if (userData) { + // this.user = JSON.parse(userData); + //} + if (token && rememberMe) { + // Erstellen des Headers mit dem Token + const headers = new HttpHeaders({ + 'Authorization': token, + }); + this.http.get(`${API_URL}/authenticationapi/validateToken`, { headers: headers }).subscribe({ + next: (response) => { + this.router.navigate(['/home']); + }, + error: (err) => { + console.error(err); + this.logOut(); // Bereinigung im Fehlerfall + } + }); + } else { + this.logOut(); // Bereinigung, falls Remember Me nicht gesetzt ist + } + } async getUser() { try { @@ -120,6 +149,9 @@ export class AuthService { async logOut() { this.user = null; + // Bereinigen des lokalen Speichers + localStorage.removeItem('access_token'); + localStorage.removeItem('remember_me'); this.router.navigate(['/login-form']); } }