示例#1
0
        public async Task <WebStatus> AuthenticateLongCodeAsync(string longCode)
        {
            _logger.LogDebug("Begin long code (one time link) authentication");

            var response = await _oneTimeCodeService.CheckOneTimeCodeAsync(longCode, _httpContext.Request.GetClientNonce());

            switch (response.Status.StatusCode)
            {
            case CheckOneTimeCodeStatusCode.VerifiedWithoutNonce:
            case CheckOneTimeCodeStatusCode.VerifiedWithNonce:
                await _eventNotificationService.NotifyEventAsync(response.Result.SentTo, EventType.SignInSuccess, SignInType.LongCode.ToString());

                var nonceWasValid = response.Status.StatusCode == CheckOneTimeCodeStatusCode.VerifiedWithNonce;
                return(await SignInAndRedirectAsync(SignInMethod.Link, response.Result.SentTo, null, response.Result.RedirectUrl, nonceWasValid));

            case CheckOneTimeCodeStatusCode.Expired:
                await _eventNotificationService.NotifyEventAsync(response.Result.SentTo, EventType.SignInFail, SignInType.LongCode.ToString());

                return(Unauthenticated(_localizer["The sign in link expired."]));

            case CheckOneTimeCodeStatusCode.CodeIncorrect:
                return(WebStatus.Error(_localizer["Not found."], HttpStatusCode.NotFound));

            case CheckOneTimeCodeStatusCode.NotFound:
                return(Unauthenticated(_localizer["The sign in link is invalid."]));

            case CheckOneTimeCodeStatusCode.ServiceFailure:
            default:
                return(ServerError(_localizer["Something went wrong."]));
            }
        }
示例#2
0
        private void brsMain_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            WebStatus temp = FillHelper.GetPageStatus(brsMain);

            if (CurrentStatus != temp)
            {
                CurrentStatus = temp;
                WebStatusChange(this, null);
            }
        }
示例#3
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."]));
        }
 public static JsonResult ToJsonResult(this WebStatus status)
 {
     return(new JsonResult(new {
         Message = status.Text,
         Messages = status.Messages,
         HasError = status.HasError,
         IsOK = status.IsOk
     })
     {
         StatusCode = (int)status.StatusCode
     });
 }
        public static WebStatus CheckQueryAvailability()
        {
            WebStatus result = WebStatus.InternetAvailable;

            if (!NetworkConnection.IsInternetAvailable())
            {
                result = WebStatus.NoInternet;
            }
            else if (NetworkConnection.IsInternetOnMeteredConnection())
            {
                result = WebStatus.MeteredInternet;
            }
            return(result);
        }
        public static WebStatus GetStatus(this ModelStateDictionary modelState, HttpStatusCode errorStatusCode = HttpStatusCode.BadRequest)
        {
            var status = new WebStatus();

            foreach (var error in modelState.Values.SelectMany(modelStateEntry => modelStateEntry.Errors))
            {
                status.AddError(error.ErrorMessage ?? "Internal Server Error"); // if an exception, don't leak the potentially sensitive details
            }

            if (status.HasError)
            {
                status.StatusCode = errorStatusCode;
            }
            return(status);
        }
示例#7
0
        private void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            if (browser.ReadyState == WebBrowserReadyState.Complete)
            {
                string strCookie = browser.Document.Cookie;
                if (string.IsNullOrEmpty(strCookie))
                {
                    return;
                }
                if (browser.Url.ToString() == hostWithHead + webinfo.connectVerUrl)
                {
                    if (currStatus == WebStatus.NoIn)
                    {
                        CookieContainer.SetCookies(new Uri(hostWithHead), strCookie);
                        currStatus = WebStatus.InHost;
                        browser.Navigate(loginPageUrl);
                        return;
                    }
                }

                string       body   = browser.Document.Body.OuterHtml;
                string       key    = webinfo.cookieKey;
                string       keyval = null;
                HtmlTagClass htc    = HtmlTagClass.getTagInfo(body, key);
                if (htc == null)
                {
                    return;
                }


                cookieContainer.SetCookies(new Uri(hostWithHead), getCookie(htc.KeyValue, htc.AttValue, "/"));

                string[] arr = strCookie.Split(';');
                for (int i = 0; i < arr.Length; i++)
                {
                    string[] carr = arr[i].Split('=');
                    CookieContainer.SetCookies(new Uri(hostWithHead), getCookie(carr[0], carr.Length == 1 ? "" : carr[1], "/"));
                }
                if (browser.Url.ToString() == loginPageUrl)
                {
                    if (currStatus == WebStatus.InHost)
                    {
                        currStatus = WebStatus.InLogin;
                    }
                }
                //Login(gc.ClientUserName, gc.ClientPassword, null);
            }
        }
