#19 Added forget and reset password

已合併
safet.purkovic 3 年之前 將 1 次代碼提交從 feature/reset_password_be合併至 BE_dev

+ 387
- 0
Diligent.WebAPI.Business/Services/Emailer.cs 查看文件

@@ -0,0 +1,387 @@
using System.Net.Mail;
using System.Net;
using Diligent.WebAPI.Contracts.Models;

namespace Diligent.WebAPI.Business.Services
{
/// <summary>
/// Provieds an API for sending emails in both sync & async fashion, as well as sending emails with delays.
/// </summary>
public class Emailer : IEmailer
{
private readonly AuthorizationSettings _settings;

public Emailer()
{
_settings = new AuthorizationSettings
{
SmtpServer = "smtp.mailtrap.io",
SmtpPort = 2525,
SmtpUseSSL = true,
SmtpUsername = "460e3c49f02e37",
SmtpPassword = "66443869eaad55",
SmtpFrom = "noreply@hrcenter.net",
SmtpFromName = "HRCenter Team"
};
}

/// <summary>
/// Sends an email asynchronously and inserts a new <see cref="DiligEmail"/> record to the underlying database.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="cc"></param>
/// <exception cref="ArgumentException"></exception>
/// <see cref="ArgumentNullException"/>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public async Task<bool> SendEmailAndWriteToDbAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null)
{
var emailResult = await SendEmailAsync(to, subject, body, isHtml, cc);

var email = CreateEmail(to, subject, body, isHtml);

if (emailResult)
{
email.SentTime = DateTime.UtcNow;
}

//var dbResult = await WriteEmailToDbAsync(email);

return emailResult; // && dbResult > 0;
}

public async Task<bool> SendEmailAndWriteToDbAsync(string to, string subject, string body, bool isHtml = false, List<string> cc = null)
{
return await SendEmailAndWriteToDbAsync(new List<string> { to }, subject, body, isHtml, cc);
}

/// <summary>
/// Sends an email synchronously and inserts a new <see cref="DiligEmail"/> record to the underlying database.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="cc"></param>
/// <exception cref="ArgumentException"></exception>
/// <see cref="ArgumentNullException"/>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public bool SendEmailAndWriteToDb(List<string> to, string subject, string body, bool isHtml = false,
List<string> cc = null)
{
var emailResult = SendEmail(to, subject, body, isHtml, cc);

var email = CreateEmail(to, subject, body, isHtml);

if (emailResult)
{
email.SentTime = DateTime.UtcNow;
}

//var dbResult = WriteEmailToDb(email);

return emailResult; // && dbResult > 0;
}

public bool SendEmailAndWriteToDb(string to, string subject, string body, bool isHtml = false,
List<string> cc = null)
{
return SendEmailAndWriteToDb(new List<string> { to }, subject, body, isHtml, cc);
}

/// <summary>
/// Sends an email synchronously.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="cc"></param>
/// <exception cref="ArgumentException"></exception>
/// <see cref="ArgumentNullException"/>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public bool SendEmail(List<string> to, string subject, string body, bool isHtml = false,
List<string> cc = null)
{
try
{
using (var smtp = GetSmtpClient())
{
var message = GetMailMessage(to, subject, body, isHtml, cc);

smtp.Send(message);

return true;
}
}
catch (ArgumentException)
{
throw;
}
catch (Exception e)
{
throw new Exception("Failed to send email message.", e);
}
}

/// <summary>
/// Sends an email asynchronously.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="cc"></param>
/// <exception cref="ArgumentException"></exception>
/// <see cref="ArgumentNullException"/>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public async Task<bool> SendEmailAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null)
{
try
{
using (var smtp = GetSmtpClient())
{
var message = GetMailMessage(to, subject, body, isHtml, cc);

await smtp.SendMailAsync(message);

return true;
}
}
catch (ArgumentException)
{
throw;
}
catch (Exception e)
{
throw new Exception("Failed to send email message.", e);
}
}

