Add password hashing, user settings with 2FA, and project image lightbox.
Hardens auth with PBKDF2, lockouts, local QR setup, and safer embeds while moving account security under the username menu.
This commit is contained in:
+169
-14
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Security;
|
||||
using atakanozbancom.Models.classes;
|
||||
@@ -10,33 +8,190 @@ namespace atakanozbancom.Controllers
|
||||
{
|
||||
public class loginController : Controller
|
||||
{
|
||||
// GET: Login
|
||||
Context c = new Context();
|
||||
private readonly Context c = new Context();
|
||||
private const int MaxPasswordFailures = 8;
|
||||
private const int MaxTotpFailures = 5;
|
||||
private const int LockMinutes = 15;
|
||||
|
||||
public ActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Index(admin ad)
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult Index(admin ad)
|
||||
{
|
||||
var bilgiler = c.admins.FirstOrDefault(x => x.username == ad.username && x.password == ad.password);
|
||||
if (bilgiler != null)
|
||||
{
|
||||
FormsAuthentication.SetAuthCookie(bilgiler.username, false);
|
||||
Session["username"] = bilgiler.username.ToString();
|
||||
return RedirectToAction("Index", "admin");
|
||||
}
|
||||
else
|
||||
if (ad == null || string.IsNullOrWhiteSpace(ad.username) || string.IsNullOrWhiteSpace(ad.password))
|
||||
{
|
||||
ViewBag.Error = "Username and password are required.";
|
||||
return View();
|
||||
}
|
||||
|
||||
var user = c.admins.FirstOrDefault(x => x.username == ad.username);
|
||||
if (user == null)
|
||||
{
|
||||
ViewBag.Error = "Invalid username or password.";
|
||||
return View();
|
||||
}
|
||||
|
||||
if (user.login_lock_until.HasValue && user.login_lock_until.Value > DateTime.UtcNow)
|
||||
{
|
||||
ViewBag.Error = "Account temporarily locked. Try again later.";
|
||||
return View();
|
||||
}
|
||||
|
||||
if (!PasswordHasher.Verify(ad.password, user.password))
|
||||
{
|
||||
user.login_failed_count++;
|
||||
if (user.login_failed_count >= MaxPasswordFailures)
|
||||
{
|
||||
user.login_lock_until = DateTime.UtcNow.AddMinutes(LockMinutes);
|
||||
user.login_failed_count = 0;
|
||||
}
|
||||
c.SaveChanges();
|
||||
ViewBag.Error = "Invalid username or password.";
|
||||
return View();
|
||||
}
|
||||
|
||||
user.login_failed_count = 0;
|
||||
user.login_lock_until = null;
|
||||
|
||||
if (!PasswordHasher.IsHashed(user.password))
|
||||
{
|
||||
user.password = PasswordHasher.Hash(ad.password);
|
||||
}
|
||||
|
||||
c.SaveChanges();
|
||||
ClearPending2Fa();
|
||||
|
||||
if (user.two_factor_enabled && !string.IsNullOrWhiteSpace(user.two_factor_secret))
|
||||
{
|
||||
if (user.totp_lock_until.HasValue && user.totp_lock_until.Value > DateTime.UtcNow)
|
||||
{
|
||||
ViewBag.Error = "Too many failed 2FA attempts. Try again later.";
|
||||
return View();
|
||||
}
|
||||
|
||||
Session["Pending2FaUserId"] = user.id;
|
||||
Session["Pending2FaUsername"] = user.username;
|
||||
Session["Pending2FaExpiresUtc"] = DateTime.UtcNow.AddMinutes(10);
|
||||
return RedirectToAction("Verify");
|
||||
}
|
||||
|
||||
CompleteLogin(user.username);
|
||||
return RedirectToAction("Index", "admin");
|
||||
}
|
||||
|
||||
public ActionResult Verify()
|
||||
{
|
||||
if (!HasValidPending2Fa())
|
||||
{
|
||||
ClearPending2Fa();
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult Verify(string code)
|
||||
{
|
||||
if (!HasValidPending2Fa())
|
||||
{
|
||||
ClearPending2Fa();
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var pendingId = Convert.ToInt32(Session["Pending2FaUserId"]);
|
||||
var pendingUsername = Session["Pending2FaUsername"].ToString();
|
||||
|
||||
var user = c.admins.FirstOrDefault(x => x.id == pendingId && x.username == pendingUsername);
|
||||
if (user == null || !user.two_factor_enabled || string.IsNullOrWhiteSpace(user.two_factor_secret))
|
||||
{
|
||||
ClearPending2Fa();
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
if (user.totp_lock_until.HasValue && user.totp_lock_until.Value > DateTime.UtcNow)
|
||||
{
|
||||
ViewBag.Error = "Too many failed attempts. Try again later.";
|
||||
return View();
|
||||
}
|
||||
|
||||
var secret = ResolveTotpSecret(user);
|
||||
if (string.IsNullOrWhiteSpace(secret) || !TotpHelper.VerifyCode(secret, code))
|
||||
{
|
||||
user.totp_failed_count++;
|
||||
if (user.totp_failed_count >= MaxTotpFailures)
|
||||
{
|
||||
user.totp_lock_until = DateTime.UtcNow.AddMinutes(LockMinutes);
|
||||
user.totp_failed_count = 0;
|
||||
ViewBag.Error = "Too many failed attempts. Try again later.";
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewBag.Error = "Invalid authentication code.";
|
||||
}
|
||||
c.SaveChanges();
|
||||
return View();
|
||||
}
|
||||
|
||||
user.totp_failed_count = 0;
|
||||
user.totp_lock_until = null;
|
||||
c.SaveChanges();
|
||||
|
||||
ClearPending2Fa();
|
||||
CompleteLogin(user.username);
|
||||
return RedirectToAction("Index", "admin");
|
||||
}
|
||||
|
||||
public ActionResult logout()
|
||||
{
|
||||
ClearPending2Fa();
|
||||
Session.Remove("username");
|
||||
Session.Abandon();
|
||||
FormsAuthentication.SignOut();
|
||||
return RedirectToAction("index", "login");
|
||||
}
|
||||
|
||||
private void CompleteLogin(string username)
|
||||
{
|
||||
FormsAuthentication.SetAuthCookie(username, false);
|
||||
Session["username"] = username;
|
||||
}
|
||||
|
||||
private bool HasValidPending2Fa()
|
||||
{
|
||||
if (Session["Pending2FaUserId"] == null || Session["Pending2FaUsername"] == null)
|
||||
return false;
|
||||
|
||||
var expires = Session["Pending2FaExpiresUtc"] as DateTime?;
|
||||
if (expires == null || expires.Value < DateTime.UtcNow)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ResolveTotpSecret(admin user)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(user.two_factor_secret))
|
||||
return null;
|
||||
|
||||
var unprotected = SecretProtector.Unprotect(user.two_factor_secret);
|
||||
if (!string.IsNullOrWhiteSpace(unprotected))
|
||||
return unprotected;
|
||||
|
||||
return user.two_factor_secret;
|
||||
}
|
||||
|
||||
private void ClearPending2Fa()
|
||||
{
|
||||
Session.Remove("Pending2FaUserId");
|
||||
Session.Remove("Pending2FaUsername");
|
||||
Session.Remove("Pending2FaSetupSecret");
|
||||
Session.Remove("Pending2FaExpiresUtc");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user