public async Task<bool> RenderAsync(string name, object model, IOwinEnvironment context, CancellationToken cancellationToken)
        {
            var httpContext = context.Request[MicrosoftHttpContextKey] as HttpContext;
            if (httpContext == null)
            {
                _logger.Error($"Request dictionary does not contain '{MicrosoftHttpContextKey}'", nameof(RazorViewRenderer));
                return false;
            }

            // TODO ideally this would be done by the existing middleware pipeline
            // if authentication and view rendering were split up
            GetUserIdentity(httpContext, _logger);

            var actionContext = GetActionContext(httpContext);

            ViewEngineResult viewEngineResult;
            if (IsApplicationRelativePath(name))
            {
                var basePath = Directory.GetCurrentDirectory();
                _logger.Trace($"Getting view '{name}' relative to '{basePath}'");
                viewEngineResult = _viewEngine.GetView(basePath, name, true);
            }
            else
            {
                viewEngineResult = _viewEngine.FindView(actionContext, name, true);
            }

            if (!viewEngineResult.Success)
            {
                _logger.Trace($"Could not find Razor view '{name}'", nameof(RazorViewRenderer));
                return false;
            }

            var view = viewEngineResult.View;

            using (var writer = new StreamWriter(context.Response.Body))
            {
                var viewDataDictionary = new ViewDataDictionary(
                    new EmptyModelMetadataProvider(),
                    new ModelStateDictionary())
                {
                    Model = model
                };

                var viewContext = new ViewContext(
                    actionContext,
                    view,
                    viewDataDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    writer,
                    new HtmlHelperOptions());

                cancellationToken.ThrowIfCancellationRequested();
                await view.RenderAsync(viewContext);

                return true;
            }
        }
        public Task <bool> HandleRedirectAsync(IOwinEnvironment context, string nextUri = null)
        {
            // Use the provided next URI, or default to stormpath.web.login.nextUri
            var parsedNextUri = string.IsNullOrEmpty(nextUri)
                ? new Uri(_configuration.Web.Login.NextUri, UriKind.Relative)
                : new Uri(nextUri, UriKind.RelativeOrAbsolute);

            // Ensure this is a relative URI
            var nextLocation = parsedNextUri.IsAbsoluteUri
                ? parsedNextUri.PathAndQuery
                : parsedNextUri.OriginalString;

            return(HttpResponse.Redirect(context, nextLocation));
        }
        protected override async Task <bool> GetHtmlAsync(IOwinEnvironment context, IClient client, CancellationToken cancellationToken)
        {
            var queryString = QueryStringParser.Parse(context.Request.QueryString, _logger);

            var stateToken       = queryString.GetString("state");
            var parsedStateToken = new StateTokenParser(client, _configuration.Client.ApiKey, stateToken, _logger);

            if (!parsedStateToken.Valid)
            {
                _logger.Warn("State token was invalid", nameof(GoogleCallbackRoute));
                return(await HttpResponse.Redirect(context, SocialExecutor.CreateErrorUri(_configuration.Web.Login, stateToken: null)));
            }

            var code = queryString.GetString("code");

            if (string.IsNullOrEmpty(code))
            {
                _logger.Warn("Social code was empty", nameof(GithubCallbackRoute));
                return(await HttpResponse.Redirect(context, SocialExecutor.CreateErrorUri(_configuration.Web.Login, stateToken)));
            }

            var application = await client.GetApplicationAsync(_configuration.Application.Href, cancellationToken);

            var socialExecutor = new SocialExecutor(client, _configuration, _handlers, _logger);

            try
            {
                var providerRequest = client.Providers()
                                      .Google()
                                      .Account()
                                      .SetCode(code)
                                      .Build();

                var loginResult =
                    await socialExecutor.LoginWithProviderRequestAsync(context, providerRequest, cancellationToken);

                await socialExecutor.HandleLoginResultAsync(
                    context,
                    application,
                    loginResult,
                    cancellationToken);

                return(await socialExecutor.HandleRedirectAsync(client, context, loginResult, parsedStateToken.Path, cancellationToken));
            }
            catch (Exception ex)
            {
                _logger.Warn($"Got '{ex.Message}' during social login request", source: nameof(GoogleCallbackRoute));
                return(await HttpResponse.Redirect(context, SocialExecutor.CreateErrorUri(_configuration.Web.Login, stateToken)));
            }
        }
