wip move login logic from dialog to its own component

This commit is contained in:
Mads Rasmussen
2018-11-09 09:59:36 +01:00
parent 1f3e67645d
commit 4c3ac097ca
5 changed files with 727 additions and 24 deletions

View File

@@ -0,0 +1,433 @@
(function () {
'use strict';
angular
.module('umbraco.directives')
.component('umbLogin', {
templateUrl: 'views/components/application/umb-login.html',
controller: UmbLoginController,
controllerAs: 'vm',
bindings: {
isTimedOut: "<",
onLogin: "&"
}
});
function UmbLoginController($scope, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) {
const vm = this;
let twoFactorloginDialog = null;
vm.invitedUser = null;
vm.invitedUserPasswordModel = {
password: "",
confirmPassword: "",
buttonState: "",
passwordPolicies: null,
passwordPolicyText: ""
};
vm.loginStates = {
submitButton: "init"
};
vm.avatarFile = {
filesHolder: null,
uploadStatus: null,
uploadProgress: 0,
maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB",
acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes),
uploaded: false
};
vm.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset;
vm.errorMsg = "";
vm.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
vm.externalLoginProviders = externalLoginInfo.providers;
vm.externalLoginInfo = externalLoginInfo;
vm.resetPasswordCodeInfo = resetPasswordCodeInfo;
vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage;
vm.$onInit = onInit;
vm.togglePassword = togglePassword;
vm.changeAvatar = changeAvatar;
vm.getStarted = getStarted;
vm.inviteSavePassword = inviteSavePassword;
vm.showLogin = showLogin;
vm.showRequestPasswordReset = showRequestPasswordReset;
vm.showSetPassword = showSetPassword;
vm.loginSubmit = loginSubmit;
vm.requestPasswordResetSubmit = requestPasswordResetSubmit;
function onInit() {
// Check if it is a new user
const inviteVal = $location.search().invite;
//1 = enter password, 2 = password set, 3 = invalid token
if (inviteVal && (inviteVal === "1" || inviteVal === "2")) {
$q.all([
//get the current invite user
authResource.getCurrentInvitedUser().then(function (data) {
vm.invitedUser = data;
},
function () {
//it failed so we should remove the search
$location.search('invite', null);
}),
//get the membership provider config for password policies
authResource.getMembershipProviderConfig().then(function (data) {
vm.invitedUserPasswordModel.passwordPolicies = data;
//localize the text
localizationService.localize("errorHandling_errorInPasswordFormat", [
vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength,
vm.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars
]).then(function (data) {
vm.invitedUserPasswordModel.passwordPolicyText = data;
});
})
]).then(function () {
vm.inviteStep = Number(inviteVal);
});
} else if (inviteVal && inviteVal === "3") {
vm.inviteStep = Number(inviteVal);
}
// set the welcome greeting
setGreeting();
// show the correct panel
if (vm.resetPasswordCodeInfo.resetCodeModel) {
vm.showSetPassword();
}
else if (vm.resetPasswordCodeInfo.errors.length > 0) {
vm.view = "password-reset-code-expired";
}
else {
vm.showLogin();
}
}
function togglePassword() {
var elem = $("form[name='vm.loginForm'] input[name='password']");
elem.attr("type", (elem.attr("type") === "text" ? "password" : "text"));
$(".password-text.show, .password-text.hide").toggle();
}
function changeAvatar(files, event) {
if (files && files.length > 0) {
upload(files[0]);
}
}
function getStarted() {
$location.search('invite', null);
submit(true);
}
function inviteSavePassword () {
if (formHelper.submitForm({ scope: $scope })) {
vm.invitedUserPasswordModel.buttonState = "busy";
currentUserResource.performSetInvitedUserPassword(vm.invitedUserPasswordModel.password)
.then(function (data) {
//success
formHelper.resetForm({ scope: $scope });
vm.invitedUserPasswordModel.buttonState = "success";
//set the user and set them as logged in
vm.invitedUser = data;
userService.setAuthenticationSuccessful(data);
vm.inviteStep = 2;
}, function (err) {
formHelper.handleError(err);
vm.invitedUserPasswordModel.buttonState = "error";
});
}
}
function showLogin() {
vm.errorMsg = "";
resetInputValidation();
vm.view = "login";
setFieldFocus("loginForm", "username");
}
function showRequestPasswordReset() {
vm.errorMsg = "";
resetInputValidation();
vm.view = "request-password-reset";
vm.showEmailResetConfirmation = false;
setFieldFocus("requestPasswordResetForm", "email");
}
function showSetPassword() {
vm.errorMsg = "";
resetInputValidation();
vm.view = "set-password";
setFieldFocus("setPasswordForm", "password");
}
function loginSubmit(login, password) {
//TODO: Do validation properly like in the invite password update
//if the login and password are not empty we need to automatically
// validate them - this is because if there are validation errors on the server
// then the user has to change both username & password to resubmit which isn't ideal,
// so if they're not empty, we'll just make sure to set them to valid.
if (login && password && login.length > 0 && password.length > 0) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
if (vm.loginForm.$invalid) {
return;
}
vm.loginStates.submitButton = "busy";
userService.authenticate(login, password)
.then(function (data) {
vm.loginStates.submitButton = "success";
if(vm.onLogin) {
vm.onLogin();
}
},
function (reason) {
//is Two Factor required?
if (reason.status === 402) {
vm.errorMsg = "Additional authentication required";
show2FALoginDialog(reason.data.twoFactorView, submit);
}
else {
vm.loginStates.submitButton = "error";
vm.errorMsg = reason.errorMsg;
//set the form inputs to invalid
vm.loginForm.username.$setValidity("auth", false);
vm.loginForm.password.$setValidity("auth", false);
}
});
//setup a watch for both of the model values changing, if they change
// while the form is invalid, then revalidate them so that the form can
// be submitted again.
vm.loginForm.username.$viewChangeListeners.push(function () {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
});
vm.loginForm.password.$viewChangeListeners.push(function () {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
});
}
function requestPasswordResetSubmit(email) {
//TODO: Do validation properly like in the invite password update
if (email && email.length > 0) {
vm.requestPasswordResetForm.email.$setValidity('auth', true);
}
vm.showEmailResetConfirmation = false;
if (vm.requestPasswordResetForm.$invalid) {
return;
}
vm.errorMsg = "";
authResource.performRequestPasswordReset(email)
.then(function () {
//remove the email entered
vm.email = "";
vm.showEmailResetConfirmation = true;
}, function (reason) {
vm.errorMsg = reason.errorMsg;
vm.requestPasswordResetForm.email.$setValidity("auth", false);
});
vm.requestPasswordResetForm.email.$viewChangeListeners.push(function () {
if (vm.requestPasswordResetForm.email.$invalid) {
vm.requestPasswordResetForm.email.$setValidity('auth', true);
}
});
}
function setPasswordSubmit(password, confirmPassword) {
vm.showSetPasswordConfirmation = false;
if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) {
vm.setPasswordForm.password.$setValidity('auth', true);
vm.setPasswordForm.confirmPassword.$setValidity('auth', true);
}
if (vm.setPasswordForm.$invalid) {
return;
}
//TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword
authResource.performSetPassword(vm.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, vm.resetPasswordCodeInfo.resetCodeModel.resetCode)
.then(function () {
vm.showSetPasswordConfirmation = true;
vm.resetComplete = true;
//reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again
resetPasswordCodeInfo.resetCodeModel = null;
}, function (reason) {
if (reason.data && reason.data.Message) {
vm.errorMsg = reason.data.Message;
}
else {
vm.errorMsg = reason.errorMsg;
}
vm.setPasswordForm.password.$setValidity("auth", false);
vm.setPasswordForm.confirmPassword.$setValidity("auth", false);
});
vm.setPasswordForm.password.$viewChangeListeners.push(function () {
if (vm.setPasswordForm.password.$invalid) {
vm.setPasswordForm.password.$setValidity('auth', true);
}
});
vm.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () {
if (vm.setPasswordForm.confirmPassword.$invalid) {
vm.setPasswordForm.confirmPassword.$setValidity('auth', true);
}
});
}
////
function setGreeting() {
const date = new Date();
localizationService.localize("login_greeting" + date.getDay()).then(function (label) {
$scope.greeting = label;
});
}
function upload(file) {
vm.avatarFile.uploadProgress = 0;
Upload.upload({
url: umbRequestHelper.getApiUrl("currentUserApiBaseUrl", "PostSetAvatar"),
fields: {},
file: file
}).progress(function (evt) {
if (vm.avatarFile.uploadStatus !== "done" && vm.avatarFile.uploadStatus !== "error") {
// set uploading status on file
vm.avatarFile.uploadStatus = "uploading";
// calculate progress in percentage
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
// set percentage property on file
vm.avatarFile.uploadProgress = progressPercentage;
}
}).success(function (data, status, headers, config) {
vm.avatarFile.uploadProgress = 100;
// set done status on file
vm.avatarFile.uploadStatus = "done";
vm.invitedUser.avatars = data;
vm.avatarFile.uploaded = true;
}).error(function (evt, status, headers, config) {
// set status done
vm.avatarFile.uploadStatus = "error";
// If file not found, server will return a 404 and display this message
if (status === 404) {
vm.avatarFile.serverErrorMessage = "File not found";
}
else if (status == 400) {
//it's a validation error
vm.avatarFile.serverErrorMessage = evt.message;
}
else {
//it's an unhandled error
//if the service returns a detailed error
if (evt.InnerException) {
vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
//Check if its the common "too large file" exception
if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) {
vm.avatarFile.serverErrorMessage = "File too large to upload";
}
} else if (evt.Message) {
vm.avatarFile.serverErrorMessage = evt.Message;
}
}
});
}
function setFieldFocus(form, field) {
$timeout(function () {
$("form[name='" + form + "'] input[name='" + field + "']").focus();
});
}
function show2FALoginDialog(view, callback) {
if (!twoFactorloginDialog) {
twoFactorloginDialog = dialogService.open({
//very special flag which means that global events cannot close this dialog
manualClose: true,
template: view,
modalClass: "login-overlay",
animation: "slide",
show: true,
callback: callback
});
}
}
function resetInputValidation() {
vm.confirmPassword = "";
vm.password = "";
vm.login = "";
if (vm.loginForm) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
if (vm.requestPasswordResetForm) {
vm.requestPasswordResetForm.email.$setValidity("auth", true);
}
if (vm.setPasswordForm) {
vm.setPasswordForm.password.$setValidity('auth', true);
vm.setPasswordForm.confirmPassword.$setValidity('auth', true);
}
}
}
})();

