open source Streetwriters.Messenger

This commit is contained in:
Abdullah Atta
2022-12-28 17:02:50 +05:00
parent 037854cd3d
commit b26b0871c8
9 changed files with 388 additions and 2 deletions

View File

@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetwriters.Common", "Str
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetwriters.Data", "Streetwriters.Data\Streetwriters.Data.csproj", "{CBBA4BD8-B348-4CF0-A72A-0D3DE0E6001D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetwriters.Messenger", "Streetwriters.Messenger\Streetwriters.Messenger.csproj", "{BDA80415-6C8D-4481-AC31-E5B4D73E9629}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +32,9 @@ Global
{CBBA4BD8-B348-4CF0-A72A-0D3DE0E6001D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBBA4BD8-B348-4CF0-A72A-0D3DE0E6001D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBBA4BD8-B348-4CF0-A72A-0D3DE0E6001D}.Release|Any CPU.Build.0 = Release|Any CPU
{BDA80415-6C8D-4481-AC31-E5B4D73E9629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDA80415-6C8D-4481-AC31-E5B4D73E9629}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDA80415-6C8D-4481-AC31-E5B4D73E9629}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDA80415-6C8D-4481-AC31-E5B4D73E9629}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -25,19 +25,25 @@ Once you are inside the `./notesnook-sync-server` directory, run:
dotnet restore Notesnook.sln
```
Then build the Notesnook.API project:
To build the `Notesnook.API` project:
```bash
dotnet build Notesnook.API/Notesnook.API.csproj
```
To build the `Streetwriters.Messenger` project:
```bash
dotnet build Streetwriters.Messenger/Streetwriters.Messenger.csproj
```
## TODO Self-hosting
**Note: Self-hosting the Notesnook Sync Server is not yet possible. We are working to enable full on-premise self hosting so stay tuned!**
- [x] Open source the Sync server
- [ ] Open source the Identity server
- [ ] Open source the SSE Messaging infrastructure
- [x] Open source the SSE Messaging infrastructure
- [ ] Fully Dockerize all services
- [ ] Publish on DockerHub
- [ ] Write self hosting docs

View File

@@ -0,0 +1,45 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2022 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Linq;
using System.Threading.Tasks;
using Lib.AspNetCore.ServerSentEvents;
using System.Security.Claims;
namespace Streetwriters.Messenger.Helpers
{
public class SSEHelper
{
public static async Task SendEventToUserAsync(string data, IServerSentEventsService sseService, string userId, string originTokenId = null)
{
var clients = sseService.GetClients().Where(c => c.User.FindFirstValue("sub") == userId);
foreach (var client in clients)
{
if (originTokenId != null && client.User.FindFirstValue("jti") == originTokenId) continue;
if (!client.IsConnected) continue;
await client.SendEventAsync(data);
}
}
public static async Task SendEventToAllUsersAsync(string data, IServerSentEventsService sseService)
{
await sseService.SendEventAsync(data);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2022 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
namespace Streetwriters.Messenger
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseKestrel((options) =>
{
options.Limits.MaxRequestBodySize = long.MaxValue;
#if DEBUG
options.ListenAnyIP(int.Parse(Servers.MessengerServer.Port));
#else
options.ListenAnyIP(443, listenerOptions =>
{
listenerOptions.UseHttps(Servers.OriginSSLCertificate);
});
options.ListenAnyIP(80);
options.Listen(IPAddress.Parse(Servers.MessengerServer.Hostname), int.Parse(Servers.MessengerServer.Port));
#endif
});
});
}
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53323",
"sslPort": 44382
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Streetwriters.Messenger": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,64 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2022 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Lib.AspNetCore.ServerSentEvents;
using Streetwriters.Messenger.Helpers;
using System.Text.Json;
namespace Streetwriters.Messenger.Services
{
internal class HeartbeatService : BackgroundService
{
#region Fields
private const string HEARTBEAT_MESSAGE_FORMAT = "Streetwriters Heartbeat ({0} UTC)";
private readonly IServerSentEventsService _serverSentEventsService;
#endregion
#region Constructor
public HeartbeatService(IServerSentEventsService serverSentEventsService)
{
_serverSentEventsService = serverSentEventsService;
}
#endregion
#region Methods
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var message = JsonSerializer.Serialize(new
{
type = "heartbeat",
data = JsonSerializer.Serialize(new
{
t = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
})
});
await SSEHelper.SendEventToAllUsersAsync(message, _serverSentEventsService);
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
#endregion
}
}

View File

@@ -0,0 +1,142 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2022 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Reactive.Subjects;
using System.Text.Json;
using Lib.AspNetCore.ServerSentEvents;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Streetwriters.Common;
using Streetwriters.Common.Extensions;
using Streetwriters.Common.Messages;
using Streetwriters.Messenger.Helpers;
using Streetwriters.Messenger.Services;
using WampSharp.AspNetCore.WebSockets.Server;
using WampSharp.Binding;
using WampSharp.V2;
using WampSharp.V2.Realm;
namespace Streetwriters.Messenger
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddCors();
services.AddDistributedMemoryCache(delegate (MemoryDistributedCacheOptions cacheOptions)
{
cacheOptions.SizeLimit = 262144000L;
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddOAuth2Introspection("introspection", options =>
{
options.Authority = Servers.IdentityServer.ToString();
options.ClientSecret = Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET");
options.ClientId = "notesnook";
options.SaveToken = true;
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(30);
// TODO
options.DiscoveryPolicy.RequireHttps = false;
});
services.AddServerSentEvents();
services.AddSingleton<IHostedService, HeartbeatService>();
services.AddResponseCompression(options =>
{
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "text/event-stream" });
});
services.AddHealthChecks();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (!env.IsDevelopment())
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
}
app.UseCors("notesnook");
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
var options = new ServerSentEventsOptions();
options.Authorization = new ServerSentEventsAuthorization()
{
AuthenticationSchemes = "introspection"
};
app.MapServerSentEvents("/sse", options);
app.UseWamp(WampServers.MessengerServer, (realm, server) =>
{
IServerSentEventsService service = app.ApplicationServices.GetRequiredService<IServerSentEventsService>();
realm.Subscribe<SendSSEMessage>(server.Topics.SendSSETopic, async (ev) =>
{
var message = JsonSerializer.Serialize(ev.Message);
if (ev.SendToAll)
{
await SSEHelper.SendEventToAllUsersAsync(message, service);
}
else
{
await SSEHelper.SendEventToUserAsync(message, service, ev.UserId, ev.OriginTokenId);
}
});
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/health");
});
}
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<StartupObject>Streetwriters.Messenger.Program</StartupObject>
<LangVersion>10.0</LangVersion>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Lib.AspNetCore.ServerSentEvents" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.0" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.0" NoWarn="NU1605" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Streetwriters.Common\Streetwriters.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}