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