Esempio n. 4
0
        public async Task <bool> RenderAsync(string name, object model, IOwinEnvironment context, CancellationToken cancellationToken)
        {
            foreach (var renderer in _renderers)
            {
                if (await renderer.RenderAsync(name, model, context, cancellationToken))
                {
                    _logger.Info($"Rendered view '{name}' using {_rendererNames[renderer]}", nameof(CompositeViewRenderer));
                    return(true);
                }
            }

            _logger.Error($"Could not render view '{name}' using any available renderer", nameof(CompositeViewRenderer));
            return(false);
        }
        protected override Task <bool> PostAsync(
            IOwinEnvironment context,
            IClient client,
            ContentNegotiationResult contentNegotiationResult,
            CancellationToken cancellationToken)
        {
            // The Stormpath middleware uses POSTs to /logout to avoid GETs mutating state,
            // so we need to account for that here
            var options = _options as IdSiteRedirectOptions ?? new IdSiteRedirectOptions();

            return(options.Logout
                ? HandleIdSiteRedirectAsync(context, client, cancellationToken)
                : Task.FromResult(false));
        }
        public static void AddTokenCookiesToResponse(IOwinEnvironment context, IClient client, IOauthGrantAuthenticationResult grantResult, StormpathConfiguration configuration, ILogger logger)
        {
            if (!string.IsNullOrEmpty(grantResult.AccessTokenString))
            {
                var expirationDate = client.NewJwtParser().Parse(grantResult.AccessTokenString).Body.Expiration;
                SetTokenCookie(context, configuration.Web.AccessTokenCookie, grantResult.AccessTokenString, expirationDate, IsSecureRequest(context), logger);
            }

            if (!string.IsNullOrEmpty(grantResult.RefreshTokenString))
            {
                var expirationDate = client.NewJwtParser().Parse(grantResult.RefreshTokenString).Body.Expiration;
                SetTokenCookie(context, configuration.Web.RefreshTokenCookie, grantResult.RefreshTokenString, expirationDate, IsSecureRequest(context), logger);
            }
        }
        private async Task <bool> LoginAndRedirectAsync(
            IOwinEnvironment context,
            IClient client,
            IOauthGrantAuthenticationResult grantResult,
            string nextPath,
            CancellationToken cancellationToken)
        {
            var executor = new LoginExecutor(client, _configuration, _handlers, _logger);
            await executor.HandlePostLoginAsync(context, grantResult, cancellationToken);

            await executor.HandleRedirectAsync(context, nextPath);

            return(true);
        }
Esempio n. 8
0
        protected override async Task <bool> PostJsonAsync(IOwinEnvironment context, IClient client, ContentType bodyContentType, CancellationToken cancellationToken)
        {
            var model = await PostBodyParser.ToModel <VerifyEmailPostModel>(context, bodyContentType, _logger, cancellationToken);

            var jsonSuccessHandler = new Func <CancellationToken, Task <bool> >(ct => JsonResponse.Ok(context));

            return(await ResendVerification(
                       email : model.Email,
                       client : client,
                       environment : context,
                       errorHandler : null, // Errors are caught in AbstractRouteMiddleware
                       successHandler : jsonSuccessHandler,
                       cancellationToken : cancellationToken));
        }
        public async Task HandlePostLoginAsync(
            IOwinEnvironment context,
            IOauthGrantAuthenticationResult grantResult,
            CancellationToken cancellationToken)
        {
            var accessToken = await grantResult.GetAccessTokenAsync(cancellationToken);

            var account = await accessToken.GetAccountAsync(cancellationToken);

            var postLoginHandlerContext = new PostLoginContext(context, account);
            await _handlers.PostLoginHandler(postLoginHandlerContext, cancellationToken);

            // Add Stormpath cookies
            Cookies.AddTokenCookiesToResponse(context, _client, grantResult, _configuration, _logger);
        }
Esempio n. 10
0
        public async Task <bool> HandleRedirectAsync(
            IClient client,
            IOwinEnvironment environment,
            ExternalLoginResult loginResult,
            string nextUri,
            CancellationToken cancellationToken)
        {
            var loginExecutor = new LoginExecutor(_client, _configuration, _handlers, _logger);

            var defaultNextPath = loginResult.IsNewAccount
                ? _configuration.Web.Register.NextUri
                : _configuration.Web.Login.NextUri;

            return(await loginExecutor.HandleRedirectAsync(environment, nextUri, defaultNextPath));
        }
