Files
atakanozbancom/Controllers/loginController.cs
T
Atakan Doğan Özban e97a71e5b2 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.
2026-07-17 18:49:34 +02:00

198 lines
6.4 KiB
C#

using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Security;
using atakanozbancom.Models.classes;
namespace atakanozbancom.Controllers
{
public class loginController : Controller
{
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]
[ValidateAntiForgeryToken]
public ActionResult Index(admin ad)
{
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");
}
}
}