/// <summary>
/// Creates a <see cref="SmtpClient"/> object and configures it.
/// </summary>
/// <returns></returns>
public SmtpClient GetSmtpClient()
{
var smtp = new SmtpClient(_settings.SmtpServer) { Timeout = 1000000 };

if (!string.IsNullOrWhiteSpace(_settings.SmtpUsername))
{
smtp.UseDefaultCredentials = false;
smtp.Credentials = new NetworkCredential(
_settings.SmtpUsername,
_settings.SmtpPassword);
smtp.EnableSsl = _settings.SmtpUseSSL;
smtp.Port = _settings.SmtpPort;
}

return smtp;
}

/// <summary>
/// Creates a new <see cref="MailMessage"/> from the specified arguments.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="cc"></param>
/// <exception cref="ArgumentException"></exception>
/// <returns></returns>
public MailMessage GetMailMessage(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null)
{
var message = new MailMessage
{
Sender = new MailAddress(_settings.SmtpFrom, _settings.SmtpFromName),
From = new MailAddress(_settings.SmtpFrom),
Subject = subject,
Body = body,
IsBodyHtml = isHtml
};



if (to.Any())
{
message.To.Add(string.Join(",", to.Where(email => !string.IsNullOrWhiteSpace(email))));
}
else
{
throw new ArgumentException("The list of recipient emails can not be empty");
}

if (cc != null && cc.Any())
{
message.CC.Add(string.Join(",", cc.Where(email => !string.IsNullOrWhiteSpace(email))));
}

return message;
}

/// <summary>
/// Sends an email aysnchronously. If the "dont send before" argument is specified, then the email will be sent with a delay - after the specified time.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="dontSendBefore"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public async Task<bool> SendEmailWithDelayAsync(List<string> to, string subject, string body, bool isHtml = false, DateTime? dontSendBefore = null)
{
try
{
var email = CreateEmail(to, subject, body, isHtml, dontSendBefore);

//var result = await WriteEmailToDbAsync(email);

return true;
}
catch (ArgumentException)
{
throw;
}
catch (Exception e)
{
throw new Exception("Error while attempting to send an email with delay.", e);
}
}

/// <summary>
/// Creates a <see cref="DiligEmail"/> object with specified arguments.
/// </summary>
/// <param name="to"></param>
/// <param name="subject"></param>
/// <param name="body"></param>
/// <param name="isHtml"></param>
/// <param name="attachments"></param>
/// <param name="dontSendBefore"></param>
/// <exception cref="ArgumentException"></exception>
/// <returns></returns>
public DiligEmail CreateEmail(List<string> to, string subject, string body, bool isHtml = false,
DateTime? dontSendBefore = null)
{
if (!to.Any())
{
throw new ArgumentException("The list of recipient emails can not be empty");
}
var email = new DiligEmail
{
To = to.Aggregate((previous, next) => previous + ";" + next),
Subject = subject,
Body = body,
IsHtml = isHtml,
DontSendBefore = dontSendBefore,
CreateTime = DateTime.UtcNow
};



return email;
}

/// <summary>
/// Fills the specified <see cref="DiligEmail"/> object with the specified <see cref="EmailAttachment"/> list.
/// </summary>
/// <param name="email"></param>
/// <param name="attachments"></param>
/// <exception cref="ArgumentException"></exception>
/// <returns></returns>
//public DiligEmail FillEmailAttachments(DiligEmail email)
//{
// if (email == null)
// {
// throw new ArgumentNullException(nameof(email), "Email can not be null");
// }

// if (attachments != null && attachments.Any())
// {
// attachments.ForEach(attachment =>
// {
// email.DiligEmailAttachments.Add(new DiligEmailAttachment
// {
// FileName = attachment.FileName,
// SourceFileName = attachment.SourceFileName,
// Type = attachment.Type,
// Disposition = attachment.Disposition
// });
// });
// }

// return email;
//}

/// <summary>
/// Writes the specified <see cref="DiligEmail"/> object to the underlying database.
/// </summary>
/// <param name="email"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
//public async Task<int> WriteEmailToDbAsync(DiligEmail email)
//{
// try
// {
// if (email == null)
// {
// throw new ArgumentNullException(nameof(email), "Email can not be null");
// }
// _entities.DiligEmails.Add(email);