示例#8
0
        private void InitializeProperties()
        {
            CurrentStatus            = WebStatus.NotReady;
            cbbUriType.SelectedIndex = 0;
            BaseURI                          = NormalURI;
            lblUrlType.Text                  = "正常网址 |";
            btnInput.Enabled                 = false;
            btnExamFirst.Enabled             = false;
            btnExamSecond.Enabled            = false;
            cbbUriType.SelectedIndexChanged += new EventHandler(cbbUriType_SelectedIndexChanged);
            this.WebStatusChange            += new EventHandler(frmMain_WebStatusChange);
            pgbFinish.Maximum                = 1;
            pgbFinish.Minimum                = 0;

            FillHelper.FillCountChange += new FillUtility.FillCountEventHandle(FillHelper_FillCountChange);
        }
示例#9
0
        private void InitializeProperties()
        {
            CurrentStatus = WebStatus.NotReady;
            cbbUriType.SelectedIndex = 0;
            BaseURI = NormalURI;
            lblUrlType.Text = "正常网址 |";
            btnInput.Enabled = false;
            btnExamFirst.Enabled = false;
            btnExamSecond.Enabled = false;
            cbbUriType.SelectedIndexChanged += new EventHandler(cbbUriType_SelectedIndexChanged);
            this.WebStatusChange += new EventHandler(frmMain_WebStatusChange);
            pgbFinish.Maximum = 1;
            pgbFinish.Minimum = 0;

            FillHelper.FillCountChange += new FillUtility.FillCountEventHandle(FillHelper_FillCountChange);
        }
示例#10
0
        public async Task <Response <ChangeEmailViewModel, WebStatus> > CancelEmailAddressChangeAsync(string longCode)
        {
            _logger.LogTrace("Cancel email address change");

            var response = await _oneTimeCodeService.CheckOneTimeCodeAsync(longCode, null);

            if (response.Status.StatusCode != CheckOneTimeCodeStatusCode.VerifiedWithoutNonce) // TODO: investigate if VerifiedWithNonce is possible and valid
            {
                return(Response.Web.Error <ChangeEmailViewModel>(_localizer["Invalid code."], HttpStatusCode.BadRequest));
            }
            var userResponse = await _userStore.GetUserByPreviousEmailAsync(response.Result.SentTo);

            if (userResponse.HasError)
            {
                return(Response.Web.Error <ChangeEmailViewModel>(_localizer["User not found."], HttpStatusCode.BadRequest));
            }
            var user = userResponse.Result;

            var changes = new Dictionary <string, string>
            {
                ["email"] = response.Result.SentTo,
                [PasswordlessLoginConstants.Security.PreviousEmailClaimType] = null
            };
            var updateUserResponse = await _userStore.PatchUserAsync(user.SubjectId, changes.ToLookup(x => x.Key, x => x.Value), true);

            if (updateUserResponse.HasError)
            {
                var patchStatus = new WebStatus(updateUserResponse.Status);
                patchStatus.StatusCode = HttpStatusCode.BadRequest;
                return(new Response <ChangeEmailViewModel, WebStatus>(patchStatus));
            }
            var updatedUser = updateUserResponse.Result;
            var viewModel   = new ChangeEmailViewModel()
            {
                OldEmail = user.Email,
                NewEmail = updatedUser.Email,
            };
            await _eventNotificationService.NotifyEventAsync(viewModel.OldEmail, EventType.CancelEmailChange, $"Reverted to {viewModel.NewEmail}");

            return(Response.Web.Success(viewModel, _localizer["Email address change has been reverted."]));
        }
