diff --git a/.gitignore b/.gitignore index dd88ba3..086c047 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore appsettings.*.json +key.json # User-specific files *.rsuser diff --git a/src/BirdsiteLive.ActivityPub/Actor.cs b/src/BirdsiteLive.ActivityPub/Actor.cs new file mode 100644 index 0000000..d6e7cbd --- /dev/null +++ b/src/BirdsiteLive.ActivityPub/Actor.cs @@ -0,0 +1,21 @@ +using System; +using System.Text.Json.Serialization; + +namespace BirdsiteLive.ActivityPub +{ + public class Actor + { + [JsonPropertyName("@context")] + public string[] context { get; set; } = new[] {"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"}; + public string id { get; set; } + public string type { get; set; } + public string preferredUsername { get; set; } + public string name { get; set; } + public string summary { get; set; } + public string url { get; set; } + public string inbox { get; set; } + public PublicKey publicKey { get; set; } + public Image icon { get; set; } + public Image image { get; set; } + } +} diff --git a/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj b/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj new file mode 100644 index 0000000..2632b10 --- /dev/null +++ b/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/BirdsiteLive.ActivityPub/Image.cs b/src/BirdsiteLive.ActivityPub/Image.cs new file mode 100644 index 0000000..22c09fd --- /dev/null +++ b/src/BirdsiteLive.ActivityPub/Image.cs @@ -0,0 +1,9 @@ +namespace BirdsiteLive.ActivityPub +{ + public class Image + { + public string type { get; set; } = "image"; + public string mediaType { get; set; } + public string url { get; set; } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.ActivityPub/PublicKey.cs b/src/BirdsiteLive.ActivityPub/PublicKey.cs new file mode 100644 index 0000000..3c3b908 --- /dev/null +++ b/src/BirdsiteLive.ActivityPub/PublicKey.cs @@ -0,0 +1,9 @@ +namespace BirdsiteLive.ActivityPub +{ + public class PublicKey + { + public string id { get; set; } + public string owner { get; set; } + public string publicKeyPem { get; set; } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive/Models/InstanceSettings.cs b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs similarity index 68% rename from src/BirdsiteLive/Models/InstanceSettings.cs rename to src/BirdsiteLive.Common/Settings/InstanceSettings.cs index 816d4f3..aabe822 100644 --- a/src/BirdsiteLive/Models/InstanceSettings.cs +++ b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs @@ -1,4 +1,4 @@ -namespace BirdsiteLive.Models +namespace BirdsiteLive.Common.Settings { public class InstanceSettings { diff --git a/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj b/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj new file mode 100644 index 0000000..50eb4d2 --- /dev/null +++ b/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + + + + + diff --git a/src/BirdsiteLive.Domain/CryptoService.cs b/src/BirdsiteLive.Domain/CryptoService.cs new file mode 100644 index 0000000..151344c --- /dev/null +++ b/src/BirdsiteLive.Domain/CryptoService.cs @@ -0,0 +1,26 @@ +using BirdsiteLive.Domain.Factories; + +namespace BirdsiteLive.Domain +{ + public interface ICryptoService + { + string GetUserPem(string id); + } + + public class CryptoService : ICryptoService + { + private readonly IMagicKeyFactory _magicKeyFactory; + + #region Ctor + public CryptoService(IMagicKeyFactory magicKeyFactory) + { + _magicKeyFactory = magicKeyFactory; + } + #endregion + + public string GetUserPem(string id) + { + return _magicKeyFactory.GetMagicKey().AsPEM; + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Domain/Factories/MagicKeyFactory.cs b/src/BirdsiteLive.Domain/Factories/MagicKeyFactory.cs new file mode 100644 index 0000000..990c5f4 --- /dev/null +++ b/src/BirdsiteLive.Domain/Factories/MagicKeyFactory.cs @@ -0,0 +1,41 @@ +using System.IO; +using BirdsiteLive.Cryptography; + +namespace BirdsiteLive.Domain.Factories +{ + public interface IMagicKeyFactory + { + MagicKey GetMagicKey(); + } + + public class MagicKeyFactory : IMagicKeyFactory + { + private const string Path = "key.json"; + private static MagicKey _magicKey; + + #region Ctor + public MagicKeyFactory() + { + + } + #endregion + + public MagicKey GetMagicKey() + { + //Cached key + if (_magicKey != null) return _magicKey; + + //Generate key if needed + if (!File.Exists(Path)) + { + var key = MagicKey.Generate(); + File.WriteAllText(Path, key.PrivateKey); + } + + //Load and return key + var serializedKey = File.ReadAllText(Path); + _magicKey = new MagicKey(serializedKey); + return _magicKey; + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs new file mode 100644 index 0000000..019c953 --- /dev/null +++ b/src/BirdsiteLive.Domain/UserService.cs @@ -0,0 +1,57 @@ +using System; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.Twitter.Models; + +namespace BirdsiteLive.Domain +{ + public interface IUserService + { + Actor GetUser(TwitterUser twitterUser); + } + + public class UserService : IUserService + { + private readonly ICryptoService _cryptoService; + private readonly string _host; + + #region Ctor + public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService) + { + _cryptoService = cryptoService; + _host = $"https://{instanceSettings.Domain.Replace("https://",string.Empty).Replace("http://", string.Empty).TrimEnd('/')}"; + } + #endregion + + public Actor GetUser(TwitterUser twitterUser) + { + var user = new Actor + { + id = $"{_host}/users/{twitterUser.Acct}", + type = "Person", + preferredUsername = twitterUser.Acct, + name = twitterUser.Name, + inbox = $"{_host}/users/{twitterUser.Acct}/inbox", + summary = twitterUser.Description, + url = $"{_host}/@{twitterUser.Acct}", + publicKey = new PublicKey() + { + id = $"{_host}/users/{twitterUser.Acct}#main-key", + owner = $"{_host}/users/{twitterUser.Acct}", + publicKeyPem = _cryptoService.GetUserPem(twitterUser.Acct) + }, + icon = new Image + { + mediaType = "image/jpeg", + url = twitterUser.ProfileImageUrl + }, + image = new Image + { + mediaType = "image/jpeg", + url = twitterUser.ProfileBannerURL + } + }; + return user; + } + } +} diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index b743257..963429a 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -15,7 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Common", "Bird EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A32D3458-09D0-4E0A-BA4B-8C411B816B94}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Cryptography.Tests", "Tests\BirdsiteLive.Cryptography.Tests\BirdsiteLive.Cryptography.Tests.csproj", "{155D46A4-2D05-47F2-8FFC-0B7C412A7652}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Cryptography.Tests", "Tests\BirdsiteLive.Cryptography.Tests\BirdsiteLive.Cryptography.Tests.csproj", "{155D46A4-2D05-47F2-8FFC-0B7C412A7652}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Domain", "BirdsiteLive.Domain\BirdsiteLive.Domain.csproj", "{D48450EE-D8BD-4228-9864-043AC88F7EE0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.ActivityPub", "BirdsiteLive.ActivityPub\BirdsiteLive.ActivityPub.csproj", "{7463E1E2-9736-4A46-8507-010BDD8ECFBB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,6 +47,14 @@ Global {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Debug|Any CPU.Build.0 = Debug|Any CPU {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Release|Any CPU.ActiveCfg = Release|Any CPU {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Release|Any CPU.Build.0 = Release|Any CPU + {D48450EE-D8BD-4228-9864-043AC88F7EE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D48450EE-D8BD-4228-9864-043AC88F7EE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D48450EE-D8BD-4228-9864-043AC88F7EE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D48450EE-D8BD-4228-9864-043AC88F7EE0}.Release|Any CPU.Build.0 = Release|Any CPU + {7463E1E2-9736-4A46-8507-010BDD8ECFBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7463E1E2-9736-4A46-8507-010BDD8ECFBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7463E1E2-9736-4A46-8507-010BDD8ECFBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7463E1E2-9736-4A46-8507-010BDD8ECFBB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -52,6 +64,8 @@ Global {77C559D1-80A2-4B1C-A566-AE2D156944A4} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} {E64E7501-5DB8-4620-BA35-BA59FD746ABA} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} {155D46A4-2D05-47F2-8FFC-0B7C412A7652} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} + {D48450EE-D8BD-4228-9864-043AC88F7EE0} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} + {7463E1E2-9736-4A46-8507-010BDD8ECFBB} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/BirdsiteLive/BirdsiteLive.csproj b/src/BirdsiteLive/BirdsiteLive.csproj index ca145a6..d302ae7 100644 --- a/src/BirdsiteLive/BirdsiteLive.csproj +++ b/src/BirdsiteLive/BirdsiteLive.csproj @@ -15,6 +15,7 @@ + diff --git a/src/BirdsiteLive/Controllers/UserController.cs b/src/BirdsiteLive/Controllers/UserController.cs deleted file mode 100644 index d5d15fb..0000000 --- a/src/BirdsiteLive/Controllers/UserController.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BirdsiteLive.Twitter; -using Microsoft.AspNetCore.Mvc; - -namespace BirdsiteLive.Controllers -{ - public class UserController : Controller - { - private readonly ITwitterService _twitterService; - - #region Ctor - public UserController(ITwitterService twitterService) - { - _twitterService = twitterService; - } - #endregion - - [Route("/@{id}")] - [Route("/user/{id}")] - public IActionResult Index(string id) - { - var user = _twitterService.GetUser(id); - if (user == null) return NotFound(); - - var r = Request.Headers["Accept"].First(); - - if (r.Contains("application/activity+json")) - return Json(new { test = "test" }); - - return View(user); - } - } -} \ No newline at end of file diff --git a/src/BirdsiteLive/Controllers/UsersController.cs b/src/BirdsiteLive/Controllers/UsersController.cs new file mode 100644 index 0000000..ae275e2 --- /dev/null +++ b/src/BirdsiteLive/Controllers/UsersController.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BirdsiteLive.Domain; +using BirdsiteLive.Twitter; +using Microsoft.AspNetCore.Mvc; + +namespace BirdsiteLive.Controllers +{ + public class UsersController : Controller + { + private readonly ITwitterService _twitterService; + private readonly IUserService _userService; + + #region Ctor + public UsersController(ITwitterService twitterService, IUserService userService) + { + _twitterService = twitterService; + _userService = userService; + } + #endregion + + [Route("/@{id}")] + [Route("/users/{id}")] + public IActionResult Index(string id) + { + var user = _twitterService.GetUser(id); + if (user == null) return NotFound(); + + var r = Request.Headers["Accept"].First(); + if (r.Contains("application/activity+json")) + { + var apUser = _userService.GetUser(user); + return Json(apUser); + } + + return View(user); + } + + [Route("/users/{id}/inbox")] + [HttpPost] + public async Task Inbox() + { + var r = Request; + using (var reader = new StreamReader(Request.Body)) + { + var body = await reader.ReadToEndAsync(); + + // Do something + } + + return Ok(); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive/Controllers/WellKnownController.cs b/src/BirdsiteLive/Controllers/WellKnownController.cs index 9a0ceed..89c7f7c 100644 --- a/src/BirdsiteLive/Controllers/WellKnownController.cs +++ b/src/BirdsiteLive/Controllers/WellKnownController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using BirdsiteLive.Common.Settings; using BirdsiteLive.Models; using BirdsiteLive.Twitter; using Microsoft.AspNetCore.Mvc; diff --git a/src/BirdsiteLive/Startup.cs b/src/BirdsiteLive/Startup.cs index b696d03..6d07aaa 100644 --- a/src/BirdsiteLive/Startup.cs +++ b/src/BirdsiteLive/Startup.cs @@ -45,9 +45,13 @@ namespace BirdsiteLive var twitterSettings = Configuration.GetSection("Twitter").Get(); services.For().Use(x => twitterSettings); + var instanceSettings = Configuration.GetSection("Instance").Get(); + services.For().Use(x => instanceSettings); + services.Scan(_ => { _.Assembly("BirdsiteLive.Twitter"); + _.Assembly("BirdsiteLive.Domain"); _.TheCallingAssembly(); //_.AssemblyContainingType(); diff --git a/src/BirdsiteLive/Views/User/Index.cshtml b/src/BirdsiteLive/Views/Users/Index.cshtml similarity index 100% rename from src/BirdsiteLive/Views/User/Index.cshtml rename to src/BirdsiteLive/Views/Users/Index.cshtml