| { | { | ||||
| Task<AuthenticateResponseDto?> Authenticate(AuthenticateRequestDto model); | Task<AuthenticateResponseDto?> Authenticate(AuthenticateRequestDto model); | ||||
| Task<RefreshTokenResultDto> RefreshTokenAsync(RefreshTokenRequestDto model); | |||||
| Task<IEnumerable<User?>> GetAll(); | Task<IEnumerable<User?>> GetAll(); | ||||
| Task<User?> GetById(int id); | Task<User?> GetById(int id); | ||||
| Task CreateUser(CreateUserRequestDto model); | Task CreateUser(CreateUserRequestDto model); | ||||
| Task<RefreshToken?> GetRefreshTokenByUserId(int userId); | |||||
| Task UpdateRefreshToken(RefreshToken refreshToken); | |||||
| } | } | ||||
| } | } |
| private readonly AuthorizationSettings _authSettings; | private readonly AuthorizationSettings _authSettings; | ||||
| private readonly UserManager<User> _userManager; | private readonly UserManager<User> _userManager; | ||||
| private readonly IMapper _mapper; | private readonly IMapper _mapper; | ||||
| private readonly DatabaseContext _databaseContext; | |||||
| public UserService(IOptions<AuthorizationSettings> authSettings, UserManager<User> userManager, IMapper mapper) | |||||
| public UserService(IOptions<AuthorizationSettings> authSettings, UserManager<User> userManager, IMapper mapper, DatabaseContext databaseContext) | |||||
| { | { | ||||
| _authSettings = authSettings.Value; | _authSettings = authSettings.Value; | ||||
| _userManager = userManager; | _userManager = userManager; | ||||
| _mapper = mapper; | _mapper = mapper; | ||||
| _databaseContext = databaseContext; | |||||
| } | } | ||||
| public async Task<IEnumerable<User?>> GetAll() => | public async Task<IEnumerable<User?>> GetAll() => | ||||
| return null; | return null; | ||||
| // authentication successful so generate jwt token | // authentication successful so generate jwt token | ||||
| var token = GenerateJwtToken(user); | |||||
| var token = await GenerateJwtToken(user, true); | |||||
| return new AuthenticateResponseDto | return new AuthenticateResponseDto | ||||
| { | { | ||||
| Username = user.UserName, | Username = user.UserName, | ||||
| FirstName = user.FirstName, | FirstName = user.FirstName, | ||||
| LastName = user.LastName, | LastName = user.LastName, | ||||
| Token = token | |||||
| Token = token, | |||||
| RefreshToken = token | |||||
| }; | }; | ||||
| } | } | ||||
| private string GenerateJwtToken(User user) | |||||
| private async Task<string> GenerateJwtToken(User user, bool authenticate = false) | |||||
| { | { | ||||
| // generate token that is valid for 7 days | // generate token that is valid for 7 days | ||||
| var tokenHandler = new JwtSecurityTokenHandler(); | var tokenHandler = new JwtSecurityTokenHandler(); | ||||
| var key = Encoding.ASCII.GetBytes(_authSettings.Secret); | var key = Encoding.ASCII.GetBytes(_authSettings.Secret); | ||||
| var tokenDescriptor = new SecurityTokenDescriptor | var tokenDescriptor = new SecurityTokenDescriptor | ||||
| { | { | ||||
| Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }), | |||||
| Expires = DateTime.UtcNow.AddMinutes(2), | |||||
| Subject = new ClaimsIdentity(new[] { | |||||
| new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), | |||||
| new Claim("id", user.Id.ToString()) | |||||
| }), | |||||
| Expires = DateTime.UtcNow.AddMinutes(_authSettings.JwtExpiredTime), | |||||
| SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) | ||||
| }; | }; | ||||
| var token = tokenHandler.CreateToken(tokenDescriptor); | var token = tokenHandler.CreateToken(tokenDescriptor); | ||||
| return tokenHandler.WriteToken(token); | |||||
| var writedToken = tokenHandler.WriteToken(token); | |||||
| var refreshToken = new RefreshToken | |||||
| { | |||||
| Token = writedToken, | |||||
| JwtId = token.Id, | |||||
| UserId = user.Id, | |||||
| User = user, | |||||
| CreationDate = DateTime.UtcNow, | |||||
| ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime) | |||||
| }; | |||||
| var existRefreshToken = await _databaseContext.RefreshTokens.Where(x => x.Id == user.Id).FirstOrDefaultAsync(); | |||||
| if(existRefreshToken != null) | |||||
| { | |||||
| existRefreshToken.Token = writedToken; | |||||
| existRefreshToken.JwtId = token.Id; | |||||
| existRefreshToken.CreationDate = DateTime.UtcNow; | |||||
| existRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime); | |||||
| if (authenticate) | |||||
| { | |||||
| existRefreshToken.Used = false; | |||||
| existRefreshToken.Invalidated = false; | |||||
| } | |||||
| //_databaseContext.RefreshTokens.Update(existRefreshToken); | |||||
| await UpdateRefreshToken(existRefreshToken); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _databaseContext.RefreshTokens.AddAsync(refreshToken); | |||||
| } | |||||
| await _databaseContext.SaveChangesAsync(); | |||||
| return writedToken; | |||||
| } | |||||
| public async Task<RefreshTokenResultDto> RefreshTokenAsync(RefreshTokenRequestDto model) | |||||
| { | |||||
| var validatedToken = GetPrincipalFromToken(model.Token); | |||||
| if (validatedToken == null) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "Invalid token" }; | |||||
| } | |||||
| var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value); | |||||
| var expiryDateTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) | |||||
| .AddSeconds(expiryDateUnix); | |||||
| if (expiryDateTimeUtc > DateTime.UtcNow) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "This token hasn't expired yet" }; | |||||
| } | |||||
| var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value; | |||||
| var storedRefreshToken = await _databaseContext.RefreshTokens.SingleOrDefaultAsync(x => x.Token == model.RefreshToken); | |||||
| if (storedRefreshToken == null) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "This refresh token does not exist" }; | |||||
| } | |||||
| if (DateTime.UtcNow > storedRefreshToken.ExpiryDate) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "This refresh token has expired" }; | |||||
| } | |||||
| if (storedRefreshToken.Invalidated) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "This refresh token has been invalidated" }; | |||||
| } | |||||
| if (storedRefreshToken.Used) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "This refresh token has been used" }; | |||||
| } | |||||
| if (storedRefreshToken.JwtId != jti) | |||||
| { | |||||
| return new RefreshTokenResultDto { Error = "This refresh token does not match this JWT" }; | |||||
| } | |||||
| storedRefreshToken.Used = true; | |||||
| _databaseContext.RefreshTokens.Update(storedRefreshToken); | |||||
| await _databaseContext.SaveChangesAsync(); | |||||
| var user = await _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value); | |||||
| var token = await GenerateJwtToken(user); | |||||
| return new RefreshTokenResultDto | |||||
| { | |||||
| Token = token | |||||
| }; | |||||
| } | |||||
| private ClaimsPrincipal GetPrincipalFromToken(string token) | |||||
| { | |||||
| var tokenHandler = new JwtSecurityTokenHandler(); | |||||
| var key = Encoding.ASCII.GetBytes(_authSettings.Secret); | |||||
| var tokenValidationParameters = new TokenValidationParameters | |||||
| { | |||||
| ValidateIssuerSigningKey = true, | |||||
| IssuerSigningKey = new SymmetricSecurityKey(key), | |||||
| ValidateIssuer = false, | |||||
| ValidateAudience = false, | |||||
| RequireExpirationTime = false, | |||||
| ValidateLifetime = true, | |||||
| // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) | |||||
| //ClockSkew = TimeSpan.Zero | |||||
| }; | |||||
| try | |||||
| { | |||||
| var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken); | |||||
| if (!IsJwtWithValidSecurityAlgorithm(validatedToken)) | |||||
| { | |||||
| return null; | |||||
| } | |||||
| return principal; | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| return null; | |||||
| } | |||||
| } | |||||
| private bool IsJwtWithValidSecurityAlgorithm(SecurityToken validatedToken) | |||||
| { | |||||
| return (validatedToken is JwtSecurityToken jwtSecurityToken) && | |||||
| jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, | |||||
| StringComparison.InvariantCultureIgnoreCase); | |||||
| } | |||||
| public async Task<RefreshToken?> GetRefreshTokenByUserId(int userId) | |||||
| { | |||||
| return await _databaseContext.RefreshTokens.Where(x => x.UserId == userId).FirstOrDefaultAsync(); | |||||
| } | |||||
| public async Task UpdateRefreshToken(RefreshToken refreshToken) | |||||
| { | |||||
| _databaseContext.RefreshTokens.Update(refreshToken); | |||||
| await _databaseContext.SaveChangesAsync(); | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| public class AuthorizationSettings | public class AuthorizationSettings | ||||
| { | { | ||||
| public string Secret { get; set; } | public string Secret { get; set; } | ||||
| public int JwtExpiredTime { get; set; } | |||||
| public int JwtRefreshExpiredTime { get; set; } | |||||
| } | } | ||||
| } | } |
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||||
| { | |||||
| public class AuthFailedResponse | |||||
| { | |||||
| public string Error { get; set; } | |||||
| } | |||||
| } |
| public string LastName { get; set; } | public string LastName { get; set; } | ||||
| public string Username { get; set; } | public string Username { get; set; } | ||||
| public string Token { get; set; } | public string Token { get; set; } | ||||
| public string RefreshToken { get; set; } | |||||
| } | } | ||||
| } | } |
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||||
| { | |||||
| public class RefreshTokenRequestDto | |||||
| { | |||||
| public string Token { get; set; } | |||||
| public string RefreshToken { get; set; } | |||||
| } | |||||
| } |
| | |||||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||||
| { | |||||
| public class RefreshTokenResponseDto | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public string Token { get; set; } | |||||
| public string JwtId { get; set; } | |||||
| public DateTime CreationDate { get; set; } | |||||
| public DateTime ExpiryDate { get; set; } | |||||
| public bool Used { get; set; } | |||||
| public bool Invalidated { get; set; } | |||||
| public int UserId { get; set; } | |||||
| } | |||||
| } |
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||||
| { | |||||
| public class RefreshTokenResultDto | |||||
| { | |||||
| public string? Token { get; set; } | |||||
| public string? Error { get; set; } | |||||
| } | |||||
| } |
| | |||||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||||
| { | |||||
| public class RefreshedTokenResponseDto | |||||
| { | |||||
| public string Token { get; set; } | |||||
| public string RefreshToken { get; set; } | |||||
| } | |||||
| } |
| public DbSet<InsurancePolicy> InsurancePolicies { get; set; } | public DbSet<InsurancePolicy> InsurancePolicies { get; set; } | ||||
| public DbSet<WebhookSubscription> WebhookSubscriptions { get; set; } | public DbSet<WebhookSubscription> WebhookSubscriptions { get; set; } | ||||
| public DbSet<WebhookDefinition> WebhookDefinitions { get; set; } | public DbSet<WebhookDefinition> WebhookDefinitions { get; set; } | ||||
| public DbSet<RefreshToken> RefreshTokens { get; set; } | |||||
| public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } | public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } | ||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.ComponentModel.DataAnnotations.Schema; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Diligent.WebAPI.Data.Entities | |||||
| { | |||||
| public class RefreshToken | |||||
| { | |||||
| [Key] | |||||
| public int Id { get; set; } | |||||
| public string Token { get; set; } | |||||
| public string JwtId { get; set; } | |||||
| public DateTime CreationDate { get; set; } | |||||
| public DateTime ExpiryDate { get; set; } | |||||
| public bool Used { get; set; } | |||||
| public bool Invalidated { get; set; } | |||||
| public int UserId { get; set; } | |||||
| [ForeignKey(nameof(UserId))] | |||||
| public User User { get; set; } | |||||
| } | |||||
| } |
| // <auto-generated /> | |||||
| using System; | |||||
| using Diligent.WebAPI.Data; | |||||
| using Microsoft.EntityFrameworkCore; | |||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | |||||
| using Microsoft.EntityFrameworkCore.Metadata; | |||||
| using Microsoft.EntityFrameworkCore.Migrations; | |||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | |||||
| #nullable disable | |||||
| namespace Diligent.WebAPI.Data.Migrations | |||||
| { | |||||
| [DbContext(typeof(DatabaseContext))] | |||||
| [Migration("20221024112939_AddedRefreshToken")] | |||||
| partial class AddedRefreshToken | |||||
| { | |||||
| protected override void BuildTargetModel(ModelBuilder modelBuilder) | |||||
| { | |||||
| #pragma warning disable 612, 618 | |||||
| modelBuilder | |||||
| .HasAnnotation("ProductVersion", "6.0.10") | |||||
| .HasAnnotation("Relational:MaxIdentifierLength", 128); | |||||
| SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.AppRole", b => | |||||
| { | |||||
| b.Property<int>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("int"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||||
| b.Property<string>("ConcurrencyStamp") | |||||
| .IsConcurrencyToken() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Name") | |||||
| .HasMaxLength(256) | |||||
| .HasColumnType("nvarchar(256)"); | |||||
| b.Property<string>("NormalizedName") | |||||
| .HasMaxLength(256) | |||||
| .HasColumnType("nvarchar(256)"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("NormalizedName") | |||||
| .IsUnique() | |||||
| .HasDatabaseName("RoleNameIndex") | |||||
| .HasFilter("[NormalizedName] IS NOT NULL"); | |||||
| b.ToTable("AspNetRoles", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.InsuranceCompany", b => | |||||
| { | |||||
| b.Property<long>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("bigint"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||||
| b.Property<string>("City") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Country") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<DateTime>("CreatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime?>("DeletedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<string>("Fax") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("LegalAddress") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("LegalEmail") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Name") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("PhoneNumber") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("PostalCode") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<DateTime?>("UpdatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.HasKey("Id"); | |||||
| b.ToTable("InsuranceCompanies"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.InsurancePolicy", b => | |||||
| { | |||||
| b.Property<long>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("bigint"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||||
| b.Property<DateTime>("CreatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime?>("DeletedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime>("EndDate") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<long>("InsurerId") | |||||
| .HasColumnType("bigint"); | |||||
| b.Property<decimal>("Premium") | |||||
| .HasColumnType("decimal(18,2)"); | |||||
| b.Property<DateTime>("StartDate") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<string>("Type") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<DateTime?>("UpdatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("InsurerId"); | |||||
| b.ToTable("InsurancePolicies"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.Insurer", b => | |||||
| { | |||||
| b.Property<long>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("bigint"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||||
| b.Property<string>("Address") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("City") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Country") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<DateTime>("CreatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime>("DateOfBirth") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime?>("DeletedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<string>("Email") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("FirstName") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<long>("InsuranceCompanyId") | |||||
| .HasColumnType("bigint"); | |||||
| b.Property<string>("LastName") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("PhoneNumber") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("PostalCode") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<DateTime?>("UpdatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("InsuranceCompanyId"); | |||||
| b.ToTable("Insurers"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.RefreshToken", b => | |||||
| { | |||||
| b.Property<int>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("int"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||||
| b.Property<DateTime>("CreationDate") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime>("ExpiryDate") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<bool>("Invalidated") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<string>("JwtId") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Token") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<bool>("Used") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<int>("UserId") | |||||
| .HasColumnType("int"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("UserId"); | |||||
| b.ToTable("RefreshTokens"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.User", b => | |||||
| { | |||||
| b.Property<int>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("int"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||||
| b.Property<int>("AccessFailedCount") | |||||
| .HasColumnType("int"); | |||||
| b.Property<string>("ConcurrencyStamp") | |||||
| .IsConcurrencyToken() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Email") | |||||
| .HasMaxLength(256) | |||||
| .HasColumnType("nvarchar(256)"); | |||||
| b.Property<bool>("EmailConfirmed") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<string>("FirstName") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("LastName") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<bool>("LockoutEnabled") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<DateTimeOffset?>("LockoutEnd") | |||||
| .HasColumnType("datetimeoffset"); | |||||
| b.Property<string>("NormalizedEmail") | |||||
| .HasMaxLength(256) | |||||
| .HasColumnType("nvarchar(256)"); | |||||
| b.Property<string>("NormalizedUserName") | |||||
| .HasMaxLength(256) | |||||
| .HasColumnType("nvarchar(256)"); | |||||
| b.Property<string>("PasswordHash") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("PhoneNumber") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<bool>("PhoneNumberConfirmed") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<string>("SecurityStamp") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<bool>("TwoFactorEnabled") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<string>("UserName") | |||||
| .HasMaxLength(256) | |||||
| .HasColumnType("nvarchar(256)"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("NormalizedEmail") | |||||
| .HasDatabaseName("EmailIndex"); | |||||
| b.HasIndex("NormalizedUserName") | |||||
| .IsUnique() | |||||
| .HasDatabaseName("UserNameIndex") | |||||
| .HasFilter("[NormalizedUserName] IS NOT NULL"); | |||||
| b.ToTable("AspNetUsers", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.WebhookDefinition", b => | |||||
| { | |||||
| b.Property<long>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("bigint"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||||
| b.Property<DateTime>("CreatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime?>("DeletedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<string>("Description") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("DisplayName") | |||||
| .IsRequired() | |||||
| .HasMaxLength(100) | |||||
| .HasColumnType("nvarchar(100)"); | |||||
| b.Property<string>("Name") | |||||
| .IsRequired() | |||||
| .HasMaxLength(100) | |||||
| .HasColumnType("nvarchar(100)"); | |||||
| b.Property<DateTime?>("UpdatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.HasKey("Id"); | |||||
| b.ToTable("WebhookDefinitions"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.WebhookSubscription", b => | |||||
| { | |||||
| b.Property<long>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("bigint"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||||
| b.Property<DateTime>("CreatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime?>("DeletedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<bool>("IsActive") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<DateTime?>("UpdatedAtUtc") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<long>("WebhookDefinitionId") | |||||
| .HasColumnType("bigint"); | |||||
| b.Property<string>("WebhookURL") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("WebhookDefinitionId"); | |||||
| b.ToTable("WebhookSubscriptions"); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b => | |||||
| { | |||||
| b.Property<int>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("int"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||||
| b.Property<string>("ClaimType") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("ClaimValue") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<int>("RoleId") | |||||
| .HasColumnType("int"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("RoleId"); | |||||
| b.ToTable("AspNetRoleClaims", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b => | |||||
| { | |||||
| b.Property<int>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("int"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||||
| b.Property<string>("ClaimType") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("ClaimValue") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<int>("UserId") | |||||
| .HasColumnType("int"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("UserId"); | |||||
| b.ToTable("AspNetUserClaims", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b => | |||||
| { | |||||
| b.Property<string>("LoginProvider") | |||||
| .HasColumnType("nvarchar(450)"); | |||||
| b.Property<string>("ProviderKey") | |||||
| .HasColumnType("nvarchar(450)"); | |||||
| b.Property<string>("ProviderDisplayName") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<int>("UserId") | |||||
| .HasColumnType("int"); | |||||
| b.HasKey("LoginProvider", "ProviderKey"); | |||||
| b.HasIndex("UserId"); | |||||
| b.ToTable("AspNetUserLogins", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b => | |||||
| { | |||||
| b.Property<int>("UserId") | |||||
| .HasColumnType("int"); | |||||
| b.Property<int>("RoleId") | |||||
| .HasColumnType("int"); | |||||
| b.HasKey("UserId", "RoleId"); | |||||
| b.HasIndex("RoleId"); | |||||
| b.ToTable("AspNetUserRoles", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b => | |||||
| { | |||||
| b.Property<int>("UserId") | |||||
| .HasColumnType("int"); | |||||
| b.Property<string>("LoginProvider") | |||||
| .HasColumnType("nvarchar(450)"); | |||||
| b.Property<string>("Name") | |||||
| .HasColumnType("nvarchar(450)"); | |||||
| b.Property<string>("Value") | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.HasKey("UserId", "LoginProvider", "Name"); | |||||
| b.ToTable("AspNetUserTokens", (string)null); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.InsurancePolicy", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.Insurer", "Insurer") | |||||
| .WithMany() | |||||
| .HasForeignKey("InsurerId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| b.Navigation("Insurer"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.Insurer", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.InsuranceCompany", "InsuranceCompany") | |||||
| .WithMany() | |||||
| .HasForeignKey("InsuranceCompanyId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| b.Navigation("InsuranceCompany"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.RefreshToken", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.User", "User") | |||||
| .WithMany() | |||||
| .HasForeignKey("UserId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| b.Navigation("User"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.WebhookSubscription", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.WebhookDefinition", "WebhookDefinition") | |||||
| .WithMany() | |||||
| .HasForeignKey("WebhookDefinitionId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| b.Navigation("WebhookDefinition"); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.AppRole", null) | |||||
| .WithMany() | |||||
| .HasForeignKey("RoleId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.User", null) | |||||
| .WithMany() | |||||
| .HasForeignKey("UserId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.User", null) | |||||
| .WithMany() | |||||
| .HasForeignKey("UserId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.AppRole", null) | |||||
| .WithMany() | |||||
| .HasForeignKey("RoleId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.User", null) | |||||
| .WithMany() | |||||
| .HasForeignKey("UserId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| }); | |||||
| modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.User", null) | |||||
| .WithMany() | |||||
| .HasForeignKey("UserId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| }); | |||||
| #pragma warning restore 612, 618 | |||||
| } | |||||
| } | |||||
| } |
| using System; | |||||
| using Microsoft.EntityFrameworkCore.Migrations; | |||||
| #nullable disable | |||||
| namespace Diligent.WebAPI.Data.Migrations | |||||
| { | |||||
| public partial class AddedRefreshToken : Migration | |||||
| { | |||||
| protected override void Up(MigrationBuilder migrationBuilder) | |||||
| { | |||||
| migrationBuilder.CreateTable( | |||||
| name: "RefreshTokens", | |||||
| columns: table => new | |||||
| { | |||||
| Id = table.Column<int>(type: "int", nullable: false) | |||||
| .Annotation("SqlServer:Identity", "1, 1"), | |||||
| Token = table.Column<string>(type: "nvarchar(max)", nullable: false), | |||||
| JwtId = table.Column<string>(type: "nvarchar(max)", nullable: false), | |||||
| CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false), | |||||
| ExpiryDate = table.Column<DateTime>(type: "datetime2", nullable: false), | |||||
| Used = table.Column<bool>(type: "bit", nullable: false), | |||||
| Invalidated = table.Column<bool>(type: "bit", nullable: false), | |||||
| UserId = table.Column<int>(type: "int", nullable: false) | |||||
| }, | |||||
| constraints: table => | |||||
| { | |||||
| table.PrimaryKey("PK_RefreshTokens", x => x.Id); | |||||
| table.ForeignKey( | |||||
| name: "FK_RefreshTokens_AspNetUsers_UserId", | |||||
| column: x => x.UserId, | |||||
| principalTable: "AspNetUsers", | |||||
| principalColumn: "Id", | |||||
| onDelete: ReferentialAction.Cascade); | |||||
| }); | |||||
| migrationBuilder.CreateIndex( | |||||
| name: "IX_RefreshTokens_UserId", | |||||
| table: "RefreshTokens", | |||||
| column: "UserId"); | |||||
| } | |||||
| protected override void Down(MigrationBuilder migrationBuilder) | |||||
| { | |||||
| migrationBuilder.DropTable( | |||||
| name: "RefreshTokens"); | |||||
| } | |||||
| } | |||||
| } |
| b.ToTable("Insurers"); | b.ToTable("Insurers"); | ||||
| }); | }); | ||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.RefreshToken", b => | |||||
| { | |||||
| b.Property<int>("Id") | |||||
| .ValueGeneratedOnAdd() | |||||
| .HasColumnType("int"); | |||||
| SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||||
| b.Property<DateTime>("CreationDate") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<DateTime>("ExpiryDate") | |||||
| .HasColumnType("datetime2"); | |||||
| b.Property<bool>("Invalidated") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<string>("JwtId") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<string>("Token") | |||||
| .IsRequired() | |||||
| .HasColumnType("nvarchar(max)"); | |||||
| b.Property<bool>("Used") | |||||
| .HasColumnType("bit"); | |||||
| b.Property<int>("UserId") | |||||
| .HasColumnType("int"); | |||||
| b.HasKey("Id"); | |||||
| b.HasIndex("UserId"); | |||||
| b.ToTable("RefreshTokens"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.User", b => | modelBuilder.Entity("Diligent.WebAPI.Data.Entities.User", b => | ||||
| { | { | ||||
| b.Property<int>("Id") | b.Property<int>("Id") | ||||
| b.Navigation("InsuranceCompany"); | b.Navigation("InsuranceCompany"); | ||||
| }); | }); | ||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.RefreshToken", b => | |||||
| { | |||||
| b.HasOne("Diligent.WebAPI.Data.Entities.User", "User") | |||||
| .WithMany() | |||||
| .HasForeignKey("UserId") | |||||
| .OnDelete(DeleteBehavior.Cascade) | |||||
| .IsRequired(); | |||||
| b.Navigation("User"); | |||||
| }); | |||||
| modelBuilder.Entity("Diligent.WebAPI.Data.Entities.WebhookSubscription", b => | modelBuilder.Entity("Diligent.WebAPI.Data.Entities.WebhookSubscription", b => | ||||
| { | { | ||||
| b.HasOne("Diligent.WebAPI.Data.Entities.WebhookDefinition", "WebhookDefinition") | b.HasOne("Diligent.WebAPI.Data.Entities.WebhookDefinition", "WebhookDefinition") |
| return Ok(response); | return Ok(response); | ||||
| } | } | ||||
| [HttpPost("refresh")] | |||||
| public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequestDto model) | |||||
| { | |||||
| var response = await _userService.RefreshTokenAsync(model); | |||||
| if (response.Error != null) | |||||
| { | |||||
| return BadRequest(new AuthFailedResponse { Error = response.Error }); | |||||
| } | |||||
| return Ok(new RefreshedTokenResponseDto { Token = response.Token, RefreshToken = response.Token }); | |||||
| } | |||||
| } | } | ||||
| } | } |
| var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); | var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); | ||||
| if (token != null) | if (token != null) | ||||
| AttachUserToContext(context, userService, token); | |||||
| await AttachUserToContext(context, userService, token); | |||||
| await _next(context); | await _next(context); | ||||
| } | } | ||||
| private void AttachUserToContext(HttpContext context, IUserService userService, string token) | |||||
| private async Task AttachUserToContext(HttpContext context, IUserService userService, string token) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| IssuerSigningKey = new SymmetricSecurityKey(key), | IssuerSigningKey = new SymmetricSecurityKey(key), | ||||
| ValidateIssuer = false, | ValidateIssuer = false, | ||||
| ValidateAudience = false, | ValidateAudience = false, | ||||
| RequireExpirationTime = false, | |||||
| ValidateLifetime = true, | |||||
| // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) | // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) | ||||
| ClockSkew = TimeSpan.Zero | |||||
| //ClockSkew = TimeSpan.Zero | |||||
| }, out SecurityToken validatedToken); | }, out SecurityToken validatedToken); | ||||
| var jwtToken = (JwtSecurityToken)validatedToken; | var jwtToken = (JwtSecurityToken)validatedToken; | ||||
| var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); | var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); | ||||
| // attach user to context on successful jwt validation | // attach user to context on successful jwt validation | ||||
| context.Items["User"] = userService.GetById(userId); | |||||
| context.Items["User"] = await userService.GetById(userId); | |||||
| await UpdateRefreshToken(context, userService, userId); | |||||
| } | } | ||||
| catch | catch | ||||
| { | { | ||||
| // user is not attached to context so request won't have access to secure routes | // user is not attached to context so request won't have access to secure routes | ||||
| } | } | ||||
| } | } | ||||
| private async Task UpdateRefreshToken(HttpContext context, IUserService userService, int userId) | |||||
| { | |||||
| var refreshToken = await userService.GetRefreshTokenByUserId(userId); | |||||
| if (refreshToken == null) | |||||
| return; | |||||
| refreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(30); | |||||
| await userService.UpdateRefreshToken(refreshToken); | |||||
| } | |||||
| } | } | ||||
| } | } |
| "WebApi": "Server=.;Database=HRCenter;Trusted_Connection=True;MultipleActiveResultSets=true" | "WebApi": "Server=.;Database=HRCenter;Trusted_Connection=True;MultipleActiveResultSets=true" | ||||
| }, | }, | ||||
| "Authorization": { | "Authorization": { | ||||
| "JwtExpiredTime": "5", | |||||
| "JwtRefreshExpiredTime": "30", | |||||
| "Secret": "SECRET_ASKGFH#$_#((Y)#I%EWJGDSJTGKEOS@$SAF" | "Secret": "SECRET_ASKGFH#$_#((Y)#I%EWJGDSJTGKEOS@$SAF" | ||||
| } | } | ||||
| } | } |