Example #1
0
        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."]));
        }
Example #2
0
        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."]));
            }
        }
Example #3
0
        public async Task <WebStatus> SignOutAsync()
        {
            await _signInService.SignOutAsync();

            return(WebStatus.Success());
        }
Example #4
0
        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."]));
            }
        }