I want to see if this is as streamlined as possible. Most of the logic is accomplished through methods in the model. The controller does one of four things:
- redirects new users to registration
- sends locked users to reset password
- logs the user in and redirects them to where they should go
- fails the login and allows them to try again
The Controller
using System.Web.Mvc;
using System.Web.Security;
using Authenticator.Models;
namespace Authenticator.Controllers
{
public class HomeController : Controller
{
// Get index
public ActionResult Index()
{
return View();
}
[HttpPost()]
public ActionResult Index(LoginViewModel model)
{
// Redirect User to Register if they do not exist;
if (model.VerifyAccountExists() == false)
{
return RedirectToAction("Register");
}
// Determine if account is locked and redirect user to reset password.
if (model.VerifyLock())
{
return RedirectToAction("PasswordReset");
}
if (model.VerifyPassword())
{
model.PassLogin();
FormsAuthentication.SetAuthCookie(model.UserId, model.RememberMe);
return Redirect(model.GetAppUrl());
}
else
{
model.FailLogin();
ModelState.AddModelError("", "Incorrect Network ID or Password.");
}
return View(model);
}
// shared view data
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewBag.BodyClass = "authenticator";
}
}
}
The View Model
using System;
using System.Linq;
using System.Data;
using System.Web.Helpers;
namespace Authenticator.Models
{
public class LoginViewModel
{
public string UserId { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
public bool VerifyAccountExists()
{
using (var db = new WebContext())
{
if (db.UserAccounts.Count(p => p.UserId == UserId) > 0)
{
return true;
}
else
{
return false;
}
}
}
public bool VerifyPassword()
{
if (VerifyAccountExists() == false)
{
return false;
}
using (var db = new WebContext())
{
UserAccount account = db.UserAccounts.Find(UserId);
return Crypto.VerifyHashedPassword(account.PasswordHash, Password);
}
}
public bool VerifyLock()
{
if (VerifyAccountExists() == false)
{
return false;
}
using (var db = new WebContext())
{
UserAccount account = db.UserAccounts.Find(UserId);
return account.Lock;
}
}
public bool FailLogin()
{
if (VerifyAccountExists() == false)
{
return false;
}
using (var db = new WebContext())
{
UserAccount account = db.UserAccounts.Find(UserId);
switch (account.FailedAttempts)
{
case 1 - 3:
account.FailedAttempts = account.FailedAttempts + 1;
account.DateUpdated = DateTime.Now;
break;
default:
account.FailedAttempts = 4;
account.Lock = true;
account.DateUpdated = DateTime.Now;
break;
}
db.Entry(account).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
public bool PassLogin()
{
if (VerifyAccountExists() == false)
{
return false;
}
using (var db = new WebContext())
{
UserAccount account = db.UserAccounts.Find(UserId);
account.FailedAttempts = 0;
account.Lock = false;
account.DateLastLogin = DateTime.Now;
account.DateUpdated = DateTime.Now;
db.Entry(account).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
public string GetAppUrl()
{
using (var db = new WebContext())
{
var query = (from a in db.Permissions
join b in db.UserPermissions on a.Id equals b.PermissionId
join c in db.Applications on a.Name equals c.AppName
where a.Type == PermissionType.AppAccess & b.UserId == UserId
select c).ToList();
switch (query.Count)
{
case 0:
return "http://azshisp11/Tickets/Create?subject==Request%20For%20Application%20Access";
// TODO: code ticket logic to except this argument
case 1:
return query[0].AppUrl;
default:
return "http://azshisp11/Dashboard/" + UserId;
}
}
}
}
}
Known Issues
- I am aware that the
GetAppUrl()
method needs to be adjusted and have addressed that issue here.
if (VerifyAccountExists() == false) {return false;}
logic from each method in the model. \$\endgroup\$