diff --git a/Notesnook.API/Notesnook.API.csproj b/Notesnook.API/Notesnook.API.csproj
index 31d6f73..57b4b67 100644
--- a/Notesnook.API/Notesnook.API.csproj
+++ b/Notesnook.API/Notesnook.API.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/Notesnook.API/Services/UserService.cs b/Notesnook.API/Services/UserService.cs
index cf3ed9e..a255fdb 100644
--- a/Notesnook.API/Services/UserService.cs
+++ b/Notesnook.API/Services/UserService.cs
@@ -76,7 +76,7 @@ namespace Notesnook.API.Services
if (!Constants.IS_SELF_HOSTED)
{
- await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.CreateSubscriptionTopic, new CreateSubscriptionMessage
+ await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.CreateSubscriptionTopic, new CreateSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
@@ -115,7 +115,7 @@ namespace Notesnook.API.Services
{
await Slogger.Error(nameof(GetUserAsync), "Repairing user subscription.", JsonSerializer.Serialize(response));
// user was partially created. We should continue the process here.
- await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.CreateSubscriptionTopic, new CreateSubscriptionMessage
+ await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.CreateSubscriptionTopic, new CreateSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
@@ -182,22 +182,22 @@ namespace Notesnook.API.Services
if (!Constants.IS_SELF_HOSTED)
{
- await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
+ await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
UserId = userId
});
}
- await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
+ await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
SendToAll = false,
OriginTokenId = jti,
UserId = userId,
Message = new Message
{
- Type = "userDeleted",
- Data = JsonSerializer.Serialize(new { reason = "accountDeleted" })
+ Type = "logout",
+ Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
}
});
diff --git a/Notesnook.API/Startup.cs b/Notesnook.API/Startup.cs
index cbb96f4..0a055a7 100644
--- a/Notesnook.API/Startup.cs
+++ b/Notesnook.API/Startup.cs
@@ -34,6 +34,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.ResponseCompression;
+using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -152,6 +153,7 @@ namespace Notesnook.API
context.HttpContext.User = context.Principal;
return Task.CompletedTask;
};
+ options.CacheKeyGenerator = (options, token) => (token + ":" + "reference_token").Sha256();
options.SaveToken = true;
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(30);
@@ -244,10 +246,14 @@ namespace Notesnook.API
app.UseWamp(WampServers.NotesnookServer, (realm, server) =>
{
IUserService service = app.GetScopedService();
- realm.Subscribe(server.Topics.DeleteUserTopic, async (ev) =>
+
+ realm.Subscribe(IdentityServerTopics.DeleteUserTopic, async (ev) =>
{
await service.DeleteUserAsync(ev.UserId, null);
});
+
+ IDistributedCache cache = app.GetScopedService();
+ realm.Subscribe(IdentityServerTopics.ClearCacheTopic, (ev) => ev.Keys.ForEach((key) => cache.Remove(key)));
});
app.UseRouting();
diff --git a/Streetwriters.Common/Clients.cs b/Streetwriters.Common/Clients.cs
index 1718464..bd65a10 100644
--- a/Streetwriters.Common/Clients.cs
+++ b/Streetwriters.Common/Clients.cs
@@ -41,7 +41,7 @@ namespace Streetwriters.Common
EmailConfirmedRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/verified",
OnEmailConfirmed = async (userId) =>
{
- await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
+ await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
UserId = userId,
Message = new Message
diff --git a/Streetwriters.Common/Extensions/StringExtensions.cs b/Streetwriters.Common/Extensions/StringExtensions.cs
index 1a9e796..28b0f17 100644
--- a/Streetwriters.Common/Extensions/StringExtensions.cs
+++ b/Streetwriters.Common/Extensions/StringExtensions.cs
@@ -26,15 +26,11 @@ namespace System
{
public static class StringExtensions
{
- public static string ToSha256(this string rawData, int maxLength = 12)
+ public static string Sha256(this string input)
{
- // Create a SHA256
- using (SHA256 sha256Hash = SHA256.Create())
- {
- // ComputeHash - returns byte array
- byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
- return ToHex(bytes, 0, maxLength);
- }
+ var bytes = Encoding.UTF8.GetBytes(input);
+ var hash = SHA256.HashData(bytes);
+ return Convert.ToBase64String(hash);
}
public static byte[] CompressBrotli(this string input)
diff --git a/Streetwriters.Common/Messages/ClearCacheMessage.cs b/Streetwriters.Common/Messages/ClearCacheMessage.cs
new file mode 100644
index 0000000..c06105d
--- /dev/null
+++ b/Streetwriters.Common/Messages/ClearCacheMessage.cs
@@ -0,0 +1,38 @@
+/*
+This file is part of the Notesnook Sync Server project (https://notesnook.com/)
+
+Copyright (C) 2023 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 .
+*/
+
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+using Streetwriters.Common.Enums;
+using Streetwriters.Common.Interfaces;
+
+namespace Streetwriters.Common.Messages
+{
+ public class ClearCacheMessage
+ {
+ public ClearCacheMessage(List keys)
+ {
+ this.Keys = keys;
+ }
+
+ [JsonPropertyName("keys")]
+ public List Keys { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Streetwriters.Common/WampServers.cs b/Streetwriters.Common/WampServers.cs
index bcf6bb3..d275430 100644
--- a/Streetwriters.Common/WampServers.cs
+++ b/Streetwriters.Common/WampServers.cs
@@ -97,23 +97,22 @@ namespace Streetwriters.Common
public class MessengerServerTopics
{
- public string SendSSETopic => "com.streetwriters.sse.send";
+ public const string SendSSETopic = "com.streetwriters.sse.send";
}
public class SubscriptionServerTopics
{
- public string CreateSubscriptionTopic => "com.streetwriters.subscriptions.create";
- public string DeleteSubscriptionTopic => "com.streetwriters.subscriptions.delete";
+ public const string CreateSubscriptionTopic = "com.streetwriters.subscriptions.create";
+ public const string DeleteSubscriptionTopic = "com.streetwriters.subscriptions.delete";
}
public class IdentityServerTopics
{
- public string CreateSubscriptionTopic => "com.streetwriters.subscriptions.create";
- public string DeleteSubscriptionTopic => "com.streetwriters.subscriptions.delete";
+ public const string ClearCacheTopic = "com.streetwriters.identity.clear_cache";
+ public const string DeleteUserTopic = "com.streetwriters.identity.delete_user";
}
public class NotesnookServerTopics
{
- public string DeleteUserTopic => "com.streetwriters.notesnook.user.delete";
}
}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Config.cs b/Streetwriters.Identity/Config.cs
index 8754480..2f09272 100644
--- a/Streetwriters.Identity/Config.cs
+++ b/Streetwriters.Identity/Config.cs
@@ -20,9 +20,7 @@ along with this program. If not, see .
using IdentityServer4;
using IdentityServer4.Models;
using Streetwriters.Common;
-using System;
using System.Collections.Generic;
-using System.Linq;
namespace Streetwriters.Identity
{
diff --git a/Streetwriters.Identity/Controllers/AccountController.cs b/Streetwriters.Identity/Controllers/AccountController.cs
index ffbc9c0..e66316d 100644
--- a/Streetwriters.Identity/Controllers/AccountController.cs
+++ b/Streetwriters.Identity/Controllers/AccountController.cs
@@ -21,9 +21,12 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.Claims;
+using System.Text.Json;
using System.Threading.Tasks;
using AspNetCore.Identity.Mongo.Model;
+using IdentityServer4;
using IdentityServer4.Configuration;
+using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@@ -280,7 +283,7 @@ namespace Streetwriters.Identity.Controllers
if (result.Succeeded)
{
await UserManager.SetUserNameAsync(user, form.NewEmail);
- await SendEmailChangedMessageAsync(user.Id.ToString());
+ await SendLogoutMessageAsync(user.Id.ToString(), "Email changed.");
return Ok();
}
}
@@ -292,7 +295,7 @@ namespace Streetwriters.Identity.Controllers
var result = await UserManager.ChangePasswordAsync(user, form.OldPassword, form.NewPassword);
if (result.Succeeded)
{
- await SendPasswordChangedMessageAsync(user.Id.ToString());
+ await SendLogoutMessageAsync(user.Id.ToString(), "Password changed.");
return Ok();
}
return BadRequest(result.Errors.ToErrors());
@@ -306,7 +309,7 @@ namespace Streetwriters.Identity.Controllers
result = await UserManager.AddPasswordAsync(user, form.NewPassword);
if (result.Succeeded)
{
- await SendPasswordChangedMessageAsync(user.Id.ToString());
+ await SendLogoutMessageAsync(user.Id.ToString(), "Password reset.");
return Ok();
}
}
@@ -334,7 +337,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id)) return BadRequest($"Unable to find user with ID '{user.Id.ToString()}'.");
+ if (!await IsUserValidAsync(user, client.Id)) return BadRequest($"Unable to find user with ID '{user.Id}'.");
var jti = User.FindFirstValue("jti");
@@ -343,37 +346,43 @@ namespace Streetwriters.Identity.Controllers
ClientId = client.Id,
SubjectId = user.Id.ToString()
});
+ var refreshTokenKey = GetHashedKey(refresh_token, PersistedGrantTypes.RefreshToken);
+ var removedKeys = new List();
foreach (var grant in grants)
{
- if (!all && (grant.Data.Contains(jti) || grant.Data.Contains(refresh_token))) continue;
+ if (!all && (grant.Data.Contains(jti) || grant.Key == refreshTokenKey)) continue;
await PersistedGrantStore.RemoveAsync(grant.Key);
+ removedKeys.Add(grant.Key);
}
+
+ await WampServers.NotesnookServer.PublishMessageAsync(IdentityServerTopics.ClearCacheTopic, new ClearCacheMessage(removedKeys));
+ await WampServers.MessengerServer.PublishMessageAsync(IdentityServerTopics.ClearCacheTopic, new ClearCacheMessage(removedKeys));
+ await WampServers.SubscriptionServer.PublishMessageAsync(IdentityServerTopics.ClearCacheTopic, new ClearCacheMessage(removedKeys));
+ await SendLogoutMessageAsync(user.Id.ToString(), "Session revoked.");
return Ok();
}
- private async Task SendPasswordChangedMessageAsync(string userId)
+ private static string GetHashedKey(string value, string grantType)
{
- await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
+ return (value + ":" + grantType).Sha256();
+ }
+
+ private async Task SendLogoutMessageAsync(string userId, string reason)
+ {
+ await SendMessageAsync(userId, new Message
{
- UserId = userId,
- OriginTokenId = User.FindFirstValue("jti"),
- Message = new Message
- {
- Type = "userPasswordChanged"
- }
+ Type = "logout",
+ Data = JsonSerializer.Serialize(new { reason })
});
}
- private async Task SendEmailChangedMessageAsync(string userId)
+ private async Task SendMessageAsync(string userId, Message message)
{
- await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
+ await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
UserId = userId,
OriginTokenId = User.FindFirstValue("jti"),
- Message = new Message
- {
- Type = "userEmailChanged"
- }
+ Message = message
});
}
diff --git a/Streetwriters.Identity/Startup.cs b/Streetwriters.Identity/Startup.cs
index 961e231..e459251 100644
--- a/Streetwriters.Identity/Startup.cs
+++ b/Streetwriters.Identity/Startup.cs
@@ -201,7 +201,7 @@ namespace Streetwriters.Identity
app.UseWamp(WampServers.IdentityServer, (realm, server) =>
{
- realm.Subscribe(server.Topics.CreateSubscriptionTopic, async (CreateSubscriptionMessage message) =>
+ realm.Subscribe(SubscriptionServerTopics.CreateSubscriptionTopic, async (CreateSubscriptionMessage message) =>
{
using (var serviceScope = app.ApplicationServices.CreateScope())
{
@@ -210,7 +210,7 @@ namespace Streetwriters.Identity
await MessageHandlers.CreateSubscription.Process(message, userManager);
}
});
- realm.Subscribe(server.Topics.DeleteSubscriptionTopic, async (DeleteSubscriptionMessage message) =>
+ realm.Subscribe(SubscriptionServerTopics.DeleteSubscriptionTopic, async (DeleteSubscriptionMessage message) =>
{
using (var serviceScope = app.ApplicationServices.CreateScope())
{
diff --git a/Streetwriters.Messenger/Startup.cs b/Streetwriters.Messenger/Startup.cs
index 5be2209..4bbff02 100644
--- a/Streetwriters.Messenger/Startup.cs
+++ b/Streetwriters.Messenger/Startup.cs
@@ -28,6 +28,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.ResponseCompression;
+using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -74,11 +75,11 @@ namespace Streetwriters.Messenger
options.Authority = Servers.IdentityServer.ToString();
options.ClientSecret = Constants.NOTESNOOK_API_SECRET;
options.ClientId = "notesnook";
+ options.DiscoveryPolicy.RequireHttps = false;
+ options.CacheKeyGenerator = (options, token) => (token + ":" + "reference_token").Sha256();
options.SaveToken = true;
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(30);
- // TODO
- options.DiscoveryPolicy.RequireHttps = false;
});
services.AddServerSentEvents();
@@ -119,7 +120,7 @@ namespace Streetwriters.Messenger
app.UseWamp(WampServers.MessengerServer, (realm, server) =>
{
IServerSentEventsService service = app.ApplicationServices.GetRequiredService();
- realm.Subscribe(server.Topics.SendSSETopic, async (ev) =>
+ realm.Subscribe(MessengerServerTopics.SendSSETopic, async (ev) =>
{
var message = JsonSerializer.Serialize(ev.Message);
if (ev.SendToAll)
@@ -131,6 +132,9 @@ namespace Streetwriters.Messenger
await SSEHelper.SendEventToUserAsync(message, service, ev.UserId, ev.OriginTokenId);
}
});
+
+ IDistributedCache cache = app.GetScopedService();
+ realm.Subscribe(IdentityServerTopics.ClearCacheTopic, (ev) => ev.Keys.ForEach((key) => cache.Remove(key)));
});
app.UseEndpoints(endpoints =>
diff --git a/Streetwriters.Messenger/Streetwriters.Messenger.csproj b/Streetwriters.Messenger/Streetwriters.Messenger.csproj
index 1799ef0..9d02d87 100644
--- a/Streetwriters.Messenger/Streetwriters.Messenger.csproj
+++ b/Streetwriters.Messenger/Streetwriters.Messenger.csproj
@@ -13,7 +13,7 @@
-
+