Esempio n. 11
0
        protected virtual Task <bool> GetAsync(IOwinEnvironment context, IClient client, ContentNegotiationResult contentNegotiationResult, CancellationToken cancellationToken)
        {
            if (contentNegotiationResult.ContentType == ContentType.Json)
            {
                return(GetJsonAsync(context, client, cancellationToken));
            }

            if (contentNegotiationResult.ContentType == ContentType.Html)
            {
                return(GetHtmlAsync(context, client, cancellationToken));
            }

            // Do nothing and pass on to next middleware.
            return(Task.FromResult(false));
        }
        public async Task <ExternalLoginResult> LoginWithProviderRequestAsync(
            IOwinEnvironment environment,
            IProviderAccountRequest providerRequest,
            CancellationToken cancellationToken)
        {
            var application = await _client.GetApplicationAsync(_configuration.Application.Href, cancellationToken);

            var result = await application.GetAccountAsync(providerRequest, cancellationToken);

            return(new ExternalLoginResult
            {
                Account = result.Account,
                IsNewAccount = result.IsNewAccount
            });
        }
Esempio n. 13
0
        private async Task <bool> ExecuteRefreshFlow(IOwinEnvironment context, IClient client, string refreshToken, CancellationToken cancellationToken)
        {
            var application = await client.GetApplicationAsync(_configuration.Application.Href, cancellationToken);

            var refreshGrantRequest = OauthRequests.NewRefreshGrantRequest()
                                      .SetRefreshToken(refreshToken)
                                      .Build();

            var tokenResult = await application.NewRefreshGrantAuthenticator()
                              .AuthenticateAsync(refreshGrantRequest, cancellationToken);

            var sanitizer = new GrantResultResponseSanitizer();

            return(await JsonResponse.Ok(context, sanitizer.SanitizeResponseWithRefreshToken(tokenResult)).ConfigureAwait(false));
        }
        private string CreateFullUserAgent(IOwinEnvironment context)
        {
            var callingAgent = string.Empty;

            if (context != null)
            {
                callingAgent = string
                               .Join(" ", context.Request.Headers.Get("X-Stormpath-Agent") ?? new string[0])
                               .Trim();
            }

            return(string
                   .Join(" ", callingAgent, userAgentBuilder.GetUserAgent())
                   .Trim());
        }
        public async Task <IOauthGrantAuthenticationResult> PasswordGrantAsync(
            IOwinEnvironment environment,
            IApplication application,
            Func <string, CancellationToken, Task> errorHandler,
            string login,
            string password,
            CancellationToken cancellationToken)
        {
            var preLoginHandlerContext = new PreLoginContext(environment)
            {
                Login = login
            };

            await _handlers.PreLoginHandler(preLoginHandlerContext, cancellationToken);

            if (preLoginHandlerContext.Result != null)
            {
                if (!preLoginHandlerContext.Result.Success)
                {
                    var message = string.IsNullOrEmpty(preLoginHandlerContext.Result.ErrorMessage)
                        ? "An error has occurred. Please try again."
                        : preLoginHandlerContext.Result.ErrorMessage;
                    await errorHandler(message, cancellationToken);

                    return(null);
                }
            }

            var passwordGrantRequest = OauthRequests.NewPasswordGrantRequest()
                                       .SetLogin(preLoginHandlerContext.Login)
                                       .SetPassword(password);

            if (preLoginHandlerContext.AccountStore != null)
            {
                passwordGrantRequest.SetAccountStore(preLoginHandlerContext.AccountStore);
            }

            if (!string.IsNullOrEmpty(preLoginHandlerContext.OrganizationNameKey))
            {
                passwordGrantRequest.SetOrganizationNameKey(preLoginHandlerContext.OrganizationNameKey);
            }

            var passwordGrantAuthenticator = application.NewPasswordGrantAuthenticator();
            var grantResult = await passwordGrantAuthenticator
                              .AuthenticateAsync(passwordGrantRequest.Build(), cancellationToken);

            return(grantResult);
        }
        public static async Task <bool> Create(IOwinEnvironment context, int statusCode, string message, CancellationToken cancellationToken)
        {
            context.Response.StatusCode = statusCode;
            context.Response.Headers.SetString("Content-Type", Constants.JsonContentType);
            Caching.AddDoNotCacheHeaders(context);

            var errorModel = new
            {
                status  = statusCode,
                message = message
            };

            await context.Response.WriteAsync(Serializer.Serialize(errorModel), Encoding.UTF8, cancellationToken);

            return(true);
        }
