diff --git a/Controllers/RvrController.cs b/Controllers/RvrController.cs index 780b8bc..5f99c53 100644 --- a/Controllers/RvrController.cs +++ b/Controllers/RvrController.cs @@ -38,10 +38,10 @@ namespace rockfishCore.Controllers #if (DEBUG) private const string LICENSE_SERVER_URL = "http://localhost:3001/"; - private const string SUPPORT_EMAIL = "cardjohn@ayanova.com"; + public const string SUPPORT_EMAIL = "cardjohn@ayanova.com"; #else private const string LICENSE_SERVER_URL = "https://rockfish.ayanova.com/"; - private const string SUPPORT_EMAIL="support@ayanova.com"; + public const string SUPPORT_EMAIL="support@ayanova.com"; #endif [HttpPost] @@ -66,7 +66,7 @@ namespace rockfishCore.Controllers System.Diagnostics.Debug.WriteLine("RvRController:Post - TODO: Test MustBeOlderThan date code"); //if there is an active trial for this db then can't do this they must request we re-release it or completely zap the database instead - var MustBeOlderThan = DateUtil.DateToEpoch(DateTime.Now.AddDays(-45)); + var MustBeOlderThan = DateUtil.DateToEpoch(DateTime.Now.AddDays((RavenKeyFactory.TRIAL_PERIOD_DAYS * -1))); if (await ct.TrialRequest.Where(z => z.DbId == r.DbId && z.DtProcessed != null && z.DtProcessed > MustBeOlderThan).AnyAsync()) { return BadRequest("E1000 - Can't trial; there is already an active trial license issued for this database Id"); @@ -114,7 +114,7 @@ namespace rockfishCore.Controllers var rfUrl = LICENSE_SERVER_URL + $"default.htm#!/trialEdit/{req.Id}"; var body = $"Email address {req.Email} was just verified for {req.ContactName} at {req.CompanyName}.\r\nTrial key is ready to be processed now:\r\n{rfUrl}"; //send confirmation email - RfMail.SendMessage("support@ayanova.com", "support@ayanova.com", "AyaNova trial request requiring action", body, false); + RfMail.SendMessage("support@ayanova.com", SUPPORT_EMAIL, "AyaNova trial request requiring action", body, false); return new ContentResult { diff --git a/Controllers/TrialController.cs b/Controllers/TrialController.cs index 5d4e205..0e4cfcb 100644 --- a/Controllers/TrialController.cs +++ b/Controllers/TrialController.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; - +using rockfishCore.Util; namespace rockfishCore.Controllers { @@ -65,44 +65,74 @@ namespace rockfishCore.Controllers return Ok(rec); } - - // PUT: api/TrialRequest/5 - [HttpPut("{id}")] - public async Task PutTrialRequest([FromRoute] long id, [FromBody] TrialRequest TrialRequest) + //Approve + [HttpPost("approve/{id}")] + public async Task ApproveTrial([FromRoute] long id) { if (!ModelState.IsValid) - { return BadRequest(ModelState); + var trial = await ct.TrialRequest.SingleOrDefaultAsync(m => m.Id == id); + if (trial == null) + return NotFound(); + //DO APPROVE + //check not already processed and ignore if so + if(trial.DtProcessed!=null){ + //already processed, nothing to do here + return BadRequest("Already processed"); } - if (id != TrialRequest.Id) - { - return BadRequest(); - } + //generate license key and insert in record + trial.Key = RavenKeyFactory.GetRavenTrialKey(trial.DbId, trial.CompanyName); + trial.Status = TrialRequest.TrialRequestStatus.Approved; + trial.DtProcessed = DateUtil.NowAsEpoch(); + await ct.SaveChangesAsync(); - ct.Entry(TrialRequest).State = EntityState.Modified; + //send approved email to user + var body = $"Your trial license request has been approved.\r\nThe license will fetch and install automatically shortly or you can fetch it now in the License form menu."; + //send confirmation email + RfMail.SendMessage("support@ayanova.com", trial.Email, "AyaNova trial request approved", body, false); - try - { - await ct.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) - { - if (!await TrialRequestExistsAsync(id)) - { - return NotFound(); - } - else - { - throw; - } - } + return Ok(trial); - - return NoContent(); } + //Reject + [HttpPost("reject/{id}")] + public async Task RejectTrial([FromRoute] long id, [FromQuery] string rejectReason) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + var trial = await ct.TrialRequest.SingleOrDefaultAsync(m => m.Id == id); + if (trial == null) + return NotFound(); + //DO REJECT + //check not already processed and ignore if so + if(trial.DtProcessed!=null){ + //already processed, nothing to do here + return BadRequest("Already processed"); + } + //generate license key and insert in record + trial.RejectReason=rejectReason; + trial.Status = TrialRequest.TrialRequestStatus.Rejected; + trial.DtProcessed = DateUtil.NowAsEpoch(); + await ct.SaveChangesAsync(); + + //send approved email to user + string reason=string.Empty; + if(!string.IsNullOrWhiteSpace(rejectReason)){ + reason=$"The request was rejected due to:\r\n{rejectReason}"; + } + var body = $"Your trial license request was not approved.\r\n{reason}"; + //send confirmation email + RfMail.SendMessage("support@ayanova.com", trial.Email, "AyaNova trial request approved", body, false); + + return Ok(trial); + + } + + + private async Task TrialRequestExistsAsync(long id) @@ -110,7 +140,7 @@ namespace rockfishCore.Controllers return await ct.TrialRequest.AnyAsync(e => e.Id == id); } - + //------------------------------------------------------ }//eoc diff --git a/util/RavenKeyFactory.cs b/util/RavenKeyFactory.cs index bd0af3d..fd5ee0e 100644 --- a/util/RavenKeyFactory.cs +++ b/util/RavenKeyFactory.cs @@ -5,8 +5,6 @@ using System.IO; using Org.BouncyCastle.Security; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.OpenSsl; -using rockfishCore.Models; -using rockfishCore.Util; @@ -30,6 +28,8 @@ namespace rockfishCore.Util //This feature name means it's a trial key private const string TRIAL_FEATURE_NAME = "TrialMode"; + public const int TRIAL_PERIOD_DAYS = 45; + //This feature name means it's a SAAS or rental mode key for month to month hosted service private const string RENTAL_FEATURE_NAME = "ServiceMode"; @@ -202,6 +202,38 @@ namespace rockfishCore.Util #endregion + + + + public static string GetRavenTrialKey(Guid dbid, string CompanyName) + { + + //Build a sample test key, sign it and return it + AyaNovaLicenseKey k = new AyaNovaLicenseKey(); + k.LicenseFormat = "2018"; + + var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds); + string sId = vv.ToString(); + if (sId.Contains(",")) + sId = sId.Split('.')[0]; + k.Id = $"00-{sId}"; + k.RegisteredTo = CompanyName; + k.DbId = dbid; + + //trial period time limit + k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddDays(TRIAL_PERIOD_DAYS); + + //flag as trial key not regular key + k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 }); + + //add every possible feature + k.Features.Add(new LicenseFeature() { Feature = ACCOUNTING_FEATURE_NAME, Count = 0 }); + k.Features.Add(new LicenseFeature() { Feature = SERVICE_TECHS_FEATURE_NAME, Count = 1000 }); + + return genKey(k); + } + + #region RAVEN test code for development //Trial key magic number for development and testing, all other guids will be fully licensed diff --git a/wwwroot/js/app.api.js b/wwwroot/js/app.api.js index f3b9167..19a2a42 100644 --- a/wwwroot/js/app.api.js +++ b/wwwroot/js/app.api.js @@ -11,7 +11,7 @@ */ /*global $, io, app */ -app.api = (function() { +app.api = (function () { "use strict"; var initModule, getAuthHeaderObject, @@ -32,7 +32,7 @@ app.api = (function() { ////////////////////////////////////////////////////////////////////////////////////// // NOT AUTHORIZED ERROR HANDLER - $(document).ajaxError(function(event, jqxhr, settings, thrownError) { + $(document).ajaxError(function (event, jqxhr, settings, thrownError) { //unauthorized? Trigger logout which will trigger login after clearing creds if (jqxhr.status == 401) { window.location.replace("#!/logout"); @@ -46,7 +46,7 @@ app.api = (function() { // Return the auth token header // // - getAuthHeaderObject = function() { + getAuthHeaderObject = function () { return { Authorization: "Bearer " + app.shell.stateMap.user.token }; @@ -59,7 +59,7 @@ app.api = (function() { //Create //Route app.post('/api/:obj_type/create', function (req, res) { // - create = function(apiRoute, objData, callback) { + create = function (apiRoute, objData, callback) { $.ajax({ method: "post", dataType: "json", @@ -67,10 +67,10 @@ app.api = (function() { headers: getAuthHeaderObject(), contentType: "application/json; charset=utf-8", data: JSON.stringify(objData), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -83,17 +83,17 @@ app.api = (function() { ///////////////// //Get - get anything, the caller provides the route, this should replace most legacy get // - get = function(apiRoute, callback) { + get = function (apiRoute, callback) { $.ajax({ method: "GET", dataType: "json", url: app.shell.stateMap.apiUrl + apiRoute, headers: getAuthHeaderObject(), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -108,7 +108,7 @@ app.api = (function() { //Update //route: app.post('/api/:obj_type/update/:id', function (req, res) { // - update = function(objType, objData, callback) { + update = function (objType, objData, callback) { var theId; if (!objData.id) { return callback({ @@ -125,14 +125,14 @@ app.api = (function() { headers: getAuthHeaderObject(), contentType: "application/json; charset=utf-8", data: JSON.stringify(objData), - success: function(data, textStatus) { + success: function (data, textStatus) { if (data == null) { data = { ok: 1 }; } callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -144,16 +144,16 @@ app.api = (function() { /////////////////////////////////////////////////////////// //remove Item - remove = function(apiRoute, callback) { + remove = function (apiRoute, callback) { $.ajax({ method: "DELETE", dataType: "json", url: app.shell.stateMap.apiUrl + apiRoute, headers: getAuthHeaderObject(), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -167,7 +167,7 @@ app.api = (function() { // uploadFile // (ajax route to upload a file) // - uploadFile = function(apiRoute, objData, callback) { + uploadFile = function (apiRoute, objData, callback) { $.ajax({ method: "post", dataType: "json", @@ -176,10 +176,10 @@ app.api = (function() { contentType: false, processData: false, data: objData, - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -192,7 +192,7 @@ app.api = (function() { ////////////////////////////////////////////////////////////// //putAction - ad-hoc put method used to trigger actions etc // - putAction = function(apiRoute, callback) { + putAction = function (apiRoute, callback) { $.ajax({ method: "put", dataType: "json", @@ -200,14 +200,14 @@ app.api = (function() { headers: getAuthHeaderObject(), contentType: "application/json; charset=utf-8", //data: JSON.stringify(objData), - success: function(data, textStatus) { + success: function (data, textStatus) { if (data == null) { data = { ok: 1 }; } callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -216,6 +216,34 @@ app.api = (function() { } }); }; + + ////////////////////////////////////////////////////////////// + //postAction - ad-hoc post method used to trigger actions etc + // (becuase it shouldn't have been put in the first place above) + postAction = function (apiRoute, callback) { + $.ajax({ + method: "post", + dataType: "json", + url: app.shell.stateMap.apiUrl + apiRoute, + headers: getAuthHeaderObject(), + contentType: "application/json; charset=utf-8", + success: function (data, textStatus) { + if (data == null) { + data = { ok: 1 }; + } + + callback(data); + }, + error: function (jqXHR, textStatus, errorThrown) { + callback({ + error: 1, + msg: textStatus + "\n" + errorThrown, + error_detail: {} + }); + } + }); + }; + ////////////////////////////////////////////////////////////////////////////////////// // LICENSE KEY RELATED API METHODS @@ -223,7 +251,7 @@ app.api = (function() { //CreateLicense //Route app.post('/api/license/create', function (req, res) { // - createLicense = function(objData, callback) { + createLicense = function (objData, callback) { $.ajax({ method: "post", dataType: "text", @@ -231,10 +259,10 @@ app.api = (function() { headers: getAuthHeaderObject(), contentType: "application/json; charset=utf-8", data: JSON.stringify(objData), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -249,16 +277,16 @@ app.api = (function() { //Fetch license requests //route: app.get('/api/license/requests', function (req, res) { // - getLicenseRequests = function(callback) { + getLicenseRequests = function (callback) { $.ajax({ method: "GET", dataType: "json", url: app.shell.stateMap.apiUrl + "license/requests", headers: getAuthHeaderObject(), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -273,16 +301,16 @@ app.api = (function() { //Fetch generated response to license request //route: app.get('/api/license/generateFromRequest/:uid', function (req, res) { // - generateFromRequest = function(uid, callback) { + generateFromRequest = function (uid, callback) { $.ajax({ method: "GET", dataType: "json", url: app.shell.stateMap.apiUrl + "license/generateFromRequest/" + uid, headers: getAuthHeaderObject(), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -296,7 +324,7 @@ app.api = (function() { //Email license request response //app.post('/api/license/email_response', function (req, res) { // - licenseEmailResponse = function(objData, callback) { + licenseEmailResponse = function (objData, callback) { $.ajax({ method: "post", dataType: "text", @@ -304,10 +332,10 @@ app.api = (function() { headers: getAuthHeaderObject(), contentType: "application/json; charset=utf-8", data: JSON.stringify(objData), - success: function(data, textStatus) { + success: function (data, textStatus) { callback(data); }, - error: function(jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, errorThrown) { callback({ error: 1, msg: textStatus + "\n" + errorThrown, @@ -317,7 +345,7 @@ app.api = (function() { }); }; - initModule = function() {}; + initModule = function () {}; return { initModule: initModule, @@ -329,6 +357,7 @@ app.api = (function() { update: update, uploadFile: uploadFile, putAction: putAction, + postAction: postAction, createLicense: createLicense, getLicenseRequests: getLicenseRequests, generateFromRequest: generateFromRequest, diff --git a/wwwroot/js/app.trialEdit.js b/wwwroot/js/app.trialEdit.js index f2d21a2..8b33c9e 100644 --- a/wwwroot/js/app.trialEdit.js +++ b/wwwroot/js/app.trialEdit.js @@ -12,16 +12,34 @@ app.trialEdit = (function () { //---------------- BEGIN MODULE SCOPE VARIABLES -------------- var stateMap = {}, - onSave, onDelete, + onApprove, onReject, onDelete, configModule, initModule; //----------------- END MODULE SCOPE VARIABLES --------------- //------------------- BEGIN EVENT HANDLERS ------------------- - //ONSAVE + //ONAPPROVE // - onSave = function (event) { + onApprove = function (event) { + event.preventDefault(); + $.gevent.publish('app-clear-error'); + + var isFetched=$('#fetched').prop('checked') + + + app.api.postAction('trial/fetched/' + stateMap.id + "/" + isFetched, function (res) { + if (res.error) { + $.gevent.publish('app-show-error', res.msg); + } + }); + + return false; //prevent default? + }; + + //ONREJECT + // + onReject = function (event) { event.preventDefault(); $.gevent.publish('app-clear-error'); @@ -37,6 +55,7 @@ app.trialEdit = (function () { return false; //prevent default? }; + //ONDELETE // onDelete = function (event) { diff --git a/wwwroot/js/templates/app.trialEdit.handlebars b/wwwroot/js/templates/app.trialEdit.handlebars index 68ffd6f..67469ff 100644 --- a/wwwroot/js/templates/app.trialEdit.handlebars +++ b/wwwroot/js/templates/app.trialEdit.handlebars @@ -48,7 +48,7 @@
- +
diff --git a/wwwroot/js/templates/templates.js b/wwwroot/js/templates/templates.js index 44ec4ea..228aa88 100644 --- a/wwwroot/js/templates/templates.js +++ b/wwwroot/js/templates/templates.js @@ -1 +1 @@ -!function(){var n=Handlebars.template,e=Handlebars.templates=Handlebars.templates||{};e["app.authenticate"]=n({compiler:[8,">= 4.3.0"],main:function(n,e,a,l,i){return'
\n Rockfish logo\n

Login

\n
\n

\n

\n

\n
\n
'},useData:!0}),e["app.customerEdit"]=n({compiler:[8,">= 4.3.0"],main:function(n,e,a,l,i){return'
\n
\n\n\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n
\n\n\n
\n \n
\n\n\n\n
\n \n