v1.3.0 Restructure Saxonite workspace and upgrade to .NET 10 (#1)
Move Saxonite’s projects into a dedicated `Workspace` folder and align the solution with .NET 10 and the current Common workspace. Update encryption integration, test project structure and coverage, and repository guidance and upgrade notes. - Relocate application and library projects while retaining Git rename history. - Split tests into Admin, Online, and Shared projects; add representative behavioral and serialization tests. - Update Common integration, application version, and repository documentation. Application version: 1.3.0 Reviewed-on: #1 Co-authored-by: andy <andy@sarand.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SaxContactMethodHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Utilities.
|
||||
//-------------------------------------------------------------------
|
||||
public static Sax.ContactMethodType GetContactMethodType(Sax.ContactMethod contactMethod)
|
||||
{
|
||||
switch (contactMethod)
|
||||
{
|
||||
case Sax.ContactMethod.PhoneMain:
|
||||
case Sax.ContactMethod.PhoneMobile:
|
||||
case Sax.ContactMethod.PhoneHome:
|
||||
case Sax.ContactMethod.PhoneOffice:
|
||||
return Sax.ContactMethodType.Phone;
|
||||
case Sax.ContactMethod.Email:
|
||||
return Sax.ContactMethodType.Email;
|
||||
case Sax.ContactMethod.Website:
|
||||
case Sax.ContactMethod.Wikipedia:
|
||||
case Sax.ContactMethod.Location:
|
||||
case Sax.ContactMethod.LinkedIn:
|
||||
case Sax.ContactMethod.Twitter:
|
||||
case Sax.ContactMethod.Facebook:
|
||||
case Sax.ContactMethod.Instagram:
|
||||
return Sax.ContactMethodType.WebUrl;
|
||||
}
|
||||
return Sax.ContactMethodType.Unknown;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Validate Contact Method value.</summary>
|
||||
public static bool IsValidContactMethodValue(Sax.ContactMethod method, String value)
|
||||
{
|
||||
Sax.ContactMethodType contactMethodType = GetContactMethodType(method);
|
||||
|
||||
// Method specific value validation.
|
||||
if (contactMethodType == Sax.ContactMethodType.Phone)
|
||||
{
|
||||
// Phone number.
|
||||
return SacsDataValidationHelper.IsValidPhone(value);
|
||||
}
|
||||
else if (contactMethodType == Sax.ContactMethodType.Email)
|
||||
{
|
||||
// Email address.
|
||||
return SacsDataValidationHelper.IsValidEmailAddress(value);
|
||||
}
|
||||
else if (contactMethodType == Sax.ContactMethodType.WebUrl)
|
||||
{
|
||||
if (method == Sax.ContactMethod.Wikipedia)
|
||||
{
|
||||
// Wikipedia.
|
||||
return SacsDataValidationHelper.IsValidWikipediaUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.Location)
|
||||
{
|
||||
// Location URL.
|
||||
return SacsDataValidationHelper.IsValidLocationUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.LinkedIn)
|
||||
{
|
||||
// LinkedIn.
|
||||
return SacsDataValidationHelper.IsValidLinkedInUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.Twitter)
|
||||
{
|
||||
// X (was Twitter).
|
||||
return SacsDataValidationHelper.IsValidTwitterUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.Facebook)
|
||||
{
|
||||
// Facebook.
|
||||
return SacsDataValidationHelper.IsValidFacebookUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.Instagram)
|
||||
{
|
||||
// Instagram.
|
||||
return SacsDataValidationHelper.IsValidInstagramUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.YouTube)
|
||||
{
|
||||
// YouTube.
|
||||
return SacsDataValidationHelper.IsValidYouTubeUrl(value);
|
||||
}
|
||||
else if (method == Sax.ContactMethod.Twitch)
|
||||
{
|
||||
// Twitch.
|
||||
return SacsDataValidationHelper.IsValidTwitchUrl(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic URL.
|
||||
return SacsDataValidationHelper.IsValidUrl(value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Saxonite.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxPasswordHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Configuration parameters.
|
||||
public class PasswordParams
|
||||
{
|
||||
public int MinLength = 8;
|
||||
public int MinLengthNoComplexity = 12;
|
||||
}
|
||||
public static PasswordParams Params { get; } = new PasswordParams();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Validate password against parameters.</summary>
|
||||
public static bool IsValidPassword(String password)
|
||||
{
|
||||
return GetPasswordStrength(password) >= 4;
|
||||
}
|
||||
/// <summary>
|
||||
/// Assess the password strength - 4+ is valid.
|
||||
/// <para>0 - Blank</para>
|
||||
/// <para>1 - < min length - 2 [red]</para>
|
||||
/// <para>1 - < min length and only 1 character type [red]</para>
|
||||
/// <para>2 - < min length and only 2 character types [red]</para>
|
||||
/// <para>2 - < min length without complexity - 2 and only 1 character type [red]</para>
|
||||
/// <para>3 - < min length, min length with 3 character types [amber]</para>
|
||||
/// <para>3 - Min length without complexity - 2 [amber]</para>
|
||||
/// <para>3 - Other invalid passwords [amber]</para>
|
||||
/// <para>4 - Min length with 4 character types [green]</para>
|
||||
/// <para>4 - Min length without complexity [green]</para>
|
||||
/// <para>5 - Min lenth + 2 with 4 character types [green]</para>
|
||||
/// <para>5 - Min length without complexity with 3 character types [green]</para>
|
||||
/// </summary>
|
||||
public static int GetPasswordStrength(String password)
|
||||
{
|
||||
String fn = "PasswordHelper.GetPasswordStrength";
|
||||
|
||||
// 0 - Blank.
|
||||
if (StringUtil.IsEmpty(password)) return 0;
|
||||
|
||||
// Count number of different types of character.
|
||||
int characterTypeCount = 0;
|
||||
try
|
||||
{
|
||||
if (Regex.Matches(password, "[A-Z]").Count > 0) characterTypeCount++;
|
||||
if (Regex.Matches(password, "[a-z]").Count > 0) characterTypeCount++;
|
||||
if (Regex.Matches(password, "[0-9]").Count > 0) characterTypeCount++;
|
||||
if (Regex.Matches(password, "[^a-zA-Z0-9]").Count > 0) characterTypeCount++;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Debug(fn + " " + ExceptionUtil.GetLogMessage(exc));
|
||||
}
|
||||
int passwordLength = StringUtil.GetLength(password);
|
||||
|
||||
// 1 - Less than min length - 2;
|
||||
if (passwordLength < Params.MinLength - 2)
|
||||
return 1;
|
||||
|
||||
// 1 - Less than min length and only 1 character type.
|
||||
if (passwordLength < Params.MinLength && characterTypeCount == 1)
|
||||
return 1;
|
||||
|
||||
// 2 - Less than min length and only 2 character types.
|
||||
if (passwordLength < Params.MinLength && characterTypeCount == 2)
|
||||
return 2;
|
||||
|
||||
// 2 - Less than min length without complexity - 2 and only 1 character type.
|
||||
if (passwordLength < Params.MinLengthNoComplexity - 2 && characterTypeCount == 1)
|
||||
return 2;
|
||||
|
||||
// 3 - Less than minimum length.
|
||||
if (passwordLength < Params.MinLength)
|
||||
return 3;
|
||||
|
||||
// 3 - Minimum length with only 3 character types.
|
||||
if (passwordLength >= Params.MinLength && passwordLength < Params.MinLengthNoComplexity && characterTypeCount == 3)
|
||||
return 3;
|
||||
|
||||
// 3 - Minimum length without complexity - 2 with less than 4 character types.
|
||||
if (passwordLength >= Params.MinLengthNoComplexity - 2 && passwordLength < Params.MinLengthNoComplexity && characterTypeCount < 4)
|
||||
return 3;
|
||||
|
||||
// 3 Catch-all - ensure only valid passwords score 4 or more.
|
||||
if (passwordLength < Params.MinLengthNoComplexity && characterTypeCount < 4)
|
||||
return 3;
|
||||
|
||||
// 4 - Min length with 4 character types.
|
||||
if (passwordLength < Params.MinLengthNoComplexity)
|
||||
return 4;
|
||||
|
||||
// 4 - Min length without complexity.
|
||||
if (passwordLength >= Params.MinLengthNoComplexity && passwordLength < Params.MinLengthNoComplexity + 2 && characterTypeCount < 4)
|
||||
return 4;
|
||||
|
||||
// 5 - Anything more complex.
|
||||
return 5;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private static PasswordHasher<SaxUserAccount> passwordHasher = new PasswordHasher<SaxUserAccount>();
|
||||
/// <summary>Get the hashed value that corresponds to the password.</summary>
|
||||
public static String GetPasswordHash(SaxUserAccount userAccount, String password)
|
||||
{
|
||||
return passwordHasher.HashPassword(userAccount, password);
|
||||
}
|
||||
/// <summary>Verify password against hashed value stored against user account.</summary>
|
||||
public static bool VerifyHashedPassword(SaxUserAccount userAccount, String password)
|
||||
{
|
||||
return passwordHasher.VerifyHashedPassword(userAccount, userAccount.PasswordHashed, password) != PasswordVerificationResult.Failed;
|
||||
}
|
||||
/// <summary>Verify password against hashed value with user account as reference.</summary>
|
||||
public static bool VerifyHashedPassword(SaxUserAccount userAccount, String password, String passwordHashed)
|
||||
{
|
||||
return passwordHasher.VerifyHashedPassword(userAccount, passwordHashed, password) != PasswordVerificationResult.Failed;
|
||||
}
|
||||
/// <summary>Check whether the password needs rehashing.</summary>
|
||||
public static bool PasswordRequiresRehash(SaxUserAccount userAccount, String password)
|
||||
{
|
||||
return passwordHasher.VerifyHashedPassword(userAccount, userAccount.PasswordHashed, password) == PasswordVerificationResult.SuccessRehashNeeded;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SaxResourceHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public enum TextResource
|
||||
{
|
||||
// Email stylesheets.
|
||||
Email_Main_Css,
|
||||
// Email partial content.
|
||||
Email_Header_Html,
|
||||
Email_Footer_Html,
|
||||
// Email templates.
|
||||
Email_Security_AccountActivated_Html,
|
||||
Email_Security_RequestPassword_Html,
|
||||
// Data - countries etc.
|
||||
Data_CountriesWithRegions_Txt,
|
||||
}
|
||||
public enum ImageResource
|
||||
{
|
||||
Saxonite_logo_h75_png,
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public static Assembly MyAssembly { get; } = typeof(SaxResourceHelper).GetTypeInfo().Assembly;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Text resources.
|
||||
|
||||
/// <summary> Retrieve contents of text resource in this assembly by type.</summary>
|
||||
public static String GetResourceText(TextResource textResource)
|
||||
{
|
||||
String partialPath = "";
|
||||
String resourceFilename = "";
|
||||
switch (textResource)
|
||||
{
|
||||
// Email stylesheet.
|
||||
case TextResource.Email_Main_Css:
|
||||
resourceFilename = "sax-email-main.css";
|
||||
break;
|
||||
// Email partial content.
|
||||
case TextResource.Email_Header_Html:
|
||||
resourceFilename = "sax-email-header.html";
|
||||
break;
|
||||
case TextResource.Email_Footer_Html:
|
||||
resourceFilename = "sax-email-footer.html";
|
||||
break;
|
||||
// Email templates.
|
||||
case TextResource.Email_Security_AccountActivated_Html:
|
||||
resourceFilename = "sax-email-account-activated.html";
|
||||
break;
|
||||
case TextResource.Email_Security_RequestPassword_Html:
|
||||
resourceFilename = "sax-email-request-password.html";
|
||||
break;
|
||||
// Data.
|
||||
case TextResource.Data_CountriesWithRegions_Txt:
|
||||
resourceFilename = "CountriesWithRegions.txt";
|
||||
break;
|
||||
}
|
||||
return GetResourceText(resourceFilename, partialPath);
|
||||
}
|
||||
|
||||
/// <summary> Retrieve contents of text resource in this assembly by name.</summary>
|
||||
public static String GetResourceText(String resourceFilename, String partialPath = "")
|
||||
{
|
||||
if (!StringUtil.IsBlank(resourceFilename))
|
||||
{
|
||||
String resourceText = SacsResourceHelper.GetManifestResourceAsText(MyAssembly, partialPath, resourceFilename);
|
||||
if (!StringUtil.IsBlank(resourceText))
|
||||
return resourceText;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Image resources.
|
||||
public static byte[] GetResourceImage(ImageResource imageResource)
|
||||
{
|
||||
String partialPath = "";
|
||||
String resourceFilename = "";
|
||||
switch (imageResource)
|
||||
{
|
||||
case ImageResource.Saxonite_logo_h75_png:
|
||||
resourceFilename = "saxonite-logo-h75.png";
|
||||
break;
|
||||
}
|
||||
if (!StringUtil.IsEmpty(resourceFilename))
|
||||
{
|
||||
var assembly = typeof(SaxResourceHelper).GetTypeInfo().Assembly;
|
||||
byte[] resourceData = SacsResourceHelper.GetManifestResourceAsBinary(assembly, partialPath, resourceFilename);
|
||||
if (resourceData != null)
|
||||
return resourceData;
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Resource stream.
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve stream to resource in this assembly.
|
||||
/// </summary>
|
||||
public static Stream GetResourceStream(String resourceFilename, String partialPath = "")
|
||||
{
|
||||
if (!StringUtil.IsBlank(resourceFilename))
|
||||
{
|
||||
return SacsResourceHelper.GetManifestResourceStream(MyAssembly, partialPath, resourceFilename);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user