Rename public affiliate route to /support with localized nav labels, and include affiliate/social/wallpaper admin, 2FA login, and related site updates. Co-authored-by: Cursor <cursoragent@cursor.com>
198 lines
6.4 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|