diff --git a/Streetwriters.Common/Models/Subscription.cs b/Streetwriters.Common/Models/Subscription.cs
index 683cbea..9eab742 100644
--- a/Streetwriters.Common/Models/Subscription.cs
+++ b/Streetwriters.Common/Models/Subscription.cs
@@ -1,80 +1,102 @@
-/*
-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.ComponentModel.DataAnnotations;
-using System.Runtime.Serialization;
-using System.Text.Json.Serialization;
-using MongoDB.Bson;
-using MongoDB.Bson.Serialization.Attributes;
-using Streetwriters.Common.Enums;
-using Streetwriters.Common.Interfaces;
-
-namespace Streetwriters.Common.Models
-{
- public class Subscription : ISubscription
- {
- public Subscription()
- {
- Id = ObjectId.GenerateNewId().ToString();
- }
-
- [BsonId]
- [BsonRepresentation(BsonType.ObjectId)]
- [JsonPropertyName("id")]
- public string Id { get; set; }
-
- [JsonPropertyName("userId")]
- public string UserId { get; set; }
-
- [JsonIgnore]
- public string OrderId { get; set; }
- [JsonIgnore]
- public string SubscriptionId { get; set; }
-
- [BsonRepresentation(BsonType.Int32)]
- [JsonPropertyName("appId")]
- public ApplicationType AppId { get; set; }
-
- [JsonPropertyName("start")]
- public long StartDate { get; set; }
-
- [JsonPropertyName("expiry")]
- public long ExpiryDate { get; set; }
-
- [BsonRepresentation(BsonType.Int32)]
- [JsonPropertyName("provider")]
- public SubscriptionProvider Provider { get; set; }
-
- [BsonRepresentation(BsonType.Int32)]
- [JsonPropertyName("type")]
- public SubscriptionType Type { get; set; }
-
- [JsonPropertyName("cancelURL")]
- public string CancelURL { get; set; }
-
- [JsonPropertyName("updateURL")]
- public string UpdateURL { get; set; }
-
- [JsonPropertyName("productId")]
- public string ProductId { get; set; }
-
- [JsonIgnore]
- public int TrialExtensionCount { get; set; }
- }
-}
+/*
+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;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using Streetwriters.Common.Enums;
+using Streetwriters.Common.Interfaces;
+
+namespace Streetwriters.Common.Models
+{
+ public class Subscription : ISubscription
+ {
+ public Subscription()
+ {
+ Id = ObjectId.GenerateNewId().ToString();
+ }
+
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("userId")]
+ public string UserId { get; set; }
+
+ [JsonIgnore]
+ public string OrderId { get; set; }
+ [JsonIgnore]
+ public string SubscriptionId { get; set; }
+
+ [BsonRepresentation(BsonType.Int32)]
+ [JsonPropertyName("appId")]
+ public ApplicationType AppId { get; set; }
+
+ [JsonPropertyName("start")]
+ public long StartDate { get; set; }
+
+ [JsonPropertyName("expiry")]
+ public long ExpiryDate { get; set; }
+
+ [BsonRepresentation(BsonType.Int32)]
+ [JsonPropertyName("provider")]
+ public SubscriptionProvider Provider { get; set; }
+
+ [BsonRepresentation(BsonType.Int32)]
+ [JsonPropertyName("type")]
+ [Obsolete("Use SubscriptionPlan and SubscriptionStatus instead.")]
+ public SubscriptionType Type { get; set; }
+
+ [JsonPropertyName("cancelURL")]
+ public string CancelURL { get; set; }
+
+ [JsonPropertyName("updateURL")]
+ public string UpdateURL { get; set; }
+
+ [JsonPropertyName("googlePurchaseToken")]
+ public string? GooglePurchaseToken { get; set; }
+
+ [JsonPropertyName("productId")]
+ public string ProductId { get; set; }
+
+ [JsonIgnore]
+ public int TrialExtensionCount { get; set; }
+
+ [JsonPropertyName("trialExpiry")]
+ public long TrialExpiryDate { get; set; }
+
+ [JsonPropertyName("trialsAvailed")]
+ public SubscriptionPlan[] TrialsAvailed { get; set; }
+
+ [JsonPropertyName("updatedAt")]
+ public long UpdatedAt { get; set; }
+
+ [BsonRepresentation(BsonType.Int32)]
+ [JsonPropertyName("plan")]
+ public SubscriptionPlan Plan { get; set; }
+
+ [BsonRepresentation(BsonType.Int32)]
+ [JsonPropertyName("status")]
+ public SubscriptionStatus Status { get; set; }
+ }
+}
diff --git a/Streetwriters.Identity/Controllers/SignupController.cs b/Streetwriters.Identity/Controllers/SignupController.cs
index 2dedaf9..2fa5643 100644
--- a/Streetwriters.Identity/Controllers/SignupController.cs
+++ b/Streetwriters.Identity/Controllers/SignupController.cs
@@ -109,7 +109,7 @@ namespace Streetwriters.Identity.Controllers
await UserManager.AddToRoleAsync(user, client.Id);
if (Constants.IS_SELF_HOSTED)
{
- await UserManager.AddClaimAsync(user, UserService.SubscriptionTypeToClaim(client.Id, Common.Enums.SubscriptionType.PREMIUM));
+ await UserManager.AddClaimAsync(user, UserService.SubscriptionPlanToClaim(client.Id, SubscriptionPlan.BELIEVER));
}
else
{
diff --git a/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs b/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs
index 4bd4e9e..53feb52 100644
--- a/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs
+++ b/Streetwriters.Identity/MessageHandlers/CreateSubscription.cs
@@ -1,49 +1,50 @@
-/*
-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.Threading.Tasks;
-using Streetwriters.Common.Messages;
-using Streetwriters.Common.Models;
-using Streetwriters.Common;
-using Microsoft.AspNetCore.Identity;
-using System.Security.Claims;
-using System.Linq;
-using Streetwriters.Identity.Services;
-
-namespace Streetwriters.Identity.MessageHandlers
-{
- public class CreateSubscription
- {
- public static async Task Process(CreateSubscriptionMessage message, UserManager userManager)
- {
- var user = await userManager.FindByIdAsync(message.UserId);
- var client = Clients.FindClientByAppId(message.AppId);
- if (client == null || user == null) return;
-
- IdentityUserClaim statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == UserService.GetClaimKey(client.Id));
- Claim subscriptionClaim = UserService.SubscriptionTypeToClaim(client.Id, message.Type);
- if (statusClaim?.ClaimValue == subscriptionClaim.Value) return;
- if (statusClaim != null)
- await userManager.ReplaceClaimAsync(user, statusClaim.ToClaim(), subscriptionClaim);
- else
- await userManager.AddClaimAsync(user, subscriptionClaim);
- }
-
- }
+/*
+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.Threading.Tasks;
+using Streetwriters.Common.Messages;
+using Streetwriters.Common.Models;
+using Streetwriters.Common;
+using Microsoft.AspNetCore.Identity;
+using System.Security.Claims;
+using System.Linq;
+using Streetwriters.Identity.Services;
+
+namespace Streetwriters.Identity.MessageHandlers
+{
+ public class CreateSubscription
+ {
+ public static async Task Process(CreateSubscriptionMessage message, UserManager userManager)
+ {
+ var user = await userManager.FindByIdAsync(message.UserId);
+ var client = Clients.FindClientByAppId(message.AppId);
+ if (client == null || user == null) return;
+
+ IdentityUserClaim statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == UserService.GetClaimKey(client.Id));
+ Claim subscriptionClaim = UserService.SubscriptionTypeToClaim(client.Id, message.Type);
+ if (statusClaim?.ClaimValue == subscriptionClaim.Value) return;
+ if (statusClaim != null)
+ await userManager.ReplaceClaimAsync(user, statusClaim.ToClaim(), subscriptionClaim);
+ // we no longer accept legacy subscriptions.
+ // else
+ // await userManager.AddClaimAsync(user, subscriptionClaim);
+ }
+
+ }
}
\ No newline at end of file
diff --git a/Streetwriters.Identity/MessageHandlers/CreateSubscriptionV2.cs b/Streetwriters.Identity/MessageHandlers/CreateSubscriptionV2.cs
new file mode 100644
index 0000000..ac845eb
--- /dev/null
+++ b/Streetwriters.Identity/MessageHandlers/CreateSubscriptionV2.cs
@@ -0,0 +1,49 @@
+/*
+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.Threading.Tasks;
+using Streetwriters.Common.Messages;
+using Streetwriters.Common.Models;
+using Streetwriters.Common;
+using Microsoft.AspNetCore.Identity;
+using System.Security.Claims;
+using System.Linq;
+using Streetwriters.Identity.Services;
+
+namespace Streetwriters.Identity.MessageHandlers
+{
+ public class CreateSubscriptionV2
+ {
+ public static async Task Process(CreateSubscriptionMessageV2 message, UserManager userManager)
+ {
+ var user = await userManager.FindByIdAsync(message.UserId);
+ var client = Clients.FindClientByAppId(message.AppId);
+ if (client == null || user == null) return;
+
+ IdentityUserClaim statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == UserService.GetClaimKey(client.Id));
+ Claim subscriptionClaim = UserService.SubscriptionPlanToClaim(client.Id, message.Plan);
+ if (statusClaim?.ClaimValue == subscriptionClaim.Value) return;
+ if (statusClaim != null)
+ await userManager.ReplaceClaimAsync(user, statusClaim.ToClaim(), subscriptionClaim);
+ else
+ await userManager.AddClaimAsync(user, subscriptionClaim);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Services/MFAService.cs b/Streetwriters.Identity/Services/MFAService.cs
index 11603d0..834f8b8 100644
--- a/Streetwriters.Identity/Services/MFAService.cs
+++ b/Streetwriters.Identity/Services/MFAService.cs
@@ -169,10 +169,12 @@ namespace Streetwriters.Identity.Services
if ((method != MFAMethods.Email && method != MFAMethods.SMS) || !IsValidMFAMethod(method))
throw new Exception("Invalid method.");
+ var userPlan = UserService.GetUserSubscriptionPlan(client.Id, user);
if (isSetup &&
method == MFAMethods.SMS &&
- !UserService.IsUserPremium(client.Id, user))
- throw new Exception("Due to the high costs of SMS, currently 2FA via SMS is only available for Pro users.");
+ !UserService.IsUserPremium(client.Id, user) &&
+ userPlan != SubscriptionPlan.BELIEVER && userPlan != SubscriptionPlan.PRO)
+ throw new Exception("Due to the high costs of SMS, 2FA via SMS is only available on Pro & Believer plans.");
// if (!user.EmailConfirmed) throw new Exception("Please confirm your email before activating 2FA by email.");
await GetAuthenticatorDetailsAsync(user, client);
diff --git a/Streetwriters.Identity/Services/UserService.cs b/Streetwriters.Identity/Services/UserService.cs
index e900479..056c941 100644
--- a/Streetwriters.Identity/Services/UserService.cs
+++ b/Streetwriters.Identity/Services/UserService.cs
@@ -1,89 +1,127 @@
-/*
-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.Linq;
-using System.Security.Claims;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Identity;
-using Streetwriters.Common.Enums;
-using Streetwriters.Common.Models;
-
-namespace Streetwriters.Identity.Services
-{
- public class UserService
- {
- public static SubscriptionType GetUserSubscriptionStatus(string clientId, User user)
- {
- var claimKey = GetClaimKey(clientId);
- var status = user.Claims.FirstOrDefault((c) => c.ClaimType == claimKey).ClaimValue;
- switch (status)
- {
- case "basic":
- return SubscriptionType.BASIC;
- case "trial":
- return SubscriptionType.TRIAL;
- case "premium":
- return SubscriptionType.PREMIUM;
- case "premium_canceled":
- return SubscriptionType.PREMIUM_CANCELED;
- case "premium_expired":
- return SubscriptionType.PREMIUM_EXPIRED;
- default:
- return SubscriptionType.BASIC;
- }
- }
-
-
-
- public static bool IsUserPremium(string clientId, User user)
- {
- var status = GetUserSubscriptionStatus(clientId, user);
- return status == SubscriptionType.PREMIUM || status == SubscriptionType.PREMIUM_CANCELED;
- }
-
- public static Claim SubscriptionTypeToClaim(string clientId, SubscriptionType type)
- {
- var claimKey = GetClaimKey(clientId);
- switch (type)
- {
- case SubscriptionType.BASIC:
- return new Claim(claimKey, "basic");
- case SubscriptionType.TRIAL:
- return new Claim(claimKey, "trial");
- case SubscriptionType.PREMIUM:
- return new Claim(claimKey, "premium");
- case SubscriptionType.PREMIUM_CANCELED:
- return new Claim(claimKey, "premium_canceled");
- case SubscriptionType.PREMIUM_EXPIRED:
- return new Claim(claimKey, "premium_expired");
- }
- return null;
- }
-
- public static string GetClaimKey(string clientId)
- {
- return $"{clientId}:status";
- }
-
- public static async Task IsUserValidAsync(UserManager userManager, User user, string clientId)
- {
- return user != null && await userManager.IsInRoleAsync(user, clientId);
- }
- }
+/*
+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.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Streetwriters.Common.Enums;
+using Streetwriters.Common.Models;
+
+namespace Streetwriters.Identity.Services
+{
+ public class UserService
+ {
+ public static SubscriptionType GetUserSubscriptionStatus(string clientId, User user)
+ {
+ var claimKey = GetClaimKey(clientId);
+ var status = user.Claims.FirstOrDefault((c) => c.ClaimType == claimKey).ClaimValue;
+ switch (status)
+ {
+ case "basic":
+ return SubscriptionType.BASIC;
+ case "trial":
+ return SubscriptionType.TRIAL;
+ case "premium":
+ return SubscriptionType.PREMIUM;
+ case "premium_canceled":
+ return SubscriptionType.PREMIUM_CANCELED;
+ case "premium_expired":
+ return SubscriptionType.PREMIUM_EXPIRED;
+ default:
+ return SubscriptionType.BASIC;
+ }
+ }
+
+ public static SubscriptionPlan GetUserSubscriptionPlan(string clientId, User user)
+ {
+ var claimKey = GetClaimKey(clientId);
+ var status = user.Claims.FirstOrDefault((c) => c.ClaimType == claimKey).ClaimValue;
+ switch (status)
+ {
+ case "free":
+ return SubscriptionPlan.FREE;
+ case "believer":
+ return SubscriptionPlan.BELIEVER;
+ case "education":
+ return SubscriptionPlan.EDUCATION;
+ case "essential":
+ return SubscriptionPlan.ESSENTIAL;
+ case "pro":
+ return SubscriptionPlan.PRO;
+ default:
+ return SubscriptionPlan.FREE;
+ }
+ }
+
+ public static bool IsUserPremium(string clientId, User user)
+ {
+ var status = GetUserSubscriptionStatus(clientId, user);
+ return status == SubscriptionType.PREMIUM || status == SubscriptionType.PREMIUM_CANCELED;
+ }
+
+ public static Claim SubscriptionTypeToClaim(string clientId, SubscriptionType type)
+ {
+ var claimKey = GetClaimKey(clientId);
+ switch (type)
+ {
+ case SubscriptionType.BASIC:
+ return new Claim(claimKey, "basic");
+ case SubscriptionType.TRIAL:
+ return new Claim(claimKey, "trial");
+ case SubscriptionType.PREMIUM:
+ return new Claim(claimKey, "premium");
+ case SubscriptionType.PREMIUM_CANCELED:
+ return new Claim(claimKey, "premium_canceled");
+ case SubscriptionType.PREMIUM_EXPIRED:
+ return new Claim(claimKey, "premium_expired");
+ }
+ return null;
+ }
+
+ public static Claim SubscriptionPlanToClaim(string clientId, SubscriptionPlan plan)
+ {
+ var claimKey = GetClaimKey(clientId);
+ switch (plan)
+ {
+ case SubscriptionPlan.FREE:
+ return new Claim(claimKey, "free");
+ case SubscriptionPlan.BELIEVER:
+ return new Claim(claimKey, "believer");
+ case SubscriptionPlan.EDUCATION:
+ return new Claim(claimKey, "education");
+ case SubscriptionPlan.ESSENTIAL:
+ return new Claim(claimKey, "essential");
+ case SubscriptionPlan.PRO:
+ return new Claim(claimKey, "pro");
+ }
+ return null;
+ }
+
+ public static string GetClaimKey(string clientId)
+ {
+ return $"{clientId}:status";
+ }
+
+ public static async Task IsUserValidAsync(UserManager userManager, User user, string clientId)
+ {
+ return user != null && await userManager.IsInRoleAsync(user, clientId);
+ }
+ }
}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Startup.cs b/Streetwriters.Identity/Startup.cs
index 531324c..a45fde3 100644
--- a/Streetwriters.Identity/Startup.cs
+++ b/Streetwriters.Identity/Startup.cs
@@ -244,6 +244,17 @@ namespace Streetwriters.Identity
await MessageHandlers.CreateSubscription.Process(message, userManager);
}
});
+
+ realm.Subscribe(SubscriptionServerTopics.CreateSubscriptionV2Topic, async (CreateSubscriptionMessageV2 message) =>
+ {
+ using (var serviceScope = app.ApplicationServices.CreateScope())
+ {
+ var services = serviceScope.ServiceProvider;
+ var userManager = services.GetRequiredService>();
+ await MessageHandlers.CreateSubscriptionV2.Process(message, userManager);
+ }
+ });
+
realm.Subscribe(SubscriptionServerTopics.DeleteSubscriptionTopic, async (DeleteSubscriptionMessage message) =>
{
using (var serviceScope = app.ApplicationServices.CreateScope())