// var result = await _entities.SaveChangesAsync();

// return result;
// }
// catch (Exception e)
// {
// throw new Exception("Failed to write entry into the database", e);
// }
//}

/// <summary>
/// Writes the specified <see cref="DiligEmail"/> object to the underlying database.
/// </summary>
/// <param name="email"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
//public int WriteEmailToDb(DiligEmail email)
//{
// try
// {
// if (email == null)
// {
// throw new ArgumentNullException(nameof(email), "Email can not be null");
// }
// _entities.DiligEmails.Add(email);

// var result = _entities.SaveChanges();

// return result;
// }
// catch (ArgumentException)
// {
// throw;
// }
// catch (Exception e)
// {
// throw new Exception("Failed to write entry into the database", e);
// }
//}
}
}

+ 19
- 0
Diligent.WebAPI.Business/Services/Interfaces/IEmailer.cs 查看文件

@@ -0,0 +1,19 @@
using Diligent.WebAPI.Contracts.Models;
using System.Net.Mail;

namespace Diligent.WebAPI.Business.Services.Interfaces
{
public interface IEmailer
{
DiligEmail CreateEmail(List<string> to, string subject, string body, bool isHtml = false, DateTime? dontSendBefore = null);
MailMessage GetMailMessage(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null);
SmtpClient GetSmtpClient();
bool SendEmail(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null);
bool SendEmailAndWriteToDb(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null);
bool SendEmailAndWriteToDb(string to, string subject, string body, bool isHtml = false, List<string> cc = null);
Task<bool> SendEmailAndWriteToDbAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null);
Task<bool> SendEmailAndWriteToDbAsync(string to, string subject, string body, bool isHtml = false, List<string> cc = null);
Task<bool> SendEmailAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null);
Task<bool> SendEmailWithDelayAsync(List<string> to, string subject, string body, bool isHtml = false, DateTime? dontSendBefore = null);
}
}

+ 4
- 0
Diligent.WebAPI.Business/Services/Interfaces/IUserService.cs 查看文件

@@ -18,5 +18,9 @@
Task UpdateRefreshToken(RefreshToken refreshToken);

Task<ServiceResponseDTO<string>> DeleteRefreshToken(int userId);

Task<ServiceResponseDTO<object>> GetEmailConfirmationUrlAsync(string email);

Task<ServiceResponseDTO<object>> PasswordResetAsync(string email, string code, string password);
}
}

+ 79
- 7
Diligent.WebAPI.Business/Services/UserService.cs 查看文件

@@ -1,5 +1,12 @@
using Newtonsoft.Json;
using Diligent.WebAPI.Business.Services.Interfaces;
using Diligent.WebAPI.Data.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Net;
using System.Security.Policy;

