global: make ready for self hosting

This exposes a few more env vars for configuration &
bypasses the Subscription backend
so each user is Pro by default. This is required because there won't be
any mechanism for the user to upgrade. We'll also have to disable a few
things on the client side to avoid confusion.
This commit is contained in:
Abdullah Atta
2022-12-30 11:23:02 +05:00
parent 3f89ebc214
commit c0fab8a735
21 changed files with 207 additions and 320 deletions

View File

@@ -17,10 +17,15 @@ 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/>.
*/
#if !DEBUG
using System.Net;
#endif
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Streetwriters.Common;
using System.Linq;
namespace Notesnook.API
{
@@ -28,8 +33,6 @@ namespace Notesnook.API
{
public static async Task Main(string[] args)
{
DotNetEnv.Env.TraversePath().Load();
IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
@@ -43,16 +46,7 @@ namespace Notesnook.API
.UseKestrel((options) =>
{
options.Limits.MaxRequestBodySize = long.MaxValue;
#if DEBUG
options.ListenAnyIP(int.Parse(Servers.NotesnookAPI.Port));
#else
options.ListenAnyIP(443, listenerOptions =>
{
listenerOptions.UseHttps(Servers.OriginSSLCertificate);
});
options.ListenAnyIP(80);
options.Listen(IPAddress.Parse(Servers.NotesnookAPI.Hostname), int.Parse(Servers.NotesnookAPI.Port));
#endif
options.ListenAnyIP(Servers.NotesnookAPI.Port);
});
});
}

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -41,7 +42,6 @@ namespace Notesnook.API.Services
public S3Service()
{
var config = new AmazonS3Config
{
#if DEBUG

View File

@@ -29,6 +29,7 @@ using Notesnook.API.Models.Responses;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Extensions;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
using Streetwriters.Data.Interfaces;
@@ -73,14 +74,17 @@ namespace Notesnook.API.Services
Salt = GetSalt()
});
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.CreateSubscriptionTopic, new CreateSubscriptionMessage
if (!Constants.IS_SELF_HOSTED)
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.BASIC,
UserId = response.UserId,
StartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
});
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.CreateSubscriptionTopic, new CreateSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.BASIC,
UserId = response.UserId,
StartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
});
}
await Slogger<UserService>.Info(nameof(CreateUserAsync), "New user created.", JsonSerializer.Serialize(response));
}
@@ -90,31 +94,49 @@ namespace Notesnook.API.Services
UserResponse response = await httpClient.ForwardAsync<UserResponse>(this.HttpContextAccessor, $"{Servers.IdentityServer.ToString()}/account", HttpMethod.Get);
if (!response.Success) return response;
SubscriptionResponse subscriptionResponse = await httpClient.ForwardAsync<SubscriptionResponse>(this.HttpContextAccessor, $"{Servers.SubscriptionServer}/subscriptions", HttpMethod.Get);
if (repair && subscriptionResponse.StatusCode == 404)
ISubscription subscription = null;
if (Constants.IS_SELF_HOSTED)
{
await Slogger<UserService>.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
subscription = new Subscription
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.TRIAL,
UserId = response.UserId,
StartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
ExpiryTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
});
// just a dummy object
subscriptionResponse.Subscription = new Subscription
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.TRIAL,
Type = SubscriptionType.PREMIUM,
UserId = response.UserId,
StartDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
ExpiryDate = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
// this date doesn't matter as the subscription is static.
ExpiryDate = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeMilliseconds()
};
}
else
{
SubscriptionResponse subscriptionResponse = await httpClient.ForwardAsync<SubscriptionResponse>(this.HttpContextAccessor, $"{Servers.SubscriptionServer}/subscriptions", HttpMethod.Get);
if (repair && subscriptionResponse.StatusCode == 404)
{
await Slogger<UserService>.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
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.TRIAL,
UserId = response.UserId,
StartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
ExpiryTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
});
// just a dummy object
subscriptionResponse.Subscription = new Subscription
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.TRIAL,
UserId = response.UserId,
StartDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
ExpiryDate = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
};
}
subscription = subscriptionResponse.Subscription;
}
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == response.UserId);
if (repair && userSettings == null)
@@ -130,7 +152,7 @@ namespace Notesnook.API.Services
}
response.AttachmentsKey = userSettings.AttachmentsKey;
response.Salt = userSettings.Salt;
response.Subscription = subscriptionResponse.Subscription;
response.Subscription = subscription;
return response;
}
@@ -153,11 +175,14 @@ namespace Notesnook.API.Services
Repositories.Attachments.DeleteByUserId(userId);
Repositories.UsersSettings.Delete((u) => u.UserId == userId);
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
if (!Constants.IS_SELF_HOSTED)
{
AppId = ApplicationType.NOTESNOOK,
UserId = userId
});
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
UserId = userId
});
}
await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
{

View File

@@ -73,7 +73,11 @@ namespace Notesnook.API
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var dbSettings = Configuration.GetSection("MongoDbSettings").Get<DbSettings>();
var dbSettings = new DbSettings
{
ConnectionString = Constants.MONGODB_CONNECTION_STRING,
DatabaseName = Constants.MONGODB_DATABASE_NAME
};
services.AddSingleton<IDbSettings>(dbSettings);
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

View File

@@ -22,13 +22,14 @@ using System.Collections.Generic;
using System.Linq;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
namespace Streetwriters.Common
{
public class Clients
{
private static IClient Notesnook = new Client
private static Client Notesnook = new Client
{
Id = "notesnook",
Name = "Notesnook",
@@ -36,20 +37,34 @@ namespace Streetwriters.Common
SenderName = Constants.NOTESNOOK_SENDER_NAME,
Type = ApplicationType.NOTESNOOK,
AppId = ApplicationType.NOTESNOOK,
AccountRecoveryRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/verified",
EmailConfirmedRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/recovery",
OnEmailConfirmed = async (userId) =>
{
await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
{
UserId = userId,
Message = new Message
{
Type = "emailConfirmed",
Data = null
}
});
}
};
public static Dictionary<string, IClient> ClientsMap = new Dictionary<string, IClient>
public static Dictionary<string, Client> ClientsMap = new Dictionary<string, Client>
{
{ "notesnook", Notesnook }
};
public static IClient FindClientById(string id)
public static Client FindClientById(string id)
{
if (!IsValidClient(id)) return null;
return ClientsMap[id];
}
public static IClient FindClientByAppId(ApplicationType appId)
public static Client FindClientByAppId(ApplicationType appId)
{
switch (appId)
{

View File

@@ -23,25 +23,47 @@ namespace Streetwriters.Common
{
public class Constants
{
public static bool IS_SELF_HOSTED => Environment.GetEnvironmentVariable("SELF_HOSTED") == "1";
// S3 related
public static string S3_ACCESS_KEY = Environment.GetEnvironmentVariable("S3_ACCESS_KEY");
public static string S3_ACCESS_KEY_ID = Environment.GetEnvironmentVariable("S3_ACCESS_KEY_ID");
public static string S3_SERVICE_URL = Environment.GetEnvironmentVariable("S3_HOST");
public static string S3_REGION = Environment.GetEnvironmentVariable("S3_HOST");
public static string S3_ACCESS_KEY => Environment.GetEnvironmentVariable("S3_ACCESS_KEY");
public static string S3_ACCESS_KEY_ID => Environment.GetEnvironmentVariable("S3_ACCESS_KEY_ID");
public static string S3_SERVICE_URL => Environment.GetEnvironmentVariable("S3_SERVICE_URL");
public static string S3_REGION => Environment.GetEnvironmentVariable("S3_REGION");
// SMTP settings
public static string SMTP_USERNAME = Environment.GetEnvironmentVariable("SMTP_USERNAME");
public static string SMTP_PASSWORD = Environment.GetEnvironmentVariable("SMTP_PASSWORD");
public static string SMTP_HOST = Environment.GetEnvironmentVariable("SMTP_HOST");
public static string SMTP_PORT = Environment.GetEnvironmentVariable("SMTP_PORT");
public static string SMTP_REPLYTO_NAME = Environment.GetEnvironmentVariable("SMTP_REPLYTO_NAME");
public static string SMTP_REPLYTO_EMAIL = Environment.GetEnvironmentVariable("SMTP_REPLYTO_EMAIL");
public static string NOTESNOOK_SENDER_EMAIL = Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_EMAIL");
public static string NOTESNOOK_SENDER_NAME = Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_NAME");
public static string SMTP_USERNAME => Environment.GetEnvironmentVariable("SMTP_USERNAME");
public static string SMTP_PASSWORD => Environment.GetEnvironmentVariable("SMTP_PASSWORD");
public static string SMTP_HOST => Environment.GetEnvironmentVariable("SMTP_HOST");
public static string SMTP_PORT => Environment.GetEnvironmentVariable("SMTP_PORT");
public static string SMTP_REPLYTO_NAME => Environment.GetEnvironmentVariable("SMTP_REPLYTO_NAME");
public static string SMTP_REPLYTO_EMAIL => Environment.GetEnvironmentVariable("SMTP_REPLYTO_EMAIL");
public static string NOTESNOOK_SENDER_EMAIL => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_EMAIL");
public static string NOTESNOOK_SENDER_NAME => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_NAME");
public static string NOTESNOOK_API_SECRET = Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET");
public static string NOTESNOOK_APP_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_APP_HOST");
public static string NOTESNOOK_API_SECRET => Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET");
// MessageBird is used for SMS sending
public static string MESSAGEBIRD_ACCESS_KEY = Environment.GetEnvironmentVariable("MESSAGEBIRD_ACCESS_KEY");
public static string MESSAGEBIRD_ACCESS_KEY => Environment.GetEnvironmentVariable("MESSAGEBIRD_ACCESS_KEY");
// internal
public static string ORIGIN_CERT_PATH => Environment.GetEnvironmentVariable("ORIGIN_CERT_PATH");
public static string ORIGIN_CERT_KEY_PATH => Environment.GetEnvironmentVariable("ORIGIN_CERT_KEY_PATH");
public static string MONGODB_CONNECTION_STRING => Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING");
public static string MONGODB_DATABASE_NAME => Environment.GetEnvironmentVariable("MONGODB_DATABASE_NAME");
// Server discovery
public static int NOTESNOOK_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_PORT"));
public static string NOTESNOOK_SERVER_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_HOST");
public static string NOTESNOOK_SERVER_DOMAIN => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_DOMAIN");
public static int IDENTITY_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("IDENTITY_SERVER_PORT"));
public static string IDENTITY_SERVER_HOST => Environment.GetEnvironmentVariable("IDENTITY_SERVER_HOST");
public static string IDENTITY_SERVER_DOMAIN => Environment.GetEnvironmentVariable("IDENTITY_SERVER_DOMAIN");
public static int SSE_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SSE_SERVER_PORT"));
public static string SSE_SERVER_HOST => Environment.GetEnvironmentVariable("SSE_SERVER_HOST");
public static string SSE_SERVER_DOMAIN => Environment.GetEnvironmentVariable("SSE_SERVER_DOMAIN");
}
}

View File

@@ -17,6 +17,8 @@ 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.Tasks;
using Streetwriters.Common.Enums;
namespace Streetwriters.Common.Interfaces
@@ -29,5 +31,8 @@ namespace Streetwriters.Common.Interfaces
ApplicationType AppId { get; set; }
string SenderEmail { get; set; }
string SenderName { get; set; }
string EmailConfirmedRedirectURL { get; }
string AccountRecoveryRedirectURL { get; }
Func<string, Task> OnEmailConfirmed { get; set; }
}
}

View File

@@ -17,9 +17,11 @@ 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.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
@@ -35,5 +37,9 @@ namespace Streetwriters.Common.Models
public ApplicationType AppId { get; set; }
public string SenderEmail { get; set; }
public string SenderName { get; set; }
public string EmailConfirmedRedirectURL { get; set; }
public string AccountRecoveryRedirectURL { get; set; }
public Func<string, Task> OnEmailConfirmed { get; set; }
}
}

