public async Task ResetPasswordAsync(ILoginProcess process, string newPassword) { var session = EntityHelper.GetSession(process); var ctx = session.Context; ctx.ThrowIf(process.Status != LoginProcessStatus.Active, ClientFaultCodes.InvalidAction, "ProcessStatus", "Process is not active."); if (process.ExpiresOn < App.TimeService.UtcNow) { process.Status = LoginProcessStatus.Expired; session.SaveChanges(); ctx.ThrowIf(true, ClientFaultCodes.InvalidAction, "Process", "Process expired."); } ctx.ThrowIf(process.ProcessType != LoginProcessType.PasswordReset, ClientFaultCodes.InvalidValue, "ProcessType", "Invalid login process type."); ctx.ThrowIf(process.PendingFactors != ExtraFactorTypes.None, ClientFaultCodes.InvalidAction, "PendingSteps", "The login process has verification steps pending"); //Ok, we're ready var login = process.Login; ChangeUserPassword(login, newPassword, oneTimeByAdmin: false); //will log incident and fire event login.Flags &= ~LoginFlags.Suspended; //clear Suspended flag //complete process process.Status = LoginProcessStatus.Completed; session.SaveChanges(); //Send notification var factor = FindLoginFactor(login, ExtraFactorTypes.Email); process.CurrentFactor = factor; session.SaveChanges(); if (factor != null && _settings.MessagingService != null) { await _settings.MessagingService.SendMessage(ctx, LoginMessageType.PasswordResetCompleted, factor, process); } }
public void SendPin(ILoginProcess process, ILoginExtraFactor factor, string factorValue = null, string pin = null) { Util.Check(process.Login == factor.Login, "LoginProcess tries to use login factor not assigned to process's login."); var session = EntityHelper.GetSession(process); var activeAlready = GetActiveConfirmationProcesses(factor, LoginProcessType.FactorVerification); session.Context.ThrowIf(activeAlready.Count > 2, ClientFaultCodes.InvalidAction, "Factor", "Cannot send pin - several pins already sent and are waiting for confirmation."); process.CurrentFactor = factor; if (factor.FactorType == ExtraFactorTypes.GoogleAuthenticator) { //do not generate or send pin } else { pin = pin ?? _settings.PinGenerator(process, factor); //login factor may be coming from user (password reset), or from factor record (verify email) if (string.IsNullOrWhiteSpace(factorValue)) { factorValue = factor.FactorValue; } process.CurrentPin = pin; SendPin(process, factor.FactorType, factorValue, pin); } session.SaveChanges(); var logMsg = StringHelper.SafeFormat("Pin sent to user {0} using factor '{1}'. ", factor.Login.UserName, factorValue); OnLoginEvent(session.Context, LoginEventType.PinSent, factor.Login, logMsg); }
public bool CheckSecretQuestionAnswer(ILoginProcess process, ISecretQuestion question, string userAnswer) { var session = EntityHelper.GetSession(process); var qa = process.Login.SecretQuestionAnswers.FirstOrDefault(a => a.Question == question); session.Context.ThrowIfNull(qa, ClientFaultCodes.InvalidValue, "question", "The question is not registered as user question. Question: {0}", question.Question); var match = CheckUserAnswer(qa, userAnswer); if (!match) { process.FailCount++; session.SaveChanges(); OnLoginEvent(session.Context, LoginEventType.QuestionAnswersFailed, process.Login, "Secret questions check failed."); return(false); } // Add question number to list of answered questions in process record. // If all questions are answered, clear Pending step CheckQuestionAnswers var strAllAnswered = process.AnsweredQuestions ?? string.Empty; var answeredNumbers = strAllAnswered.Split(',').Select(sn => sn.Trim()).ToList(); var newAnsNum = qa.Number.ToString(); if (!answeredNumbers.Contains(newAnsNum)) { answeredNumbers.Add(newAnsNum); process.AnsweredQuestions = string.Join(",", answeredNumbers); if (answeredNumbers.Count == process.Login.SecretQuestionAnswers.Count) { //Set questions as answers process.PendingFactors &= ~ExtraFactorTypes.SecretQuestions; } session.SaveChanges(); } return(true); }
public bool CheckAllSecretQuestionAnswers(ILoginProcess process, IList <SecretQuestionAnswer> answers) { var session = EntityHelper.GetSession(process); var context = session.Context; bool result = true; foreach (var qa in process.Login.SecretQuestionAnswers) { var ans = answers.FirstOrDefault(a => a.QuestionId == qa.Question.Id); if (ans == null) { result = false; //question not answered } else { result &= CheckUserAnswer(qa, ans.Answer); } } if (!result) { process.FailCount++; session.SaveChanges(); OnLoginEvent(context, LoginEventType.QuestionAnswersFailed, process.Login, "Secret questions check failed."); return(false); } // Success // Save numbers of answered questions, clear flag in pending steps process.AnsweredQuestions = string.Join(",", process.Login.SecretQuestionAnswers.Select(qa => qa.Number)); process.PendingFactors &= ~ExtraFactorTypes.SecretQuestions; session.SaveChanges(); OnLoginEvent(context, LoginEventType.QuestionAnswersSucceeded, process.Login, "Secret questions check succeeded."); return(result); }
public async Task SendPinAsync(ILoginProcess process, ILoginExtraFactor factor, string factorValue = null, string pin = null) { Util.Check(process.Login == factor.Login, "LoginProcess tries to use login factor not assigned to process's login."); var session = EntityHelper.GetSession(process); var activeAlready = GetActiveConfirmationProcesses(factor, LoginProcessType.FactorVerification); session.Context.ThrowIf(activeAlready.Count > 2, ClientFaultCodes.InvalidAction, "Factor", "Cannot send pin - several pins already sent and are waiting for confirmation."); process.CurrentFactor = factor; if (factor.FactorType == ExtraFactorTypes.GoogleAuthenticator) { //do not generate or send pin } else { Util.Check(_settings.MessagingService != null, "Login messaging service not provided, cannot send pin."); pin = pin ?? _settings.PinGenerator(process, factor); if (string.IsNullOrWhiteSpace(factorValue)) { factorValue = factor.FactorValue; } process.CurrentPin = pin; var logMsg = Util.SafeFormat("Pin sent to user {0} using factor '{1}'. ", factor.Login.UserName, factorValue); OnLoginEvent(session.Context, LoginEventType.PinSent, factor.Login, logMsg); await _settings.MessagingService.SendMessage(session.Context, LoginMessageType.Pin, factor, process); } session.SaveChanges(); }
public bool CheckAllSecretQuestionAnswers(ILoginProcess process, IList<SecretQuestionAnswer> answers) { var session = EntityHelper.GetSession(process); var context = session.Context; bool result = true; foreach(var qa in process.Login.SecretQuestionAnswers) { var ans = answers.FirstOrDefault(a => a.QuestionId == qa.Question.Id); if(ans == null) result = false ; //question not answered else result &= CheckUserAnswer(qa, ans.Answer); } if(!result) { process.FailCount++; session.SaveChanges(); OnLoginEvent(context, LoginEventType.QuestionAnswersFailed, process.Login, "Secret questions check failed."); LogIncident(context, LoginIncidentType, "InvalidQuestionAnswer", "Invalid answer(s) to secret questions, user: "******",", process.Login.SecretQuestionAnswers.Select(qa => qa.Number)); process.PendingFactors &= ~ExtraFactorTypes.SecretQuestions; session.SaveChanges(); OnLoginEvent(context, LoginEventType.QuestionAnswersSucceeded, process.Login, "Secret questions check succeeded."); return result; }
public void ResetPassword(ILoginProcess process, string newPassword) { var session = EntityHelper.GetSession(process); var ctx = session.Context; ctx.ThrowIf(process.Status != LoginProcessStatus.Active, ClientFaultCodes.InvalidAction, "ProcessStatus", "Process is not active."); if (process.ExpiresOn < App.TimeService.UtcNow) { process.Status = LoginProcessStatus.Expired; session.SaveChanges(); ctx.ThrowIf(true, ClientFaultCodes.InvalidAction, "Process", "Process expired."); } ctx.ThrowIf(process.ProcessType != LoginProcessType.PasswordReset, ClientFaultCodes.InvalidValue, "ProcessType", "Invalid login process type."); ctx.ThrowIf(process.PendingFactors != ExtraFactorTypes.None, ClientFaultCodes.InvalidAction, "PendingSteps", "The login process has verification steps pending"); //Ok, we're ready var login = process.Login; ChangeUserPassword(login, newPassword, oneTimeByAdmin: false); //will log incident and fire event login.Flags &= ~LoginFlags.Suspended; //clear Suspended flag //complete process process.Status = LoginProcessStatus.Completed; session.SaveChanges(); //Send notification var email = FindLoginFactor(login, ExtraFactorTypes.Email); if (email != null) { SendNotification(session.Context, LoginNotificationTypes.PasswordReset, NotificationMediaTypes.Email, email, login.UserId, new Dictionary <string, object>() { { "UserName", login.UserName } }); } }
//This metod is a default pin generator and is referenced by LoginModuleSettings.PinGenerator public static string DefaultGeneratePin(ILoginProcess process, ILoginExtraFactor factor) { switch(factor.FactorType) { case ExtraFactorTypes.Phone: case ExtraFactorTypes.Email: default: return RandomHelper.GenerateRandomString(3, CharKinds.UpperLetter) + RandomHelper.GenerateRandomString(5, CharKinds.Digit); } }
public static LoginProcess ToModel(this ILoginProcess process) { if (process == null) { return(null); } return(new LoginProcess() { Token = process.Token, CompletedFactors = process.CompletedFactors, PendingFactors = process.PendingFactors }); }
//TODO: block the IP of the originator of the process after multiple failures, build IncidentTrigger for this public void AbortPasswordReset(ILoginProcess process) { var session = EntityHelper.GetSession(process); if (process.Status != LoginProcessStatus.Active) { return; } process.Status = LoginProcessStatus.AbortedAsFraud; OnLoginEvent(session.Context, LoginEventType.ProcessAborted, process.Login, "Login process aborted"); session.SaveChanges(); }
//This metod is a default pin generator and is referenced by LoginModuleSettings.PinGenerator public static string DefaultGeneratePin(ILoginProcess process, ILoginExtraFactor factor) { switch (factor.FactorType) { case ExtraFactorTypes.Phone: return(RandomHelper.GenerateSafeRandomNumber(5)); case ExtraFactorTypes.Email: default: return(RandomHelper.GenerateSafeRandomWord(10)); } }
public Task SendMessage(OperationContext context, LoginMessageType messageType, ILoginExtraFactor factor, ILoginProcess process = null) { if (messageType == LoginMessageType.Pin) { PinMessages.Add(new PinInfoMessage() { Pin = process.CurrentPin, Email = process.CurrentFactor.FactorValue }); } return(Task.CompletedTask); }
//TODO: block the IP of the originator of the process after multiple failures, build IncidentTrigger for this public void AbortPasswordReset(ILoginProcess process) { var session = EntityHelper.GetSession(process); if(process.Status != LoginProcessStatus.Active) return; process.Status = LoginProcessStatus.AbortedAsFraud; LogIncident(session.Context, LoginIncidentType, "Aborted:" + process.ProcessType, "Process aborted, user: "******"PasswordReset", "Password reset aborted for " + process.Login.UserName, "AbortAsFraud", operationContext: session.Context); OnLoginEvent(session.Context, LoginEventType.ProcessAborted, process.Login, "Login process aborted"); session.SaveChanges(); }
//TODO: block the IP of the originator of the process after multiple failures, build IncidentTrigger for this public void AbortPasswordReset(ILoginProcess process) { var session = EntityHelper.GetSession(process); if (process.Status != LoginProcessStatus.Active) { return; } process.Status = LoginProcessStatus.AbortedAsFraud; LogIncident(session.Context, LoginIncidentType, "Aborted:" + process.ProcessType, "Process aborted, user: "******"PasswordReset", "Password reset aborted for " + process.Login.UserName, "AbortAsFraud", operationContext: session.Context); OnLoginEvent(session.Context, LoginEventType.ProcessAborted, process.Login, "Login process aborted"); session.SaveChanges(); }
protected virtual void SendPin(ILoginProcess process, ExtraFactorTypes factorType, string factor, string pin) { var session = EntityHelper.GetSession(process); string mediaType = GetMediaType(factorType); Util.CheckNotEmpty(mediaType, "Cannot send pin, unsupported factor type: {0}.", factorType); var notificationType = GetPinNotificationType(process.ProcessType); var userId = process.Login.UserId; var msg = new NotificationMessage() { Type = notificationType, MediaType = mediaType, Recipients = factor, MainRecipientUserId = userId, Culture = session.Context.UserCulture }; msg.From = _settings.DefaultEmailFrom; msg.Parameters[LoginNotificationKeys.BackHitUrlBase] = _settings.BackHitUrlBase; msg.Parameters[LoginNotificationKeys.Pin] = pin; msg.Parameters[LoginNotificationKeys.ProcessToken] = process.Token; msg.Parameters[LoginNotificationKeys.UserName] = process.Login.UserName; _notificationService.Send(session.Context, msg); }
//submit pin entered by user or from clicked URL public bool SubmitPin(ILoginProcess process, string pin) { var session = EntityHelper.GetSession(process); session.Context.ThrowIfEmpty(pin, ClientFaultCodes.InvalidAction, "Pin", "Pin may not be empty."); pin = pin.ToUpperInvariant(); session.Context.ThrowIfEmpty(process.CurrentPin, ClientFaultCodes.InvalidAction, "Pin", "Process.Pin is not expected by the login process."); session.Context.ThrowIfNull(process.CurrentFactor, ClientFaultCodes.InvalidAction, "Pin", "Current factor is not set in login process."); bool match; if (process.CurrentFactor.FactorType == ExtraFactorTypes.GoogleAuthenticator) { var secret = process.CurrentFactor.FactorValue; match = GoogleAuthenticator.GoogleAuthenticatorUtil.CheckPasscode(secret, pin); } else { match = process.CurrentPin == pin; } if (!match) { process.FailCount++; OnLoginEvent(session.Context, LoginEventType.PinMismatch, process.Login); var msg = StringHelper.SafeFormat("Invalid pin submitted, user {0}, pin: {1}", process.Login.UserName, pin); LogIncident(session.Context, LoginIncidentType, "InvalidPin", msg, process.Login, process.Login.UserName); return(false); } // Pins match; we allow repeated submits of the same pin; we nullify CurrentFactor on first successful submit, // but keep CurrentPin for future matches if (process.CurrentFactor != null) { OnLoginEvent(session.Context, LoginEventType.PinMatched, process.Login, "Pin matched, factor verified: " + process.CurrentFactor.FactorType); process.PendingFactors &= ~process.CurrentFactor.FactorType; process.CompletedFactors |= process.CurrentFactor.FactorType; if (process.ProcessType == LoginProcessType.FactorVerification) { process.CurrentFactor.VerifiedOn = App.TimeService.UtcNow; process.Status = LoginProcessStatus.Completed; } process.CurrentFactor = null; //nullify factor but keep pin } session.SaveChanges(); if (process.ProcessType == LoginProcessType.FactorVerification) { CheckLoginFactorsSetupCompleted(process.Login); } return(true); }
protected virtual void SendPin(ILoginProcess process, ExtraFactorTypes factorType, string factor, string pin) { var session = EntityHelper.GetSession(process); string mediaType = GetMediaType(factorType); Util.CheckNotEmpty(mediaType, "Cannot send pin, unsupported factor type: {0}.", factorType); var notificationType = GetPinNotificationType(process.ProcessType); var userId = process.Login.UserId; var msg = new NotificationMessage() { Type = notificationType, MediaType = mediaType, Recipients = factor, MainRecipientUserId = userId, Culture = session.Context.UserCulture }; msg.From = _settings.DefaultEmailFrom; msg.Parameters[LoginNotificationKeys.BackHitUrlBase] = _settings.BackHitUrlBase; msg.Parameters[LoginNotificationKeys.Pin] = pin; msg.Parameters[LoginNotificationKeys.ProcessToken] = process.Token; msg.Parameters[LoginNotificationKeys.UserName] = process.Login.UserName; _notificationService.Send(session.Context, msg); }
public bool CheckSecretQuestionAnswer(ILoginProcess process, ISecretQuestion question, string userAnswer) { var session = EntityHelper.GetSession(process); var qa = process.Login.SecretQuestionAnswers.FirstOrDefault(a => a.Question == question); session.Context.ThrowIfNull(qa, ClientFaultCodes.InvalidValue, "question", "The question is not registered as user question. Question: {0}", question.Question); var match = CheckUserAnswer(qa, userAnswer); if(!match) { process.FailCount++; session.SaveChanges(); OnLoginEvent(session.Context, LoginEventType.QuestionAnswersFailed, process.Login, "Secret questions check failed."); var msg = StringHelper.SafeFormat("Invalid answer to secret question '{0}'; user {1}.", question.Question, process.Login.UserName); LogIncident(session.Context, LoginIncidentType, "InvalidQuestionAnswer", msg, process.Login, process.Login.UserName); return false; } // Add question number to list of answered questions in process record. // If all questions are answered, clear Pending step CheckQuestionAnswers var strAllAnswered = process.AnsweredQuestions ?? string.Empty; var answeredNumbers = strAllAnswered.Split(',').Select(sn => sn.Trim()).ToList(); var newAnsNum = qa.Number.ToString(); if(!answeredNumbers.Contains(newAnsNum)) { answeredNumbers.Add(newAnsNum); process.AnsweredQuestions = string.Join(",", answeredNumbers); if(answeredNumbers.Count == process.Login.SecretQuestionAnswers.Count) { //Set questions as answers process.PendingFactors &= ~ExtraFactorTypes.SecretQuestions; } session.SaveChanges(); } return true; }
//submit pin entered by user or from clicked URL public bool SubmitPin(ILoginProcess process, string pin) { var session = EntityHelper.GetSession(process); session.Context.ThrowIfEmpty(pin, ClientFaultCodes.InvalidAction, "Pin", "Pin may not be empty."); pin = pin.ToUpperInvariant(); session.Context.ThrowIfEmpty(process.CurrentPin, ClientFaultCodes.InvalidAction, "Pin", "Process.Pin is not expected by the login process."); session.Context.ThrowIfNull(process.CurrentFactor, ClientFaultCodes.InvalidAction, "Pin", "Current factor is not set in login process."); bool match; if (process.CurrentFactor.FactorType == ExtraFactorTypes.GoogleAuthenticator) { var secret = process.CurrentFactor.Info.DecryptString(_settings.EncryptionChannelName); match = GoogleAuthenticator.GoogleAuthenticatorUtil.CheckPasscode(secret, pin); } else { match = process.CurrentPin == pin; } if(!match) { process.FailCount++; OnLoginEvent(session.Context, LoginEventType.PinMismatch, process.Login); var msg = StringHelper.SafeFormat("Invalid pin submitted, user {0}, pin: {1}", process.Login.UserName, pin); LogIncident(session.Context, LoginIncidentType, "InvalidPin", msg, process.Login, process.Login.UserName); return false; } // Pins match; we allow repeated submits of the same pin; we nullify CurrentFactor on first successful submit, // but keep CurrentPin for future matches if(process.CurrentFactor != null) { OnLoginEvent(session.Context, LoginEventType.PinMatched, process.Login, "Pin matched, factor verified: " + process.CurrentFactor.FactorType); process.PendingFactors &= ~process.CurrentFactor.FactorType; process.CompletedFactors |= process.CurrentFactor.FactorType; if(process.ProcessType == LoginProcessType.FactorVerification) { process.CurrentFactor.VerifiedOn = App.TimeService.UtcNow; process.Status = LoginProcessStatus.Completed; } process.CurrentFactor = null; //nullify factor but keep pin } session.SaveChanges(); if(process.ProcessType == LoginProcessType.FactorVerification) CheckLoginFactorsSetupCompleted(process.Login); return true; }
public string GeneratePin(ILoginProcess process, ILoginExtraFactor factor) { return(_settings.PinGenerator(process, factor)); }
public void SendPin(ILoginProcess process, ILoginExtraFactor factor, string factorValue = null, string pin = null) { Util.Check(process.Login == factor.Login, "LoginProcess tries to use login factor not assigned to process's login."); var session = EntityHelper.GetSession(process); var activeAlready = GetActiveConfirmationProcesses(factor, LoginProcessType.FactorVerification); session.Context.ThrowIf(activeAlready.Count > 2, ClientFaultCodes.InvalidAction, "Factor", "Cannot send pin - several pins already sent and are waiting for confirmation."); process.CurrentFactor = factor; if (factor.FactorType == ExtraFactorTypes.GoogleAuthenticator) { //do not generate or send pin } else { pin = pin ?? _settings.PinGenerator(process, factor); if (string.IsNullOrWhiteSpace(factorValue)) factorValue = factor.Info.DecryptString(_settings.EncryptionChannelName); process.CurrentPin = pin; SendPin(process, factor.FactorType, factorValue, pin); } session.SaveChanges(); var logMsg = StringHelper.SafeFormat("Pin sent to user {0} using factor '{1}'. ", factor.Login.UserName, factorValue); OnLoginEvent(session.Context, LoginEventType.PinSent, factor.Login, logMsg); }
public void ResetPassword(ILoginProcess process, string newPassword) { var session = EntityHelper.GetSession(process); var ctx = session.Context; ctx.ThrowIf(process.Status != LoginProcessStatus.Active, ClientFaultCodes.InvalidAction, "ProcessStatus", "Process is not active."); if(process.ExpiresOn < App.TimeService.UtcNow) { process.Status = LoginProcessStatus.Expired; session.SaveChanges(); ctx.ThrowIf(true, ClientFaultCodes.InvalidAction, "Process", "Process expired."); } ctx.ThrowIf(process.ProcessType != LoginProcessType.PasswordReset, ClientFaultCodes.InvalidValue, "ProcessType", "Invalid login process type."); ctx.ThrowIf(process.PendingFactors != ExtraFactorTypes.None, ClientFaultCodes.InvalidAction, "PendingSteps", "The login process has verification steps pending"); //Ok, we're ready var login = process.Login; ChangeUserPassword(login, newPassword, oneTimeByAdmin: false); //will log incident and fire event login.Flags &= ~LoginFlags.Suspended; //clear Suspended flag //complete process process.Status = LoginProcessStatus.Completed; session.SaveChanges(); //Send notification var email = FindLoginFactor(login, ExtraFactorTypes.Email); if(email != null) SendNotification(session.Context, LoginNotificationTypes.PasswordReset, NotificationMediaTypes.Email, email, login.UserId, new Dictionary<string, object>() { { "UserName", login.UserName }}); }
public string GeneratePin(ILoginProcess process, ILoginExtraFactor factor) { return _settings.PinGenerator(process, factor); }