示例#11
0
        public async Task <IActionResult> Index()
        {
            var dbStatus = await StatusService.GetDbReport();

            var activityStats = await UsersActivityStats.CreateAsync(DiscordClient);

            var data = new WebStatus()
            {
                Simple           = StatusService.GetSimpleStatus(),
                ExecutedCommands = InternalStatistics.GetCommands(),
                DBStatus         = dbStatus,
                LoggerStats      = StatusService.GetLoggerStats(),
                TriggeredEvents  = InternalStatistics.GetEvents(),
                Latency          = DiscordClient.Latency,
                ConnectionState  = DiscordClient.ConnectionState,
                LoginState       = DiscordClient.LoginState,
                BotUser          = DiscordClient.CurrentUser,
                ActivityStats    = activityStats
            };

            return(View(data));
        }
示例#12
0
        public async Task <Response <User, WebStatus> > PatchUserAsync(PatchUserModel model)
        {
            _logger.LogTrace("Patch user {0}", _httpContext.User.GetSubjectId());

            var userResponse = await GetUserAsync();

            if (userResponse.HasError)
            {
                return(userResponse);
            }
            var updateUserResponse = await _userStore.PatchUserAsync(userResponse.Result.SubjectId, model.Properties);

            if (updateUserResponse.HasError)
            {
                var status = new WebStatus(updateUserResponse.Status);
                status.StatusCode = HttpStatusCode.BadRequest;
                return(new Response <User, WebStatus>(status));
            }
            var updatedUser = updateUserResponse.Result;
            await _eventNotificationService.NotifyEventAsync(updatedUser.Email, EventType.UpdateAccount);

            return(Response.Web.Success(updatedUser));
        }
示例#13
0
 private WebStatus Unauthenticated(string message = null)
 {
     return(WebStatus.Error(message, HttpStatusCode.Unauthorized));
 }
示例#14
0
 private void brsMain_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
 {
     WebStatus temp= FillHelper.GetPageStatus(brsMain);
     if (CurrentStatus != temp)
     {
         CurrentStatus = temp;
         WebStatusChange(this, null);
     }
 }         
示例#15
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."]));
            }
        }
示例#16
0
        public async Task <WebStatus> SignOutAsync()
        {
            await _signInService.SignOutAsync();

            return(WebStatus.Success());
        }