View File

@@ -28,7 +28,7 @@ namespace Streetwriters.Common
{
public class Server
{
public string Port { get; set; }
public int Port { get; set; }
public bool IsSecure { get; set; }
public string Hostname { get; set; }
public string Domain { get; set; }
@@ -38,7 +38,7 @@ namespace Streetwriters.Common
var url = "";
url += IsSecure ? "https" : "http";
url += $"://{Hostname}";
url += IsSecure ? "" : $":{Port}";
url += IsSecure || Port == 80 ? "" : $":{Port}";
return url;
}
@@ -47,7 +47,7 @@ namespace Streetwriters.Common
var url = "";
url += IsSecure ? "ws" : "ws";
url += $"://{Hostname}";
url += $":{Port}";
url += Port == 80 ? "" : $":{Port}";
return url;
}
}
@@ -77,74 +77,39 @@ namespace Streetwriters.Common
public readonly static string HOST = GetLocalIPv4(NetworkInterfaceType.Ethernet);
public static Server S3Server { get; } = new()
{
Port = "4568",
Port = 4568,
Hostname = HOST,
IsSecure = false,
Domain = HOST
};
#else
private readonly static string HOST = "localhost";
public readonly static X509Certificate2 OriginSSLCertificate = X509Certificate2.CreateFromPemFile(Environment.GetEnvironmentVariable("ORIGIN_CERT_PATH"), Environment.GetEnvironmentVariable("ORIGIN_CERT_KEY_PATH"));
public readonly static X509Certificate2 OriginSSLCertificate = string.IsNullOrEmpty(Constants.ORIGIN_CERT_PATH) || string.IsNullOrEmpty(Constants.ORIGIN_CERT_KEY_PATH) ? null : X509Certificate2.CreateFromPemFile(Constants.ORIGIN_CERT_PATH, Environment.GetEnvironmentVariable(Constants.ORIGIN_CERT_KEY_PATH));
#endif
public static Server NotesnookAPI { get; } = new()
{
Domain = "api.notesnook.com",
Port = "5264",
#if DEBUG
IsSecure = false,
Hostname = HOST,
#else
IsSecure = true,
Hostname = "10.0.0.5",
#endif
Domain = Constants.NOTESNOOK_SERVER_DOMAIN,
Port = Constants.NOTESNOOK_SERVER_PORT,
Hostname = Constants.NOTESNOOK_SERVER_HOST,
};
public static Server MessengerServer { get; } = new()
{
Domain = "events.streetwriters.co",
Port = "7264",
#if DEBUG
IsSecure = false,
Hostname = HOST,
#else
IsSecure = true,
Hostname = "10.0.0.6",
#endif
Domain = Constants.SSE_SERVER_DOMAIN,
Port = Constants.SSE_SERVER_PORT,
Hostname = Constants.SSE_SERVER_HOST,
};
public static Server IdentityServer { get; } = new()
{
Domain = "auth.streetwriters.co",
IsSecure = false,
Port = "8264",
#if DEBUG
Hostname = HOST,
#else
Hostname = "10.0.0.4",
#endif
Domain = Constants.IDENTITY_SERVER_DOMAIN,
Port = Constants.IDENTITY_SERVER_PORT,
Hostname = Constants.IDENTITY_SERVER_HOST,
};
public static Server SubscriptionServer { get; } = new()
{
Domain = "subscriptions.streetwriters.co",
IsSecure = false,
Port = "9264",
#if DEBUG
Hostname = HOST,
#else
Hostname = "10.0.0.4",
#endif
};
public static Server PaymentsServer { get; } = new()
{
Domain = "payments.streetwriters.co",
IsSecure = false,
Port = "6264",
#if DEBUG
Hostname = HOST,
#else
Hostname = "10.0.0.4",
#endif
};
}
}

