added session token to database

added tokenvalidation
This commit is contained in:
Marcus Ferl 2024-03-04 23:00:10 +01:00
parent 23588bf663
commit 524de9bd98
12 changed files with 251 additions and 14 deletions

View File

@ -13,6 +13,7 @@ namespace Weifer.Database.EF.Entitys
public string LastName { get; set; } public string LastName { get; set; }
public string Email { get; set; } public string Email { get; set; }
public string PasswordHash { get; set; } public string PasswordHash { get; set; }
public string SessionToken { get; set; }
public DateTime CreatedOn { get; set; } public DateTime CreatedOn { get; set; }
public ICollection<ShoppingList> ShoppingLists { get; set; } public ICollection<ShoppingList> ShoppingLists { get; set; }
} }

View File

@ -0,0 +1,138 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("CustomerId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedOn")
.HasColumnType("datetime2");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SessionToken")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("CustomerId");
b.ToTable("Customers");
});
modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingItem", b =>
{
b.Property<Guid>("ShoppingItemId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Number")
.HasColumnType("int");
b.Property<bool>("Purchased")
.HasColumnType("bit");
b.Property<Guid>("ShoppingListId")
.HasColumnType("uniqueidentifier");
b.HasKey("ShoppingItemId");
b.HasIndex("ShoppingListId");
b.ToTable("ShoppingItems");
});
modelBuilder.Entity("Weifer.Database.EF.Entitys.ShoppingList", b =>
{
b.Property<Guid>("ShoppingListId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("CustomerId")
.HasColumnType("uniqueidentifier");
b.Property<string>("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
}
}
}

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Weifer.Database.EF.Migrations
{
/// <inheritdoc />
public partial class added_sessiontoken : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SessionToken",
table: "Customers",
type: "nvarchar(max)",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SessionToken",
table: "Customers");
}
}
}

View File

@ -47,6 +47,10 @@ namespace Weifer.Database.EF.Migrations
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<string>("SessionToken")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("CustomerId"); b.HasKey("CustomerId");
b.ToTable("Customers"); b.ToTable("Customers");

View File

@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Primitives;
using Weifer.Database.EF; using Weifer.Database.EF;
using Weifer.ShoppingApp.API.Controllers; using Weifer.ShoppingApp.API.Controllers;
using Weifer.ShoppingApp.API.Models; 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 customer = await dbContext.Customers.Where(cu => cu.Email == credentials.Email).FirstOrDefaultAsync();
var token = authenticationController.GenerateJwtToken(); var token = authenticationController.GenerateJwtToken();
if (customer != null)
{
customer.SessionToken = token;
await dbContext.SaveChangesAsync();
}
return Ok(new return Ok(new
{ {
token = token, // Token Information token = token, // Token Information
customer = new CustomerDto customer = new CustomerDto
{ // Kundeninformationen {
CustomerId = customer.CustomerId, CustomerId = customer.CustomerId,
FirstName = customer.FirstName, FirstName = customer.FirstName,
LastName = customer.LastName, LastName = customer.LastName,
Email = credentials.Email Email = credentials.Email
} }
}); });
} }
return Unauthorized(); return Unauthorized();
} }
[HttpGet("validateToken")]
public async Task<IActionResult> 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();
}
} }

View File

@ -10,10 +10,7 @@
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup> <PackageReference Include="Weifer.Database.EF" Version="1.0.0" />
<ItemGroup>
<ProjectReference Include="..\Weifer.Database.EF\Weifer.Database.EF.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,5 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

View File

