/// <summary> /// Generates a JWT with RSA512 using a private key loaded from the environment. /// </summary> /// <remarks> /// A JWT is compromised of 3 dot-separated, base64 strings. /// Jose_Header.JWT_Payload.JWT_Signature /// </remarks> /// <param name="payload">The data to be encrypted.</param> /// <returns>The JWT.</returns> public string GenerateJWT(Dictionary <string, string> payload) { // TODO CHECK PUBLIC KEY AND PRIVATE KEY, CHECK THEIR LENGTHS AND IF THEY INCLUDE ----BEGIN... ---END... ETC // Make sure we have the proper parameters inside the dictionary if (!payload.ContainsKey(Constants.UserTypeKey) || !payload.ContainsKey(Constants.IdKey)) { throw new ArgumentException("UserType or ID was not provided."); } // Create the header and convert it to a Base64 string Dictionary <string, string> joseHeader = new Dictionary <string, string> { { Constants.MediaType, Constants.MediaJWT }, // Media type { Constants.SigningAlgKey, Constants.SIGNING_ALGORITHM } // Signing algorithm type }; // If the expiration date wasn't already specified, then create one if (!payload.ContainsKey(Constants.EXPIRATION_FIELD)) { // Add a 20 min expiration payload.Add(Constants.EXPIRATION_FIELD, TimeUtilityService.GetEpochFromNow().ToString()); } // Base64 encode the header and payload string encodedHeader = StringUtilityService.DictionaryToString(joseHeader).ToBase64URL(); string encodedPayload = StringUtilityService.DictionaryToString(payload).ToBase64URL(); // The signature will be the hash of the header and payload string stringToSign = encodedHeader + '.' + encodedPayload; // Create the signature string signature = GetPKCSSignature(stringToSign).ToBase64URL(); return(string.Format("{0}.{1}.{2}", encodedHeader, encodedPayload, signature)); }
/// <summary> /// Refreshes a token to be active for 20 more minutes. /// </summary> /// <param name="jwt">The token that needs to be refreshed.</param> /// <returns>A new token that has been refreshed and active for 20 more minutes.</returns> public string RefreshJWT(string jwt, int minutes = Constants.TOKEN_EXPIRATION_MIN) { Dictionary <string, string> payload = _authorizationService.DecryptJWT(jwt); // Refresh the token for an additional 20 minutes payload[Constants.EXPIRATION_FIELD] = TimeUtilityService.GetEpochFromNow(minutes).ToString(); return(_authorizationService.GenerateJWT(payload)); }
/// <summary> /// Asynchronously send an email code to <paramref name="emailAddress"/> of the user /// indicated by the <paramref name="username"/>. /// </summary> /// <param name="username">The username of the user to send the email to (string)</param> /// <param name="emailAddress">The email address to send the email to (string)</param> /// <returns>Task (bool) whether the function executed without exception</returns> public async Task <bool> SendEmailVerificationAsync(string username, string emailAddress) { var message = new MimeMessage(); var bodyBuilder = new BodyBuilder(); // From the system email address to the emailAddress. message.From.Add(new MailboxAddress(Constants.SystemEmailAddress)); message.To.Add(new MailboxAddress($"{emailAddress}")); message.Subject = Constants.EmailVerificationSubject; // Generate a code the length of what's defined in the constant file. Random generator = new Random(); string emailCode = generator.Next((int)Math.Pow(10, Constants.EmailCodeLength - 1), (int)Math.Pow(10, Constants.EmailCodeLength)).ToString(); // Timestamp the email code was generated at. long emailCodeTimestamp = TimeUtilityService.CurrentUnixTime(); // Store the code and timestamp in the data store. await _userManagementService.StoreEmailCodeAsync(username, emailCode.ToString(), emailCodeTimestamp).ConfigureAwait(false); // Html body for the email code. bodyBuilder.HtmlBody = @"<td valign=""top"" align=""center"" bgcolor=""#0d1121"" style=""padding:35px 70px 30px;"" class=""em_padd""><table align=""center"" width=""100%"" border=""0"" cellspacing=""0"" cellpadding=""0"">" + @"<tr>" + @"</tr>" + @"<tr>" + @"<td height = ""15"" style = ""font-size:0px; line-height:0px; height:15px;"" > </td>" + @"</tr>" + @"<tr>" + $@"<td align = ""center"" valign = ""top"" style = ""font-family:'Open Sans', Arial, sans-serif; font-size:18px; line-height:22px; color:#fbeb59; letter-spacing:2px; padding-bottom:12px;"">YOUR EMAIL VERIFICATION CODE IS: {emailCode}</td>" + @"</tr>" + @"<tr>"; message.Body = bodyBuilder.ToMessageBody(); // Create the SMTP client with the default certificate validation callback to prevent man in the middle attacks. //var client = new SmtpClient //{ // ServerCertificateValidationCallback = (s, c, h, e) => MailService.DefaultServerCertificateValidationCallback(s, c, h, e) //}; var client = new SmtpClient(); // Connect over google SMTP, provide credentials, and asynchronously send and disconnect the client. await client.ConnectAsync(Constants.GoogleSMTP, Constants.GoogleSMTPPort, SecureSocketOptions.SslOnConnect).ConfigureAwait(false); await client.AuthenticateAsync(Constants.SystemEmailAddress, Constants.SystemEmailPassword).ConfigureAwait(false); await client.SendAsync(message).ConfigureAwait(false); await client.DisconnectAsync(true).ConfigureAwait(false); client.Dispose(); return(true); }
/// <summary> /// Submits a ticket to the system /// </summary> /// <param name="category">The category of the ticket</param> /// <param name="description">The description inside the ticket</param> /// <returns>Whether the ticket was saved or not</returns> public async Task <bool> SubmitTicketAsync(Constants.TicketCategories category, string description) { TicketRecord ticketRecord = new TicketRecord(TimeUtilityService.CurrentUnixTime(), category.ToString(), Constants.TicketStatuses.Unresolved.ToString(), Constants.TicketFlagColors.None.ToString(), description); await ticketDAO.CreateAsync(ticketRecord); return(true); }
/// <summary> /// Increment the registration failures of the anonymous user defined by the <paramref name="ipAddress"/>. /// </summary> /// <param name="ipAddress">The ip address to increment the registration failures of (string)</param> /// <param name="maxTimeBeforeFailureReset">The time before their failures reset (TimeSpan)</param> /// <param name="maxNumberOfTries">The max number of registration tries before they get locked (int)</param> /// <returns>Task (bool) whether the funciton executed without exception</returns> public async Task <bool> IncrementRegistrationFailuresAsync(string ipAddress, TimeSpan maxTimeBeforeFailureReset, int maxNumberOfTries) { IPAddressObject ip = await GetIPAddressInfoAsync(ipAddress).ConfigureAwait(false); IPAddressRecord record; // Need to check if the maxtime + lastTime is less than now. // if it is then reset the failure long lastRegFailTimestamp = ip.LastRegFailTimestamp; long maxSeconds = TimeUtilityService.TimespanToSeconds(maxTimeBeforeFailureReset); long currentUnix = TimeUtilityService.CurrentUnixTime(); bool reset = false; // If the time has passed their max time before reset, reset their failures. Don't reset // if they have no last registration fail timestamp. if (lastRegFailTimestamp + maxSeconds < currentUnix && lastRegFailTimestamp != Constants.NoValueLong) { reset = true; record = new IPAddressRecord(ipAddress, registrationFailures: 0); await UpdateIPAsync(record).ConfigureAwait(false); } // Increment the user's login Failure count. int updatedRegistrationFailures = reset ? 1 : ip.RegistrationFailures + 1; // Lock the ip if they have reached the max number of tries. // Update the last reg fail time. if (updatedRegistrationFailures >= maxNumberOfTries) { record = new IPAddressRecord(ipAddress, timestampLocked: currentUnix, registrationFailures: updatedRegistrationFailures, lastRegFailTimestamp: currentUnix); // Asynchronously notify the system admin if an ip address was locked during registration. await SystemUtilityService.NotifySystemAdminAsync($"{ipAddress} was locked at {currentUnix}", Constants.SystemAdminEmailAddress).ConfigureAwait(false); } else { record = new IPAddressRecord(ipAddress, registrationFailures: updatedRegistrationFailures, lastRegFailTimestamp: currentUnix); } return(await UpdateIPAsync(record).ConfigureAwait(false)); }
/// <summary> /// Asynchronously create a user in the data store. /// </summary> /// <param name="isTemp">Used to specify whether the user is temporary.</param> /// <param name="record">The unmasked record conveying the data to create</param> /// <param name="adminName">The username of the admin performing this operation (default = system)</param> /// <param name="adminIp">The ip address of the admin performing this operation (default = localhost)</param> /// <returns>Task (bool) whether the function completed without exception</returns> public async Task <bool> CreateUserAsync(bool isTemp, UserRecord record, string adminName = Constants.SystemIdentifier, string adminIp = Constants.LocalHost) { // Check that the user of function is an admin and throw an exception if they are not. if (!adminName.Equals(Constants.SystemIdentifier)) { UserObject admin = await GetUserInfoAsync(adminName).ConfigureAwait(false); if (admin.UserType != Constants.AdminUserType) { throw new ArgumentException(Constants.MustBeAdmin); } } // Record must be unmasked. if (record.IsMasked()) { throw new ArgumentException(Constants.CreateUserRecordMasked); } // Check for user existence and throw an exception if the user already exists. if (await CheckUserExistenceAsync(record.GetData()[Constants.UserDAOusernameColumn] as string).ConfigureAwait(false)) { throw new ArgumentException(Constants.UsernameExistsLogMessage); } // If the user being created is temporary, update the timestamp to be the current unix time, otherwise // the timestamp has no value. long tempTimestamp = isTemp ? TimeUtilityService.CurrentUnixTime() : Constants.NoValueLong; record.GetData()[Constants.UserDAOtempTimestampColumn] = tempTimestamp; // Mask all the sensitive information. UserRecord resultRecord = await _maskingService.MaskAsync(record, true).ConfigureAwait(false) as UserRecord; // Insert the masked user into the datastore. await _userDAO.CreateAsync(resultRecord).ConfigureAwait(false); // Log the action. await LogAsync(DateTime.UtcNow.ToString(Constants.LoggingFormatString), Constants.SingleUserCreateOperation, adminName, adminIp).ConfigureAwait(false); return(true); }
/// <summary> /// Asynchronously increment the login failures of a user and disables that user if he has /// reached the <paramref name="maxNumberOfTries"/> parameter and his last login failure time /// hs not exceeded the <paramref name="maxTimeBeforeFailureReset"/> parameter. /// </summary> /// <param name="username">Username of the user to increment</param> /// <param name="maxTimeBeforeFailureReset">TimeSpan object to represent how long the before the login failure resets.</param> /// <param name="maxNumberOfTries">The max number of tries a user can login before he is disabled.</param> /// <returns>Returns true if the operation is successfull and false if it failed.</returns> public async Task <bool> IncrementLoginFailuresAsync(string username, TimeSpan maxTimeBeforeFailureReset, int maxNumberOfTries) { UserObject user = await GetUserInfoAsync(username).ConfigureAwait(false); UserRecord record; // Need to check if the maxtime + lastTime is less than now. // if it is then reset the failure long lastLoginFailTimestamp = user.LastLoginFailTimestamp; long maxSeconds = TimeUtilityService.TimespanToSeconds(maxTimeBeforeFailureReset); long currentUnix = TimeUtilityService.CurrentUnixTime(); bool reset = false; // If the time has passed their max time before reset, reset their failures. Don't reset if // they have no last login fail timestamp. if (lastLoginFailTimestamp + maxSeconds < currentUnix && lastLoginFailTimestamp != Constants.NoValueLong) { reset = true; record = new UserRecord(username, loginFailures: 0); await UpdateUserAsync(record).ConfigureAwait(false); } // Increment the user's login failure count. int updatedLoginFailures = reset ? 1 : user.LogInFailures + 1; // Disable the user if they have reached the max number of tries. // Update the last login fail time. if (updatedLoginFailures >= maxNumberOfTries) { record = new UserRecord(username, loginFailures: updatedLoginFailures, disabled: Constants.DisabledStatus, lastLoginFailTimestamp: currentUnix); } else { record = new UserRecord(username, loginFailures: updatedLoginFailures, lastLoginFailTimestamp: currentUnix); } await UpdateUserAsync(record).ConfigureAwait(false); return(true); }
/// <summary> /// Checks whether the current token is expired or not. /// </summary> /// <param name="jwt">The token to check.</param> /// <returns>Whether the current token is past it's 20 minute lifetime.</returns> public bool TokenIsExpired(string jwt) { Dictionary <string, string> payload = _authorizationService.DecryptJWT(jwt); // Check if the expiration key exists first if (!payload.ContainsKey(Constants.EXPIRATION_FIELD)) { throw new ArgumentException("Expiration time is not specified!"); } long expTime; bool isNumeric = long.TryParse(payload[Constants.EXPIRATION_FIELD], out expTime); // Make sure we are dealing with a number first if (!isNumeric) { throw new ArgumentException("Expiration time is not a number!"); } return(TimeUtilityService.CurrentUnixTime() > expTime); }