示例#17
0
        private async Task <WebStatus> SignInAndRedirectAsync(SignInMethod method, string username, bool?staySignedIn, string nextUrl, bool?nonceWasValid)
        {
            _logger.LogTrace("Begining sign in and redirect logic for {0}", username);

            /*
             * Do they have a partial sign in, and this was the second credential (code + password) or (password + code) [= 2 of 3]
             *  Yes, skip to SIGN IN
             * Is this an trusted browser? [= 2 of 3]
             *  Yes, skip to SIGN IN
             * Do they have NO trusted browsers? [= new account or someone who deleted all trusted browsers, 1 of 1|2]
             *  Yes, skip to SIGN IN
             * Is 2FA enabled? (they have a password set and have chosen to require it when trusting a new browser)
             *  Yes. Do partial sign in and prompt for the relevant second factor (automatically mailed code or password)
             * Used password to sign in? [= 1 of 2]
             *  Yes, skip to SIGN IN
             * NonceWasValid [= 1 of 1|2]
             *  No, back to  sign in screen [prevents someone who passively observing code in transit/storage from using it undetected]
             * (local) SIGN IN
             * Is this a new browser?
             *  Yes, redirect to TRUST NEW BROWSER
             * Is their account set to prompt them to choose a password
             *  Yes, redirect to SET PASSWORD
             * Is their account set to prompt them to enable 2FA?
             *  Yes, redirect to ENABLE 2FA
             * Has the application they are signing in to (if any) have required claims that have not been set?
             *  Yes, redirect to COMPLETE PROFILE
             * Is their account set to prompt them to see/acknowledge any other screen?
             *  Yes, redirect SOMEWHERE
             * FULL SIGN IN AND REDIRECT back to app (or to default post login page, apps page if none)
             *
             */
            //do 2FA and figure out new browser, etc here

            var userResponse = await _userStore.GetUserByUsernameAsync(username, true);

            if (userResponse.HasError)
            {
                // this could only happen if an account was removed, but a method of signing in remained
                _logger.LogError("Strangely, there is no account for {0} anymore", username);
                return(Unauthenticated(_localizer["Account not found."]));
            }

            var user = userResponse.Result;
            var addTrustForThisBrowser = false;
            var trustedBrowserResponse = await _trustedBrowserStore.GetTrustedBrowserAsync(user.SubjectId, _httpContext.Request.GetBrowserId());

            var browserIsTrusted = trustedBrowserResponse.IsOk;

            var authMethod  = (method == SignInMethod.Password) ? "pwd" : "otp";
            var authMethods = new string[] { authMethod };

            if (browserIsTrusted)
            {
                authMethods.Append("cookie");
            }
            if (_httpContext.User.Identity.IsAuthenticated && _httpContext.User.GetSubjectId() == user.SubjectId)
            {
                var previousAuthMethods = _httpContext.User.GetClaimValues("amr");
                if (previousAuthMethods.Any() && !previousAuthMethods.Contains(authMethod))
                {
                    // If already signed in and now the user has authenticated with another
                    // type of credential, this is now a multi-factor session
                    authMethods = authMethods.Append("mfa").ToArray();
                    authMethods = previousAuthMethods.Union(authMethods).ToArray();
                    if (!browserIsTrusted)
                    {
                        _logger.LogInformation("Trusting browser because {0} performed multi-factor authentication", username);
                        addTrustForThisBrowser = true;
                    }
                }
            }

            var anyTrustedBrowsers = true;

            if (!browserIsTrusted && !addTrustForThisBrowser)
            {
                _logger.LogDebug("Browser user is signing in from is not trusted.");
                var trustedBrowsersResponse = await _trustedBrowserStore.GetTrustedBrowserAsync(user.SubjectId);

                anyTrustedBrowsers = trustedBrowsersResponse.Result?.Any() ?? false;
                if (anyTrustedBrowsers)
                {
                    _logger.LogDebug("User has other browsers that are trusted.");
                    // todo: if SecurityLevel == High OR first thing was a password, do a partial sign in and prompt for second thing

                    if ((method == SignInMethod.OneTimeCode || method == SignInMethod.Link) && _options.NonceRequiredOnUntrustedBrowser && nonceWasValid == false)
                    {
                        _logger.LogWarning("Client nonce was missing or invalid. Perhaps the one time code has " +
                                           "been intercepted and an unathorized party is trying to user it. Authentication blocked.");
                        return(Unauthenticated(_localizer["Your one time code has expired. Please request a new one."]));
                    }
                }
                else if (method == SignInMethod.Link || method == SignInMethod.OneTimeCode)
                {
                    // If no trusted browsers, trust the first browser that is used to verify a one time code
                    _logger.LogInformation("Trusting first browser used by {0}", username);
                    addTrustForThisBrowser = true;
                }
                else
                {
                    _logger.LogDebug("User does not have any browsers that are trusted.");
                }
            }

            if (_options.AutoTrustBrowsers && addTrustForThisBrowser)
            {
                var description          = _httpContext.Request.Headers["User-Agent"];
                var trustBrowserResponse = await TrustBrowserAsync(user.SubjectId, description);

                if (trustBrowserResponse.IsOk)
                {
                    browserIsTrusted   = true;
                    anyTrustedBrowsers = true;
                }
            }

            var authProps = new AuthenticationProperties {
                AllowRefresh = true,
                IsPersistent = false,
            };

            if (staySignedIn == true || (method == SignInMethod.Link && browserIsTrusted))
            {
                _logger.LogTrace("Using maximum session length of {0} minutes", _options.MaxSessionLengthMinutes);
                authProps.IsPersistent = true;
                authProps.ExpiresUtc   = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(_options.MaxSessionLengthMinutes));
            }
            else
            {
                _logger.LogTrace("Using default session length of {0} minutes", _options.DefaultSessionLengthMinutes);
            }

            if (method == SignInMethod.Link || method == SignInMethod.OneTimeCode)
            {
                // remove email unconfirmed claim, if present
                var removeClaims = new Dictionary <string, string>()
                {
                    { PasswordlessLoginConstants.Security.EmailNotConfirmedClaimType, null }
                }.ToLookup(x => x.Key, x => x.Value);
                await _userStore.PatchUserAsync(user.SubjectId, removeClaims, true);
            }

            _logger.LogDebug("Signing user in: {0}", user.Email);
            _logger.LogTrace("SubjectId: {0}", user.SubjectId);

            var userName = _options.IdpUserNameClaim == "email"
                ? user.Email
                : user.Claims.FirstOrDefault(x => x.Type == _options.IdpUserNameClaim)?.Value;

            userName = userName ?? user.Email;

            var userClaims = user.Claims
                             .Where(c => _options.IdpUserClaims.Contains(c.Type))
                             .Select(c => new Claim(c.Type, c.Value))
                             .ToArray();

            if (_options.IdpUserClaims.Contains("email") && !userClaims.Any(c => c.Type == "email"))
            {
                userClaims = userClaims.Append(new Claim("email", user.Email)).ToArray();
            }

            await _signInService.SignInAsync(user.SubjectId, userName, authMethods, authProps, userClaims);

            nextUrl = ValidatedNextUrl(nextUrl);
            _logger.LogDebug("Redirecting user to: {0}", nextUrl);
            return(WebStatus.Redirect(nextUrl));
        }
