Admin auth functionally complete

This commit is contained in:
2012-12-31 14:49:41 -05:00
parent 8739251066
commit 2ec2a752cd
30 changed files with 260 additions and 79 deletions
-6
View File
@@ -6,7 +6,6 @@ body {
.center-content {
display: block;
max-width: 400px;
padding: 10px;
margin: 0 auto 20px;
}
@@ -78,10 +77,6 @@ footer {
padding: 4px;
}
.alert .close {
position: static;
}
.qtip-content dt {
line-height: 10px;
width: 110px;
@@ -211,7 +206,6 @@ dl.inline {
}
.center-content {
padding: 5px;
margin: 0 auto 10px;
}
+17 -4
View File
@@ -1,6 +1,7 @@
using System;
using System.Web.Mvc;
using System.Web.Security;
using MileageTraker.Web.DAL;
using MileageTraker.Web.ViewModels.Account;
namespace MileageTraker.Web.Controllers
@@ -21,6 +22,8 @@ namespace MileageTraker.Web.Controllers
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
var success = Membership.ValidateUser(model.Username, model.Password);
if (success)
@@ -29,10 +32,19 @@ namespace MileageTraker.Web.Controllers
TempData["StatusMessage"] = "Logged in as " + model.Username;
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
catch (UserAccountDisabledException)
{
ModelState.AddModelError("", "Account is disabled for " + model.Username + ".");
}
catch (UserLockedOutException)
{
ModelState.AddModelError("", "Too many failed password attempts for " + model.Username + ". Account is locked.");
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
@@ -40,9 +52,10 @@ namespace MileageTraker.Web.Controllers
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
TempData["StatusMessage"] = User.Identity.Name + " logged off";
FormsAuthentication.SignOut();
TempData["StatusMessage"] = "Logged off";
return RedirectToAction("Index", "CreateLog");
}
@@ -81,7 +94,7 @@ namespace MileageTraker.Web.Controllers
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: "";
: null;
ViewBag.ReturnUrl = Url.Action("Manage");
return View();
}
@@ -108,7 +121,7 @@ namespace MileageTraker.Web.Controllers
if (changePasswordSucceeded)
{
TempData["StatusMessage"] = ManageMessageId.ChangePasswordSuccess;
TempData["StatusMessage"] = "Your password has been changed.";
return RedirectToAction("Manage");
}
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
+11 -2
View File
@@ -45,7 +45,7 @@ namespace MileageTraker.Web.Controllers
[HttpParamAction]
[HttpPost]
[ActionLog]
public ViewResult Confirm(CreateLogViewModel model)
public ActionResult Confirm(CreateLogViewModel model)
{
if (ModelState.IsValid)
{
@@ -59,7 +59,16 @@ namespace MileageTraker.Web.Controllers
log.UserHostAddress = HttpContext.Request.UserHostAddress;
log.UserAgent = HttpContext.Request.UserAgent;
DataService.AddLog(log);
return View("Success", model);
TempData["StatusMessage-Type"] = "alert-success";
TempData["StatusMessage"] =
@"You've successfully created an entry
for <strong>" + model.EmployeeName + @"</strong>
traveling to <strong>" + model.CityName + @"</strong>
on <strong>" + model.Date.ToShortDateString() + @"</strong>
in Vehicle Id <strong>" + model.VehicleId + @"</strong>
ending in <strong>" + model.EndOdometer + @"</strong>
miles on the odometer.";
return RedirectToAction("Index");
}
return View("Index", model);
+65 -2
View File
@@ -24,6 +24,18 @@ namespace MileageTraker.Web.Controllers
return View(DataService.GetUser(id));
}
public JsonResult UsernameAvailable(string username)
{
var user = DataService.FindUserByUsername(username);
return Json(user == null, JsonRequestBehavior.AllowGet);
}
public JsonResult EmailAvailable(string email)
{
var user = DataService.FindUserByEmail(email);
return Json(user == null, JsonRequestBehavior.AllowGet);
}
public ActionResult Create()
{
var vm = new CreateUserViewModel
@@ -111,7 +123,7 @@ namespace MileageTraker.Web.Controllers
}
TempData["StatusMessage"] = "Changes saved for " + user.Username;
return RedirectToAction("Index");
return RedirectToAction("Details", new { id = viewModel.UserId});
}
return View(viewModel);
}
@@ -132,7 +144,6 @@ namespace MileageTraker.Web.Controllers
{
if (ModelState.IsValid)
{
// ChangePassword will throw an exception rather than return false in certain failure scenarios.
try
{
DataService.UpdateUserPassword(viewModel.UserId, viewModel.NewPassword);
@@ -149,6 +160,58 @@ namespace MileageTraker.Web.Controllers
return View(viewModel);
}
public ActionResult DisableUser(Guid id)
{
var user = DataService.GetUser(id);
if (user == null)
{
return HttpNotFound();
}
user.IsApproved = false;
DataService.UpdateUser(user);
TempData["StatusMessage"] = user.Username + " disabled";
if (Request.UrlReferrer != null)
return Redirect(Request.UrlReferrer.AbsolutePath);
return RedirectToAction("Index");
}
public ActionResult EnableUser(Guid id)
{
var user = DataService.GetUser(id);
if (user == null)
{
return HttpNotFound();
}
user.IsApproved = true;
DataService.UpdateUser(user);
TempData["StatusMessage"] = user.Username + " enabled";
if (Request.UrlReferrer != null)
return Redirect(Request.UrlReferrer.AbsolutePath);
return RedirectToAction("Index");
}
public ActionResult UnlockUser(Guid id)
{
var user = DataService.GetUser(id);
if (user == null)
{
return HttpNotFound();
}
user.IsLockedOut = false;
user.PasswordFailuresSinceLastSuccess = 0;
DataService.UpdateUser(user);
TempData["StatusMessage"] = user.Username + " unlocked";
if (Request.UrlReferrer != null)
return Redirect(Request.UrlReferrer.AbsolutePath);
return RedirectToAction("Index");
}
private static string ErrorCodeToString(MembershipCreateStatus createStatus)
{
// See http://go.microsoft.com/fwlink/?LinkID=177550 for
+1
View File
@@ -63,6 +63,7 @@ namespace MileageTraker.Web.Controllers
return View(vehicle);
}
[AllowAnonymous]
public JsonResult Exists(string vehicleId)
{
var vehicle = DataService.GetVehicle(vehicleId);
+2 -2
View File
@@ -136,11 +136,11 @@ namespace MileageTraker.Web.DAL
}
if (!user.IsApproved)
{
return false;
throw new UserAccountDisabledException();
}
if (user.IsLockedOut)
{
return false;
throw new UserLockedOutException();
}
var hashedPassword = user.Password;
var verificationSucceeded = (hashedPassword != null && Crypto.VerifyHashedPassword(hashedPassword, password));
+2
View File
@@ -475,6 +475,8 @@ namespace MileageTraker.Web.DAL
var user = GetUser(userId);
user.Password = Crypto.HashPassword(password);
user.LastPasswordChangedDate = DateTime.Now;
user.IsLockedOut = false;
user.PasswordFailuresSinceLastSuccess = 0;
UpdateUser(user);
}
+8
View File
@@ -0,0 +1,8 @@
using System;
namespace MileageTraker.Web.DAL
{
public class UserAccountDisabledException : Exception
{
}
}
+8
View File
@@ -0,0 +1,8 @@
using System;
namespace MileageTraker.Web.DAL
{
public class UserLockedOutException : Exception
{
}
}
+2 -1
View File
@@ -253,7 +253,8 @@ $(function () {
},
unhighlight: function (element) {
$(element).closest(".control-group").removeClass("error");
}
},
debug: true
});
});
-7
View File
@@ -6,7 +6,6 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using MileageTraker.Web.Models;
namespace MileageTraker.Web.Utility
{
@@ -182,11 +181,5 @@ namespace MileageTraker.Web.Utility
Convert.ToDouble(
System.Web.Security.Membership.UserIsOnlineTimeWindow)));
}
public static bool IsOnline(this User user)
{
return user.LastActivityDate != null
&& user.LastActivityDate > UserOnlineThreshold();
}
}
}
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using AutoMapper;
using MileageTraker.Web.Attributes;
@@ -9,12 +10,14 @@ namespace MileageTraker.Web.ViewModels.User
[Required]
[StringLength(64)]
[InputSize("small")]
[Remote("UsernameAvailable", "User", ErrorMessage = "Username already in use")]
public string Username { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[RegularExpression(@"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}", ErrorMessage = "Must be an email address")]
[InputSize("large")]
[Remote("EmailAvailable", "User", ErrorMessage = "Email already in use")]
public string Email { get; set; }
[Required]
+2
View File
@@ -11,6 +11,8 @@
@using (Html.BeginForm("Login", "Account", new { ViewBag.ReturnUrl }, FormMethod.Post, new {@class = "form-login"})) {
@Html.Partial("_StatusMessage")
<div class="header"></div>
<h2>@ViewBag.Title</h2>
+2
View File
@@ -4,6 +4,8 @@
ViewBag.Title = "Enter Mileage Log";
}
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("Index", "CreateLog", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
-17
View File
@@ -1,17 +0,0 @@
@model MileageTraker.Web.ViewModels.CreateLog.CreateLogViewModel
@{
ViewBag.Title = "Success";
}
<p class="alert alert-success center-content">
You've successfully created an entry
for <strong>@Html.DisplayTextFor(m => m.EmployeeName)</strong>
traveling to <strong>@Html.DisplayTextFor(m => m.CityName)</strong>
on <strong>@Html.Encode(Model.Date.ToShortDateString())</strong>
in Vehicle Id <strong>@Html.DisplayTextFor(m => m.VehicleId)</strong>
ending in <strong>@Html.DisplayTextFor(m => m.EndOdometer)</strong>
miles on the odometer.
</p>
<p class="center-content">
@Html.ActionLink("Create another", "Index", null, new { @class = "btn" })
</p>
+2
View File
@@ -6,6 +6,8 @@
@{ Html.RenderPartial("BackToLogs"); }
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("Create", "Log", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
+2
View File
@@ -6,6 +6,8 @@
@{ Html.RenderPartial("BackToLogs"); }
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
<div class="center-content label label-warning">Are you sure you wish to delete this log?</div>
-3
View File
@@ -15,9 +15,6 @@
<li class="previous">
@Html.ActionLink("Previous", "PreviousDetails", new { id = Model.LogId })
</li>
@if(TempData["Message"] != null) { // so, yeah, span doesn't go here, but it renders
<span class="alert">@TempData["Message"]</span>
}
<li class="next">
@Html.ActionLink("Next", "NextDetails", new { id = Model.LogId })
</li>
+2
View File
@@ -6,6 +6,8 @@
@{ Html.RenderPartial("BackToLogs"); }
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("Edit", "Log", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
@@ -5,6 +5,8 @@
@{ Html.RenderPartial("BackToLogs"); }
@Html.Partial("_StatusMessage")
<h2>@ViewBag.Title</h2>
<div>
@@ -5,6 +5,8 @@
@{ Html.RenderPartial("BackToLogs"); }
@Html.Partial("_StatusMessage")
<h2>@ViewBag.Title</h2>
<div>
+3 -3
View File
@@ -1,12 +1,12 @@
@if (TempData.ContainsKey("StatusMessage"))
{
<p class="center-content alert alert-info">
<p class="center-content alert @Html.Raw(TempData["StatusMessage-Type"])">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@TempData["StatusMessage"]
@Html.Raw(TempData["StatusMessage"])
</p>
} else if (ViewBag.StatusMessage != null)
{
<p class="center-content alert alert-info">
<p class="center-content alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@ViewBag.StatusMessage
</p>
+75 -5
View File
@@ -13,8 +13,19 @@
<div class="center-content well">
<dl class="dl-horizontal username">
<dt>
@Html.DisplayNameFor(m => m.Username)
</dt>
<dd>
@Html.Encode(Model.Username)
@Html.Partial("_UserStatusLabels")
</dd>
</dl>
@Html.DisplayFor(m => m.Email)
@Html.DisplayFor(m => m.Username)
@Html.DisplayFor(m => m.FullName)
<dl class="dl-horizontal roles">
<dt>
@@ -33,7 +44,7 @@
</dd>
</dl>
<dl class="dl-horizontal lastActivity">
<dl class="dl-horizontal lastActivityDate">
<dt>
@Html.DisplayNameFor(m => m.LastActivityDate)
</dt>
@@ -50,14 +61,73 @@
</dd>
</dl>
<dl class="dl-horizontal lastLoginDate">
<dt>
@Html.DisplayNameFor(m => m.LastLoginDate)
</dt>
<dd>
@if (!Model.LastLoginDate.IsSqlMinValue())
{
@Html.Encode(Model.LastLoginDate)
<span class="muted">(@Html.Encode((DateTime.Now - Model.LastLoginDate).ToVerboseStringHistoric()))</span>
}
else
{
<span class='label label-info'>Never Logged In</span>
}
</dd>
</dl>
<dl class="dl-horizontal lastPasswordChangedDate">
<dt>
@Html.DisplayNameFor(m => m.LastPasswordChangedDate)
</dt>
<dd>
@if (!Model.LastPasswordChangedDate.IsSqlMinValue())
{
@Html.Encode(Model.LastPasswordChangedDate)
<span class="muted">(@Html.Encode((DateTime.Now - Model.LastPasswordChangedDate).ToVerboseStringHistoric()))</span>
}
else
{
<span class='label label-info'>Never Changed</span>
}
</dd>
</dl>
@if (Model.IsLockedOut) {
<p class="alert alert-info">
Locked out on @Html.DisplayTextFor(m => m.LastLockoutDate)
</p>
<dl class="dl-horizontal lastLockoutDate">
<dt>
Lockout Password Date
</dt>
<dd>
@if (!Model.LastLockoutDate.IsSqlMinValue())
{
@Html.Encode(Model.LastLockoutDate)
<span class="muted">(@Html.Encode((DateTime.Now - Model.LastLockoutDate).ToVerboseStringHistoric()))</span>
}
else
{
<span class='label label-info'>Never Locked (?)</span>
}
</dd>
</dl>
}
</div>
<div class="btn-toolbar center-content">
@Html.ActionLink("Edit", "Edit", new { id = Model.UserId }, new { @class = "btn" })
@Html.ActionLink("Set Password", "SetPassword", new { id = Model.UserId }, new { @class = "btn" })
@if (Model.IsApproved)
{
@Html.ActionLink("Disable Account", "DisableUser", new {id = Model.UserId}, new {@class = "btn"})
}
else
{
@Html.ActionLink("Enable Account", "EnableUser", new {id = Model.UserId}, new {@class = "btn"})
}
@if (Model.IsLockedOut)
{
@Html.ActionLink("Unlock", "UnlockUser", new {id = Model.UserId}, new {@class = "btn"})
}
</div>
+3 -1
View File
@@ -6,6 +6,8 @@
@{ Html.RenderPartial("BackToUsers"); }
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("Edit", "User", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
@@ -25,5 +27,5 @@
}
<div class="btn-toolbar center-content">
@Html.ActionLink("Set Password", "SetPassword", new { id = Model.UserId }, new { @class = "btn" })
@Html.ActionLink("Details", "Details", new { id = Model.UserId }, new { @class = "btn" })
</div>
+11 -14
View File
@@ -1,5 +1,5 @@
@using MileageTraker.Web.Utility
@model IEnumerable<MileageTraker.Web.Models.User>
@using MileageTraker.Web.Models
@model IEnumerable<User>
@{
ViewBag.Title = "Users";
@@ -24,16 +24,13 @@
<span title="@Html.Encode(item.Email)">
@Html.Encode(item.Username)
</span>
@if (item.LastActivityDate > CustomExtensions.UserOnlineThreshold()) {
<span class='label label-info'>Online</span>
}
@if (item.IsLockedOut) {
<span class='label label-warning' title="@string.Format("Locked out on {0:d}", item.LastLockoutDate)">Locked Out</span>
}
@if (!item.IsApproved)
{
<span class='label label-inverse'>Account Disabled</span>
}</text> ),
@Html.Partial("_UserStatusLabels",
new User{
LastActivityDate = item.LastActivityDate,
IsLockedOut = item.IsLockedOut,
LastLockoutDate = item.LastLockoutDate,
IsApproved = item.IsApproved})
</text> ),
grid.Column("FullName", "Full Name"),
grid.Column("Roles", format:
@<text>
@@ -49,8 +46,8 @@
</text>),
grid.Column(format:
@<div class='btn-group'>
@Html.ActionLink("Edit", "Edit", new { id = item.UserId }, new { @class = "btn btn-mini" })
@Html.ActionLink("Details", "Details", new { id = item.UserId }, new { @class = "btn btn-mini" })
@*Html.ActionLink("Edit", "Edit", new { id = item.UserId }, new { @class = "btn btn-mini" })*@
@Html.ActionLink("Details / Edit", "Details", new { id = item.UserId }, new { @class = "btn btn-mini" })
</div>)
),
htmlAttributes: new { @class = "table table-striped table-bordered table-hover table-condensed"},
+2
View File
@@ -3,6 +3,8 @@
ViewBag.Title = "Change Password";
}
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("SetPassword", "User", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
+15
View File
@@ -0,0 +1,15 @@
@using MileageTraker.Web.Utility
@model MileageTraker.Web.Models.User
@if (Model.LastActivityDate > CustomExtensions.UserOnlineThreshold())
{
<span class='label label-info'>Online</span>
}
@if (Model.IsLockedOut)
{
<span class='label label-warning' title="@string.Format("Locked out on {0:d} (too many failed login attempts)", Model.LastLockoutDate)">Locked Out</span>
}
@if (!Model.IsApproved)
{
<span class='label label-inverse'>Account Disabled</span>
}
+2
View File
@@ -6,6 +6,8 @@
@{ Html.RenderPartial("BackToVehicles"); }
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("Create", "Vehicle", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
+2
View File
@@ -6,6 +6,8 @@
@{ Html.RenderPartial("BackToVehicles"); }
@Html.Partial("_StatusMessage")
<h2 class="center-content">@ViewBag.Title</h2>
@using (Html.BeginForm("Edit", "Vehicle", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
+3 -1
View File
@@ -124,6 +124,8 @@
<Compile Include="Controllers\UserController.cs" />
<Compile Include="DAL\CodeFirstMembershipProvider.cs" />
<Compile Include="DAL\CodeFirstRoleProvider.cs" />
<Compile Include="DAL\UserAccountDisabledException.cs" />
<Compile Include="DAL\UserLockedOutException.cs" />
<Compile Include="Migrations\201204181847082_InitialMigration.cs" />
<Compile Include="Migrations\201204181847082_InitialMigration.Designer.cs">
<DependentUpon>201204181847082_InitialMigration.cs</DependentUpon>
@@ -234,7 +236,6 @@
<Content Include="Scripts\jquery.validate.min.js" />
<Content Include="Scripts\modernizr-2.6.2.js" />
<Content Include="Scripts\Shared\Site.js" />
<Content Include="Views\CreateLog\Success.cshtml" />
<Content Include="Web.config">
<SubType>Designer</SubType>
</Content>
@@ -278,6 +279,7 @@
<Content Include="Views\User\_Roles.cshtml" />
<Content Include="Views\User\SetPassword.cshtml" />
<Content Include="Views\Shared\_StatusMessage.cshtml" />
<Content Include="Views\User\_UserStatusLabels.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.config">