Esempio n. 17
0
        public Task <bool> HandleRedirectAsync(
            IOwinEnvironment environment,
            IApplication application,
            IAccount createdAccount,
            RegisterPostModel postModel,
            Func <string, CancellationToken, Task> errorHandler,
            string stateToken,
            CancellationToken cancellationToken)
        {
            if (_configuration.Web.Register.AutoLogin &&
                createdAccount.Status != AccountStatus.Unverified)
            {
                return(HandleAutologinAsync(environment, application, errorHandler, postModel, stateToken, cancellationToken));
            }

            string nextUri;

            if (createdAccount.Status == AccountStatus.Enabled)
            {
                nextUri = $"{_configuration.Web.Login.Uri}?status=created";
            }
            else if (createdAccount.Status == AccountStatus.Unverified)
            {
                nextUri = $"{_configuration.Web.Login.Uri}?status=unverified";
            }
            else
            {
                nextUri = _configuration.Web.Login.Uri;
            }

            // Preserve the state token so that the login page can redirect after login if necessary
            if (!string.IsNullOrEmpty(stateToken))
            {
                if (nextUri.Contains("?"))
                {
                    nextUri += "&";
                }
                else
                {
                    nextUri += "?";
                }

                nextUri += $"{StringConstants.StateTokenName}={stateToken}";
            }

            return(HttpResponse.Redirect(environment, nextUri));
        }
        private async Task <bool> LoginAndRedirectAsync(
            IOwinEnvironment context,
            IClient client,
            IOauthGrantAuthenticationResult grantResult,
            bool isNewAccount,
            string nextPath,
            CancellationToken cancellationToken)
        {
            var executor = new LoginExecutor(client, _configuration, _handlers, _logger);
            await executor.HandlePostLoginAsync(context, grantResult, cancellationToken);

            var defaultNextPath = isNewAccount
                ? _configuration.Web.Register.NextUri
                : _configuration.Web.Login.NextUri;

            return(await executor.HandleRedirectAsync(context, nextPath, defaultNextPath));
        }