示例#18
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."]));
            }
        }
示例#19
0
        public async Task <Response <ChangeEmailViewModel, WebStatus> > ChangeEmailAddressAsync(
            string newEmail, string applicationId = null)
        {
            _logger.LogTrace("Change email for user {0} to {1}", _httpContext.User.GetSubjectId(), newEmail);

            var userResponse = await GetUserAsync();

            if (userResponse.HasError)
            {
                return(Response.Web.Error <ChangeEmailViewModel>("User not found.", HttpStatusCode.NotFound));
            }
            var user = userResponse.Result;

            // Check if a cancel email change code link is still valid. Note that the cancel email change code link does
            // not interfere with getting a one time code to sign in because it is associated with the OLD email address.
            var previouslyChangedEmail = user.Claims.FirstOrDefault(x => x.Type == PasswordlessLoginConstants.Security.PreviousEmailClaimType)?.Value;

            if (previouslyChangedEmail != null && (await _oneTimeCodeService.UnexpiredOneTimeCodeExistsAsync(previouslyChangedEmail)))
            {
                return(Response.Web.Error <ChangeEmailViewModel>(_localizer["You cannot change your email address again until the link to cancel the last email change (sent to your old email address) expires."], HttpStatusCode.Forbidden));
            }
            var usernameAvailableStatus = await UsernameIsReallyAvailableAsync(newEmail, user.SubjectId);

            if (usernameAvailableStatus.HasError)
            {
                return(Response.Web.Error <ChangeEmailViewModel>(_localizer["Username is not available."], HttpStatusCode.Conflict));
            }
            var oldEmail = user.Email;

            if (_passwordlessLoginOptions.SendCancelEmailChangeMessage)
            {
                var otcResponse = await _oneTimeCodeService.GetOneTimeCodeAsync(oldEmail, TimeSpan.FromHours(_passwordlessLoginOptions.CancelEmailChangeTimeWindowHours));

                var status = await _messageService.SendEmailChangedNoticeAsync(applicationId, oldEmail, otcResponse.Result.LongCode);

                if (status.HasError)
                {
                    // TODO: review to ensure that this will not prevent changing one's email address if the
                    // old address is undeliverable
                    return(Response.Web.Error <ChangeEmailViewModel>($"{_localizer["Change cancelled because of failure to send email notice:"]} {status.Text}"));
                }
            }

            var changes = new Dictionary <string, string>
            {
                ["email"] = newEmail,
                [PasswordlessLoginConstants.Security.PreviousEmailClaimType] = oldEmail
            };
            var updateUserResponse = await _userStore.PatchUserAsync(user.SubjectId, changes.ToLookup(x => x.Key, x => x.Value), true);

            if (updateUserResponse.HasError)
            {
                var patchStatus = new WebStatus(updateUserResponse.Status);
                patchStatus.StatusCode = HttpStatusCode.BadRequest;
                return(new Response <ChangeEmailViewModel, WebStatus>(patchStatus));
            }
            var updatedUser = updateUserResponse.Result;
            var viewModel   = new ChangeEmailViewModel()
            {
                OldEmail = user.Email,
                NewEmail = updatedUser.Email,
            };
            await _eventNotificationService.NotifyEventAsync(viewModel.OldEmail, EventType.EmailChange, $"Changed to {viewModel.NewEmail}");

            return(Response.Web.Success(viewModel));
        }
示例#20
0
 private WebStatus ServerError(string message = null)
 {
     return(WebStatus.Error(message, HttpStatusCode.InternalServerError));
 }