namespace Diligent.WebAPI.Business.Services
{
@@ -10,13 +17,17 @@ namespace Diligent.WebAPI.Business.Services
private readonly UserManager<User> _userManager;
private readonly IMapper _mapper;
private readonly DatabaseContext _databaseContext;
private readonly IEmailer _emailer;
private readonly ILogger<UserService> _logger;

public UserService(IOptions<AuthorizationSettings> authSettings, UserManager<User> userManager, IMapper mapper, DatabaseContext databaseContext)
public UserService(IOptions<AuthorizationSettings> authSettings, UserManager<User> userManager, IMapper mapper, DatabaseContext databaseContext, IEmailer emailer, ILogger<UserService> logger)
{
_authSettings = authSettings.Value;
_userManager = userManager;
_mapper = mapper;
_databaseContext = databaseContext;
_emailer = emailer;
_logger = logger;
}

public async Task<IEnumerable<User?>> GetAll() =>
@@ -95,10 +106,10 @@ namespace Diligent.WebAPI.Business.Services

return await GenerateToken(user);
}
public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(GoogleApiModel model)
{
if (!IsTokenValid(model.Token))
{
return new ServiceResponseDTO<AuthenticateResponseDto>
@@ -184,7 +195,7 @@ namespace Diligent.WebAPI.Business.Services

var existRefreshToken = await _databaseContext.RefreshTokens.Where(x => x.UserId == user.Id).FirstOrDefaultAsync();

if(existRefreshToken != null)
if (existRefreshToken != null)
{
existRefreshToken.Token = writedToken;
existRefreshToken.JwtId = token.Id;
@@ -230,7 +241,7 @@ namespace Diligent.WebAPI.Business.Services

var userk = await _databaseContext.Users.Where(u => u.Id == storedRefreshToken.UserId).FirstOrDefaultAsync();

if(userk == null)
if (userk == null)
{
return new RefreshTokenResultDto { Error = "There is no user which is associated with refresh token" };
}
@@ -277,7 +288,7 @@ namespace Diligent.WebAPI.Business.Services

var user = await _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value);

var token = await GenerateJwtToken(user);
var token = await GenerateJwtToken(user);

return new RefreshTokenResultDto
{
@@ -372,5 +383,66 @@ namespace Diligent.WebAPI.Business.Services

await _databaseContext.SaveChangesAsync();
}

public async Task<ServiceResponseDTO<object>> GetEmailConfirmationUrlAsync(string email)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return new ServiceResponseDTO<object>
{
IsError = true,
ErrorMessage = "Email did not find."
};
}
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);

token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));

await _emailer.SendEmailAndWriteToDbAsync(email, "Reset password", $"<a href='http://localhost:3000/reset-password?token={token}&email={email}'>RESET PASSWORD LINK</a>", isHtml: true);

user.PasswordResetToken = token;
await _databaseContext.SaveChangesAsync();
return new ServiceResponseDTO<object>
{
Data = new { code = token, email = email }
};
}

public async Task<ServiceResponseDTO<object>> PasswordResetAsync(string email, string code, string password)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return new ServiceResponseDTO<object>
{
IsError = true,
ErrorMessage = "Email did not find."
};
}
// FOR SOME REASON USERMANAGER.RESETPASSWORDASYNC returns InvalidToken. In future change this
//var passwordResetToken = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));

//IdentityResult resetResult = await _userManager.ResetPasswordAsync(user, passwordResetToken, password);
//if (resetResult.Succeeded)
await _userManager.RemovePasswordAsync(user);
await _userManager.AddPasswordAsync(user, password);
if(user.PasswordResetToken == code)
{
if (await _userManager.IsLockedOutAsync(user))
{
await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow);
}
return new ServiceResponseDTO<object> { Data = true };
}

//var errors = resetResult.Errors.Select(x => x.Description);
return new ServiceResponseDTO<object>
{
IsError = true,
ErrorMessage = "Invalid reset password token"
};
}
}
}

+ 11
- 1
Diligent.WebAPI.Business/Settings/AuthorizationSettings.cs 查看文件

@@ -1,4 +1,6 @@
namespace Diligent.WebAPI.Business.Settings
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;

namespace Diligent.WebAPI.Business.Settings
{
public class AuthorizationSettings
{
@@ -7,5 +9,13 @@
public int JwtExpiredTime { get; set; }

public int JwtRefreshExpiredTime { get; set; }
public string SmtpFrom { get; set; }
public string SmtpFromName { get; set; }
public string SmtpServer { get; set; }
public int SmtpPort { get; set; }
public bool SmtpUseSSL { get; set; }
public string SmtpUsername { get; set; }
public string SmtpPassword { get; set; }
public string ResetPasswordUrl { get; set; }
}
}

+ 15
- 0
Diligent.WebAPI.Contracts/Models/DiligEmail.cs 查看文件

@@ -0,0 +1,15 @@
namespace Diligent.WebAPI.Contracts.Models
{
public class DiligEmail
{

public long Id { get; set; }
public string To { get; set; }
public string Subject { get; set; }
public bool IsHtml { get; set; }
public string Body { get; set; }
public DateTime? DontSendBefore { get; set; }
public DateTime? CreateTime { get; set; }
public DateTime? SentTime { get; set; }
}
}

