public async Task <WebStatus> SendPasswordResetMessageAsync(SendPasswordResetMessageInputModel model) { _logger.LogDebug("Begin send password reset message for {0}", model.Username); if (!ApplicationIdIsNullOrValid(model.ApplicationId)) { return(WebStatus.Error(_localizer["Invalid application id."], HttpStatusCode.BadRequest)); } var genericMessage = _localizer["Check your email for password reset instructions."]; var userResponse = await _userStore.GetUserByUsernameAsync(model.Username); if (userResponse.HasError) { _logger.LogInformation("User not found: {0}", model.Username); // if valid email or phone number, send a message inviting them to register if (model.Username.Contains("@")) { var status = await _messageService.SendAccountNotFoundMessageAsync(model.ApplicationId, model.Username); // NOTE: ignoring status, since it doesn't matter whether it succeeded or failed await _eventNotificationService.NotifyEventAsync(model.Username, EventType.AccountNotFound, nameof(SendPasswordResetMessageAsync)); } else { _logger.LogInformation("Account not found message was not sent because provided username is not an email address: {0}", model.Username); } return(WebStatus.Success(genericMessage)); } var user = userResponse.Result; var nextUrl = SendToSetPasswordFirst(!string.IsNullOrEmpty(model.NextUrl) ? model.NextUrl : _urlService.GetDefaultRedirectUrl()); var oneTimeCodeResponse = await _oneTimeCodeService.GetOneTimeCodeAsync( user.Email, TimeSpan.FromMinutes(_options.OneTimeCodeValidityMinutes), nextUrl); if (oneTimeCodeResponse.IsOk) { var status = await _messageService.SendPasswordResetMessageAsync(model.ApplicationId, user.Email, oneTimeCodeResponse.Result.ShortCode, oneTimeCodeResponse.Result.LongCode); await _eventNotificationService.NotifyEventAsync(user.Email, EventType.RequestPasswordReset); if (status.IsOk) { status = Status.Success(genericMessage); } SetNonce(oneTimeCodeResponse.Result.ClientNonce); return(new WebStatus(status)); } _logger.LogError("Password reset message was not sent due to error encountered while generating a one time link."); return(ServerError(_localizer["Hmm. Something went wrong. Please try again."])); }
public async Task <WebStatus> RegisterAsync(RegisterInputModel model) { var genericSuccessMessage = _localizer["Please check your email to complete the sign up process."]; _logger.LogDebug("Begin registration for {0}", model.Email); if (!ApplicationIdIsNullOrValid(model.ApplicationId)) { return(WebStatus.Error(_localizer["Invalid application id."], HttpStatusCode.BadRequest)); } if (!await _userStore.UsernameIsAvailable(model.Email)) { _logger.LogDebug("Existing user found."); var sendStatus = await _messageService.SendAccountAlreadyExistsMessageAsync(model.ApplicationId, model.Email); // Return a generic success message to prevent account discovery. Only the owner of // the email address will get a customized message. return(sendStatus.IsOk ? WebStatus.Success(genericSuccessMessage) : new WebStatus(sendStatus)); } if (await _oneTimeCodeService.UnexpiredOneTimeCodeExistsAsync(model.Email)) { // Although the username is available, there is a valid one time code // that can be used to cancel an email address change, so we can't // reuse the address quite yet return(WebStatus.Error(_localizer["Email address is temporarily reserved."], HttpStatusCode.Conflict)); } _logger.LogDebug("Email address not used by an existing user. Creating a new user."); // consider: in some cases may want to restricting claims to a list predefined by the system administrator var status = new WebStatus(); if (model.Claims == null) { model.Claims = new Dictionary <string, string>(); } var internalClaims = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>( PasswordlessLoginConstants.Security.EmailNotConfirmedClaimType, "!") }; var newUser = new User() { Email = model.Email, Claims = model.Claims .Where(x => !PasswordlessLoginConstants.Security.ForbiddenClaims.Contains(x.Key) && !PasswordlessLoginConstants.Security.ProtectedClaims.Contains(x.Key)) .Union(internalClaims) .Select(x => new UserClaim() { Type = x.Key, Value = x.Value }) }; var newUserResponse = await _userStore.AddUserAsync(newUser); if (newUserResponse.HasError) { return(new WebStatus(newUserResponse.Status)); } newUser = newUserResponse.Result; await _eventNotificationService.NotifyEventAsync(newUser.Email, EventType.Register); if (!string.IsNullOrEmpty(model.Password)) { var setPasswordStatus = await _passwordService.SetPasswordAsync(newUser.SubjectId, model.Password); if (setPasswordStatus.IsOk) { await _eventNotificationService.NotifyEventAsync(newUser.Email, EventType.SetPassword); } else { status.AddWarning(_localizer["Password was not set."]); } } status.AddSuccess(genericSuccessMessage); var nextUrl = !string.IsNullOrEmpty(model.NextUrl) ? model.NextUrl : _urlService.GetDefaultRedirectUrl(); if (model.SetPassword) { _logger.LogTrace("The user will be asked to set their password after confirming the account."); nextUrl = SendToSetPasswordFirst(nextUrl); } var oneTimeCodeResponse = await _oneTimeCodeService.GetOneTimeCodeAsync(model.Email, TimeSpan.FromMinutes(_options.ConfirmAccountLinkValidityMinutes), nextUrl); switch (oneTimeCodeResponse.Status.StatusCode) { case GetOneTimeCodeStatusCode.Success: var result = await _messageService.SendWelcomeMessageAsync(model.ApplicationId, model.Email, oneTimeCodeResponse.Result.ShortCode, oneTimeCodeResponse.Result.LongCode, model.Claims); SetNonce(oneTimeCodeResponse.Result.ClientNonce); return(new WebStatus(status)); case GetOneTimeCodeStatusCode.TooManyRequests: return(WebStatus.Error(_localizer["Please wait a few minutes and try again."], HttpStatusCode.TooManyRequests)); case GetOneTimeCodeStatusCode.ServiceFailure: default: return(ServerError(_localizer["Hmm. Something went wrong. Please try again."])); } }
public async Task <WebStatus> SignOutAsync() { await _signInService.SignOutAsync(); return(WebStatus.Success()); }
public async Task <WebStatus> SendOneTimeCodeAsync(SendCodeInputModel model) { _logger.LogDebug("Begin send one time code for {0}", model.Username); if (!ApplicationIdIsNullOrValid(model.ApplicationId)) { return(WebStatus.Error(_localizer["Invalid application id."], HttpStatusCode.BadRequest)); } // Note: Need to keep messages generic as to not reveal whether an account exists or not. var usernameIsValidEmail = EmailAddressChecker.EmailIsValid(model.Username); var defaultMessage = usernameIsValidEmail ? _localizer["Message sent. Please check your email."] : _localizer["We sent a code to the email address asociated with your account (if found). Please check your email."]; // If the username provide is not an email address or phone number, tell the user "we sent you a code if you have an account" var userResponse = await _userStore.GetUserByUsernameAsync(model.Username); if (userResponse.HasError) { _logger.LogDebug("User not found"); if (!usernameIsValidEmail) { _logger.LogError("No valid email address found for user {0}", model.Username); return(WebStatus.Success(defaultMessage)); // generic message prevent account enumeration } var result = await _messageService.SendAccountNotFoundMessageAsync(model.ApplicationId, model.Username); if (result.HasError) { return(ServerError(result.Text)); } // the fact that the user doesn't have an account is communicated privately via email await _eventNotificationService.NotifyEventAsync(model.Username, EventType.AccountNotFound); return(WebStatus.Success(defaultMessage)); // generic message prevent account enumeration } var user = userResponse.Result; _logger.LogDebug("User found"); if (!EmailAddressChecker.EmailIsValid(user.Email)) { _logger.LogError("No valid email address found for user {0}", model.Username); return(WebStatus.Success(defaultMessage)); // generic message prevent account enumeration } var oneTimeCodeResponse = await _oneTimeCodeService.GetOneTimeCodeAsync( user.Email, TimeSpan.FromMinutes(_options.OneTimeCodeValidityMinutes), model.NextUrl); switch (oneTimeCodeResponse.Status.StatusCode) { case GetOneTimeCodeStatusCode.Success: var status = await _messageService.SendOneTimeCodeAndLinkMessageAsync(model.ApplicationId, model.Username, oneTimeCodeResponse.Result.ShortCode, oneTimeCodeResponse.Result.LongCode); await _eventNotificationService.NotifyEventAsync(model.Username, EventType.RequestOneTimeCode); if (status.IsOk) { status = Status.Success(defaultMessage); } SetNonce(oneTimeCodeResponse.Result.ClientNonce); return(new WebStatus(status)); case GetOneTimeCodeStatusCode.TooManyRequests: return(WebStatus.Error(_localizer["Please wait a few minutes before requesting a new code."], HttpStatusCode.TooManyRequests)); case GetOneTimeCodeStatusCode.ServiceFailure: default: return(ServerError(_localizer["Hmm. Something went wrong. Please try again."])); } }