Esempio n. 19
0
        protected override async Task <bool> PostJsonAsync(IOwinEnvironment context, IClient client, ContentType bodyContentType, CancellationToken cancellationToken)
        {
            var application = await client.GetApplicationAsync(_configuration.Application.Href, cancellationToken);

            try
            {
                var model = await PostBodyParser.ToModel <ForgotPasswordPostModel>(context, bodyContentType, _logger, cancellationToken);

                await application.SendPasswordResetEmailAsync(model.Email, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, source: "ForgotRoute.PostJson");
            }

            return(await JsonResponse.Ok(context));
        }
        private async Task <IAccount> ValidateApiCredentialsAsync(
            IOwinEnvironment context,
            IClient client,
            string id,
            string secret,
            CancellationToken cancellationToken)
        {
            var application = await client
                              .GetApplicationAsync(Configuration.Application.Href, cancellationToken)
                              .ConfigureAwait(false);

            var apiKey = await application
                         .GetApiKeys()
                         .Where(x => x.Id == id)
                         .SingleOrDefaultAsync(cancellationToken)
                         .ConfigureAwait(false);

            if (apiKey == null)
            {
                logger.Info($"API key with ID {id} was not found", nameof(ValidateApiCredentialsAsync));
                return(null);
            }

            if (apiKey.Status != ApiKeyStatus.Enabled)
            {
                logger.Info($"API key with ID {id} was found, but was disabled", nameof(ValidateApiCredentialsAsync));
                return(null);
            }

            if (!apiKey.Secret.Equals(secret, StringComparison.Ordinal))
            {
                logger.Info($"API key with ID {id} was found, but secret did not match", nameof(ValidateApiCredentialsAsync));
                return(null);
            }

            var account = await apiKey.GetAccountAsync(cancellationToken).ConfigureAwait(false);

            if (account.Status != AccountStatus.Enabled)
            {
                logger.Info($"API key with ID {id} was found, but the account is not enabled", nameof(ValidateApiCredentialsAsync));
                return(null);
            }

            return(account);
        }
        private async Task <IAccount> ValidateAccessTokenAsync(IOwinEnvironment context, IClient client, string accessTokenJwt)
        {
            var request = OauthRequests.NewJwtAuthenticationRequest()
                          .SetJwt(accessTokenJwt)
                          .Build();

            var application = await client.GetApplicationAsync(this.Configuration.Application.Href, context.CancellationToken);

            var authenticator = application.NewJwtAuthenticator();

            if (this.Configuration.Web.Oauth2.Password.ValidationStrategy == WebOauth2TokenValidationStrategy.Local)
            {
                authenticator.WithLocalValidation();
            }

            IAccessToken result = null;

            try
            {
                result = await authenticator.AuthenticateAsync(request, context.CancellationToken);
            }
            catch (InvalidJwtException jwex)
            {
                logger.Info($"Failed to authenticate the request due to a malformed or expired access token. Message: '{jwex.Message}'", nameof(ValidateAccessTokenAsync));
                return(null);
            }
            catch (ResourceException rex)
            {
                logger.Warn(rex, "Failed to authenticate the request. Invalid access_token found.", nameof(ValidateAccessTokenAsync));
                return(null);
            }

            IAccount account = null;

            try
            {
                account = await GetExpandedAccountAsync(client, result, context.CancellationToken);
            }
            catch (ResourceException ex)
            {
                logger.Error(ex, $"Failed to get account {result.AccountHref}", nameof(ValidateAccessTokenAsync));
            }

            return(account);
        }
        public static async Task <bool> Create(IOwinEnvironment context, AbstractError error, CancellationToken cancellationToken)
        {
            context.Response.StatusCode = error.StatusCode;

            if (error.Body != null)
            {
                context.Response.Headers.SetString("Content-Type", Constants.JsonContentType);
                Caching.AddDoNotCacheHeaders(context);

                await context.Response.WriteAsync(Serializer.Serialize(error.Body), Encoding.UTF8, cancellationToken);

                return(true);
            }
            else
            {
                return(true);
            }
        }
        public async Task <bool> RenderAsync(string name, object model, IOwinEnvironment context, CancellationToken cancellationToken)
        {
            var view = ViewResolver.GetView(name);

            if (view == null)
            {
                _logger.Trace($"View '{name}' is not a precompiled view", nameof(PrecompiledViewRenderer));
                return(false);
            }

            cancellationToken.ThrowIfCancellationRequested();

            _logger.Trace($"Rendering precompiled view '{name}'", nameof(PrecompiledViewRenderer));

            await view.ExecuteAsync(model, context.Response.Body);

            return(true);
        }
Esempio n. 24
0
        private Task <bool> HandleRequestAsync(IOwinEnvironment context, IClient scopedClient, ContentNegotiationResult contentNegotiationResult, CancellationToken cancellationToken)
        {
            var method = context.Request.Method;

            if (method.Equals("GET", StringComparison.OrdinalIgnoreCase))
            {
                return(GetAsync(context, scopedClient, contentNegotiationResult, cancellationToken));
            }

            if (method.Equals("POST", StringComparison.OrdinalIgnoreCase))
            {
                return(PostAsync(context, scopedClient, contentNegotiationResult, cancellationToken));
            }

            // Do nothing and pass on to next middleware.
            _logger.Trace("Request method was not GET or POST", nameof(HandleRequestAsync));
            return(Task.FromResult(false));
        }
Esempio n. 25
0
        protected override async Task <bool> PostAsync(
            IOwinEnvironment context,
            IClient client,
            ContentNegotiationResult acceptContentNegotiationResult,
            CancellationToken cancellationToken)
        {
            var executor = new LogoutExecutor(client, _configuration, _handlers, _logger);
            await executor.HandleLogoutAsync(context, cancellationToken);

            if (acceptContentNegotiationResult.ContentType == ContentType.Html)
            {
                return(await executor.HandleRedirectAsync(context));
            }

            await JsonResponse.Ok(context);

            return(true);
        }