+ 9
- 0
Diligent.WebAPI.Contracts/Models/ResetPasswordModel.cs 查看文件

@@ -0,0 +1,9 @@
namespace Diligent.WebAPI.Contracts.Models
{
public class ResetPasswordModel
{
public string Email { get; set; }
public string Code { get; set; }
public string Password { get; set; }
}
}

+ 1
- 0
Diligent.WebAPI.Data/Entities/User.cs 查看文件

@@ -6,4 +6,5 @@ public class User : IdentityUser<int>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string? PasswordResetToken { get; set; }
}

+ 600
- 0
Diligent.WebAPI.Data/Migrations/20221102121707_AddedPasswordResetToken.Designer.cs 查看文件

@@ -0,0 +1,600 @@
// <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("20221102121707_AddedPasswordResetToken")]
partial class AddedPasswordResetToken
{
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>("PasswordResetToken")
.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
}
}
}

+ 25
- 0
Diligent.WebAPI.Data/Migrations/20221102121707_AddedPasswordResetToken.cs 查看文件

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Diligent.WebAPI.Data.Migrations
{
public partial class AddedPasswordResetToken : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PasswordResetToken",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
}

protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PasswordResetToken",
table: "AspNetUsers");
}
}
}

+ 3
- 0
Diligent.WebAPI.Data/Migrations/DatabaseContextModelSnapshot.cs 查看文件

@@ -394,6 +394,9 @@ namespace Diligent.WebAPI.Data.Migrations
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");

b.Property<string>("PasswordResetToken")
.HasColumnType("nvarchar(max)");

b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");


+ 18
- 3
Diligent.WebAPI.Host/Controllers/V1/UsersController.cs 查看文件

@@ -6,19 +6,34 @@
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly IEmailer _emailer;

public UsersController(IUserService userService)
public UsersController(IUserService userService, IEmailer emailer)
{
_userService = userService;
_emailer = emailer;
}

[Authorize]
[HttpGet]
public IActionResult GetAll()
public async Task<IActionResult> GetAll()
{
return Ok("Hello from protected route");
}

[HttpGet("ForgotPassword")]
public async Task<IActionResult> ForgotPassword(string email)
{
var result = await _userService.GetEmailConfirmationUrlAsync(email);
return Ok(result);
}
[HttpPost("RessetPassword")]
public async Task<IActionResult> ResetPassword([FromBody]ResetPasswordModel model)
{
var result = await _userService.PasswordResetAsync(email:model.Email,code: model.Code,password: model.Password);
return Ok(result);
}

[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequestDto model)
{

+ 1
- 0
Diligent.WebAPI.Host/Extensions/BusinessConfigurationExtension.cs 查看文件

@@ -14,6 +14,7 @@
services.AddAutoMapper(typeof(ApplicantMappingProfile));

services.AddScoped<IInsurersService, InsurersService>();
services.AddScoped<IEmailer, Emailer>();
services.AddScoped<IInsuranceCompaniesService, InsuranceCompaniesService>();
services.AddScoped<IInsurancePoliciesService, InsurancePoliciesService>();
services.AddScoped<IWebhookSubscriptionService, WebhookSubscriptionService>();

+ 8
- 1
Diligent.WebAPI.Host/appsettings.Development.json 查看文件

@@ -5,6 +5,13 @@
"Authorization": {
"JwtExpiredTime": "5",
"JwtRefreshExpiredTime": "30",
"Secret": "SECRET_ASKGFH#$_#((Y)#I%EWJGDSJTGKEOS@$SAF"
"Secret": "SECRET_ASKGFH#$_#((Y)#I%EWJGDSJTGKEOS@$SAF",
"SmtpServer": "smtp.mailtrap.io",
"SmtpPort": 2525,
"SmtpUseSSL": true,
"SmtpUsername": "460e3c49f02e37",
"SmtpPassword": "66443869eaad55",
"SmtpFrom": "noreply@hrcenter.net",
"SmtpFromName": "HRCenter Team"
}
}

Loading…
取消
儲存