View File

@@ -17,7 +17,6 @@ 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.ComponentModel;
using System.Linq;
@@ -29,13 +28,10 @@ using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
using Streetwriters.Identity.Enums;
using Streetwriters.Identity.Handlers;
using Streetwriters.Identity.Interfaces;
using Streetwriters.Identity.Models;
using static IdentityServer4.IdentityServerConstants;
@@ -80,16 +76,15 @@ namespace Streetwriters.Identity.Controllers
var result = await UserManager.ConfirmEmailAsync(user, code);
if (!result.Succeeded) return BadRequest(result.Errors.ToErrors());
foreach (var handler in ClientHandlers.Handlers)
if (await UserManager.IsInRoleAsync(user, client.Id))
{
if (await UserManager.IsInRoleAsync(user, client.Id))
{
await handler.Value.OnEmailConfirmed(userId);
// if (client.WelcomeEmailTemplateId != null)
// await EmailSender.SendWelcomeEmailAsync(user.Email, client);
}
await client.OnEmailConfirmed(userId);
// if (client.WelcomeEmailTemplateId != null)
// await EmailSender.SendWelcomeEmailAsync(user.Email, client);
}
var redirectUrl = $"{ClientHandlers.GetClientHandler(client.Type)?.EmailConfirmedRedirectURL}?userId={userId}";
var redirectUrl = $"{client.EmailConfirmedRedirectURL}?userId={userId}";
return RedirectPermanent(redirectUrl);
}
// case TokenType.CHANGE_EMAIL:
@@ -111,7 +106,7 @@ namespace Streetwriters.Identity.Controllers
return BadRequest("Invalid token.");
var authorizationCode = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "PasswordResetAuthorizationCode");
var redirectUrl = $"{ClientHandlers.GetClientHandler(client.Type)?.AccountRecoveryRedirectURL}?userId={userId}&code={authorizationCode}";
var redirectUrl = $"{client.AccountRecoveryRedirectURL}?userId={userId}&code={authorizationCode}";
return RedirectPermanent(redirectUrl);
}
default:

View File

@@ -17,6 +17,7 @@ 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.Security.Claims;
@@ -30,6 +31,7 @@ using Streetwriters.Common.Models;
using Streetwriters.Identity.Enums;
using Streetwriters.Identity.Interfaces;
using Streetwriters.Identity.Models;
using Streetwriters.Identity.Services;
namespace Streetwriters.Identity.Controllers
{
@@ -97,7 +99,8 @@ namespace Streetwriters.Identity.Controllers
var user = await UserManager.FindByEmailAsync(form.Email);
await UserManager.AddToRoleAsync(user, client.Id);
// await UserManager.AddClaimAsync(user, new Claim("verified", "false"));
if (Constants.IS_SELF_HOSTED)
await UserManager.AddClaimAsync(user, UserService.SubscriptionTypeToClaim(client.Id, Common.Enums.SubscriptionType.PREMIUM));
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.TokenLink(user.Id.ToString(), code, client.Id, TokenType.CONFRIM_EMAIL, Request.Scheme);

View File

@@ -1,39 +0,0 @@
/*
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.Collections.Generic;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Identity.Interfaces;
namespace Streetwriters.Identity.Handlers
{
public class ClientHandlers
{
public static Dictionary<ApplicationType, IAppHandler> Handlers { get; set; } = new Dictionary<ApplicationType, IAppHandler>
{
{ ApplicationType.NOTESNOOK, new NotesnookHandler() }
};
public static IAppHandler GetClientHandler(ApplicationType type)
{
return Handlers[type];
}
}
}

View File

@@ -1,58 +0,0 @@
/*
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.Tasks;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Messages;
using Streetwriters.Identity.Interfaces;
namespace Streetwriters.Identity.Handlers
{
public class NotesnookHandler : IAppHandler
{
public string Host { get; }
public string EmailConfirmedRedirectURL { get; }
public string AccountRecoveryRedirectURL { get; }
public NotesnookHandler()
{
#if DEBUG
Host = "http://localhost:3000";
#else
Host = "https://app.notesnook.com";
#endif
EmailConfirmedRedirectURL = $"{this.Host}/account/verified";
AccountRecoveryRedirectURL = $"{this.Host}/account/recovery";
}
public async Task OnEmailConfirmed(string userId)
{
await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
{
UserId = userId,
Message = new Message
{
Type = "emailConfirmed",
Data = null
}
});
}
}
}

View File

@@ -1,31 +0,0 @@
/*
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.Threading.Tasks;
namespace Streetwriters.Identity.Interfaces
{
public interface IAppHandler
{
string Host { get; }
string EmailConfirmedRedirectURL { get; }
string AccountRecoveryRedirectURL { get; }
Task OnEmailConfirmed(string userId);
}
}

View File

@@ -21,16 +21,9 @@ using System.Threading.Tasks;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
using Streetwriters.Common;
using System.Text.Json;
using Streetwriters.Data.Repositories;
using Streetwriters.Data.Interfaces;
using Streetwriters.Common.Interfaces;
using System;
using Microsoft.AspNetCore.Identity;
using Streetwriters.Common.Enums;
using System.Security.Claims;
using System.Linq;
using Streetwriters.Identity.Interfaces;
using Streetwriters.Identity.Services;
namespace Streetwriters.Identity.MessageHandlers

View File

@@ -17,21 +17,12 @@ 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.Tasks;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Models;
using Streetwriters.Common;
using System.Text.Json;
using System.IO;
using Streetwriters.Data.Repositories;
using Streetwriters.Data.Interfaces;
using Microsoft.AspNetCore.Identity;
using System.Linq;
using IdentityServer4.Stores;
using Streetwriters.Identity.Interfaces;
namespace Streetwriters.Identity.MessageHandlers
{

View File

@@ -33,8 +33,6 @@ namespace Streetwriters.Identity
{
public static async Task Main(string[] args)
{
DotNetEnv.Env.TraversePath().Load();
IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
@@ -48,7 +46,13 @@ namespace Streetwriters.Identity
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>().UseUrls(Servers.IdentityServer.ToString());
webBuilder
.UseStartup<Startup>()
.UseKestrel((options) =>
{
options.Limits.MaxRequestBodySize = long.MaxValue;
options.ListenAnyIP(Servers.IdentityServer.Port);
});
});
}
}

View File

@@ -50,9 +50,11 @@ namespace Streetwriters.Identity.Services
public class EmailSender : IEmailSender, IAsyncDisposable
{
NNGnuPGContext NNGnuPGContext { get; set; }
SmtpClient mailClient;
public EmailSender(IConfiguration configuration)
{
NNGnuPGContext = new NNGnuPGContext(configuration.GetSection("PgpKeySettings"));
mailClient = new SmtpClient();
}
EmailTemplate Email2FATemplate = new EmailTemplate
@@ -90,12 +92,6 @@ namespace Streetwriters.Identity.Services
Subject = "Failed login attempt on your {{app_name}} account",
};
SmtpClient mailClient;
public EmailSender()
{
mailClient = new SmtpClient();
}
public async Task Send2FACodeEmailAsync(string email, string code, IClient client)
{
var template = new EmailTemplate
@@ -179,34 +175,40 @@ namespace Streetwriters.Identity.Services
private async Task SendEmailAsync(string email, IEmailTemplate template, IClient client)
{
if (!mailClient.IsConnected)
{
if (int.TryParse(Constants.SMTP_PORT, out int port))
{
await mailClient.ConnectAsync(Constants.SMTP_HOST, port, MailKit.Security.SecureSocketOptions.StartTls);
}
else
{
throw new InvalidDataException("SMTP_PORT is not a valid integer value.");
}
}
if (!mailClient.IsAuthenticated)
await mailClient.AuthenticateAsync(Constants.SMTP_USERNAME, Constants.SMTP_PASSWORD);
var message = new MimeMessage();
var sender = new MailboxAddress(client.SenderName, client.SenderEmail);
message.From.Add(sender);
message.To.Add(new MailboxAddress("", email));
message.Subject = await Template.Parse(template.Subject).RenderAsync(template.Data);
if (!string.IsNullOrEmpty(Constants.SMTP_REPLYTO_NAME) && !string.IsNullOrEmpty(Constants.SMTP_REPLYTO_EMAIL))
message.ReplyTo.Add(new MailboxAddress(Constants.SMTP_REPLYTO_NAME, Constants.SMTP_REPLYTO_EMAIL));
message.Body = await GetEmailBodyAsync(template, client, sender);
await mailClient.SendAsync(message);
}
private async Task<MimeEntity> GetEmailBodyAsync(IEmailTemplate template, IClient client, MailboxAddress sender)
{
var builder = new BodyBuilder();
try
{
if (!mailClient.IsConnected)
{
if (int.TryParse(Constants.SMTP_PORT, out int port))
{
await mailClient.ConnectAsync(Constants.SMTP_HOST, port, MailKit.Security.SecureSocketOptions.StartTls);
}
else
{
throw new InvalidDataException("SMTP_PORT is not a valid integer value.");
}
}
if (!mailClient.IsAuthenticated)
await mailClient.AuthenticateAsync(Constants.SMTP_USERNAME, Constants.SMTP_PASSWORD);
var message = new MimeMessage();
var sender = new MailboxAddress(client.SenderName, client.SenderEmail);
message.From.Add(sender);
message.To.Add(new MailboxAddress("", email));
message.Subject = await Template.Parse(template.Subject).RenderAsync(template.Data);
if (!string.IsNullOrEmpty(Constants.SMTP_REPLYTO_NAME) && !string.IsNullOrEmpty(Constants.SMTP_REPLYTO_EMAIL))
message.ReplyTo.Add(new MailboxAddress(Constants.SMTP_REPLYTO_NAME, Constants.SMTP_REPLYTO_EMAIL));
var builder = new BodyBuilder();
builder.TextBody = await Template.Parse(template.Text).RenderAsync(template.Data);
builder.HtmlBody = await Template.Parse(template.Html).RenderAsync(template.Data);
@@ -222,17 +224,16 @@ namespace Streetwriters.Identity.Services
outputStream.Seek(0, SeekOrigin.Begin);
builder.Attachments.Add($"{client.Id}_pub.asc", Encoding.ASCII.GetBytes(Encoding.ASCII.GetString(outputStream.ToArray())));
}
message.Body = MultipartSigned.Create(NNGnuPGContext, sender, DigestAlgorithm.Sha256, builder.ToMessageBody());
return MultipartSigned.Create(NNGnuPGContext, sender, DigestAlgorithm.Sha256, builder.ToMessageBody());
}
else
{
message.Body = builder.ToMessageBody();
return builder.ToMessageBody();
}
await mailClient.SendAsync(message);
}
catch (Exception ex)
catch (PrivateKeyNotFoundException)
{
Console.Error.WriteLine(ex.Message);
return builder.ToMessageBody();
}
}

View File

@@ -32,7 +32,8 @@ namespace Streetwriters.Identity.Services
private Client client;
public SMSSender()
{
client = Client.CreateDefault(Constants.MESSAGEBIRD_ACCESS_KEY);
if (!string.IsNullOrEmpty(Constants.MESSAGEBIRD_ACCESS_KEY))
client = Client.CreateDefault(Constants.MESSAGEBIRD_ACCESS_KEY);
}
public string SendOTP(string number, IClient app)

View File

@@ -59,7 +59,7 @@ namespace Streetwriters.Identity
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["MongoDbSettings:ConnectionString"];
var connectionString = Constants.MONGODB_CONNECTION_STRING;
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<ISMSSender, SMSSender>();

View File

@@ -45,16 +45,7 @@ namespace Streetwriters.Messenger
.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
options.ListenAnyIP(Servers.MessengerServer.Port);
});
});
}