Skip to content

Commit

Permalink
User invite flow review (umbraco#3000)
Browse files Browse the repository at this point in the history
  • Loading branch information
agrath authored and nul800sebastiaan committed Sep 30, 2018
1 parent 8fad718 commit 63a2a15
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 14 deletions.
6 changes: 5 additions & 1 deletion src/Umbraco.Core/Security/BackOfficeUserManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,10 @@ protected void InitUserManager(

if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<T, int>(dataProtectionProvider.Create("ASP.NET Identity"));
manager.UserTokenProvider = new DataProtectorTokenProvider<T, int>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromDays(3)
};
}

manager.UserLockoutEnabledByDefault = true;
Expand Down Expand Up @@ -748,6 +751,7 @@ protected virtual string GetCurrentRequestIpAddress()
var httpContext = HttpContext.Current == null ? (HttpContextBase)null : new HttpContextWrapper(HttpContext.Current);
return httpContext.GetCurrentRequestIpAddress();
}

}

}
4 changes: 3 additions & 1 deletion src/Umbraco.Core/Security/BackOfficeUserStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,9 @@ private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityU
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
{
anythingChanged = true;
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
//if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
user.LastLoginDate = dt;
}
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
function init() {
// Check if it is a new user
var inviteVal = $location.search().invite;
//1 = enter password, 2 = password set, 3 = invalid token
if (inviteVal && (inviteVal === "1" || inviteVal === "2")) {

$q.all([
Expand Down Expand Up @@ -58,6 +59,8 @@
$scope.inviteStep = Number(inviteVal);

});
} else if (inviteVal && inviteVal === "3") {
$scope.inviteStep = Number(inviteVal);
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,18 @@ <h1 style="margin-bottom: 10px;">Upload a photo</h1>
</umb-button>
</div>
</div>

</div>
<div ng-show="invitedUser == null && inviteStep === 3" ng-if="inviteStep === 3" class="umb-login-container">
<div class="form">
<h1 style="margin-bottom: 10px; text-align: left;">Hi there</h1>
<p style="line-height: 1.6; margin-bottom: 25px;">
<localize key="user_userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</localize>
</p>

<div ng-show="invitedUser == null" class="umb-login-container">
</div>
</div>
<div ng-show="invitedUser == null && !inviteStep" class="umb-login-container">

<div class="form">
<h1>{{greeting}}</h1>
Expand Down
3 changes: 2 additions & 1 deletion src/Umbraco.Web.UI/umbraco/config/lang/en.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1865,7 +1865,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="goToProfile">Go to user profile</key>
<key alias="groupsHelp">Add groups to assign access and permissions</key>
<key alias="inviteAnotherUser">Invite another user</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco.</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.</key>
<key alias="language">Language</key>
<key alias="languageHelp">Set the language you will see in menus and dialogs</key>
<key alias="lastLockoutDate">Last lockout date</key>
Expand Down Expand Up @@ -1929,6 +1929,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="userInvited">has been invited</key>
<key alias="userInvitedSuccessHelp">An invitation has been sent to the new user with details about how to log in to Umbraco.</key>
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar.</key>
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</key>
<key alias="userinviteAvatarMessage">Upload a picture to make it easy for other users to recognize you.</key>
<key alias="writer">Writer</key>
<key alias="translator">Translator</key>
Expand Down
3 changes: 2 additions & 1 deletion src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1858,7 +1858,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="goToProfile">Go to user profile</key>
<key alias="groupsHelp">Add groups to assign access and permissions</key>
<key alias="inviteAnotherUser">Invite another user</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco.</key>
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.</key>
<key alias="language">Language</key>
<key alias="languageHelp">Set the language you will see in menus and dialogs</key>
<key alias="lastLockoutDate">Last lockout date</key>
Expand Down Expand Up @@ -1922,6 +1922,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="userInvited">has been invited</key>
<key alias="userInvitedSuccessHelp">An invitation has been sent to the new user with details about how to log in to Umbraco.</key>
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar.</key>
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</key>
<key alias="userinviteAvatarMessage">Upload a picture to make it easy for other users to recognize you.</key>
<key alias="writer">Writer</key>
<key alias="translator">Translator</key>
Expand Down
32 changes: 30 additions & 2 deletions src/Umbraco.Web/Editors/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public async Task<HttpResponseMessage> PostLogin(LoginModel loginModel)
switch (result)
{
case SignInStatus.Success:

//get the user
var user = Services.UserService.GetByUsername(loginModel.Username);
UserManager.RaiseLoginSuccessEvent(user.Id);
Expand Down Expand Up @@ -425,6 +425,34 @@ public async Task<HttpResponseMessage> PostSetPassword(SetPasswordModel model)
}
}

//They've successfully set their password, we can now update their user account to be confirmed
//if user was only invited, then they have not been approved
//but a successful forgot password flow (e.g. if their token had expired and they did a forgot password instead of request new invite)
//means we have verified their email
if (!UserManager.IsEmailConfirmed(model.UserId))
{
await UserManager.ConfirmEmailAsync(model.UserId, model.ResetCode);
}

//if the user is invited, enable their account on forgot password
var identityUser = await UserManager.FindByIdAsync(model.UserId);
//invited is not approved, never logged in, invited date present
/*
if (LastLoginDate == default && IsApproved == false && InvitedDate != null)
return UserState.Invited;
*/
if (identityUser != null && !identityUser.IsApproved)
{
var user = Services.UserService.GetByUsername(identityUser.UserName);
//also check InvitedDate and never logged in, otherwise this would allow a disabled user to reactivate their account with a forgot password
if (user.LastLoginDate == default && user.InvitedDate != null)
{
user.IsApproved = true;
user.InvitedDate = null;
Services.UserService.Save(user);
}
}

UserManager.RaiseForgotPasswordChangedSuccessEvent(model.UserId);
return Request.CreateResponse(HttpStatusCode.OK);
}
Expand Down Expand Up @@ -524,4 +552,4 @@ private void AddModelErrors(IdentityResult result, string prefix = "")
}

}
}
}
21 changes: 15 additions & 6 deletions src/Umbraco.Web/Editors/BackOfficeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ public async Task<ActionResult> Default()
[HttpGet]
public async Task<ActionResult> VerifyInvite(string invite)
{
//if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid
//you'll exit on one of the return RedirectToAction("Default") but you're still logged in so you just get
//dumped at the default admin view with no detail
if(Security.IsAuthenticated())
{
AuthenticationManager.SignOut(
Core.Constants.Security.BackOfficeAuthenticationType,
Core.Constants.Security.BackOfficeExternalAuthenticationType);
}

if (invite == null)
{
Logger.Warn<BackOfficeController>("VerifyUser endpoint reached with invalid token: NULL");
Expand Down Expand Up @@ -119,16 +129,15 @@ public async Task<ActionResult> VerifyInvite(string invite)
if (result.Succeeded == false)
{
Logger.Warn<BackOfficeController>("Could not verify email, Error: " + string.Join(",", result.Errors) + ", Token: " + invite);
return RedirectToAction("Default");
return new RedirectResult(Url.Action("Default") + "#/login/false?invite=3");
}

//sign the user in

AuthenticationManager.SignOut(
Core.Constants.Security.BackOfficeAuthenticationType,
Core.Constants.Security.BackOfficeExternalAuthenticationType);

DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc;
await SignInManager.SignInAsync(identityUser, false, false);
//reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to SignInManager would be a breaking change
identityUser.LastLoginDateUtc = previousLastLoginDate;
await UserManager.UpdateAsync(identityUser);

return new RedirectResult(Url.Action("Default") + "#/login/false?invite=1");
}
Expand Down

0 comments on commit 63a2a15

Please sign in to comment.