diff --git a/README.md b/README.md index d49f02b1..ea6103dc 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ BotSharp is in accordance with components principle strictly, decouples every pa * Built-in multi-agents and conversation management. * Support multiple LLM platforms. * Support export/ import agent from other bot platforms directly. -* Support different open source UI [Chatbot UI](src\Plugins\BotSharp.Plugin.ChatbotUI\Chatbot-UI.md), [HuggingChat UI](src\Plugins\BotSharp.Plugin.HuggingFace\HuggingChat-UI.md). +* Support different open source UI [Chatbot UI](src/Plugins/BotSharp.Plugin.ChatbotUI/Chatbot-UI.md), [HuggingChat UI](src/Plugins/BotSharp.Plugin.HuggingFace/HuggingChat-UI.md). * Integrate with popular message channels like Facebook Messenger, Slack and Telegram. ### Quick Started @@ -34,7 +34,7 @@ BotSharp is in accordance with components principle strictly, decouples every pa PS D:\> cd BotSharp PS D:\BotSharp\> dotnet run -p .\src\WebStarter ``` -2. Run UI project, reference to [Chatbot UI](src\Plugins\BotSharp.Plugin.ChatbotUI\Chatbot-UI.md). +2. Run UI project, reference to [Chatbot UI](src/Plugins/BotSharp.Plugin.ChatbotUI/Chatbot-UI.md). ### Extension Libraries diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 5ca94ce6..28757dff 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Agents.Models; + namespace BotSharp.Abstraction.Agents; /// @@ -5,7 +7,7 @@ namespace BotSharp.Abstraction.Agents; /// public interface IAgentService { - void NewAgent(); - void DeleteAgent(); - void UpdateAgent(); + Task CreateAgent(Agent agent); + Task DeleteAgent(string id); + Task UpdateAgent(Agent agent); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/ICurrentUser.cs b/src/Infrastructure/BotSharp.Abstraction/Users/ICurrentUser.cs new file mode 100644 index 00000000..f4498a22 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/ICurrentUser.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.Users; + +public interface ICurrentUser +{ + string Id { get; } + string Email { get; } + string FirstName { get; } + string LastName { get; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs new file mode 100644 index 00000000..f54782ce --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -0,0 +1,10 @@ +using BotSharp.Abstraction.Users.Models; + +namespace BotSharp.Abstraction.Users; + +public interface IUserService +{ + Task CreateUser(User user); + Task GetToken(string authorization); + Task GetMyProfile(); +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs new file mode 100644 index 00000000..f9155a9c --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/Token.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class Token +{ + public string AccessToken { get; set; } = string.Empty; + public string RefreshToken { get; set; } = string.Empty; + public string TokenType { get; set; } = string.Empty; + public int ExpireTime { get; set; } + public string Scope { get; set; } = string.Empty; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs new file mode 100644 index 00000000..69810a13 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs @@ -0,0 +1,13 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class User +{ + public string Id { get; set; } = string.Empty; + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Salt { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs new file mode 100644 index 00000000..09d3cc24 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs @@ -0,0 +1,24 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.ApiAdapters; +using BotSharp.Core.Agents.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace BotSharp.Core.Agents; + +[Authorize] +[ApiController] +public class AgentController : ControllerBase, IApiAdapter +{ + private readonly IAgentService _agentService; + public AgentController(IAgentService agentService) + { + _agentService = agentService; + } + + [HttpPost("/agent")] + public async Task CreateAgent(AgentCreationModel agent) + { + return await _agentService.CreateAgent(agent.ToAgent()); + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs new file mode 100644 index 00000000..574f483d --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -0,0 +1,41 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Core.Repository; +using BotSharp.Core.Repository.Abstraction; +using BotSharp.Core.Repository.DbTables; +using EntityFrameworkCore.BootKit; +using Microsoft.Extensions.DependencyInjection; + +namespace BotSharp.Core.Agents.Services; + +public class AgentService : IAgentService +{ + private readonly IServiceProvider _services; + public AgentService(IServiceProvider services) + { + _services = services; + } + + public async Task CreateAgent(Agent agent) + { + var db = _services.GetRequiredService(); + var record = AgentRecord.FromAgent(agent); + + db.Transaction(delegate + { + db.Add(record); + }); + + return record.Id; + } + + public Task DeleteAgent(string id) + { + throw new NotImplementedException(); + } + + public Task UpdateAgent(Agent agent) + { + throw new NotImplementedException(); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentCreationModel.cs b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentCreationModel.cs new file mode 100644 index 00000000..00bd7d92 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentCreationModel.cs @@ -0,0 +1,18 @@ +using BotSharp.Abstraction.Agents.Models; + +namespace BotSharp.Core.Agents.ViewModels; + +public class AgentCreationModel +{ + public string Name { get; set; } + public string Description { get; set; } + + public Agent ToAgent() + { + return new Agent + { + Name = Name, + Description = Description + }; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs new file mode 100644 index 00000000..2f000c1f --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Core.Agents.ViewModels; + +public class AgentUpdateModel +{ + public string Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentViewModel.cs b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentViewModel.cs new file mode 100644 index 00000000..168525ae --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentViewModel.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Core.Agents.ViewModels; + +public class AgentViewModel +{ + public string Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 617c7071..e2080cfc 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -67,14 +67,11 @@ + - - - - diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index f7e6b7e0..9296ec35 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -1,9 +1,10 @@ +using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.TextGeneratives; +using BotSharp.Abstraction.Users; +using BotSharp.Core.Agents.Services; using BotSharp.Core.Conversations; -using BotSharp.Core.Plugins.TextGeneratives.LLamaSharp; -using BotSharp.Core.Repository; -using EntityFrameworkCore.BootKit; +using BotSharp.Core.Users.Services; +using BotSharp.Plugins.LLamaSharp; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +15,9 @@ public static class BotSharpServiceCollectionExtensions { public static IServiceCollection AddBotSharp(this IServiceCollection services, IConfiguration config) { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddSingleton(); services.AddSingleton(); @@ -59,17 +63,22 @@ public static class BotSharpServiceCollectionExtensions return DataContextHelper.GetDbContext(myDatabaseSettings, x); }); - services.AddSingleton(x => + services.AddScoped((IServiceProvider x) => { - var settings = new LlamaSharpSettings(); - config.Bind("LlamaSharp", settings); - return settings; + return DataContextHelper.GetDbContext(myDatabaseSettings, x); }); - services.AddSingleton(); } public static void RegisterPlugins(IServiceCollection services, IConfiguration config) { - + var settings = new LlamaSharpSettings(); + config.Bind("LlamaSharp", settings); + services.AddSingleton(x => + { + + return settings; + }); + + // services.AddSingleton(); } } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs new file mode 100644 index 00000000..a75e6ce7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Utilities.cs @@ -0,0 +1,23 @@ +namespace BotSharp.Core.Infrastructures; + +public static class Utilities +{ + public static string HashText(string password, string salt) + { + using var md5 = System.Security.Cryptography.MD5.Create(); + + var data = md5.ComputeHash(Encoding.UTF8.GetBytes(password + salt)); + var sb = new StringBuilder(); + foreach (var c in data) + { + sb.Append(c.ToString("x2")); + } + return sb.ToString(); + } + + public static (string, string) SplitAsTuple(this string str, string sep) + { + var splits = str.Split(sep); + return (splits[0], splits[1]); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/ChatCompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs similarity index 96% rename from src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/ChatCompletionProvider.cs rename to src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs index 1550dd80..633165a1 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/ChatCompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs @@ -1,10 +1,9 @@ using BotSharp.Abstraction.Models; -using BotSharp.Abstraction.Plugins; using BotSharp.Abstraction.TextGeneratives; using LLama; using System.IO; -namespace BotSharp.Core.Plugins.TextGeneratives.LLamaSharp; +namespace BotSharp.Plugins.LLamaSharp; public class ChatCompletionProvider : IChatCompletionProvider, IBotSharpPlugin { diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs new file mode 100644 index 00000000..6146bc95 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Plugins.LLamaSharp; + +public class LLamaSharpPlugin : IBotSharpPlugin +{ + +} diff --git a/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/LlamaSharpSettings.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs similarity index 88% rename from src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/LlamaSharpSettings.cs rename to src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs index 4720baf6..eae4e4a8 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/LlamaSharpSettings.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Core.Plugins.TextGeneratives.LLamaSharp; +namespace BotSharp.Plugins.LLamaSharp; public class LlamaSharpSettings { diff --git a/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/LLamaSharpPlugin.cs b/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/LLamaSharpPlugin.cs deleted file mode 100644 index e518c52e..00000000 --- a/src/Infrastructure/BotSharp.Core/Plugins/TextGeneratives/LLamaSharp/LLamaSharpPlugin.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace BotSharp.Core.Plugins.TextGeneratives.LLamaSharp; - -public class LLamaSharpPlugin : IBotSharpPlugin -{ - -} diff --git a/src/Infrastructure/BotSharp.Core/Repository/Abstraction/IAgentTable.cs b/src/Infrastructure/BotSharp.Core/Repository/Abstraction/IAgentTable.cs new file mode 100644 index 00000000..053aab71 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/Abstraction/IAgentTable.cs @@ -0,0 +1,5 @@ +namespace BotSharp.Core.Repository.Abstraction; + +public interface IAgentTable +{ +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/AgentDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/AgentDbContext.cs new file mode 100644 index 00000000..97072369 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/AgentDbContext.cs @@ -0,0 +1,9 @@ +using BotSharp.Core.Repository.DbTables; + +namespace BotSharp.Core.Repository; + +public class AgentDbContext : Database +{ + public IQueryable User => Table(); + public IQueryable Agent => Table(); +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/DataContextHelper.cs b/src/Infrastructure/BotSharp.Core/Repository/DataContextHelper.cs index 3110218f..c58b903e 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/DataContextHelper.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/DataContextHelper.cs @@ -1,4 +1,6 @@ +using BotSharp.Core.Repository.Abstraction; using EntityFrameworkCore.BootKit; +using Microsoft.Data.SqlClient; using System.Data.Common; namespace BotSharp.Core.Repository; @@ -23,6 +25,18 @@ public static class DataContextHelper IsRelational = false }); } + else if (typeof(T) == typeof(AgentDbContext)) + { + dc.BindDbContext(new DatabaseBind + { + ServiceProvider = serviceProvider, + MasterConnection = new SqlConnection(settings.Agent.Master), + SlaveConnections = settings.Agent.Slavers.Length == 0 ? + new List { new SqlConnection(settings.Agent.Master) } : + settings.Agent.Slavers.Select(x => new SqlConnection(x) as DbConnection).ToList(), + CreateDbIfNotExist = true + }); + } return dc; } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/DbTables/AgentRecord.cs b/src/Infrastructure/BotSharp.Core/Repository/DbTables/AgentRecord.cs new file mode 100644 index 00000000..fcfe3e41 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/DbTables/AgentRecord.cs @@ -0,0 +1,37 @@ +using BotSharp.Abstraction.Agents.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace BotSharp.Core.Repository.DbTables; + +[Table("Agent")] +public class AgentRecord : DbRecord, IAgentTable +{ + [Required] + [MaxLength(64)] + public string Name { get; set; } = string.Empty; + + [MaxLength(512)] + public string? Description { get; set; } + + [Required] + [MaxLength(36)] + public string OwnerId { get; set; } = string.Empty; + + [Required] + public DateTime CreatedDateTime { get; set; } + + [Required] + public DateTime UpdatedDateTime { get; set; } + + public static AgentRecord FromAgent(Agent agent) + { + return new AgentRecord + { + Id = agent.Id, + Name = agent.Name, + Description = agent.Description, + OwnerId = agent.OwerId + }; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/DbTables/UserRecord.cs b/src/Infrastructure/BotSharp.Core/Repository/DbTables/UserRecord.cs new file mode 100644 index 00000000..ab357436 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/DbTables/UserRecord.cs @@ -0,0 +1,62 @@ +using BotSharp.Abstraction.Users.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace BotSharp.Core.Repository.DbTables; + +[Table("User")] +public class UserRecord : DbRecord, IAgentTable +{ + [Required] + [MaxLength(64)] + public string FirstName { get; set; } = string.Empty; + + [Required] + [MaxLength(64)] + public string LastName { get; set; } = string.Empty; + + [Required] + [MaxLength(64)] + public string Email { get; set; } = string.Empty; + + [Required] + [StringLength(32)] + public string Salt { get; set; } = string.Empty; + + [Required] + [MaxLength(256)] + public string Password { get; set; } = string.Empty; + + [Required] + public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + + [Required] + public DateTime CreatedTime { get; set; } = DateTime.UtcNow; + + public static UserRecord FromUser(User user) + { + return new UserRecord + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + Email = user.Email, + Password = user.Password + }; + } + + public User ToUser() + { + return new User + { + Id = Id, + FirstName = FirstName, + LastName = LastName, + Email = Email, + Salt = Salt, + Password = Password, + CreatedTime = CreatedTime, + UpdatedTime = UpdatedTime + }; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/MyDatabaseSettings.cs b/src/Infrastructure/BotSharp.Core/Repository/MyDatabaseSettings.cs index 8227200e..1b68b80e 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/MyDatabaseSettings.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/MyDatabaseSettings.cs @@ -6,4 +6,5 @@ public class MyDatabaseSettings : DatabaseSettings { public string[] Assemblies { get; set; } public DbConnectionSetting MongoDb { get; set; } + public DbConnectionSetting Agent { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/CurrentUser.cs b/src/Infrastructure/BotSharp.Core/Users/Services/CurrentUser.cs new file mode 100644 index 00000000..f0ef926f --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/Services/CurrentUser.cs @@ -0,0 +1,25 @@ +using BotSharp.Abstraction.Users; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; + +namespace BotSharp.Core.Users.Services; + +public class CurrentUser : ICurrentUser +{ + private readonly IHttpContextAccessor _contextAccessor; + private IEnumerable _claims => _contextAccessor.HttpContext.User.Claims; + + public CurrentUser(IHttpContextAccessor contextAccessor) + { + _contextAccessor = contextAccessor; + } + + public string Id => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value; + + + public string Email => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value; + + public string FirstName => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname").Value; + + public string LastName => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname").Value; +} diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs new file mode 100644 index 00000000..96991199 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -0,0 +1,123 @@ +using BotSharp.Abstraction.Users; +using BotSharp.Abstraction.Users.Models; +using BotSharp.Core.Infrastructures; +using BotSharp.Core.Repository.DbTables; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +namespace BotSharp.Core.Users.Services; + +public class UserService : IUserService +{ + private readonly IServiceProvider _services; + private readonly ICurrentUser _user; + + public UserService(IServiceProvider services, ICurrentUser user) + { + _services = services; + _user = user; + } + + public async Task CreateUser(User user) + { + var db = _services.GetRequiredService(); + var record = db.User.FirstOrDefault(x => x.Email == user.Email.ToLower()); + if (record != null) + { + return record.ToUser(); + } + + record = UserRecord.FromUser(user); + record.Id = Guid.NewGuid().ToString(); + record.Email = user.Email.ToLower(); + record.Salt = Guid.NewGuid().ToString("N"); + record.Password = Utilities.HashText(user.Password, record.Salt); + + db.Transaction(delegate + { + db.Add(record); + }); + + return record.ToUser(); + } + + public async Task GetToken(string authorization) + { + var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization)); + var (userEmail, password) = base64.SplitAsTuple(":"); + + var db = _services.GetRequiredService(); + var record = db.User.FirstOrDefault(x => x.Email == userEmail); + if (record == null) + { + return default; + } + + if (Utilities.HashText(password, record.Salt) != record.Password) + { + return default; + } + + var accessToken = GenerateJwtToken(record); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); + return new Token + { + AccessToken = accessToken, + ExpireTime = jwt.Payload.Exp.Value, + TokenType = "Bearer", + Scope = "api" + }; + } + + private string GenerateJwtToken(UserRecord user) + { + var config = _services.GetRequiredService(); + var issuer = config["Jwt:Issuer"]; + var audience = config["Jwt:Audience"]; + var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim(JwtRegisteredClaimNames.NameId, user.Id), + new Claim(JwtRegisteredClaimNames.Email, user.Email), + new Claim(JwtRegisteredClaimNames.GivenName, user.FirstName), + new Claim(JwtRegisteredClaimNames.FamilyName, user.LastName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }), + Expires = DateTime.UtcNow.AddMinutes(5), + Issuer = issuer, + Audience = audience, + SigningCredentials = new SigningCredentials + (new SymmetricSecurityKey(key), + SecurityAlgorithms.HmacSha512Signature) + }; + var tokenHandler = new JwtSecurityTokenHandler(); + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + + public async Task GetMyProfile() + { + var userId = _user.Id; + + var db = _services.GetRequiredService(); + var user = (from u in db.User + where u.Id == userId + select new User + { + Id = u.Id, + Email = u.Email, + FirstName = u.FirstName, + LastName = u.LastName, + CreatedTime = u.CreatedTime, + UpdatedTime = u.UpdatedTime, + Password = u.Password, + }).FirstOrDefault(); + + return user; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Users/UserController.cs b/src/Infrastructure/BotSharp.Core/Users/UserController.cs new file mode 100644 index 00000000..6be3be23 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/UserController.cs @@ -0,0 +1,47 @@ +using BotSharp.Abstraction.ApiAdapters; +using BotSharp.Abstraction.Users; +using BotSharp.Abstraction.Users.Models; +using BotSharp.Core.Users.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace BotSharp.Core.Users; + +[Authorize] +[ApiController] +public class UserController : ControllerBase, IApiAdapter +{ + private readonly IUserService _userService; + public UserController(IUserService userService) + { + _userService = userService; + } + + [AllowAnonymous] + [HttpPost("/token")] + public async Task> GetToken() + { + var authcode = Request.Headers["Authorization"].ToString(); + var token = await _userService.GetToken(authcode.Split(' ')[1]); + if (token == null) + { + return Unauthorized(); + } + return Ok(token); + } + + [AllowAnonymous] + [HttpPost("/user")] + public async Task CreateUser(UserCreationModel user) + { + var createdUser = await _userService.CreateUser(user.ToUser()); + return UserViewModel.FromUser(createdUser); + } + + [HttpGet("/user/my")] + public async Task GetMyUserProfile() + { + var user = await _userService.GetMyProfile(); + return UserViewModel.FromUser(user); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Users/ViewModels/UserCreationModel.cs b/src/Infrastructure/BotSharp.Core/Users/ViewModels/UserCreationModel.cs new file mode 100644 index 00000000..23aa7e08 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/ViewModels/UserCreationModel.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.Users.Models; + +namespace BotSharp.Core.Users.ViewModels; + +public class UserCreationModel +{ + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public string Password { get; set; } + + public User ToUser() + { + return new User + { + FirstName = FirstName, + LastName = LastName, + Email = Email, + Password = Password + }; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Users/ViewModels/UserViewModel.cs b/src/Infrastructure/BotSharp.Core/Users/ViewModels/UserViewModel.cs new file mode 100644 index 00000000..ecb2c16b --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/ViewModels/UserViewModel.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.Users.Models; + +namespace BotSharp.Core.Users.ViewModels; + +public class UserViewModel +{ + public string Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + + public static UserViewModel FromUser(User user) + { + return new UserViewModel + { + Id = user.Id, + FirstName = user.FirstName, + LastName = user.LastName, + Email = user.Email + }; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 61f87ce3..a12f7db8 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -4,4 +4,7 @@ global using System.Text; global using System.Threading.Tasks; global using BotSharp.Abstraction; global using System.Linq; -global using BotSharp.Abstraction.Plugins; \ No newline at end of file +global using BotSharp.Abstraction.Plugins; +global using EntityFrameworkCore.BootKit; +global using BotSharp.Core.Repository; +global using BotSharp.Core.Repository.Abstraction; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiServiceCollectionExtensions.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiServiceCollectionExtensions.cs index 11a10e53..4e4d2c9e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiServiceCollectionExtensions.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiServiceCollectionExtensions.cs @@ -11,7 +11,7 @@ public static class AzureOpenAiServiceCollectionExtensions public static IServiceCollection AddAzureOpenAiPlatform(this IServiceCollection services, IConfiguration config) { var settings = new AzureOpenAiSettings(); - config.Bind("AzureAi", settings); + config.Bind("AzureOpenAi", settings); services.AddSingleton(x => { diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj b/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj index f01a29ba..a3a5b80b 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.HuggingFace/BotSharp.Plugin.HuggingFace.csproj b/src/Plugins/BotSharp.Plugin.HuggingFace/BotSharp.Plugin.HuggingFace.csproj index a27882a7..2bbca3fb 100644 --- a/src/Plugins/BotSharp.Plugin.HuggingFace/BotSharp.Plugin.HuggingFace.csproj +++ b/src/Plugins/BotSharp.Plugin.HuggingFace/BotSharp.Plugin.HuggingFace.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/WebStarter/Program.cs b/src/WebStarter/Program.cs index 6624336a..ae712573 100644 --- a/src/WebStarter/Program.cs +++ b/src/WebStarter/Program.cs @@ -1,18 +1,42 @@ using BotSharp.Core; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddHttpContextAccessor(); +// Add bearer authentication +builder.Services.AddAuthentication(options => +{ + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; +}).AddJwtBearer(o => +{ + o.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = builder.Configuration["Jwt:Issuer"], + ValidAudience = builder.Configuration["Jwt:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])), + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = false, + ValidateIssuerSigningKey = true + }; +}); + // Add BotSharp builder.Services.AddBotSharp(builder.Configuration); -// builder.Services.AddAzureOpenAiPlatform(builder.Configuration); +builder.Services.AddAzureOpenAiPlatform(builder.Configuration); builder.Services.AddCors(options => { @@ -34,6 +58,7 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index fd6006b0..406935a2 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -22,6 +22,7 @@ + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 32b9bfe9..d916bbbb 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -7,9 +7,10 @@ }, "AllowedHosts": "*", - "OpenAi": { - "userName": "", - "password": "" + "Jwt": { + "Issuer": "botsharp", + "Audience": "botsharp", + "Key": "31ba6052aa6f4569901facc3a41fcb4a" }, "LlamaSharp": { @@ -33,7 +34,15 @@ "MongoDb": { "Master": "mongodb://localhost:27017/chat-ui" }, + "Agent": { + "Master": "Data Source=(localdb)\\ProjectModels;Initial Catalog=Agent;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False", + "Slavers": [] + }, "UseCamelCase": true, "Assemblies": [ "BotSharp.Core" ] + }, + + "Providers": { + "ChatCompletionProvider": "BotSharp.Plugins.LLamaSharp.ChatCompletionProvider" } } diff --git a/tests/UnitTest/UnitTest.csproj b/tests/UnitTest/UnitTest.csproj index ed22915e..c3ac4f4e 100644 --- a/tests/UnitTest/UnitTest.csproj +++ b/tests/UnitTest/UnitTest.csproj @@ -10,10 +10,13 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +