diff --git a/devdocs/deploy.md b/devdocs/deploy.md index 83a5fad7..86cb2b62 100644 --- a/devdocs/deploy.md +++ b/devdocs/deploy.md @@ -6,7 +6,7 @@ If any packages have been changed in the release do a thorough security scan and ### Bump version numbers: -Search and replace 8.0.6 +Search and replace 8.0.7 webapp,server,launcher, v8migrate (don't change v8migrate unless it has it's own code changes, it's version should be it's own thing other than major release changes etc) Client end ayanova-version.js, diff --git a/devdocs/pricing.md b/devdocs/pricing.md index 5e467a52..7e5ea94e 100644 --- a/devdocs/pricing.md +++ b/devdocs/pricing.md @@ -139,7 +139,7 @@ TWO types makes the most sense after considering options: - One time fee, user can use indefinitely - self installed, hosted and maintained by customer - least profitable for us long term if they don't buy a maint. subscription -- Without maintenance subscription, eligable for Minor updates only to fix bugs no new features so in other words they buy 8.0.6 they can upgrade to any 8.0.X version release, but not 8.1 as it will be new features added that don't break backward compatibility +- Without maintenance subscription, eligable for Minor updates only to fix bugs no new features so in other words they buy 8.0.7 they can upgrade to any 8.0.X version release, but not 8.1 as it will be new features added that don't break backward compatibility - one-time payment, along with the option of a yearly maintenance fee. - This is basically our current model but we allow upgrades for subscribers - **HAS CODE IMPLICATIONS** upgrades need to check if allowed based on version number if no maintenance subscription _not_ on date of build. diff --git a/devdocs/todo.txt b/devdocs/todo.txt index 297d5e91..fd69dfea 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -35,6 +35,16 @@ WIP >>>>>>>>>>>>> todo: move the dbid to the very first position in the shareit order additional fields as only the first two will show in email and reporting todo: register domain name myayanova.com for hanging subscriber servers off of +todo: rockfish not putting time limit on subscription keys, UI needs additions and shit for that + +todo: restrict superuser account in some critical ways so it can't be used for day to day work, maybe no dashboard etc + nothing too serious that it breaks existing code but remove any features that it shouldn't be used for + Find a subtle way to do this particularly for subscription users + +todo: superuser consuming a license theoretically but shouldn't so in a subscription mode it needs to be locked down as to what it can be used for, some critical features should block it from use + fuck! Actually, just automatically deduct it from the count, whatever the count is remove one for superuser (or maybe in query filter OUT the superuser id 1!!!!) +todo: customer contact consuming a subscription license always because it doesn't knwo if they are a login or just a contact, maybe solution is to not set a login name if they arebn't a login customer + and change the counting code to ignore no login name users (but only for the customer contact count, not the inside staff count or that would be used to circumvent licening (or do we care? hmmm)) todo: TEST subscription and perpetual license code @@ -42,7 +52,8 @@ todo: TEST subscription and perpetual license code Does it prevent changing a user type? (direct db meddling) Does it prevent installing a new license when too few for existing active? Does it communicate this well to the user? - + + @@ -1259,6 +1270,6 @@ https://www.ayanova.com/download/next/ayanova-linux-x64-server.zip https://www.ayanova.com/download/next/ayanova-windows-x64-lan-setup.exe Current v8 docs home: https://www.ayanova.com/docs/next -BUILD 8.0.6 CHANGES OF NOTE +BUILD 8.0.7 CHANGES OF NOTE Subscription / perpetual license code and stuff diff --git a/dist/install/windows/x64/lan.iss b/dist/install/windows/x64/lan.iss index e821f49c..db1b0dc6 100644 --- a/dist/install/windows/x64/lan.iss +++ b/dist/install/windows/x64/lan.iss @@ -1,7 +1,7 @@ ; LAN install for internal network use only #define MyAppName "AyaNova server" -#define MyAppVersion "8.0.6" +#define MyAppVersion "8.0.7" #define MyAppPublisher "Ground Zero Tech-Works, Inc." #define MyAppURL "https://ayanova.com/" #define MyAppLauncherExeName "ayanova-launcher.exe" diff --git a/dist/install/windows/x64/standalone.iss b/dist/install/windows/x64/standalone.iss index 095c3fe8..8f4223e5 100644 --- a/dist/install/windows/x64/standalone.iss +++ b/dist/install/windows/x64/standalone.iss @@ -3,7 +3,7 @@ ; external to lan requires different config #define MyAppName "AyaNova" -#define MyAppVersion "8.0.6" +#define MyAppVersion "8.0.7" #define MyAppPublisher "Ground Zero Tech-Works, Inc." #define MyAppURL "https://ayanova.com/" #define MyAppLauncherExeName "ayanova-launcher.exe" diff --git a/server/AyaNova/AyaNova.csproj b/server/AyaNova/AyaNova.csproj index e1f0dc41..9ff8da89 100644 --- a/server/AyaNova/AyaNova.csproj +++ b/server/AyaNova/AyaNova.csproj @@ -4,8 +4,8 @@ true - 8.0.6 - 8.0.6.0 + 8.0.7 + 8.0.7.0 ayanova.ico bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 1591 diff --git a/server/AyaNova/Controllers/AuthController.cs b/server/AyaNova/Controllers/AuthController.cs index 5ab96e85..024c0558 100644 --- a/server/AyaNova/Controllers/AuthController.cs +++ b/server/AyaNova/Controllers/AuthController.cs @@ -87,7 +87,7 @@ namespace AyaNova.Api.Controllers //Multiple users are allowed the same password and login //Salt will differentiate them so get all users that match login, then try to match pw - var users = await ct.User.Where(z => z.Login == creds.Login && z.Active == true).ToListAsync(); + var users = await ct.User.Where(z => z.Login == creds.Login && z.Active == true && z.AllowLogin == true).ToListAsync(); foreach (User u in users) { @@ -168,7 +168,7 @@ namespace AyaNova.Api.Controllers } //Match to temp token that would have been set by initial credentialed login for 2fa User - var user = await ct.User.Where(z => z.TempToken == pin.TempToken && z.Active == true && z.TwoFactorEnabled == true).FirstOrDefaultAsync(); + var user = await ct.User.Where(z => z.TempToken == pin.TempToken && z.Active == true && z.AllowLogin==true && z.TwoFactorEnabled == true).FirstOrDefaultAsync(); if (user != null) @@ -393,7 +393,7 @@ namespace AyaNova.Api.Controllers { //If the user is inactive they may not login - if (!u.Active) + if (!u.Active || !u.AllowLogin) { //respond like bad creds so as not to leak information await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY); diff --git a/server/AyaNova/biz/PrimeData.cs b/server/AyaNova/biz/PrimeData.cs index d3b3ac22..24201bed 100644 --- a/server/AyaNova/biz/PrimeData.cs +++ b/server/AyaNova/biz/PrimeData.cs @@ -21,6 +21,7 @@ namespace AyaNova.Biz //ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger("PrimeData"); User u = new User(); u.Active = true; + u.AllowLogin=true; u.Name = "AyaNova SuperUser"; u.Salt = Hasher.GenerateSalt(); u.Login = "superuser"; diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs index d736ac87..7b903ecc 100644 --- a/server/AyaNova/biz/UserBiz.cs +++ b/server/AyaNova/biz/UserBiz.cs @@ -42,7 +42,8 @@ namespace AyaNova.Biz { using (AyContext ct = ServiceProviderProvider.DBContext) { - var ret = await ct.User.AsNoTracking().Where(z => z.Active == true && ( + //all users who are not the superuser, active and not a customer or headoffice + var ret = await ct.User.AsNoTracking().Where(z => z.Id != 1 && z.Active == true && ( z.UserType != UserType.Customer && z.UserType != UserType.HeadOffice)).LongCountAsync(); return ret; @@ -54,7 +55,9 @@ namespace AyaNova.Biz { using (AyContext ct = ServiceProviderProvider.DBContext) { - var ret = await ct.User.AsNoTracking().Where(z => z.Active == true && ( + //all users who are customer or head office, active and have a login name set + + var ret = await ct.User.AsNoTracking().Where(z => z.Login != null && z.Active == true && ( z.UserType == UserType.Customer || z.UserType == UserType.HeadOffice)).LongCountAsync(); return ret; @@ -864,6 +867,9 @@ namespace AyaNova.Biz if (proposedObj.Active != currentObj.Active) AddError(ApiErrorCode.NOT_AUTHORIZED, "Active"); + + if (proposedObj.AllowLogin != currentObj.AllowLogin) + AddError(ApiErrorCode.NOT_AUTHORIZED, "AllowLogin"); if (proposedObj.Name != currentObj.Name) AddError(ApiErrorCode.NOT_AUTHORIZED, "Name"); diff --git a/server/AyaNova/models/User.cs b/server/AyaNova/models/User.cs index 254f929f..493c66f9 100644 --- a/server/AyaNova/models/User.cs +++ b/server/AyaNova/models/User.cs @@ -17,6 +17,7 @@ namespace AyaNova.Models public long Id { get; set; } public uint Concurrency { get; set; } public bool Active { get; set; } + public bool AllowLogin { get; set; } [Required, MaxLength(255)] public string Name { get; set; } public AuthorizationRoles Roles { get; set; } @@ -54,6 +55,7 @@ namespace AyaNova.Models [Required] public bool Active { get; set; } + public bool AllowLogin { get; set; } [Required, MaxLength(255)] public string Name { get; set; } public DateTime? LastLogin { get; set; } @@ -116,12 +118,12 @@ namespace AyaNova.Models [JsonIgnore] public Vendor Vendor { get; set; } - + public User() { Tags = new List(); - + } diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 47d33bce..1649ca59 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -20,7 +20,7 @@ namespace AyaNova.Util /////////// CHANGE THIS ON NEW SCHEMA UPDATE //////////////////// //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImportingAsync WHEN NEW TABLES ADDED!!!! - private const int DESIRED_SCHEMA_LEVEL = 5; + private const int DESIRED_SCHEMA_LEVEL = 6; internal const long EXPECTED_COLUMN_COUNT = 1376; internal const long EXPECTED_INDEX_COUNT = 161; @@ -1482,7 +1482,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); ////////////////////////////////////////////////// // - // 8.0.6 Subscription license trans keys + // 8.0.7 Subscription license trans keys // if (currentSchema < 5) { @@ -1512,6 +1512,34 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); + ////////////////////////////////////////////////// + // + // 8.0.7 additions for customer contact licensing (allowlogin) + // + if (currentSchema < 6) + { + LogUpdateMessage(log); + + await ExecQueryAsync("ALTER TABLE auser ADD column allowlogin BOOL"); + await ExecQueryAsync("UPDTE TABLE auser SET allowlogin=true WHERE active=true"); + + + //english translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'AllowLogin', 'Allow login' FROM atranslation t where t.baselanguage = 'en'"); + + //spanish translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'AllowLogin', 'Permitir acceso' FROM atranslation t where t.baselanguage = 'es'"); + + //french translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'AllowLogin', 'Autoriser la connexion' FROM atranslation t where t.baselanguage = 'fr'"); + + //german translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'AllowLogin', 'Login erlauben' FROM atranslation t where t.baselanguage = 'de'"); + await SetSchemaLevelAsync(++currentSchema); + } + + + //######################################### //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! diff --git a/server/AyaNova/util/AyaNovaVersion.cs b/server/AyaNova/util/AyaNovaVersion.cs index 1ae629c8..075a0390 100644 --- a/server/AyaNova/util/AyaNovaVersion.cs +++ b/server/AyaNova/util/AyaNovaVersion.cs @@ -5,7 +5,7 @@ namespace AyaNova.Util /// internal static class AyaNovaVersion { - public const string VersionString = "8.0.6"; + public const string VersionString = "8.0.7"; public const string FullNameAndVersion = "AyaNova server " + VersionString; public const string CurrentApiVersion="v8"; }//eoc diff --git a/server/AyaNova/util/License.cs b/server/AyaNova/util/License.cs index 1d1e87a9..dc8a1e47 100644 --- a/server/AyaNova/util/License.cs +++ b/server/AyaNova/util/License.cs @@ -123,7 +123,7 @@ namespace AyaNova.Core /// Fetch the license status of the feature in question /// /// - /// LicenseFeature object or null if there is no license + /// LicenseFeature object or null if there is no matching license feature or license is missing public LicenseFeature GetLicenseFeature(string Feature) { if (IsEmpty) @@ -145,7 +145,7 @@ namespace AyaNova.Core { get { - return GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count; + return GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME)?.Count ?? 0; } } @@ -153,7 +153,7 @@ namespace AyaNova.Core { get { - return GetLicenseFeature(ACTIVE_INTERNAL_USERS_FEATURE_NAME).Count; + return GetLicenseFeature(ACTIVE_INTERNAL_USERS_FEATURE_NAME)?.Count ?? 0; } } @@ -161,7 +161,7 @@ namespace AyaNova.Core { get { - return GetLicenseFeature(ACTIVE_CUSTOMER_USERS_FEATURE_NAME).Count; + return GetLicenseFeature(ACTIVE_CUSTOMER_USERS_FEATURE_NAME)?.Count ?? 0; } } @@ -855,10 +855,10 @@ namespace AyaNova.Core //SUBSCRIPTION USER COUNTS - new license causes exceeding counts? - long NewInsideUserLicensedCount = ParsedNewKey.GetLicenseFeature(ACTIVE_INTERNAL_USERS_FEATURE_NAME).Count; - long ExistingActiveInsideUserCount = await AyaNova.Biz.UserBiz.ActiveInsideUserCountAsync(); + long NewInsideUserLicensedCount = ParsedNewKey.ActiveInternalUsersCount; + long ExistingActiveInsideUserCount = await AyaNova.Biz.UserBiz.ActiveInternalUserCountAsync(); - long NewCustomerLicensedCount = ParsedNewKey.GetLicenseFeature(ACTIVE_CUSTOMER_USERS_FEATURE_NAME).Count; + long NewCustomerLicensedCount = ParsedNewKey.ActiveCustomerContactUsersCount; long ExistingCustomerUserCount = await AyaNova.Biz.UserBiz.ActiveCustomerContactUserCountAsync(); string err = "E1020 - Can't install license: "; bool throwit = false; @@ -879,7 +879,7 @@ namespace AyaNova.Core #else //PERPETUAL, vet the TECHCOUNT - new license causes exceeding count? - long NewTechCount = ParsedNewKey.GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count; + long NewTechCount = ParsedNewKey.ActiveTechsCount; if (await AyaNova.Biz.UserBiz.ActiveTechUserCountAsync() > NewTechCount) { //attempt to set enough of the eldest last login techs to inactive