diff --git a/Pronto-MIA/Pronto-MIA/BusinessLogic/API/Interceptors/UserStateRequestInterceptor.cs b/Pronto-MIA/Pronto-MIA/BusinessLogic/API/Interceptors/UserStateRequestInterceptor.cs index 72df92d..52f7686 100644 --- a/Pronto-MIA/Pronto-MIA/BusinessLogic/API/Interceptors/UserStateRequestInterceptor.cs +++ b/Pronto-MIA/Pronto-MIA/BusinessLogic/API/Interceptors/UserStateRequestInterceptor.cs @@ -1,7 +1,116 @@ namespace Pronto_MIA.BusinessLogic.API.Interceptors { - public class UserStateRequestInterceptor + using System; + using System.Globalization; + using System.Linq; + using System.Security.Authentication; + using System.Security.Claims; + using System.Threading; + using System.Threading.Tasks; + using HotChocolate; + using HotChocolate.Execution; + using Microsoft.AspNetCore.Http; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Configuration; + using Pronto_MIA.BusinessLogic.API.Types; + using Pronto_MIA.DataAccess; + using Pronto_MIA.Domain.Entities; + using Pronto_MIA.Services; + + /// + /// Class containing the interceptor method used to add the + /// to every request. + /// + public static class UserStateRequestInterceptor { - + /// + /// Adds to every request so that + /// information on the requesting user is available if necessary. + /// Also checks if the user is existing and allowed to authenticate. + /// + /// The configuration of the application used + /// to create a dbContext. + /// The httpContext of the current request. + /// + /// The executor of the current request. + /// The query request builder which will be + /// extended by the . + /// The cancellation token of the request. + /// A task that can be awaited. + /// Throws a query exception if + /// the user could not be found or the token used for authentication + /// has been invalidated. + public static async ValueTask AddUserState( + IConfiguration cfg, + HttpContext context, + IRequestExecutor executor, + IQueryRequestBuilder builder, + CancellationToken token) + { + ApiUserState apiUserState = default; + var identity = context.User.Identity; + if (identity is { IsAuthenticated: true }) + { + var user = await GetUser(cfg, context); + CheckIfTokenRevoked(context, user.LastInvalidated); + apiUserState = new ApiUserState(user); + } + + builder.SetProperty( + ApiUserGlobalState.ApiUserStateName, + apiUserState); + } + + private static async Task GetUser( + IConfiguration cfg, + HttpContext context) + { + await using (var dbContext = new ProntoMiaDbContext( + DatabaseService.GetOptions(cfg))) + { + var userId = int.Parse(context + .User.FindFirstValue(ClaimTypes.NameIdentifier)); + var userName = context + .User.FindFirstValue(ClaimTypes.Name); + var user = dbContext.Users + .Include(u => u.Department) + .Include(u => u.AccessControlList) + .SingleOrDefault(u => u.Id == userId); + if (user == null) + { + throw GetAuthorizationException(); + } + + return user; + } + } + + private static void CheckIfTokenRevoked( + HttpContext context, + DateTime lastInvalidated) + { + var issuedAt = context + .User.FindFirstValue("issuedAt"); + if (issuedAt == null) + { + throw GetAuthorizationException(); + } + + var issuedAtTime = DateTime.Parse( + issuedAt, CultureInfo.InvariantCulture); + if (lastInvalidated > issuedAtTime) + { + throw new AuthenticationException(); + } + } + + private static GraphQLException GetAuthorizationException() + { + return new ( + ErrorBuilder.New() + .SetMessage("The current user is not authorized " + + "to access this resource.") + .SetCode("AUTH_NOT_AUTHORIZED").Build()); + } } -} \ No newline at end of file +} diff --git a/Pronto-MIA/Pronto-MIA/DataAccess/Managers/UserManager.cs b/Pronto-MIA/Pronto-MIA/DataAccess/Managers/UserManager.cs index ec2c34f..a4d9be2 100644 --- a/Pronto-MIA/Pronto-MIA/DataAccess/Managers/UserManager.cs +++ b/Pronto-MIA/Pronto-MIA/DataAccess/Managers/UserManager.cs @@ -4,6 +4,7 @@ namespace Pronto_MIA.DataAccess.Managers using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; @@ -220,6 +221,8 @@ private string GenerateToken(User user) new Claim( ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName), + new Claim("issuedAt", DateTime.UtcNow.ToString( + CultureInfo.InvariantCulture)), }; var token = new JwtSecurityToken( @@ -298,6 +301,7 @@ private void UpdatePassword(string password, User user) this.CheckPasswordPolicy(password); var generator = HashGeneratorFactory.GetGeneratorForUser(user); user.PasswordHash = generator.HashPassword(password); + user.LastInvalidated = DateTime.UtcNow; } /// diff --git a/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/20210603094129_TokenInvalidation-add.Designer.cs b/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/20210603094129_TokenInvalidation-add.Designer.cs new file mode 100644 index 0000000..c7025f1 --- /dev/null +++ b/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/20210603094129_TokenInvalidation-add.Designer.cs @@ -0,0 +1,280 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Pronto_MIA.DataAccess; + +namespace Pronto_MIA.DataAccess.Migrations +{ + [DbContext(typeof(ProntoMiaDbContext))] + [Migration("20210603094129_TokenInvalidation-add")] + partial class TokenInvalidationadd + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.6") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.AccessControlList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CanEditDepartmentDeploymentPlans") + .HasColumnType("boolean"); + + b.Property("CanEditDepartmentUsers") + .HasColumnType("boolean"); + + b.Property("CanEditDepartments") + .HasColumnType("boolean"); + + b.Property("CanEditDeploymentPlans") + .HasColumnType("boolean"); + + b.Property("CanEditOwnDepartment") + .HasColumnType("boolean"); + + b.Property("CanEditUsers") + .HasColumnType("boolean"); + + b.Property("CanViewDepartmentDeploymentPlans") + .HasColumnType("boolean"); + + b.Property("CanViewDepartmentUsers") + .HasColumnType("boolean"); + + b.Property("CanViewDepartments") + .HasColumnType("boolean"); + + b.Property("CanViewDeploymentPlans") + .HasColumnType("boolean"); + + b.Property("CanViewOwnDepartment") + .HasColumnType("boolean"); + + b.Property("CanViewUsers") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AccessControlLists"); + + b.HasData( + new + { + Id = -1, + CanEditDepartmentDeploymentPlans = false, + CanEditDepartmentUsers = false, + CanEditDepartments = true, + CanEditDeploymentPlans = true, + CanEditOwnDepartment = false, + CanEditUsers = true, + CanViewDepartmentDeploymentPlans = false, + CanViewDepartmentUsers = false, + CanViewDepartments = true, + CanViewDeploymentPlans = true, + CanViewOwnDepartment = false, + CanViewUsers = true, + UserId = -1 + }); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.DeploymentPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AvailableFrom") + .HasColumnType("timestamp without time zone"); + + b.Property("AvailableUntil") + .HasColumnType("timestamp without time zone"); + + b.Property("DepartmentId") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("FileExtension") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileUuid") + .IsRequired() + .HasColumnType("text"); + + b.Property("Published") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("FileUuid") + .IsUnique(); + + b.ToTable("DeploymentPlans"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.FcmToken", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("FcmTokens"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("DepartmentId") + .HasColumnType("integer"); + + b.Property("HashGenerator") + .HasColumnType("text"); + + b.Property("HashGeneratorOptions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("LastInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp without time zone") + .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = -1, + HashGenerator = "Pbkdf2Generator", + HashGeneratorOptions = "{\"SaltSize\":128,\"HashIterations\":1500,\"HashSize\":512,\"Salt\":\"A+16bv/SvaC7ZJgS7u+CB8nN32PBUAbJuT09NigsCzQx6/CxS1I/5laUaFoJNZ3QhTm4TqFnWYzokdrvrUxbOEN0MN3ZhINcblSLF9LwbZeiT0nYOnQgTBEPL0KoszXdm8x2mYXHAJFYQ9KOsIZregzuiBQfSqsFfR2uDnFHm9o=\"}", + LastInvalidated = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + PasswordHash = new byte[] { 160, 170, 181, 57, 56, 11, 64, 111, 255, 135, 44, 16, 13, 143, 10, 57, 47, 144, 79, 128, 47, 6, 240, 159, 91, 163, 33, 239, 51, 140, 5, 59, 228, 56, 155, 57, 18, 151, 143, 191, 223, 64, 140, 19, 23, 125, 90, 34, 241, 76, 199, 118, 197, 240, 49, 56, 110, 38, 182, 112, 19, 172, 195, 113, 47, 223, 184, 44, 27, 214, 16, 242, 169, 148, 104, 135, 116, 43, 152, 103, 130, 206, 221, 110, 254, 123, 231, 102, 42, 239, 67, 130, 53, 113, 12, 150, 249, 50, 117, 225, 113, 38, 174, 80, 246, 112, 27, 104, 229, 197, 63, 76, 246, 218, 33, 63, 197, 145, 244, 65, 91, 81, 228, 81, 208, 91, 117, 249, 101, 112, 179, 10, 130, 136, 0, 168, 150, 224, 43, 124, 151, 107, 110, 10, 23, 83, 212, 80, 45, 113, 201, 148, 17, 114, 46, 169, 232, 169, 138, 135, 89, 53, 154, 213, 123, 208, 0, 155, 155, 0, 44, 249, 199, 222, 211, 120, 137, 158, 135, 20, 247, 225, 64, 119, 64, 177, 76, 43, 106, 59, 205, 69, 30, 104, 84, 115, 252, 213, 154, 16, 235, 86, 107, 165, 86, 125, 87, 171, 100, 92, 114, 185, 85, 117, 119, 147, 128, 31, 168, 227, 83, 203, 123, 182, 229, 205, 165, 114, 12, 231, 171, 5, 15, 199, 227, 175, 168, 225, 180, 30, 90, 122, 175, 224, 109, 166, 93, 79, 69, 82, 95, 47, 78, 64, 213, 105, 13, 104, 183, 153, 8, 239, 49, 170, 233, 125, 79, 93, 78, 154, 51, 210, 50, 108, 169, 27, 204, 101, 47, 232, 179, 174, 45, 14, 234, 165, 167, 76, 171, 213, 98, 251, 82, 8, 195, 74, 161, 64, 111, 78, 101, 146, 217, 143, 43, 248, 254, 233, 54, 140, 96, 182, 10, 27, 227, 64, 121, 70, 19, 161, 37, 249, 7, 73, 27, 215, 160, 207, 19, 172, 124, 6, 176, 71, 16, 75, 32, 92, 143, 100, 188, 175, 189, 227, 113, 249, 235, 68, 238, 151, 60, 134, 209, 189, 104, 1, 219, 157, 84, 149, 179, 50, 219, 8, 81, 188, 68, 194, 8, 98, 118, 135, 197, 212, 153, 226, 240, 162, 98, 253, 63, 125, 29, 112, 194, 162, 113, 175, 5, 162, 114, 208, 107, 177, 202, 88, 127, 196, 166, 82, 5, 61, 254, 6, 172, 248, 243, 140, 155, 93, 246, 184, 238, 132, 207, 112, 120, 79, 140, 30, 224, 112, 197, 209, 228, 90, 194, 214, 42, 229, 85, 167, 27, 12, 85, 179, 197, 131, 120, 158, 57, 251, 14, 55, 0, 244, 42, 240, 134, 91, 107, 152, 201, 46, 104, 3, 94, 191, 76, 114, 242, 42, 28, 120, 121, 161, 215, 5, 23, 57, 171, 150, 191, 224, 86, 25, 248, 120, 176, 240, 37, 151, 195, 74, 208, 146, 104, 159, 34, 147, 107, 239, 156, 198, 190, 97, 196, 132, 15, 54, 33, 75, 152, 78, 166, 227, 174, 236, 74, 67, 237, 184 }, + UserName = "Admin" + }); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.AccessControlList", b => + { + b.HasOne("Pronto_MIA.Domain.Entities.User", "User") + .WithOne("AccessControlList") + .HasForeignKey("Pronto_MIA.Domain.Entities.AccessControlList", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.DeploymentPlan", b => + { + b.HasOne("Pronto_MIA.Domain.Entities.Department", "Department") + .WithMany("DeploymentPlans") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.FcmToken", b => + { + b.HasOne("Pronto_MIA.Domain.Entities.User", "Owner") + .WithMany("FcmTokens") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.User", b => + { + b.HasOne("Pronto_MIA.Domain.Entities.Department", "Department") + .WithMany("Users") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.Department", b => + { + b.Navigation("DeploymentPlans"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("Pronto_MIA.Domain.Entities.User", b => + { + b.Navigation("AccessControlList"); + + b.Navigation("FcmTokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/20210603094129_TokenInvalidation-add.cs b/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/20210603094129_TokenInvalidation-add.cs new file mode 100644 index 0000000..33094cc --- /dev/null +++ b/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/20210603094129_TokenInvalidation-add.cs @@ -0,0 +1,28 @@ +// +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Pronto_MIA.DataAccess.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class TokenInvalidationadd : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastInvalidated", + table: "Users", + type: "timestamp without time zone", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastInvalidated", + table: "Users"); + } + } +} diff --git a/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/ProntoMiaDbContextModelSnapshot.cs b/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/ProntoMiaDbContextModelSnapshot.cs index 370b8bd..99e8ed8 100644 --- a/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/ProntoMiaDbContextModelSnapshot.cs +++ b/Pronto-MIA/Pronto-MIA/DataAccess/Migrations/ProntoMiaDbContextModelSnapshot.cs @@ -185,6 +185,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("jsonb"); + b.Property("LastInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp without time zone") + .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + b.Property("PasswordHash") .IsRequired() .HasColumnType("bytea"); @@ -208,6 +213,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) Id = -1, HashGenerator = "Pbkdf2Generator", HashGeneratorOptions = "{\"SaltSize\":128,\"HashIterations\":1500,\"HashSize\":512,\"Salt\":\"A+16bv/SvaC7ZJgS7u+CB8nN32PBUAbJuT09NigsCzQx6/CxS1I/5laUaFoJNZ3QhTm4TqFnWYzokdrvrUxbOEN0MN3ZhINcblSLF9LwbZeiT0nYOnQgTBEPL0KoszXdm8x2mYXHAJFYQ9KOsIZregzuiBQfSqsFfR2uDnFHm9o=\"}", + LastInvalidated = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), PasswordHash = new byte[] { 160, 170, 181, 57, 56, 11, 64, 111, 255, 135, 44, 16, 13, 143, 10, 57, 47, 144, 79, 128, 47, 6, 240, 159, 91, 163, 33, 239, 51, 140, 5, 59, 228, 56, 155, 57, 18, 151, 143, 191, 223, 64, 140, 19, 23, 125, 90, 34, 241, 76, 199, 118, 197, 240, 49, 56, 110, 38, 182, 112, 19, 172, 195, 113, 47, 223, 184, 44, 27, 214, 16, 242, 169, 148, 104, 135, 116, 43, 152, 103, 130, 206, 221, 110, 254, 123, 231, 102, 42, 239, 67, 130, 53, 113, 12, 150, 249, 50, 117, 225, 113, 38, 174, 80, 246, 112, 27, 104, 229, 197, 63, 76, 246, 218, 33, 63, 197, 145, 244, 65, 91, 81, 228, 81, 208, 91, 117, 249, 101, 112, 179, 10, 130, 136, 0, 168, 150, 224, 43, 124, 151, 107, 110, 10, 23, 83, 212, 80, 45, 113, 201, 148, 17, 114, 46, 169, 232, 169, 138, 135, 89, 53, 154, 213, 123, 208, 0, 155, 155, 0, 44, 249, 199, 222, 211, 120, 137, 158, 135, 20, 247, 225, 64, 119, 64, 177, 76, 43, 106, 59, 205, 69, 30, 104, 84, 115, 252, 213, 154, 16, 235, 86, 107, 165, 86, 125, 87, 171, 100, 92, 114, 185, 85, 117, 119, 147, 128, 31, 168, 227, 83, 203, 123, 182, 229, 205, 165, 114, 12, 231, 171, 5, 15, 199, 227, 175, 168, 225, 180, 30, 90, 122, 175, 224, 109, 166, 93, 79, 69, 82, 95, 47, 78, 64, 213, 105, 13, 104, 183, 153, 8, 239, 49, 170, 233, 125, 79, 93, 78, 154, 51, 210, 50, 108, 169, 27, 204, 101, 47, 232, 179, 174, 45, 14, 234, 165, 167, 76, 171, 213, 98, 251, 82, 8, 195, 74, 161, 64, 111, 78, 101, 146, 217, 143, 43, 248, 254, 233, 54, 140, 96, 182, 10, 27, 227, 64, 121, 70, 19, 161, 37, 249, 7, 73, 27, 215, 160, 207, 19, 172, 124, 6, 176, 71, 16, 75, 32, 92, 143, 100, 188, 175, 189, 227, 113, 249, 235, 68, 238, 151, 60, 134, 209, 189, 104, 1, 219, 157, 84, 149, 179, 50, 219, 8, 81, 188, 68, 194, 8, 98, 118, 135, 197, 212, 153, 226, 240, 162, 98, 253, 63, 125, 29, 112, 194, 162, 113, 175, 5, 162, 114, 208, 107, 177, 202, 88, 127, 196, 166, 82, 5, 61, 254, 6, 172, 248, 243, 140, 155, 93, 246, 184, 238, 132, 207, 112, 120, 79, 140, 30, 224, 112, 197, 209, 228, 90, 194, 214, 42, 229, 85, 167, 27, 12, 85, 179, 197, 131, 120, 158, 57, 251, 14, 55, 0, 244, 42, 240, 134, 91, 107, 152, 201, 46, 104, 3, 94, 191, 76, 114, 242, 42, 28, 120, 121, 161, 215, 5, 23, 57, 171, 150, 191, 224, 86, 25, 248, 120, 176, 240, 37, 151, 195, 74, 208, 146, 104, 159, 34, 147, 107, 239, 156, 198, 190, 97, 196, 132, 15, 54, 33, 75, 152, 78, 166, 227, 174, 236, 74, 67, 237, 184 }, UserName = "Admin" }); diff --git a/Pronto-MIA/Pronto-MIA/Domain/Entities/User.cs b/Pronto-MIA/Pronto-MIA/Domain/Entities/User.cs index 3c37689..f2fda89 100644 --- a/Pronto-MIA/Pronto-MIA/Domain/Entities/User.cs +++ b/Pronto-MIA/Pronto-MIA/Domain/Entities/User.cs @@ -1,5 +1,6 @@ namespace Pronto_MIA.Domain.Entities { + using System; using System.Collections.Generic; using HotChocolate; using HotChocolate.AspNetCore.Authorization; @@ -102,5 +103,11 @@ protected User() /// [GraphQLIgnore] public virtual ICollection FcmTokens { get; set; } + + /// + /// Gets or sets the time the users jwt-tokens were last invalidated. + /// + [GraphQLIgnore] + public DateTime LastInvalidated { get; set; } } } diff --git a/Pronto-MIA/Pronto-MIA/Domain/EntityTypeConfigs/UserTypeConfig.cs b/Pronto-MIA/Pronto-MIA/Domain/EntityTypeConfigs/UserTypeConfig.cs index 3a0180d..9f3ee61 100644 --- a/Pronto-MIA/Pronto-MIA/Domain/EntityTypeConfigs/UserTypeConfig.cs +++ b/Pronto-MIA/Pronto-MIA/Domain/EntityTypeConfigs/UserTypeConfig.cs @@ -1,5 +1,6 @@ namespace Pronto_MIA.Domain.EntityTypeConfigs { + using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Pronto_MIA.Domain.Entities; @@ -25,6 +26,9 @@ public void Configure(EntityTypeBuilder builder) builder.Property(u => u.HashGeneratorOptions) .IsRequired() .HasColumnType("jsonb"); + builder.Property(u => u.LastInvalidated) + .IsRequired() + .HasDefaultValue(default(DateTime)); builder.HasMany(u => u.FcmTokens) .WithOne(t => t.Owner); builder.HasOne(u => u.AccessControlList) diff --git a/Pronto-MIA/Pronto-MIA/Services/GraphQLService.cs b/Pronto-MIA/Pronto-MIA/Services/GraphQLService.cs index 8346b56..56346c5 100644 --- a/Pronto-MIA/Pronto-MIA/Services/GraphQLService.cs +++ b/Pronto-MIA/Pronto-MIA/Services/GraphQLService.cs @@ -1,17 +1,13 @@ namespace Pronto_MIA.Services { using System.Diagnostics.CodeAnalysis; - using System.Security.Claims; - using System.Threading; - using System.Threading.Tasks; - using HotChocolate.Execution; using HotChocolate.Types; - using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pronto_MIA.BusinessLogic.API; using Pronto_MIA.BusinessLogic.API.EntityExtensions; + using Pronto_MIA.BusinessLogic.API.Interceptors; using Pronto_MIA.BusinessLogic.API.Logging; using Pronto_MIA.BusinessLogic.API.Types; using Pronto_MIA.BusinessLogic.API.Types.Mutation; @@ -38,12 +34,12 @@ public static void AddGraphQLService(this IServiceCollection services) { var serviceProvider = services.BuildServiceProvider(); var config = serviceProvider.GetService(); - services.AddGraphQLServer() .AddAuthorization() .AddHttpRequestInterceptor( (context, executor, builder, token) => - AddUserState(config, context, executor, builder, token)) + UserStateRequestInterceptor.AddUserState( + config, context, executor, builder, token)) .AddType() .AddType() .AddType() @@ -64,39 +60,5 @@ public static void AddGraphQLService(this IServiceCollection services) new QueryLogger( sp.GetApplicationService>())); } - - /// - /// Adds user information to the global state cache so that it may be - /// accessed in a query. - /// - private static ValueTask AddUserState( - IConfiguration cfg, - HttpContext context, - IRequestExecutor executor, - IQueryRequestBuilder builder, - CancellationToken token) - { - var dbContext = new ProntoMiaDbContext( - DatabaseService.GetOptions(cfg)); - - ApiUserState apiUserState = default; - var identity = context.User.Identity; - if (identity is { IsAuthenticated: true }) - { - var userId = int.Parse(context - .User.FindFirstValue(ClaimTypes.NameIdentifier)); - var userName = context - .User.FindFirstValue(ClaimTypes.Name); - var user = dbContext.Users.Find(userId); - - apiUserState = new ApiUserState(user); - } - - builder.SetProperty( - ApiUserGlobalState.ApiUserStateName, - apiUserState); - - return ValueTask.CompletedTask; - } } }