diff --git a/Sevomin.Models/ChangePasswordViewModel.cs b/Sevomin.Models/ChangePasswordViewModel.cs new file mode 100644 index 0000000..0340c2f --- /dev/null +++ b/Sevomin.Models/ChangePasswordViewModel.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +namespace Sevomin.Models +{ + public class ChangePasswordViewModel + { + [DisplayName("رمز عبور فعلی")] + [Required(ErrorMessage = "ورود {0} الزامی است.")] + [DataType(DataType.Password)] + public string Password { get; set; } + + [DisplayName("رمز عبور جدید")] + [Required(ErrorMessage = "ورود {0} الزامی است.")] + [DataType(DataType.Password)] + public string NewPassword { get; set; } + + [DisplayName("تایید رمز عبور")] + [Required(ErrorMessage = "ورود {0} الزامی است.")] + [DataType(DataType.Password)] + [Compare("NewPassword", ErrorMessage = "تایید رمز عبور با رمز عبور جدید مطابقت ندارد.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/Sevomin.Models/ForgotPasswordViewModel.cs b/Sevomin.Models/ForgotPasswordViewModel.cs new file mode 100644 index 0000000..e0860e6 --- /dev/null +++ b/Sevomin.Models/ForgotPasswordViewModel.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +namespace Sevomin.Models +{ + public class ForgotPasswordViewModel + { + [DisplayName("ایمیل")] + [Required(ErrorMessage = "ورود {0} الزامی است.")] + public string Email { get; set; } + } +} diff --git a/Sevomin.Models/Helpers/SevominEmailer.cs b/Sevomin.Models/Helpers/SevominEmailer.cs index ac042ae..7e75273 100644 --- a/Sevomin.Models/Helpers/SevominEmailer.cs +++ b/Sevomin.Models/Helpers/SevominEmailer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Mail; using System.Text; @@ -7,18 +8,67 @@ using System.Threading.Tasks; namespace Sevomin.Models.Helpers { + public enum EmailType + { + EmailConfirmation, + PasswordReset, + NewPassword + } + public class SevominEmailer { public Dictionary Parameters { get; set; } + public EmailType EmailType { get; set; } + private string EmailFolderPath; + private string EmailConfirmationFilePath; + private string NewPasswordFilePath; + private string PasswordResetFilePath; public SevominEmailer() { - + EmailFolderPath = Path.Combine(System.Web.HttpContext.Current.Server.MapPath("~/app_data"), "emails"); + if (!Directory.Exists(EmailFolderPath)) + throw new ApplicationException("Emails folder does not exist in the right address."); + EmailConfirmationFilePath = Path.Combine(EmailFolderPath, "email-confirmation.html"); + NewPasswordFilePath = Path.Combine(EmailFolderPath, "new-password.html"); + PasswordResetFilePath = Path.Combine(EmailFolderPath, "password-reset.html"); + if(!File.Exists(EmailConfirmationFilePath)) + throw new ApplicationException("Email confirmation template does not exist in the right address."); + if (!File.Exists(NewPasswordFilePath)) + throw new ApplicationException("New password template does not exist in the right address."); + if (!File.Exists(PasswordResetFilePath)) + throw new ApplicationException("Password reset template does not exist in the right address."); + Parameters = new Dictionary(); + } + public SevominEmailer(Dictionary parameters, EmailType emailType) : this() + { + Parameters = parameters; + EmailType = emailType; } - public async void Send(string to, string subject, string template, bool isHtml) + public async Task SendAsync(string to, string subject, bool isHtml) { SmtpClient client = new SmtpClient(); MailMessage msg = new MailMessage(); + string template; + switch (EmailType) + { + case EmailType.EmailConfirmation: + template = + File.ReadAllText(EmailConfirmationFilePath, Encoding.UTF8); + break; + case EmailType.PasswordReset: + template = + File.ReadAllText(PasswordResetFilePath, Encoding.UTF8); + break; + case EmailType.NewPassword: + template = + File.ReadAllText(NewPasswordFilePath, Encoding.UTF8); + break; + default: + template = string.Empty; + break; + } + foreach (var address in to.Split(',')) msg.To.Add(address); msg.From = new MailAddress("no-reply@sevom.in", "سومین - مرکز کاریابی کنترل پروژه"); @@ -28,14 +78,16 @@ namespace Sevomin.Models.Helpers msg.IsBodyHtml = isHtml; Func getBody = () => { - foreach (var param in Parameters) - template = template.Replace(param.Key, param.Value); + foreach (var param in Parameters) + template = template.Replace(string.Format("[{0}]", param.Key), param.Value); return template; }; - msg.Body = getBody(); - - client.SendAsync(msg, null); + msg.Body = getBody(); + await Task.Run(() => + { + client.Send(msg); + }); } } -} +} \ No newline at end of file diff --git a/Sevomin.Models/Repositories/UserRepository.cs b/Sevomin.Models/Repositories/UserRepository.cs index 1f71637..72e40a8 100644 --- a/Sevomin.Models/Repositories/UserRepository.cs +++ b/Sevomin.Models/Repositories/UserRepository.cs @@ -45,7 +45,7 @@ namespace Sevomin.Models.Repositories public User Find(string identifier) { - throw new NotImplementedException(); + return SevominDbContext.Current.Users.FirstOrDefault(u => u.UserName.ToLower() == identifier.ToLower()); } public void Delete(User entity) @@ -55,13 +55,7 @@ namespace Sevomin.Models.Repositories public void Save() { - throw new NotImplementedException(); - } - - - public User Find(long identifier) - { - throw new NotImplementedException(); + SevominDbContext.Current.SaveChanges(); } } } diff --git a/Sevomin.Models/ResetPasswordViewModel.cs b/Sevomin.Models/ResetPasswordViewModel.cs new file mode 100644 index 0000000..8fa16c3 --- /dev/null +++ b/Sevomin.Models/ResetPasswordViewModel.cs @@ -0,0 +1,18 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +namespace Sevomin.Models +{ + public class ResetPasswordViewModel + { + [DisplayName("رمز عبور")] + [Required(ErrorMessage = "ورود {0} الزامی است.")] + [DataType(DataType.Password)] + public string Password { get; set; } + + [DisplayName("تایید رمز عبور")] + [Required(ErrorMessage = "ورود {0} الزامی است.")] + [DataType(DataType.Password)] + [Compare("Password", ErrorMessage = "تایید رمز عبور با رمز عبور اصلی مطابقت ندارد.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/Sevomin.Models/Sevomin.Models.csproj b/Sevomin.Models/Sevomin.Models.csproj index ae6fd8b..58a3510 100644 --- a/Sevomin.Models/Sevomin.Models.csproj +++ b/Sevomin.Models/Sevomin.Models.csproj @@ -72,6 +72,9 @@ + + + diff --git a/Sevomin.WebFrontend.Controllers/AccountController.cs b/Sevomin.WebFrontend.Controllers/AccountController.cs index 7b3578d..9f158ec 100644 --- a/Sevomin.WebFrontend.Controllers/AccountController.cs +++ b/Sevomin.WebFrontend.Controllers/AccountController.cs @@ -70,6 +70,14 @@ namespace Sevomin.WebFrontend.Controllers else if(user is Dovomin) await UserManager.AddToRoleAsync(user.Id, "Dovomin"); +#if !DEBUG + SevominEmailer emailer = new SevominEmailer(); + emailer.EmailType = EmailType.EmailConfirmation; + emailer.Parameters.Add("display-name", user.DisplayName); + emailer.Parameters.Add("confirmation-code", user.ConfirmationCode); + await emailer.SendAsync(user.Email, "تایید عضویت در سومین", true); +#endif + await SignInAsync(user, isPersistent: false); return RedirectToAction("MyProfile", "Account"); } @@ -96,7 +104,7 @@ namespace Sevomin.WebFrontend.Controllers if (user == null) return HttpNotFound(); - if (Request.IsAuthenticated) + if (Request.IsAuthenticated && User.Identity.Name.ToLower() != user.UserName.ToLower()) { ViewBag.Result = new PostResultViewModel(false, string.Format("شما با نام کاربری {0} در سایت وارد شده اید. نمی توانید حساب کاربری {1} را تایید نمایید.", @@ -146,12 +154,96 @@ namespace Sevomin.WebFrontend.Controllers return View(model); } - public ActionResult Logout() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); } + + public ActionResult Forgot() + { + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult Forgot(ForgotPasswordViewModel model) + { + var user = UserRepository.Current.Find(model.Email); + if (user == null) + { + ViewBag.Result = new PostResultViewModel(false, "کاربری با این آدرس ایمیل یافت نشد. لطفا دوباره تلاش کنید."); + return View(); + } + user.ConfirmationCode = Sevomin.Models.User.GetConfirmationCode(); + UserRepository.Current.Save(); + +#if !DEBUG + SevominEmailer emailer = new SevominEmailer(); + emailer.EmailType = EmailType.PasswordReset; + emailer.Parameters.Add("display-name", user.DisplayName); + emailer.Parameters.Add("reset-code", user.ConfirmationCode); + await emailer.SendAsync(user.Email, "بازنشانی رمز عبور در سومین", true); +#endif + + ViewBag.Result = new PostResultViewModel(true, "آدرس بازنشانی رمز عبور برای شما ارسال شد."); + return View(); + } + + public ActionResult ResetPassword(string code) + { + var user = UserRepository.Current.FindWithConfirmationCode(code); + if (user == null) + return HttpNotFound(); + + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ResetPassword(string code, ResetPasswordViewModel model) + { + var user = UserRepository.Current.FindWithConfirmationCode(code); + if (user == null) + return HttpNotFound(); + + await UserManager.RemovePasswordAsync(user.Id); + await UserManager.AddPasswordAsync(user.Id, model.Password); + user.ConfirmationCode = string.Empty; + UserRepository.Current.Save(); + + ViewBag.Result = new PostResultViewModel(true, "رمز عبور شما با موفقیت بازنشانی شد."); + + return View(); + } + + [Authorize] + public ActionResult ChangePassword() + { + return View(); + } + + [Authorize] + [HttpPost] + public async Task ChangePassword(ChangePasswordViewModel model) + { + var user = await UserManager.FindAsync(User.Identity.Name, model.Password); + if (user != null) + { + await UserManager.RemovePasswordAsync(user.Id); + await UserManager.AddPasswordAsync(user.Id, model.NewPassword); + UserRepository.Current.Save(); + + ViewBag.Result = new PostResultViewModel(true, "رمز عبور شما با موفقیت به روز شد."); + return View(); + } + else + { + ViewBag.Result = new PostResultViewModel(false, "رمز عبور فعلی وارد شده با اطلاعات ما مطابقت ندارد. لطفا دوباره تلاش کنید."); + return View(); + } + } + [Authorize] public async Task MyProfile(bool? success) diff --git a/Sevomin.WebFrontend/App_Start/RouteConfig.cs b/Sevomin.WebFrontend/App_Start/RouteConfig.cs index ec4e7d3..ecc33fe 100644 --- a/Sevomin.WebFrontend/App_Start/RouteConfig.cs +++ b/Sevomin.WebFrontend/App_Start/RouteConfig.cs @@ -64,6 +64,21 @@ namespace Sevomin.WebFrontend url: "dovomin/id-{userId}", defaults: new { controller = "Account", action = "Dovomin" } ); + routes.MapRoute( + name: "ForgotPassword", + url: "forgot-password", + defaults: new { controller = "Account", action = "Forgot" } + ); + routes.MapRoute( + name: "ResetPassword", + url: "reset-password/{code}", + defaults: new { controller = "Account", action = "ResetPassword" } + ); + routes.MapRoute( + name: "ChangePassword", + url: "change-password", + defaults: new { controller = "Account", action = "ChangePassword" } + ); #endregion #region For Jobs diff --git a/Sevomin.WebFrontend/Content/intro.css b/Sevomin.WebFrontend/Content/intro.css index 97fb4ce..ad61802 100644 --- a/Sevomin.WebFrontend/Content/intro.css +++ b/Sevomin.WebFrontend/Content/intro.css @@ -1,5 +1,5 @@ .introduction{ - height: 270px; + min-height: 270px; } .intro-icon, #sevomin-intro-logo{ text-align: center; @@ -20,6 +20,13 @@ #sevomin-intro-logo img{ width: 150px; } +ul#intro-parts{ + margin: 0; + padding: 0; +} +ul#intro-parts li{ + list-style: none; +} #s1, #s2, #s3 { font-size: 0.95em; color: #444444; diff --git a/Sevomin.WebFrontend/Sevomin.WebFrontend.csproj b/Sevomin.WebFrontend/Sevomin.WebFrontend.csproj index c4e3c5f..2c5667d 100644 --- a/Sevomin.WebFrontend/Sevomin.WebFrontend.csproj +++ b/Sevomin.WebFrontend/Sevomin.WebFrontend.csproj @@ -122,6 +122,9 @@ + + + @@ -318,6 +321,9 @@ + + + Web.config diff --git a/Sevomin.WebFrontend/Views/Account/ChangePassword.cshtml b/Sevomin.WebFrontend/Views/Account/ChangePassword.cshtml new file mode 100644 index 0000000..862ca4d --- /dev/null +++ b/Sevomin.WebFrontend/Views/Account/ChangePassword.cshtml @@ -0,0 +1,51 @@ +@model Sevomin.Models.ChangePasswordViewModel + +@{ + ViewBag.Title = "تغییر کلمه عبور"; +} +@Html.Partial("PostResult", ViewBag.Result as Sevomin.Models.PostResultViewModel) + +
+
+ @using (Html.BeginForm("ChangePassword", "Account", FormMethod.Post, new { role = "form" })) + { + @Html.AntiForgeryToken() +
+
+
+ @Html.LabelFor(model => model.Password, new { @class = "control-label" }) +
+
+ @Html.PasswordFor(model => model.Password, new { @class = "form-control ltr" }) + @Html.ValidationMessageFor(model => model.Password) +
+
+
+
+ @Html.LabelFor(model => model.NewPassword, new { @class = "control-label" }) +
+
+ @Html.PasswordFor(model => model.NewPassword, new { @class = "form-control ltr" }) + @Html.ValidationMessageFor(model => model.NewPassword) +
+
+
+
+ @Html.LabelFor(model => model.ConfirmPassword, new { @class = "control-label" }) +
+
+ @Html.PasswordFor(model => model.ConfirmPassword, new { @class = "form-control ltr" }) + @Html.ValidationMessageFor(model => model.ConfirmPassword) +
+
+
+
+ +
+
+
+ } +
+
\ No newline at end of file diff --git a/Sevomin.WebFrontend/Views/Account/Forgot.cshtml b/Sevomin.WebFrontend/Views/Account/Forgot.cshtml new file mode 100644 index 0000000..9f669e1 --- /dev/null +++ b/Sevomin.WebFrontend/Views/Account/Forgot.cshtml @@ -0,0 +1,34 @@ +@model Sevomin.Models.ForgotPasswordViewModel + +@{ + ViewBag.Title = "بازنشانی رمز عبور"; +} +@Html.Partial("PostResult", ViewBag.Result as Sevomin.Models.PostResultViewModel) + +
+
+ @using (Html.BeginForm("Forgot", "Account", FormMethod.Post, new { role = "form" })) + { + @Html.AntiForgeryToken() +
+ +
+
+ @Html.LabelFor(model => model.Email, new { @class = "control-label" }) +
+
+ @Html.TextBoxFor(model => model.Email, new { @class = "form-control ltr" }) + @Html.ValidationMessageFor(model => model.Email) +
+
+
+
+ +
+
+
+ } +
+
\ No newline at end of file diff --git a/Sevomin.WebFrontend/Views/Account/Login.cshtml b/Sevomin.WebFrontend/Views/Account/Login.cshtml index a025df8..27998a0 100644 --- a/Sevomin.WebFrontend/Views/Account/Login.cshtml +++ b/Sevomin.WebFrontend/Views/Account/Login.cshtml @@ -11,7 +11,7 @@

اگر قبلا در سایت ثبت‌نام کرده‌اید می‌توانید با وارد کردن کد کاربری و کلمه عبور وارد سایت شوید. اگر قبلا در سایت ثبت‌نام نکرده‌اید می‌توانید هم‌اکنون به هر بخشی که مایل هستید بروید و اطلاعات موجود در سایت را مرور کنید (به جز مواردی که از سوی اشخاص محرمانه معرفی شده‌اند). می‌توانید با مراجعه به صفحه اول سایت ثبت‌نام نیز بکنید تا بتوانید برای آگهی‌های استخدام اعلام آمادگی کنید، آگهی استخدام ثبت کنید و ... -

+

@@ -24,24 +24,28 @@
- @Html.LabelFor(model => model.Username, new { @class = "control-label" }) -
+
+ @Html.LabelFor(model => model.Username, new { @class = "control-label" }) +
+
@Html.TextBoxFor(model => model.Username, new { @class = "form-control ltr" }) @Html.ValidationMessageFor(model => model.Username)
- @Html.LabelFor(model => model.Password, new { @class = "control-label" }) -
+
+ @Html.LabelFor(model => model.Password, new { @class = "control-label" }) +
+
@Html.PasswordFor(model => model.Password, new { @class = "form-control ltr" }) @Html.ValidationMessageFor(model => model.Password)
-
- +
-
+
+ @Html.ActionLink("کلمه عبور خود را فراموش کرده اید؟", "Forgot")
diff --git a/Sevomin.WebFrontend/Views/Account/ResetPassword.cshtml b/Sevomin.WebFrontend/Views/Account/ResetPassword.cshtml new file mode 100644 index 0000000..f896da7 --- /dev/null +++ b/Sevomin.WebFrontend/Views/Account/ResetPassword.cshtml @@ -0,0 +1,42 @@ +@model Sevomin.Models.ResetPasswordViewModel + +@{ + ViewBag.Title = "بازنشانی رمز عبور"; +} +@Html.Partial("PostResult", ViewBag.Result as Sevomin.Models.PostResultViewModel) + +
+
+ @using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { role = "form" })) + { + @Html.AntiForgeryToken() +
+
+
+ @Html.LabelFor(model => model.Password, new { @class = "control-label" }) +
+
+ @Html.PasswordFor(model => model.Password, new { @class = "form-control ltr" }) + @Html.ValidationMessageFor(model => model.Password) +
+
+
+
+ @Html.LabelFor(model => model.ConfirmPassword, new { @class = "control-label" }) +
+
+ @Html.PasswordFor(model => model.ConfirmPassword, new { @class = "form-control ltr" }) + @Html.ValidationMessageFor(model => model.ConfirmPassword) +
+
+
+
+ +
+
+
+ } +
+
\ No newline at end of file diff --git a/Sevomin.WebFrontend/Views/Shared/Intro.cshtml b/Sevomin.WebFrontend/Views/Shared/Intro.cshtml index 92ad6c8..5396a7e 100644 --- a/Sevomin.WebFrontend/Views/Shared/Intro.cshtml +++ b/Sevomin.WebFrontend/Views/Shared/Intro.cshtml @@ -20,12 +20,14 @@
-
-
کارفرمای جویای متخصص
-
متخصص جویای کار
-
ما، که ارتباطی موثر بین شما برقرار می‌کنیم
-
+
+
+
+
    +
  • کارفرمای جویای متخصص
  • +
  • متخصص جویای کار
  • +
  • ما، که ارتباطی موثر بین شما برقرار می‌کنیم
  • +
@Html.Partial("IntroSignup") diff --git a/Sevomin.WebFrontend/Views/Shared/IntroSignup.cshtml b/Sevomin.WebFrontend/Views/Shared/IntroSignup.cshtml index c24f244..5654d45 100644 --- a/Sevomin.WebFrontend/Views/Shared/IntroSignup.cshtml +++ b/Sevomin.WebFrontend/Views/Shared/IntroSignup.cshtml @@ -17,6 +17,7 @@

ما به شما کمک می‌کنیم که نیرویی مطابق با نیازهای خود بیابید.

تمام این خدمات رایگان ارائه می‌شوند.

+
@using (Html.BeginForm("Signup", "Account", FormMethod.Post, new { role = "form" })) { @Html.AntiForgeryToken() @@ -48,6 +49,7 @@

حتی می‌توانید رزومه و مشخصات خود را در اختیار ما قرار دهید تا خودمان کارهای متناسب را برایتان بیابیم و از طرف شما اعلام آمادگی کنیم.

تمام این خدمات رایگان ارائه می‌شوند.

+
@using (Html.BeginForm("Signup", "Account", FormMethod.Post, new { role = "form" })) { @Html.AntiForgeryToken() diff --git a/Sevomin.WebFrontend/Views/Shared/Navbar.cshtml b/Sevomin.WebFrontend/Views/Shared/Navbar.cshtml index 3ddbbc5..098f594 100644 --- a/Sevomin.WebFrontend/Views/Shared/Navbar.cshtml +++ b/Sevomin.WebFrontend/Views/Shared/Navbar.cshtml @@ -22,7 +22,12 @@ } else { -
  • ویرایش پروفایل
  • +
  • خروج از سایت
  • } diff --git a/Sevomin.WebFrontend/Views/Shared/PostResult.cshtml b/Sevomin.WebFrontend/Views/Shared/PostResult.cshtml index 6805512..973f709 100644 --- a/Sevomin.WebFrontend/Views/Shared/PostResult.cshtml +++ b/Sevomin.WebFrontend/Views/Shared/PostResult.cshtml @@ -1,3 +1,3 @@ @if (Model != null && Model is Sevomin.Models.PostResultViewModel) { -
    @Model.Message
    +
    @MvcHtmlString.Create(Model.Message)
    } \ No newline at end of file diff --git a/Sevomin.WebFrontend/Web.config b/Sevomin.WebFrontend/Web.config index 5da6bf9..4d05837 100644 --- a/Sevomin.WebFrontend/Web.config +++ b/Sevomin.WebFrontend/Web.config @@ -32,6 +32,13 @@ + + + + + + +