@ -1,4 +1,4 @@
import { Component, HostBinding } from '@angular/core'; import { Component, HostBinding, OnInit } from '@angular/core';
import { AuthService, ScreenService, AppInfoService } from './shared/services'; import { AuthService, ScreenService, AppInfoService } from './shared/services';
@Component({ @Component({
@ -6,12 +6,15 @@ import { AuthService, ScreenService, AppInfoService } from './shared/services';
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent { export class AppComponent implements OnInit {
@HostBinding('class') get getClass() { @HostBinding('class') get getClass() {
return Object.keys(this.screen.sizes).filter(cl => this.screen.sizes[cl]).join(' '); return Object.keys(this.screen.sizes).filter(cl => this.screen.sizes[cl]).join(' ');
} }
constructor(private authService: AuthService, private screen: ScreenService, public appInfo: AppInfoService) { } constructor(private authService: AuthService, private screen: ScreenService, public appInfo: AppInfoService) { }
ngOnInit(): void {
this.authService.checkAuthenticationStatus();
}
isAuthenticated() { isAuthenticated() {
return this.authService.loggedIn; return this.authService.loggedIn;

View File

@ -15,7 +15,7 @@
</dxi-item> </dxi-item>
<dxi-item dataField="rememberMe" editorType="dxCheckBox" <dxi-item dataField="rememberMe" editorType="dxCheckBox"
[editorOptions]="{ text: 'Remember me', elementAttr: { class: 'form-text' } }"> [editorOptions]="{ text: 'Remember me', elementAttr: { class: 'form-text' }, value: remember_me, onValueChanged: rememberMeChanged }">
<dxo-label [visible]="false"></dxo-label> <dxo-label [visible]="false"></dxo-label>
</dxi-item> </dxi-item>

View File

@ -15,6 +15,7 @@ import { AuthService } from '../../services';
export class LoginFormComponent { export class LoginFormComponent {
loading = false; loading = false;
loginCredentials: any = {}; loginCredentials: any = {};
remember_me: boolean = false;
constructor(private authService: AuthService, private router: Router) { } constructor(private authService: AuthService, private router: Router) { }
@ -24,7 +25,7 @@ export class LoginFormComponent {
this.loading = true; this.loading = true;
try { try {
const result = await this.authService.logIn(email, password); const result = await this.authService.logIn(email, password, this.remember_me);
this.loading = false; this.loading = false;
var message = "Success" var message = "Success"
notify({ notify({
@ -49,6 +50,11 @@ export class LoginFormComponent {
} }
} }
rememberMeChanged = (e: any) => {
this.remember_me = e.value;
console.log(this.remember_me)
}
onCreateAccountClick = () => { onCreateAccountClick = () => {
this.router.navigate(['/create-account']); this.router.navigate(['/create-account']);
} }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router'; import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router';
import { Observable, firstValueFrom, tap } from 'rxjs'; 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 { environment } from '../../../environment/environment';
import { CustomerDto } from '../../types/CustomerDto'; import { CustomerDto } from '../../types/CustomerDto';
@ -29,9 +29,9 @@ export class AuthService {
this._lastAuthenticatedPath = value; this._lastAuthenticatedPath = value;
} }
constructor(private router: Router, private http: HttpClient) { } constructor(private router: Router, private http: HttpClient) {}
async logIn(email: string, password: string): Promise<any> { async logIn(email: string, password: string, rememberMe: boolean = false): Promise<any> {
const result = await firstValueFrom(this.http.post<any>(`${API_URL}/authenticationapi/login`, { email, password })); const result = await firstValueFrom(this.http.post<any>(`${API_URL}/authenticationapi/login`, { email, password }));
if (result.token) { if (result.token) {
@ -42,12 +42,41 @@ export class AuthService {
lastName: result.customer.lastName, lastName: result.customer.lastName,
avatarUrl: "https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/07.png" avatarUrl: "https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/07.png"
}; };
}
localStorage.setItem('access_token', result.token); localStorage.setItem('access_token', result.token);
// localStorage.setItem('user_data', JSON.stringify(this.user));
}
if (rememberMe) {
localStorage.setItem('remember_me', 'true');
}
return result; 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<any>(`${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() { async getUser() {
try { try {
@ -120,6 +149,9 @@ export class AuthService {
async logOut() { async logOut() {
this.user = null; this.user = null;
// Bereinigen des lokalen Speichers
localStorage.removeItem('access_token');
localStorage.removeItem('remember_me');
this.router.navigate(['/login-form']); this.router.navigate(['/login-form']);
} }
} }