Add new user verification code.

This commit is contained in:
Haiping Chen 2024-05-25 20:34:29 -05:00
parent f584c3190f
commit e8656a99c0
13 changed files with 125 additions and 22 deletions

View file

@ -17,10 +17,11 @@ public interface IBotSharpRepository
#endregion
#region User
User? GetUserByEmail(string email);
User? GetUserById(string id);
User? GetUserByUserName(string userName);
void CreateUser(User user);
User? GetUserByEmail(string email) => throw new NotImplementedException();
User? GetUserById(string id) => throw new NotImplementedException();
User? GetUserByUserName(string userName) => throw new NotImplementedException();
void CreateUser(User user) => throw new NotImplementedException();
void UpdateUserVerified(string userId) => throw new NotImplementedException();
#endregion
#region Agent

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Users.Models;
using BotSharp.OpenAPI.ViewModels.Users;
namespace BotSharp.Abstraction.Users;
@ -6,6 +7,7 @@ public interface IUserService
{
Task<User> GetUser(string id);
Task<User> CreateUser(User user);
Task<Token> ActiveUser(UserActivationModel model);
Task<Token?> GetToken(string authorization);
Task<User> GetMyProfile();
}

View file

@ -14,6 +14,8 @@ public class User
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; } = UserRole.Client;
public string? VerificationCode { get; set; }
public bool Verified { get; set; }
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -0,0 +1,7 @@
namespace BotSharp.OpenAPI.ViewModels.Users;
public class UserActivationModel
{
public string UserName { get; set; }
public string VerificationCode { get; set; }
}

View file

@ -0,0 +1,9 @@
namespace BotSharp.Abstraction.Users.Settings;
public class AccountSetting
{
/// <summary>
/// Whether to enable verification code to verify the authenticity of new users
/// </summary>
public bool NewUserVerification { get; set; }
}

View file

@ -5,6 +5,7 @@ using BotSharp.Core.Plugins;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Messaging.JsonConverters;
using BotSharp.Abstraction.Users.Settings;
namespace BotSharp.Core;
@ -84,6 +85,10 @@ public static class BotSharpCoreExtensions
return settingService.Bind<PluginSettings>("PluginLoader");
});
var accountSettings = new AccountSetting();
config.Bind("Account", accountSettings);
services.AddScoped(x => accountSettings);
var loader = new PluginLoader(services, config, pluginSettings);
loader.Load(assembly =>
{

View file

@ -1,6 +1,5 @@
using BotSharp.Abstraction.Loggers.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Users.Models;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -176,20 +175,6 @@ public class BotSharpDbContext : Database, IBotSharpRepository
=> throw new NotImplementedException();
#endregion
#region User
public User? GetUserByEmail(string email)
=> throw new NotImplementedException();
public User? GetUserById(string id)
=> throw new NotImplementedException();
public User? GetUserByUserName(string userName)
=> throw new NotImplementedException();
public void CreateUser(User user)
=> throw new NotImplementedException();
#endregion
#region Execution Log
public void AddExecutionLogs(string conversationId, List<string> logs)
{

View file

@ -32,4 +32,13 @@ public partial class FileRepository
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
public void UpdateUserVerified(string userId)
{
var user = GetUserById(userId);
user.Verified = true;
var dir = Path.Combine(_dbSettings.FileRepository, "users", user.Id);
var path = Path.Combine(dir, "user.json");
File.WriteAllText(path, JsonSerializer.Serialize(user, _options));
}
}

View file

@ -1,4 +1,6 @@
using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.Users.Settings;
using BotSharp.OpenAPI.ViewModels.Users;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using NanoidDotNet;
@ -12,12 +14,17 @@ public class UserService : IUserService
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
private readonly ILogger _logger;
private readonly AccountSetting _setting;
public UserService(IServiceProvider services, IUserIdentity user, ILogger<UserService> logger)
public UserService(IServiceProvider services,
IUserIdentity user,
ILogger<UserService> logger,
AccountSetting setting)
{
_services = services;
_user = user;
_logger = logger;
_setting = setting;
}
public async Task<User> CreateUser(User user)
@ -51,6 +58,12 @@ public class UserService : IUserService
record.Salt = Guid.NewGuid().ToString("N");
record.Password = Utilities.HashText(user.Password, record.Salt);
if (_setting.NewUserVerification)
{
record.VerificationCode = Nanoid.Generate(alphabet: "0123456789", size: 6);
record.Verified = false;
}
db.CreateUser(record);
_logger.LogWarning($"Created new user account: {record.Id} {record.UserName}");
@ -120,6 +133,11 @@ public class UserService : IUserService
return default;
}
if (_setting.NewUserVerification && !record.Verified)
{
return default;
}
#if !DEBUG
if (Utilities.HashText(password, record.Salt) != record.Password)
{
@ -206,4 +224,43 @@ public class UserService : IUserService
var user = db.GetUserById(id);
return user;
}
public async Task<Token> ActiveUser(UserActivationModel model)
{
var id = model.UserName;
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
if (record == null)
{
record = db.GetUserByUserName(id);
}
if (record == null)
{
return default;
}
if (record.VerificationCode != model.VerificationCode)
{
return default;
}
if (record.Verified)
{
return default;
}
db.UpdateUserVerified(record.Id);
var accessToken = GenerateJwtToken(record);
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var token = new Token
{
AccessToken = accessToken,
ExpireTime = jwt.Payload.Exp.Value,
TokenType = "Bearer",
Scope = "api"
};
return token;
}
}

View file

@ -61,6 +61,18 @@ public class UserController : ControllerBase
return UserViewModel.FromUser(createdUser);
}
[AllowAnonymous]
[HttpPost("/user/activate")]
public async Task<ActionResult<Token>> ActivateUser(UserActivationModel model)
{
var token = await _userService.ActiveUser(model);
if (token == null)
{
return Unauthorized();
}
return Ok(token);
}
[HttpGet("/user/me")]
public async Task<UserViewModel> GetMyUserProfile()
{

View file

@ -13,7 +13,8 @@ public class UserDocument : MongoBase
public string Source { get; set; } = "internal";
public string? ExternalId { get; set; }
public string Role { get; set; }
public string? VerificationCode { get; set; }
public bool Verified { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
@ -30,7 +31,9 @@ public class UserDocument : MongoBase
Salt = Salt,
Source = Source,
ExternalId = ExternalId,
Role = Role
Role = Role,
VerificationCode = VerificationCode,
Verified = Verified,
};
}
}

View file

@ -40,10 +40,20 @@ public partial class MongoRepository
Source = user.Source,
ExternalId = user.ExternalId,
Role = user.Role,
VerificationCode = user.VerificationCode,
Verified = user.Verified,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
_dc.Users.InsertOne(userCollection);
}
public void UpdateUserVerified(string userId)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
var update = Builders<UserDocument>.Update.Set(x => x.Verified, true)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Users.UpdateOne(filter, update);
}
}

View file

@ -27,6 +27,7 @@ public partial class PlaywrightWebDriver
try
{
_logger.LogInformation($"SendHttpRequest: {args.Url}");
var response = await EvaluateScript<object>(message.ContextId, script);
result.IsSuccess = true;
result.Body = JsonSerializer.Serialize(response);