View File

@@ -3,34 +3,18 @@ angular.module('umbraco.services')
var currentUser = null;
var lastUserId = null;
var loginDialog = null;
//this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
// this is used so that we know when to go and get the user's remaining seconds directly.
var lastServerTimeoutSet = null;
function openLoginDialog(isTimedOut) {
if (!loginDialog) {
loginDialog = dialogService.open({
//very special flag which means that global events cannot close this dialog
manualClose: true,
template: 'views/common/dialogs/login.html',
modalClass: "login-overlay",
animation: "slide",
show: true,
callback: onLoginDialogClose,
dialogData: {
isTimedOut: isTimedOut
}
});
}
//broadcast a global event that the user is no longer logged in
const args = { isTimedOut: isTimedOut };
eventsService.emit("app.notAuthenticated", args);
}
function onLoginDialogClose(success) {
loginDialog = null;
if (success) {
requestRetryQueue.retryAll(currentUser.name);
}
@@ -164,9 +148,6 @@ angular.module('umbraco.services')
lastServerTimeoutSet = null;
currentUser = null;
//broadcast a global event that the user is no longer logged in
eventsService.emit("app.notAuthenticated");
openLoginDialog(isLogout === undefined ? true : !isLogout);
}

View File

@@ -17,6 +17,7 @@ function MainController($scope, $location, appState, treeService, notificationsS
$scope.overlay = {};
$scope.drawer = {};
$scope.search = {};
$scope.login = {};
$scope.removeNotification = function (index) {
notificationsService.remove(index);
@@ -48,11 +49,19 @@ function MainController($scope, $location, appState, treeService, notificationsS
};
var evts = [];
//when a user logs out or timesout
evts.push(eventsService.on("app.notAuthenticated", function () {
evts.push(eventsService.on("app.notAuthenticated", function (evt, data) {
$scope.authenticated = null;
$scope.user = null;
// show the login screen
if(data) {
$scope.login.isTimedOut = data.isTimedOut;
}
$scope.login.show = true;
}));
evts.push(eventsService.on("app.userRefresh", function(evt) {
@@ -72,6 +81,10 @@ function MainController($scope, $location, appState, treeService, notificationsS
$scope.authenticated = data.authenticated;
$scope.user = data.user;
if($scope.authenticated === true) {
$scope.login.show = false;
}
updateChecker.check().then(function (update) {
if (update && update !== "null") {
if (update.type !== "None") {

View File

@@ -0,0 +1,271 @@
<div class="login-overlay">
<div id="login" class="umb-modalcolumn umb-dialog" ng-class="{'show-validation': vm.loginForm.$invalid}" ng-cloak>
<div class="login-overlay__background-image" ng-if="vm.backgroundImage" ng-style="{'background-image':'url(' + vm.backgroundImage + ')'}"></div>
<div class="login-overlay__logo">
<img ng-src="assets/img/application/logo.png" ng-srcset="assets/img/application/logo@2x.png 2x, assets/img/application/logo@3x.png 3x">
</div>
<div ng-show="vm.invitedUser != null" class="umb-login-container">
<form name="vm.inviteUserPasswordForm" novalidate="" ng-submit="vm.inviteSavePassword()" val-form-manager>
<div class="form" ng-if="vm.inviteStep === 1">
<h1 style="margin-bottom: 10px; text-align: left;">Hi, {{vm.invitedUser.name}}</h1>
<p style="line-height: 1.6; margin-bottom: 25px;">
<localize key="user_userinviteWelcomeMessage">Welcome to Umbraco! Just need to get your password and avatar setup and then you're good to go</localize>
</p>
<div class="control-group" ng-class="{error: setPasswordForm.password.$invalid}">
<label>
<localize key="user_newPassword">New password</localize>
<small style="font-size: 13px;">{{vm.invitedUserPasswordModel.passwordPolicyText}}</small>
</label>
<input type="password" ng-model="vm.invitedUserPasswordModel.password" name="password" class="-full-width-input" umb-auto-focus required val-server-field="value" ng-minlength="{{vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength}}" />
<span ng-messages="inviteUserPasswordForm.password.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="user_passwordIsBlank">Your new password cannot be blank!</localize></span>
<span class="help-inline" ng-message="minlength">Minimum {{vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters</span>
<span class="help-inline" ng-message="valServerField">{{inviteUserPasswordForm.password.errorMsg}}</span>
</span>
</div>
<div class="control-group" ng-class="{error: setPasswordForm.confirmPassword.$invalid}">
<label><localize key="user_confirmNewPassword">Confirm new password</localize></label>
<input type="password" ng-model="vm.invitedUserPasswordModel.confirmPassword" name="confirmPassword" class="-full-width-input" required val-compare="password" />
<span ng-messages="inviteUserPasswordForm.confirmPassword.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valCompare"><localize key="user_passwordMismatch">The confirmed password doesn't match the new password!</localize></span>
</span>
</div>
<div class="flex justify-between items-center">
<umb-button
type="submit"
button-style="success"
state="vm.invitedUserPasswordModel.buttonState"
label="Save password">
</umb-button>
</div>
</div>
</form>
<div class="form" ng-if="inviteStep === 2">
<div class="flex justify-center items-center">
<ng-form name="vm.avatarForm">
<umb-progress-bar
style="max-width: 100px; margin-bottom: 5px;"
ng-show="vm.avatarFile.uploadStatus === 'uploading'"
progress="{{ vm.avatarFile.uploadProgress }}"
size="s">
</umb-progress-bar>
<div class="umb-info-local-item text-error mt3" ng-if="vm.avatarFile.uploadStatus === 'error'">
{{ vm.avatarFile.serverErrorMessage }}
</div>
<a class="umb-avatar-btn"
ngf-select
ng-model="vm.avatarFile.filesHolder"
ngf-change="vm.changeAvatar($files, $event)"
ngf-multiple="false"
ngf-pattern="{{vm.avatarFile.acceptedFileTypes}}"
ngf-max-size="{{ vm.avatarFile.maxFileSize }}">
<umb-avatar
color="gray"
size="xl"
unknown-char="+"
img-src="{{vm.invitedUser.avatars[3]}}"
img-srcset="{{vm.invitedUser.avatars[4]}} 2x, {{invitedUser.avatars[4]}} 3x">
</umb-avatar>
</a>
</ng-form>
</div>
<h1 style="margin-bottom: 10px;">Upload a photo</h1>
<p style="text-align: center; margin-bottom: 25px; line-height: 1.6em;">
<localize key="user_userinviteAvatarMessage"></localize>
</p>
<div class="flex justify-center items-center">
<umb-button
type="button"
button-style="success"
label="Get started"
action="vm.getStarted()">
</umb-button>
<umb-button
ng-if="!vm.avatarFile.uploaded"
type="button"
button-style="link"
label="Skip"
action="vm.getStarted()">
</umb-button>
</div>
</div>
</div>
<div ng-show="vm.invitedUser == null && vm.inviteStep === 3" ng-if="vm.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>
</div>
<div ng-show="vm.invitedUser == null && !vm.inviteStep" class="umb-login-container">
<div class="form">
<h1>{{greeting}}</h1>
<div ng-show="vm.view == 'login'">
<p>
<span ng-show="vm.isTimedOut"><localize key="login_timeout">Log in below</localize>.</span>
</p>
<div class="external-logins" ng-if="vm.externalLoginProviders.length > 0">
<div class="text-error" ng-repeat="error in vm.externalLoginInfo.errors">
<span>{{error}}</span>
</div>
<form method="POST" name="vm.externalLoginForm" action="{{vm.externalLoginFormAction}}">
<div ng-repeat="login in vm.externalLoginProviders">
<button type="submit" class="btn btn-block btn-social"
ng-class="login.properties.SocialStyle"
id="{{login.authType}}" name="provider" value="{{login.authType}}"
title="Log in using your {{login.caption}} account">
<i class="fa" ng-class="login.properties.SocialIcon"></i>
<localize key="login_signInWith">Sign in with</localize> {{login.caption}}
</button>
</div>
</form>
</div>
<form method="POST" name="vm.loginForm" ng-submit="vm.loginSubmit(vm.login, vm.password)">
<div class="control-group" ng-show="vm.loginForm.$invalid">
<div class="text-error">{{vm.errorMsg}}</div>
</div>
<div class="control-group" ng-class="{error: vm.loginForm.username.$invalid}">
<label><localize key="general_username">Username</localize></label>
<input type="text" ng-model="vm.login" name="username" class="-full-width-input" localize="placeholder" placeholder="@placeholders_usernameHint" />
</div>
<div class="control-group" ng-class="{error: vm.loginForm.password.$invalid}">
<label><localize key="general_password">Password</localize></label>
<input type="password" ng-model="vm.password" name="password" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" />
<div class="password-toggle">
<a href="#" prevent-default ng-click="vm.togglePassword()">
<span class="password-text show"><localize key="login_showPassword">Show password</localize></span>
<span class="password-text hide"><localize key="login_hidePassword">Hide password</localize></span>
</a>
</div>
</div>
<div class="flex justify-between items-center">
<umb-button
button-style="success"
size="m"
label-key="general_login"
state="vm.loginStates.submitButton"
type="submit">
</umb-button>
<div ng-show="allowPasswordReset">
<a class="muted" style="text-decoration: underline;" href="#" prevent-default ng-click="vm.showRequestPasswordReset()"><localize key="login_forgottenPassword">Forgotten password?</localize></a>
</div>
</div>
</form>
</div>
<div ng-show="vm.view == 'request-password-reset'">
<p>
<localize key="login_forgottenPasswordInstruction">An email will be sent to the address specified with a link to reset your password</localize>
</p>
<form method="POST" name="vm.requestPasswordResetForm" ng-submit="vm.requestPasswordResetSubmit(email)">
<div class="control-group" ng-class="{error: requestPasswordResetForm.email.$invalid}">
<label><localize key="general_email">Email</localize></label>
<input type="email" val-email ng-model="email" name="email" class="-full-width-input" localize="placeholder" placeholder="@placeholders_email" />
</div>
<div class="control-group" ng-show="requestPasswordResetForm.$invalid">
<div class="text-error">{{errorMsg}}</div>
</div>
<div class="control-group" ng-show="vm.showEmailResetConfirmation">
<div class="text-info">
<localize key="login_requestPasswordResetConfirmation">An email with password reset instructions will be sent to the specified address if it matched our records</localize>
</div>
</div>
<div class="flex justify-between items-center">
<button type="submit" class="btn btn-success" val-trigger-change="#login .form input"><localize key="general_submit">Submit</localize></button>
<a class="muted" href="#" prevent-default ng-click="showLogin()" style="text-decoration: underline;"><localize key="login_returnToLogin">Return to login form</localize></a>
</div>
</form>
</div>
<div ng-show="vm.view == 'set-password'">
<p ng-hide="vm.resetComplete">
<localize key="login_setPasswordInstruction">Please provide a new password.</localize>
</p>
<form method="POST" name="vm.setPasswordForm" ng-submit="vm.setPasswordSubmit(vm.password, vm.confirmPassword)">
<div ng-hide="vm.resetComplete" class="control-group" ng-class="{error: setPasswordForm.password.$invalid}">
<label><localize key="user_newPassword">New password</localize></label>
<input type="password" ng-model="vm.password" name="password" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" />
</div>
<div ng-hide="vm.resetComplete" class="control-group" ng-class="{error: setPasswordForm.confirmPassword.$invalid}">
<label><localize key="user_confirmNewPassword">Confirm new password</localize></label>
<input type="password" ng-model="vm.confirmPassword" name="confirmPassword" class="-full-width-input" localize="placeholder" placeholder="@placeholders_confirmPassword" />
</div>
<div ng-hide="vm.resetComplete" class="control-group" ng-show="setPasswordForm.$invalid">
<div class="text-error">{{vm.errorMsg}}</div>
</div>
<div class="control-group" ng-show="vm.showSetPasswordConfirmation">
<div class="text-info">
<localize key="login_setPasswordConfirmation">Your new password has been set and you may now use it to log in.</localize>
</div>
</div>
<div class="flex justify-between items-center">
<button ng-hide="vm.resetComplete" type="submit" class="btn btn-success" val-trigger-change="#login .form input"><localize key="general_submit">Submit</localize></button>
<a class="muted" href="#" prevent-default ng-click="vm.showLogin()"><localize key="login_returnToLogin">Return to login form</localize></a>
</div>
</form>
</div>
<div ng-show="vm.view == 'password-reset-code-expired'">
<div class="text-error" ng-repeat="error in vm.resetPasswordCodeInfo.errors">
<span>{{error}}</span>
</div>
<div class="switch-view">
<a class="muted" href="#" prevent-default ng-click="vm.showLogin()"><localize key="login_returnToLogin">Return to login form</localize></a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -112,6 +112,11 @@
view="ysodOverlay.view">
</umb-overlay>
<umb-login
ng-if="login.show"
on-login="">
</umb-login>
@Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath }), Model.Features, UmbracoConfig.For.GlobalSettings())
<script>