Esempio n. 26
0
        protected override async Task <bool> GetJsonAsync(IOwinEnvironment context, IClient client, CancellationToken cancellationToken)
        {
            var queryString = QueryStringParser.Parse(context.Request.QueryString, _logger);
            var spToken     = queryString.GetString("sptoken");

            if (string.IsNullOrEmpty(spToken))
            {
                return(await Error.Create(context, new BadRequest("sptoken parameter not provided."), cancellationToken));
            }

            var application = await client.GetApplicationAsync(_configuration.Application.Href, cancellationToken);

            await application.VerifyPasswordResetTokenAsync(spToken, cancellationToken);

            // Errors are caught in AbstractRouteMiddleware

            return(await JsonResponse.Ok(context));
        }
        private Task <IAccount> TryBasicAuthenticationAsync(IOwinEnvironment context, IClient client, CancellationToken cancellationToken)
        {
            try
            {
                var basicHeaderParser = new BasicAuthenticationParser(context.Request.Headers.GetString("Authorization"), logger);
                if (!basicHeaderParser.IsValid)
                {
                    return(Task.FromResult <IAccount>(null));
                }

                logger.Info("Using Basic header to authenticate request", nameof(TryBasicAuthenticationAsync));
                return(ValidateApiCredentialsAsync(context, client, basicHeaderParser.Username, basicHeaderParser.Password, cancellationToken));
            }
            catch (Exception ex)
            {
                logger.Warn(ex, source: nameof(TryBasicAuthenticationAsync));
                return(Task.FromResult <IAccount>(null));
            }
        }
Esempio n. 28
0
        protected override async Task <bool> GetJsonAsync(IOwinEnvironment context, IClient client, CancellationToken cancellationToken)
        {
            var queryString = QueryStringParser.Parse(context.Request.QueryString, _logger);
            var spToken     = queryString.GetString("sptoken");

            if (string.IsNullOrEmpty(spToken))
            {
                return(await Error.Create(context, new BadRequest("sptoken parameter not provided."), cancellationToken));
            }

            var account = await client.VerifyAccountEmailAsync(spToken, cancellationToken);

            // Errors are caught in AbstractRouteMiddleware

            var postVerifyEmailContext = new PostVerifyEmailContext(context, account);
            await _handlers.PostVerifyEmailHandler(postVerifyEmailContext, cancellationToken);

            return(await JsonResponse.Ok(context));
        }
        protected override async Task <bool> GetJsonAsync(IOwinEnvironment context, IClient client, CancellationToken cancellationToken)
        {
            Caching.AddDoNotCacheHeaders(context);

            var stormpathAccount = context.Request[OwinKeys.StormpathUser] as IAccount;

            var expansionOptions = _configuration.Web.Me.Expand;

            if (expansionOptions.Any(e => e.Value))
            {
                stormpathAccount = await GetExpandedAccount(stormpathAccount.Href, client, expansionOptions, cancellationToken);
            }

            var responseModel = new
            {
                account = await SanitizeExpandedAccount(stormpathAccount, expansionOptions, cancellationToken)
            };

            return(await JsonResponse.Ok(context, responseModel));
        }
        private async Task RevokeHeaderToken(IOwinEnvironment context, IClient client, CancellationToken cancellationToken)
        {
            var bearerHeaderParser = new BearerAuthenticationParser(context.Request.Headers.GetString("Authorization"), _logger);

            if (!bearerHeaderParser.IsValid)
            {
                return;
            }

            var revoker = new TokenRevoker(client, _logger)
                          .AddToken(bearerHeaderParser.Token);

            try
            {
                await revoker.Revoke(cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.Info(ex.Message, source: nameof(RevokeCookieTokens));
            }
        }
Esempio n. 31
0
        protected override async Task <bool> PostJsonAsync(IOwinEnvironment context, IClient client, ContentType bodyContentType, CancellationToken cancellationToken)
        {
            var model = await PostBodyParser.ToModel <ChangePasswordPostModel>(context, bodyContentType, _logger, cancellationToken);

            var application = await client.GetApplicationAsync(_configuration.Application.Href, cancellationToken);

            var account = await application.VerifyPasswordResetTokenAsync(model.SpToken, cancellationToken);

            // Errors are caught in AbstractRouteMiddleware

            var preChangePasswordContext = new PreChangePasswordContext(context, account);
            await _handlers.PreChangePasswordHandler(preChangePasswordContext, cancellationToken);

            await application.ResetPasswordAsync(model.SpToken, model.Password, cancellationToken);

            var postChangePasswordContext = new PostChangePasswordContext(context, account);
            await _handlers.PostChangePasswordHandler(postChangePasswordContext, cancellationToken);

            // TODO autologin

            return(await JsonResponse.Ok(context));
        }