From 14039d7d1a8d2209e134f929bf360266bcaa9cbd Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 22 Mar 2018 13:18:18 -0400 Subject: [PATCH] respect return url on sign in link --- src/Admin/Controllers/LoginController.cs | 19 +++++++++---- src/Admin/Models/LoginModel.cs | 1 + src/Admin/Views/Login/Index.cshtml | 1 + .../Implementations/MarkdownMailService.cs | 9 +++++-- .../Implementations/RazorMailService.cs | 9 +++++-- src/Core/Utilities/CoreHelpers.cs | 27 +++++++++++++++++++ 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/Admin/Controllers/LoginController.cs b/src/Admin/Controllers/LoginController.cs index b4bb678a94..9020d3d8af 100644 --- a/src/Admin/Controllers/LoginController.cs +++ b/src/Admin/Controllers/LoginController.cs @@ -16,9 +16,12 @@ namespace Bit.Admin.Controllers _signInManager = signInManager; } - public IActionResult Index() + public IActionResult Index(string returnUrl = null) { - return View(); + return View(new LoginModel + { + ReturnUrl = returnUrl + }); } [HttpPost] @@ -28,19 +31,25 @@ namespace Bit.Admin.Controllers if(ModelState.IsValid) { await _signInManager.PasswordlessSignInAsync(model.Email, - Url.Action("Confirm", "Login", null, Request.Scheme)); + Url.Action("Confirm", "Login", new { returnUrl = model.ReturnUrl }, Request.Scheme)); return RedirectToAction("Index", "Home"); } return View(model); } - public async Task Confirm(string email, string token) + public async Task Confirm(string email, string token, string returnUrl) { var result = await _signInManager.PasswordlessSignInAsync(email, token, false); if(!result.Succeeded) { - return View("Error"); + // TODO: error? + return RedirectToAction("Index"); + } + + if(!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); diff --git a/src/Admin/Models/LoginModel.cs b/src/Admin/Models/LoginModel.cs index 08f6cd8d4d..f4916e496b 100644 --- a/src/Admin/Models/LoginModel.cs +++ b/src/Admin/Models/LoginModel.cs @@ -7,5 +7,6 @@ namespace Bit.Admin.Models [Required] [EmailAddress] public string Email { get; set; } + public string ReturnUrl { get; set; } } } diff --git a/src/Admin/Views/Login/Index.cshtml b/src/Admin/Views/Login/Index.cshtml index a3522399f0..477dbe0876 100644 --- a/src/Admin/Views/Login/Index.cshtml +++ b/src/Admin/Views/Login/Index.cshtml @@ -7,6 +7,7 @@

Please enter your email address below to log in.

+
diff --git a/src/Core/Services/Implementations/MarkdownMailService.cs b/src/Core/Services/Implementations/MarkdownMailService.cs index f26f17e370..9aaee2f960 100644 --- a/src/Core/Services/Implementations/MarkdownMailService.cs +++ b/src/Core/Services/Implementations/MarkdownMailService.cs @@ -6,6 +6,7 @@ using Bit.Core.Models.Mail; using System.IO; using System.Net; using System.Reflection; +using Bit.Core.Utilities; namespace Bit.Core.Services { @@ -172,10 +173,14 @@ namespace Bit.Core.Services public async Task SendPasswordlessSignInAsync(string baseUrl, string token, string email) { + var url = CoreHelpers.ExtendQuery(new Uri(baseUrl), new Dictionary + { + ["email"] = email, + ["token"] = token, + }); var model = new Dictionary { - ["url"] = string.Format("{0}?email={1}&token={2}", baseUrl, WebUtility.UrlEncode(email), - WebUtility.UrlEncode(token)) + ["url"] = url.ToString() }; var message = await CreateMessageAsync("Continue Logging In", email, "PasswordlessSignIn", model); diff --git a/src/Core/Services/Implementations/RazorMailService.cs b/src/Core/Services/Implementations/RazorMailService.cs index 6e4609ceab..bd2c2bfbc8 100644 --- a/src/Core/Services/Implementations/RazorMailService.cs +++ b/src/Core/Services/Implementations/RazorMailService.cs @@ -206,10 +206,15 @@ namespace Bit.Core.Services public async Task SendPasswordlessSignInAsync(string baseUrl, string token, string email) { var message = CreateDefaultMessage("Continue Logging In", email); + + var url = CoreHelpers.ExtendQuery(new Uri(baseUrl), new Dictionary + { + ["email"] = email, + ["token"] = token, + }); var model = new PasswordlessSignInModel { - Url = string.Format("{0}?email={1}&token={2}", baseUrl, WebUtility.UrlEncode(email), - WebUtility.UrlEncode(token)) + Url = url.ToString() }; message.HtmlContent = await _engine.CompileRenderAsync("PasswordlessSignIn", model); message.TextContent = await _engine.CompileRenderAsync("PasswordlessSignIn.text", model); diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 356210038b..e4c29d47d8 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -12,6 +12,7 @@ using System.Text; using System.Text.RegularExpressions; using Dapper; using System.Globalization; +using System.Web; namespace Bit.Core.Utilities { @@ -425,5 +426,31 @@ namespace Bit.Core.Utilities return _max.Subtract(date.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture); } + + // ref: https://stackoverflow.com/a/27545010/1090359 + public static Uri ExtendQuery(Uri uri, IDictionary values) + { + var baseUri = uri.ToString(); + var queryString = string.Empty; + if(baseUri.Contains("?")) + { + var urlSplit = baseUri.Split('?'); + baseUri = urlSplit[0]; + queryString = urlSplit.Length > 1 ? urlSplit[1] : string.Empty; + } + + var queryCollection = HttpUtility.ParseQueryString(queryString); + foreach(var kvp in values ?? new Dictionary()) + { + queryCollection[kvp.Key] = kvp.Value; + } + + var uriKind = uri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative; + if(queryCollection.Count == 0) + { + return new Uri(baseUri, uriKind); + } + return new Uri(string.Format("{0}?{1}", baseUri, queryCollection), uriKind); + } } }