diff --git a/server/AyaNova/Controllers/UserController.cs b/server/AyaNova/Controllers/UserController.cs
index f2d3f9e6..e56b7e73 100644
--- a/server/AyaNova/Controllers/UserController.cs
+++ b/server/AyaNova/Controllers/UserController.cs
@@ -376,6 +376,31 @@ namespace AyaNova.Api.Controllers
return Ok(ApiOkResponse.Response(u.UserType != UserType.Customer && u.UserType != UserType.HeadOffice));
}
+ ///
+ /// Generate new random credentials for User
+ /// and email them to the user
+ ///
+ ///
+ /// User id
+ /// From route path
+ /// NoContent
+ [HttpPost("generate-creds-email/{id}")]
+ public async Task GenerateCredsAndEmailUser([FromRoute] long id, ApiVersion apiVersion)
+ {
+ if (!serverState.IsOpen)
+ return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
+ UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
+ if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
+ return StatusCode(403, new ApiNotAuthorizedResponse());
+ if (!ModelState.IsValid)
+ return BadRequest(new ApiErrorResponse(ModelState));
+ bool successfulOperation=await biz.GenerateCredsAndEmailUser(id);
+ if (successfulOperation == false)
+ return BadRequest(new ApiErrorResponse(biz.Errors));
+ else
+ return NoContent();
+ }
+
//------------
}//eoc
diff --git a/server/AyaNova/biz/NotifyEventProcessor.cs b/server/AyaNova/biz/NotifyEventProcessor.cs
index 4aa7f093..7f1bd03e 100644
--- a/server/AyaNova/biz/NotifyEventProcessor.cs
+++ b/server/AyaNova/biz/NotifyEventProcessor.cs
@@ -125,6 +125,8 @@ namespace AyaNova.Biz
}//eom
+
+
//This is told about an event and then determines if there are any subscriptions related to that event and proceses them accordingly
//todo: this should take some kind of general event type like the AyaEvent types (i.e. which CRUD operation is in effect if relevant)
//and also a biz object before and after or just before if not a change and also a AyaType
diff --git a/server/AyaNova/biz/NotifyEventType.cs b/server/AyaNova/biz/NotifyEventType.cs
index 2dafccf4..09da113b 100644
--- a/server/AyaNova/biz/NotifyEventType.cs
+++ b/server/AyaNova/biz/NotifyEventType.cs
@@ -41,7 +41,8 @@ namespace AyaNova.Biz
QuoteStatusAge = 29,//* Quote object Created / Updated, conditional on exact status selected IdValue, Tags conditional, advance notice can be set
WorkorderFinished = 30, //*Service work order is set to any status that is flagged as a "Finished" type of status. Customer & User
WorkorderCreatedForCustomer = 31, //*Service work order is created for Customer, only applies to that customer user notify sub for that customer, customer id is in conditional ID value for subscription
- WorkorderFinishedFollowUp = 32 //* Service workorder closed status follow up again after this many TIMESPAN
+ WorkorderFinishedFollowUp = 32, //* Service workorder closed status follow up again after this many TIMESPAN
+ SendUserCredentials = 33 // Internal System use only: When user generates new credentials and sends them this is the notification type for that see UserBiz GenerateCredsAndEmailUser
//NEW ITEMS REQUIRE translation KEYS
diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs
index 2f45e120..4c9c175b 100644
--- a/server/AyaNova/biz/UserBiz.cs
+++ b/server/AyaNova/biz/UserBiz.cs
@@ -310,6 +310,64 @@ namespace AyaNova.Biz
}
+ /////////////////////////////////////////////
+ // GENERATE AND EMAIL CREDS
+ //
+ internal async Task GenerateCredsAndEmailUser(long userId)
+ {
+ User dbObject = await ct.User.Include(o => o.UserOptions).FirstOrDefaultAsync(z => z.Id == userId);
+ if (dbObject == null)
+ {
+ AddError(ApiErrorCode.NOT_FOUND);
+ return false;
+ }
+ if (string.IsNullOrWhiteSpace(dbObject.UserOptions.EmailAddress))
+ {
+ AddError(ApiErrorCode.VALIDATION_REQUIRED, "EmailAddress");
+ return false;
+ }
+ var ServerUrl = ServerGlobalOpsSettingsCache.Notify.AyaNovaServerURL;
+ if (string.IsNullOrWhiteSpace(ServerUrl))
+ {
+ await NotifyEventProcessor.AddOpsProblemEvent("User::GenerateCredsAndEmailUser - The OPS Notification setting is empty for AyaNova Server URL. This prevents Notification system from linking events to openable objects.");
+ AddError(ApiErrorCode.VALIDATION_REQUIRED, "ServerUrl", "Error: no server url configured in notification settings. Can't direct user to server for login. Set server URL and try again.");
+ return false;
+ }
+
+
+ var newPassword = Hasher.GetRandomAlphanumericString(32);
+ var newLogin = Hasher.GetRandomAlphanumericString(32);
+ dbObject.Password = Hasher.hash(dbObject.Salt, newPassword);
+ dbObject.Login = newLogin;
+ await ct.SaveChangesAsync();
+
+ //send message
+ ServerUrl = ServerUrl.Trim().TrimEnd('/');
+
+ //Translations
+ List TransKeysRequired = new List();
+ TransKeysRequired.Add("UserLogin");
+ TransKeysRequired.Add("UserPassword");
+ TransKeysRequired.Add("NewCredsMessageBody");
+ TransKeysRequired.Add("NewCredsMessageTitle");
+ long EffectiveTranslationId = dbObject.UserOptions.TranslationId;
+ if (EffectiveTranslationId == 0) EffectiveTranslationId = ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID;
+ var TransDict = await TranslationBiz.GetSubsetStaticAsync(TransKeysRequired, EffectiveTranslationId);
+ var Title = TransDict["NewCredsMessageTitle"];
+ var NewCredsMessage = TransDict["NewCredsMessageBody"];
+ var Creds = $"{TransDict["UserLogin"]}:\n{newLogin}\n{TransDict["UserPassword"]}:\n{newPassword}\n";
+
+ IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer;
+
+ await m.SendEmailAsync(dbObject.UserOptions.EmailAddress, Title, $"{NewCredsMessage}{Creds}{ServerUrl}/home-user-settings", ServerGlobalOpsSettingsCache.Notify);
+
+ //Log modification and save context
+ await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified, "GeneratedNewCredentialsAndEmailedToUser"), ct);
+ return true;
+ }
+
+
+
private async Task SearchIndexAsync(User obj, bool isNew)
{
//SEARCH INDEXING
@@ -592,7 +650,7 @@ namespace AyaNova.Biz
return DownloadUser;
}
-
+
////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/server/AyaNova/util/Hasher.cs b/server/AyaNova/util/Hasher.cs
index ab35bb13..3514c8d2 100644
--- a/server/AyaNova/util/Hasher.cs
+++ b/server/AyaNova/util/Hasher.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace AyaNova.Util
@@ -32,6 +34,39 @@ namespace AyaNova.Util
return Convert.ToBase64String(salt);
}
+
+
+ public static string GetRandomAlphanumericString(int length)
+ {
+ const string alphanumericCharacters = "0123456789abcdefghijkmnopqrstuvwxyz";
+ return GetRandomString(length, alphanumericCharacters);
+ }
+
+ public static string GetRandomString(int length, IEnumerable characterSet)
+ {
+ if (length < 0)
+ throw new ArgumentException("length must not be negative", "length");
+ if (length > int.MaxValue / 8) // 250 million chars ought to be enough for anybody
+ throw new ArgumentException("length is too big", "length");
+ if (characterSet == null)
+ throw new ArgumentNullException("characterSet");
+ var characterArray = characterSet.Distinct().ToArray();
+ if (characterArray.Length == 0)
+ throw new ArgumentException("characterSet must not be empty", "characterSet");
+
+ var bytes = new byte[length * 8];
+ new RNGCryptoServiceProvider().GetBytes(bytes);
+ var result = new char[length];
+ for (int i = 0; i < length; i++)
+ {
+ ulong value = BitConverter.ToUInt64(bytes, i * 8);
+ result[i] = characterArray[value % (uint)characterArray.Length];
+ }
+ return new string(result);
+ }
+
+
+
}//eoc
}//eons
\ No newline at end of file