diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml index 6cc077a663..2e7e7f3f4c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml @@ -2,6 +2,10 @@ @model ApplicationMenu @foreach (var menuItem in Model.Items) { + var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id=\"{menuItem.ElementId}\""; + var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass; + var disabled = menuItem.IsDisabled ? "disabled" : string.Empty; + if (menuItem.IsLeaf) { if (menuItem.Url == null) @@ -9,7 +13,7 @@ continue; } - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/UserMenu/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/UserMenu/Default.cshtml index 8ba998785e..c30c7b6a0b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/UserMenu/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/UserMenu/Default.cshtml @@ -11,16 +11,20 @@ - + @if (Model.Items.Any()) { } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs index 354cfd3b00..aa4003c5f0 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs @@ -37,7 +37,7 @@ namespace Volo.Abp.UI.Navigation /// Default value: 1000. /// public int Order { get; set; } - + /// /// The URL to navigate when this menu item is selected. /// @@ -65,7 +65,7 @@ namespace Volo.Abp.UI.Navigation /// Can be used to disable this menu item. /// public bool IsDisabled { get; set; } - + /// [NotNull] public IList Items { get; } @@ -75,14 +75,27 @@ namespace Volo.Abp.UI.Navigation /// public object CustomData { get; set; } + /// + /// Can be used to render the element with a specific Id for DOM selections. + /// + public string ElementId { get; set; } + + /// + /// Can be used to render the element with extra CSS classes. + /// + public string CssClass { get; set; } + + public ApplicationMenuItem( - [NotNull] string name, + [NotNull] string name, [NotNull] string displayName, string url = null, string icon = null, int order = DefaultOrder, object customData = null, - string target = null) + string target = null, + string elementId = null, + string cssClass = null) { Check.NotNullOrWhiteSpace(name, nameof(name)); Check.NotNullOrWhiteSpace(displayName, nameof(displayName)); @@ -94,6 +107,8 @@ namespace Volo.Abp.UI.Navigation Order = order; CustomData = customData; Target = target; + ElementId = elementId; + CssClass = cssClass; Items = new List(); } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json index c324d667ea..9c8944176b 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json @@ -33,6 +33,7 @@ "PagerNext": "Next", "PagerPrevious": "Previous", "PagerInfo": "Showing {0} to {1} of {2} entries.", - "DatatableActionDropdownDefaultText": "Actions" + "DatatableActionDropdownDefaultText": "Actions", + "ChangePassword": "Change password" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json index 7086210144..f85e255058 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json @@ -33,6 +33,7 @@ "PagerNext": "Sonraki", "PagerPrevious": "Önceki", "PagerInfo": "{2} kayıttan {0} ile {1} arası gösteriliyor.", - "DatatableActionDropdownDefaultText": "İşlemler" + "DatatableActionDropdownDefaultText": "İşlemler", + "ChangePassword": "Şifre değiştir" } } \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs index ef5e661771..d021cbabe1 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.Account.Web { public AbpAccountUserMenuContributor() { - + } public Task ConfigureMenuAsync(MenuConfigurationContext context) @@ -22,6 +22,8 @@ namespace Volo.Abp.Account.Web var l = context.ServiceProvider.GetRequiredService>(); + context.Menu.AddItem(new ApplicationMenuItem("Account.ChangePassword", l["ChangePassword"], icon: "fa fa-key", url: "#", elementId: "abp-account-change-password")); + context.Menu.AddItem(new ApplicationMenuItem("Account.Logout", l["Logout"], url: "/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000)); return Task.CompletedTask; diff --git a/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json b/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json index a318da87a2..b56354053c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json +++ b/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json @@ -9,7 +9,7 @@ "UseAnotherServiceToLogin": "Başka bir servisle giriş yap", "UserLockedOutMessage": "Kullanıcı hesabı hatalı giriş denemeleri nedeniyle kilitlenmiştir. Lütfen bir süre bekleyip tekrar deneyin.", "InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!", - "LoginIsNotAllowed": "You are not allowed to login! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.", + "LoginIsNotAllowed": "Giriş yapamazsınız! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.", "SelfRegistrationDisabledMessage": "Bu uygulama için kullanıcıların kendi kendilerine kaydolmaları engellenmiştir. Yeni bir kullanıcı kaydetmek için lütfen uygulama yöneticisi ile iletişime geçin." } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs index 50ea282f15..39d510f2c1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs @@ -19,5 +19,7 @@ namespace Volo.Abp.Identity Task FindByUsernameAsync(string username); Task FindByEmailAsync(string email); + + Task ChangePasswordAsync(string currentPassword, string newPassword); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs index 3f28afb3f5..18e5748c6d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs @@ -113,7 +113,6 @@ namespace Volo.Abp.Identity await _permissionAppServiceHelper.UpdateAsync(UserPermissionValueProvider.ProviderName, id.ToString(), input); } - [Authorize(IdentityPermissions.Users.Default)] public async Task FindByUsernameAsync(string username) { return ObjectMapper.Map( @@ -121,7 +120,6 @@ namespace Volo.Abp.Identity ); } - [Authorize(IdentityPermissions.Users.Default)] public async Task FindByEmailAsync(string email) { return ObjectMapper.Map( @@ -129,6 +127,17 @@ namespace Volo.Abp.Identity ); } + public async Task ChangePasswordAsync(string currentPassword, string newPassword) + { + if (!CurrentUser.Id.HasValue) + { + throw new AbpException("Current user Id is null!"); + } + + var currentUser = await _userManager.GetByIdAsync(CurrentUser.Id.Value); + (await _userManager.ChangePasswordAsync(currentUser, currentPassword, newPassword)).CheckErrors(); + } + private async Task UpdateUserByInput(IdentityUser user, IdentityUserCreateOrUpdateDtoBase input) { (await _userManager.SetEmailAsync(user, input.Email)).CheckErrors(); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json index a1ee4371d4..c906f523b1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json @@ -25,6 +25,7 @@ "Identity.UserLockedOut": "User is locked out.", "Identity.UserLockoutNotEnabled": "Lockout is not enabled for this user.", "Identity.UserNameNotFound": "User {0} does not exist.", - "Identity.UserNotInRole": "User is not in role '{0}'." + "Identity.UserNotInRole": "User is not in role '{0}'.", + "Identity.PasswordConfirmationFailed": "Password does not match the confirm password." } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/tr.json b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/tr.json index d2401b47a4..96cf510ff8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/tr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/tr.json @@ -25,6 +25,7 @@ "Identity.UserLockedOut": "Kullanıcı hesabı kilitlenmiş.", "Identity.UserLockoutNotEnabled": "Bu kullanıcı için hesap kilitleme etkin değil.", "Identity.UserNameNotFound": "{0} kullanıcısı bulunamadı.", - "Identity.UserNotInRole": "Kullanıcı '{0}' rolünde değil." + "Identity.UserNotInRole": "Kullanıcı '{0}' rolünde değil.", + "Identity.PasswordConfirmationFailed": "Yeni şifre ile onay şifresi uyuşmuyor." } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs index 761f98d6bf..2d6ad778ae 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs @@ -64,7 +64,6 @@ namespace Volo.Abp.Identity return _userAppService.UpdatePermissionsAsync(id, input); } - //todo: add authorize attrbutes on the corresponding methods. [HttpGet] public virtual Task FindByUsernameAsync(string username) { @@ -76,5 +75,10 @@ namespace Volo.Abp.Identity { return _userAppService.FindByEmailAsync(email); } + + public Task ChangePasswordAsync(string currentPassword, string newPassword) + { + return _userAppService.ChangePasswordAsync(currentPassword, newPassword); + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index 62888d0abe..5444840fa5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; using Volo.Abp.AutoMapper; using Volo.Abp.Identity.Localization; using Volo.Abp.Identity.Web.Navigation; @@ -65,6 +67,14 @@ namespace Volo.Abp.Identity.Web options.Conventions.AuthorizePage("/Identity/Roles/CreateModal", IdentityPermissions.Roles.Create); options.Conventions.AuthorizePage("/Identity/Roles/EditModal", IdentityPermissions.Roles.Update); }); + + Configure(options => + { + options + .ScriptBundles + .Get(StandardBundles.Scripts.Global) + .AddFiles("/Pages/Identity/Shared/change-password-modal.js"); + }); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json index d0757f5a54..e63e8fb4b2 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json @@ -22,6 +22,10 @@ "NewRole": "New role", "RoleName": "Role name", "CreationTime": "Creation time", - "Permissions": "Permissions" + "Permissions": "Permissions", + "DisplayName:CurrentPassword": "Current password", + "DisplayName:NewPassword": "New password", + "DisplayName:NewPasswordConfirm": "Confirm new password", + "PasswordChangedMessage": "Your password has been changed successfully." } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json index f330cf5b70..7484851424 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json +++ b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json @@ -22,6 +22,10 @@ "NewRole": "Yeni rol", "RoleName": "Rol adı", "CreationTime": "Oluşturma zamanı", - "Permissions": "İzinler" + "Permissions": "İzinler", + "DisplayName:CurrentPassword": "Mevcut şifre", + "DisplayName:NewPassword": "Yeni şifre", + "DisplayName:NewPasswordConfirm": "Yeni şifre (tekrar)", + "PasswordChangedMessage": "Şifreniz başarıyla değiştirildi." } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/ChangePasswordModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/ChangePasswordModal.cshtml new file mode 100644 index 0000000000..54bae13cbe --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/ChangePasswordModal.cshtml @@ -0,0 +1,20 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Identity.Localization +@using Volo.Abp.Identity.Web.Pages.Identity.Shared +@model ChangePasswordModal +@inject IHtmlLocalizer L +@{ + Layout = null; +} + + + + + + + + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/ChangePasswordModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/ChangePasswordModal.cshtml.cs new file mode 100644 index 0000000000..0e753edd6c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/ChangePasswordModal.cshtml.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Identity.Localization; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Shared +{ + public class ChangePasswordModal : AbpPageModel + { + [BindProperty] + public ChangePasswordInfoModel ChangePasswordInfoModel { get; set; } + + private readonly IIdentityUserAppService _userAppService; + private readonly IStringLocalizer _localizer; + + public ChangePasswordModal(IIdentityUserAppService userAppService, + IStringLocalizer localizer) + { + _userAppService = userAppService; + _localizer = localizer; + } + + public async Task OnPostAsync() + { + ValidateModel(); + + if (ChangePasswordInfoModel.NewPassword != ChangePasswordInfoModel.NewPasswordConfirm) + { + throw new UserFriendlyException(_localizer.GetString("Identity.PasswordConfirmationFailed").Value); + } + + await _userAppService.ChangePasswordAsync(ChangePasswordInfoModel.CurrentPassword, + ChangePasswordInfoModel.NewPassword); + + return NoContent(); + } + } + + public class ChangePasswordInfoModel + { + [Required] + [StringLength(IdentityUserConsts.MaxPasswordLength)] + [Display(Name = "DisplayName:CurrentPassword")] + [DataType(DataType.Password)] + public string CurrentPassword { get; set; } + + [Required] + [StringLength(IdentityUserConsts.MaxPasswordLength)] + [Display(Name = "DisplayName:NewPassword")] + [DataType(DataType.Password)] + public string NewPassword { get; set; } + + [Required] + [StringLength(IdentityUserConsts.MaxPasswordLength)] + [Display(Name = "DisplayName:NewPasswordConfirm")] + [DataType(DataType.Password)] + public string NewPasswordConfirm { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/change-password-modal.js b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/change-password-modal.js new file mode 100644 index 0000000000..c377ea718b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Shared/change-password-modal.js @@ -0,0 +1,18 @@ +(function ($) { + + var l = abp.localization.getResource('AbpIdentity'); + var _changePasswordModal = new abp.ModalManager(abp.appPath + 'Identity/Shared/ChangePasswordModal'); + + $(function () { + + $("#abp-account-change-password").click(function (e) { + e.preventDefault(); + _changePasswordModal.open(); + }); + + _changePasswordModal.onResult(function () { + abp.message.success(l("PasswordChangedMessage")); + }); + }); + +})(jQuery); diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index a2b78b5495..e86149c47f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -37,6 +37,7 @@ +