Add support page, admin features, and auth hardening.

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>
This commit is contained in:
Atakan Doğan Özban
2026-07-29 20:25:55 +02:00
co-authored by Cursor
parent 202dc16a52
commit fc45c35dbe
149 changed files with 3496 additions and 11263 deletions
+740 -129
View File
@@ -13,12 +13,22 @@ namespace atakanozbancom.Controllers
private readonly Context db = new Context();
[Authorize]
public ActionResult Index() // shows what we have
public ActionResult Index() // admin dashboard landing page
{
ViewBag.MyProjectsCount = db.MyProjects.Count();
ViewBag.AffiliateLinksCount = db.AffiliateLinks.Count();
ViewBag.SocialIconsCount = db.SocialIcons.Count();
return View();
}
[Authorize]
public ActionResult MyProjects() // shows what we have
{
var value = db.MyProjects.ToList();
return View(value);
}
[Authorize]
[HttpGet]
public ActionResult myprojectsget(int id)
{
@@ -35,8 +45,6 @@ namespace atakanozbancom.Controllers
var vm = new AdminMyProjectsEditVM
{
id = p.id,
image = p.image,
iframe = p.iframe,
TitleEN = p.title,
DescEN = p.description,
@@ -45,63 +53,39 @@ namespace atakanozbancom.Controllers
DescTR = tr != null ? tr.description : ""
};
// NEW:
vm.medias = db.projectmedia
.Where(m => m.project_id == p.id)
.OrderBy(m => m.sort_order)
.ThenBy(m => m.id)
.ToList();
return View("myprojectsget", vm);
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult myprojectsget(AdminMyProjectsEditVM vm, HttpPostedFileBase file)
public ActionResult myprojectsget(AdminMyProjectsEditVM vm)
{
if (!ModelState.IsValid)
{
vm.medias = db.projectmedia
.Where(m => m.project_id == vm.id)
.OrderBy(m => m.sort_order)
.ThenBy(m => m.id)
.ToList();
return View("myprojectsget", vm);
}
var p = db.MyProjects.FirstOrDefault(x => x.id == vm.id);
if (p == null) return HttpNotFound();
var imageUrl = p.image;
if (file != null && file.ContentLength > 0)
{
try
{
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".webp" };
if (!allowedExtensions.Contains(ext))
{
ModelState.AddModelError("", "You can just upload these formats: '.jpg', '.jpeg', '.png', '.webp'");
return View("myprojectsget", vm);
}
var maxFileSize = 5 * 1024 * 1024;
if (file.ContentLength > maxFileSize)
{
ModelState.AddModelError("", "The size of file exceeds 5MB limit.");
return View("myprojectsget", vm);
}
var uploadsRoot = Server.MapPath("~/Content/uploads/projects");
if (!Directory.Exists(uploadsRoot))
Directory.CreateDirectory(uploadsRoot);
var fileName = Guid.NewGuid().ToString("N") + ext;
var filePath = Path.Combine(uploadsRoot, fileName);
file.SaveAs(filePath);
imageUrl = "/Content/uploads/projects/" + fileName;
}
catch (Exception ex)
{
ModelState.AddModelError("", "An error occured while upload: " + ex.Message);
return View("myprojectsget", vm);
}
}
// EN -> base
p.title = vm.TitleEN;
p.description = vm.DescEN;
p.image = imageUrl;
p.iframe = vm.iframe;
// TR -> translations
var tr = db.myprojectstranslations
@@ -121,76 +105,28 @@ namespace atakanozbancom.Controllers
return RedirectToAction("myprojectsget", new { id = p.id });
}
public ActionResult myprojectsupdate(myprojects x)
{
var ag = db.MyProjects.Find(x.id);
ag.description = x.description;
ag.title = x.title;
ag.image = x.image;
ag.iframe = x.iframe;
db.SaveChanges();
return RedirectToAction("Index");
}
[Authorize]
[HttpGet]
public ActionResult newmppost() // newmppost = New My Projects Post
{
return View(new AdminMyProjectsEditVM());
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult newmppost(AdminMyProjectsEditVM vm, HttpPostedFileBase file)
public ActionResult newmppost(AdminMyProjectsEditVM vm)
{
if (!ModelState.IsValid)
return View(vm);
string imageUrl = vm.image;
try
{
if (file != null && file.ContentLength > 0)
{
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".webp" };
if (!allowedExtensions.Contains(ext))
{
ModelState.AddModelError("", "You can just upload these formats: '.jpg', '.jpeg', '.png', '.webp'");
return View(vm);
}
var maxFileSize = 5 * 1024 * 1024;
if (file.ContentLength > maxFileSize)
{
ModelState.AddModelError("", "The size of file exceeds 5MB limit.");
return View(vm);
}
var uploadsRoot = Server.MapPath("~/Content/uploads/projects");
if (!Directory.Exists(uploadsRoot))
Directory.CreateDirectory(uploadsRoot);
var fileName = Guid.NewGuid().ToString("N") + ext;
var filePath = Path.Combine(uploadsRoot, fileName);
file.SaveAs(filePath);
imageUrl = "/Content/uploads/projects/" + fileName;
}
}
catch (Exception ex)
{
ModelState.AddModelError("", "An errror occured while upload: " + ex.Message);
return View(vm);
}
// EN -> base table
var newProject = new myprojects
{
title = vm.TitleEN,
description = vm.DescEN,
image = imageUrl,
iframe = vm.iframe
description = vm.DescEN
// image/iframe artık yok
};
db.MyProjects.Add(newProject);
@@ -201,17 +137,21 @@ namespace atakanozbancom.Controllers
{
project_id = newProject.id,
culture = "tr",
title = vm.TitleTR,
description = vm.DescTR
title = vm.TitleTR ?? "",
description = vm.DescTR ?? ""
};
db.myprojectstranslations.Add(tr);
db.SaveChanges();
TempData["ok"] = "Project added successfully!";
return RedirectToAction("Index");
return RedirectToAction("myprojectsget", new { id = newProject.id }); // direkt edit sayfasına at, media eklesin
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult myprojectsdelete(int id)
{
var mpd = db.MyProjects.Find(id); // mpd = my projects delete
@@ -220,42 +160,713 @@ namespace atakanozbancom.Controllers
db.MyProjects.Remove(mpd);
db.SaveChanges();
}
return RedirectToAction("Index");
return RedirectToAction("MyProjects");
}
//[HttpPost]
//[ValidateAntiForgeryToken]
//public ActionResult EditProject(AdminMyProjectsEditVM vm)
//{
// if (!ModelState.IsValid)
// {
// return View("myprojectsget", vm);
// }
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult addmedia(
int projectId,
int mediaType,
int sortOrder = 0,
string iframeUrl = null,
HttpPostedFileBase mediaFile = null)
{
var type = (MediaType)mediaType;
// var p = db.MyProjects.FirstOrDefault(x => x.id == vm.id);
// if (p == null) return HttpNotFound();
// 1) IFRAME -> URL
if (type == MediaType.Iframe)
{
if (string.IsNullOrWhiteSpace(iframeUrl))
{
TempData["ok"] = "Iframe URL boş olamaz.";
return RedirectToAction("myprojectsget", new { id = projectId });
}
// p.image = vm.image;
// p.iframe = vm.iframe;
// p.title = vm.TitleEN;
// p.description = vm.DescEN;
var trimmed = iframeUrl.Trim();
if (!Uri.TryCreate(trimmed, UriKind.Absolute, out var embedUri)
|| (embedUri.Scheme != Uri.UriSchemeHttps && embedUri.Scheme != Uri.UriSchemeHttp))
{
TempData["ok"] = "Iframe URL must be a valid http(s) address.";
return RedirectToAction("myprojectsget", new { id = projectId });
}
// var tr = db.myprojectstranslations
// .FirstOrDefault(x => x.project_id == p.id && (x.culture == "tr" || x.culture == "tr-TR"));
var row = new projectmedia
{
project_id = projectId,
media_type = MediaType.Iframe,
url = trimmed,
sort_order = sortOrder
};
// if (tr == null)
// {
// tr = new myprojectstranslation { project_id = p.id, culture = "tr" };
// db.myprojectstranslations.Add(tr);
// }
db.projectmedia.Add(row);
db.SaveChanges();
// tr.title = vm.TitleTR;
// tr.description = vm.DescTR;
TempData["ok"] = "Media eklendi!";
return RedirectToAction("myprojectsget", new { id = projectId });
}
// db.SaveChanges();
// 2) IMAGE/VIDEO/DOCUMENT -> FILE
if (mediaFile == null || mediaFile.ContentLength <= 0)
{
TempData["ok"] = "Dosya seçmelisin.";
return RedirectToAction("myprojectsget", new { id = projectId });
}
var ext = Path.GetExtension(mediaFile.FileName)?.ToLowerInvariant() ?? "";
// Tip bazlı uzantı whitelist
string[] allowed;
switch (type)
{
case MediaType.Image:
allowed = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif" };
break;
//case MediaType.Video:
// allowed = new[] { ".mp4", ".webm", ".mov" };
// break;
//case MediaType.Document:
// allowed = new[] { ".pdf" };
// break;
default:
TempData["ok"] = "Geçersiz media type.";
return RedirectToAction("myprojectsget", new { id = projectId });
}
if (!allowed.Contains(ext))
{
TempData["ok"] = "Bu dosya tipi kabul edilmiyor: " + ext;
return RedirectToAction("myprojectsget", new { id = projectId });
}
// Upload path
var uploadsRoot = Server.MapPath("~/Content/uploads/projects-media");
if (!Directory.Exists(uploadsRoot))
Directory.CreateDirectory(uploadsRoot);
var fileName = Guid.NewGuid().ToString("N") + ext;
var filePath = Path.Combine(uploadsRoot, fileName);
mediaFile.SaveAs(filePath);
var publicUrl = "/Content/uploads/projects-media/" + fileName;
var m = new projectmedia
{
project_id = projectId,
media_type = type,
url = publicUrl,
sort_order = sortOrder
};
db.projectmedia.Add(m);
db.SaveChanges();
TempData["ok"] = "Media eklendi!";
return RedirectToAction("myprojectsget", new { id = projectId });
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult deletemedia(int id, int projectId)
{
var m = db.projectmedia.FirstOrDefault(x => x.id == id && x.project_id == projectId);
if (m == null)
return RedirectToAction("myprojectsget", new { id = projectId });
// Dosya silme: sadece Iframe dışındakiler + URL bizim uploads klasörümüzse
if (m.media_type != MediaType.Iframe && !string.IsNullOrWhiteSpace(m.url))
{
try
{
// Güvenlik: sadece bizim klasördeki dosyaları sil
var prefix = "/Content/uploads/projects-media/";
if (m.url.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
var relative = "~" + m.url; // ~/Content/uploads/...
var physicalPath = Server.MapPath(relative);
if (System.IO.File.Exists(physicalPath))
System.IO.File.Delete(physicalPath);
}
}
catch
{
// istersen log yazdırırız, şimdilik sessiz geçiyoruz
}
}
db.projectmedia.Remove(m);
db.SaveChanges();
TempData["ok"] = "Media silindi.";
return RedirectToAction("myprojectsget", new { id = projectId });
}
// ================= AFFILIATE LINKS =================
// .svg intentionally excluded: SVGs can embed <script> and execute when opened directly.
private static readonly string[] AllowedLogoExtensions = { ".jpg", ".jpeg", ".png", ".webp", ".gif" };
private const string AffiliateLogosPrefix = "/Content/uploads/affiliate-logos/";
[Authorize]
public ActionResult AffiliateLinks()
{
var value = db.AffiliateLinks
.OrderBy(x => x.sort_order)
.ThenBy(x => x.id)
.ToList();
return View(value);
}
[Authorize]
[HttpGet]
public ActionResult newaffiliatepost()
{
return View(new AdminAffiliateLinkVM());
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult newaffiliatepost(AdminAffiliateLinkVM vm)
{
if (vm.logoFile == null || vm.logoFile.ContentLength <= 0)
{
ModelState.AddModelError("logoFile", "Logo image is required.");
}
if (!ModelState.IsValid)
return View(vm);
if (!IsSafeHttpUrl(vm.url))
{
ModelState.AddModelError("url", "URL must be a valid http(s) address.");
return View(vm);
}
var logoUrl = SaveAffiliateLogo(vm.logoFile);
if (logoUrl == null)
{
ModelState.AddModelError("logoFile", "This file type is not accepted.");
return View(vm);
}
var link = new affiliatelink
{
title = vm.TitleEN,
description = vm.DescEN,
url = vm.url,
sort_order = vm.sort_order,
image = logoUrl
};
db.AffiliateLinks.Add(link);
db.SaveChanges();
var tr = new affiliatelinktranslation
{
affiliatelink_id = link.id,
culture = "tr",
title = vm.TitleTR ?? "",
description = vm.DescTR ?? ""
};
db.affiliatelinktranslations.Add(tr);
db.SaveChanges();
TempData["ok"] = "Affiliate link added successfully!";
return RedirectToAction("AffiliateLinks");
}
[Authorize]
[HttpGet]
public ActionResult affiliatelinkget(int id)
{
var a = db.AffiliateLinks
.Include(x => x.Translations)
.FirstOrDefault(x => x.id == id);
if (a == null) return HttpNotFound();
var tr = a.Translations?.FirstOrDefault(t => t.culture == "tr" || t.culture == "tr-TR");
var vm = new AdminAffiliateLinkVM
{
id = a.id,
TitleEN = a.title,
DescEN = a.description,
TitleTR = tr != null ? tr.title : "",
DescTR = tr != null ? tr.description : "",
url = a.url,
sort_order = a.sort_order,
existingImage = a.image
};
return View(vm);
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult affiliatelinkget(AdminAffiliateLinkVM vm)
{
var a = db.AffiliateLinks.Find(vm.id);
if (a == null) return HttpNotFound();
if (!ModelState.IsValid)
{
vm.existingImage = a.image;
return View(vm);
}
if (!IsSafeHttpUrl(vm.url))
{
ModelState.AddModelError("url", "URL must be a valid http(s) address.");
vm.existingImage = a.image;
return View(vm);
}
if (vm.logoFile != null && vm.logoFile.ContentLength > 0)
{
var logoUrl = SaveAffiliateLogo(vm.logoFile);
if (logoUrl == null)
{
ModelState.AddModelError("logoFile", "This file type is not accepted.");
vm.existingImage = a.image;
return View(vm);
}
DeleteAffiliateLogo(a.image);
a.image = logoUrl;
}
a.title = vm.TitleEN;
a.description = vm.DescEN;
a.url = vm.url;
a.sort_order = vm.sort_order;
var tr = db.affiliatelinktranslations
.FirstOrDefault(x => x.affiliatelink_id == a.id && (x.culture == "tr" || x.culture == "tr-TR"));
if (tr == null)
{
tr = new affiliatelinktranslation { affiliatelink_id = a.id, culture = "tr" };
db.affiliatelinktranslations.Add(tr);
}
tr.title = vm.TitleTR;
tr.description = vm.DescTR;
db.SaveChanges();
TempData["ok"] = "Affiliate link updated successfully!";
return RedirectToAction("affiliatelinkget", new { id = a.id });
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult affiliatelinkdelete(int id)
{
var a = db.AffiliateLinks.Find(id);
if (a != null)
{
var translations = db.affiliatelinktranslations.Where(x => x.affiliatelink_id == id);
db.affiliatelinktranslations.RemoveRange(translations);
DeleteAffiliateLogo(a.image);
db.AffiliateLinks.Remove(a);
db.SaveChanges();
}
return RedirectToAction("AffiliateLinks");
}
private string SaveAffiliateLogo(HttpPostedFileBase file)
{
var ext = Path.GetExtension(file.FileName)?.ToLowerInvariant() ?? "";
if (!AllowedLogoExtensions.Contains(ext))
return null;
var uploadsRoot = Server.MapPath("~/Content/uploads/affiliate-logos");
if (!Directory.Exists(uploadsRoot))
Directory.CreateDirectory(uploadsRoot);
var fileName = Guid.NewGuid().ToString("N") + ext;
var filePath = Path.Combine(uploadsRoot, fileName);
file.SaveAs(filePath);
return AffiliateLogosPrefix + fileName;
}
private void DeleteAffiliateLogo(string publicUrl)
{
if (string.IsNullOrWhiteSpace(publicUrl)) return;
if (!publicUrl.StartsWith(AffiliateLogosPrefix, StringComparison.OrdinalIgnoreCase)) return;
try
{
var physicalPath = Server.MapPath("~" + publicUrl);
if (System.IO.File.Exists(physicalPath))
System.IO.File.Delete(physicalPath);
}
catch
{
// silently ignore, matches existing media-delete behavior
}
}
// ================= SOCIAL ICONS (footer) =================
[Authorize]
public ActionResult SocialIcons()
{
var value = db.SocialIcons
.OrderBy(x => x.sort_order)
.ThenBy(x => x.id)
.ToList();
return View(value);
}
[Authorize]
[HttpGet]
public ActionResult newsocialicon()
{
return View(new AdminSocialIconVM());
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult newsocialicon(AdminSocialIconVM vm)
{
if (!ModelState.IsValid)
return View(vm);
if (!IsSafeHttpUrl(vm.url))
{
ModelState.AddModelError("url", "URL must be a valid http(s) address.");
return View(vm);
}
var icon = new socialicon
{
icon = vm.icon,
url = vm.url,
sort_order = vm.sort_order
};
db.SocialIcons.Add(icon);
db.SaveChanges();
TempData["ok"] = "Social icon added successfully!";
return RedirectToAction("SocialIcons");
}
[Authorize]
[HttpGet]
public ActionResult socialiconget(int id)
{
var s = db.SocialIcons.Find(id);
if (s == null) return HttpNotFound();
var vm = new AdminSocialIconVM
{
id = s.id,
icon = s.icon,
url = s.url,
sort_order = s.sort_order
};
return View(vm);
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult socialiconget(AdminSocialIconVM vm)
{
var s = db.SocialIcons.Find(vm.id);
if (s == null) return HttpNotFound();
if (!ModelState.IsValid)
return View(vm);
if (!IsSafeHttpUrl(vm.url))
{
ModelState.AddModelError("url", "URL must be a valid http(s) address.");
return View(vm);
}
s.icon = vm.icon;
s.url = vm.url;
s.sort_order = vm.sort_order;
db.SaveChanges();
TempData["ok"] = "Social icon updated successfully!";
return RedirectToAction("socialiconget", new { id = s.id });
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult socialicondelete(int id)
{
var s = db.SocialIcons.Find(id);
if (s != null)
{
db.SocialIcons.Remove(s);
db.SaveChanges();
}
return RedirectToAction("SocialIcons");
}
// ================= WALLPAPER (site background) =================
private static readonly string[] AllowedWallpaperExtensions = { ".jpg", ".jpeg", ".png", ".webp", ".gif" };
private const string WallpaperPrefix = "/Content/uploads/wallpaper/";
private const string DefaultWallpaperUrl = "/web_atakanozbancom/assets/img/wp.jpg";
[Authorize]
[HttpGet]
public ActionResult Wallpaper()
{
var setting = db.SiteSettings.FirstOrDefault();
ViewBag.CurrentWallpaper = !string.IsNullOrWhiteSpace(setting?.wallpaper) ? setting.wallpaper : DefaultWallpaperUrl;
return View();
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Wallpaper(HttpPostedFileBase wallpaperFile)
{
if (wallpaperFile == null || wallpaperFile.ContentLength <= 0)
{
TempData["ok"] = "Please choose an image.";
return RedirectToAction("Wallpaper");
}
var ext = Path.GetExtension(wallpaperFile.FileName)?.ToLowerInvariant() ?? "";
if (!AllowedWallpaperExtensions.Contains(ext))
{
TempData["ok"] = "This file type is not accepted.";
return RedirectToAction("Wallpaper");
}
var uploadsRoot = Server.MapPath("~/Content/uploads/wallpaper");
if (!Directory.Exists(uploadsRoot))
Directory.CreateDirectory(uploadsRoot);
var fileName = Guid.NewGuid().ToString("N") + ext;
var filePath = Path.Combine(uploadsRoot, fileName);
wallpaperFile.SaveAs(filePath);
var publicUrl = WallpaperPrefix + fileName;
var setting = db.SiteSettings.FirstOrDefault();
if (setting == null)
{
setting = new sitesetting();
db.SiteSettings.Add(setting);
}
else
{
DeleteWallpaperFile(setting.wallpaper);
}
setting.wallpaper = publicUrl;
db.SaveChanges();
TempData["ok"] = "Wallpaper updated successfully!";
return RedirectToAction("Wallpaper");
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult WallpaperReset()
{
var setting = db.SiteSettings.FirstOrDefault();
if (setting != null && !string.IsNullOrWhiteSpace(setting.wallpaper))
{
DeleteWallpaperFile(setting.wallpaper);
setting.wallpaper = null;
db.SaveChanges();
}
TempData["ok"] = "Wallpaper reset to default.";
return RedirectToAction("Wallpaper");
}
private void DeleteWallpaperFile(string publicUrl)
{
if (string.IsNullOrWhiteSpace(publicUrl)) return;
if (!publicUrl.StartsWith(WallpaperPrefix, StringComparison.OrdinalIgnoreCase)) return;
try
{
var physicalPath = Server.MapPath("~" + publicUrl);
if (System.IO.File.Exists(physicalPath))
System.IO.File.Delete(physicalPath);
}
catch
{
}
}
[Authorize]
public ActionResult UserSettings()
{
var user = GetCurrentAdmin();
if (user == null)
return RedirectToAction("Index", "login");
ViewBag.TwoFactorEnabled = user.two_factor_enabled;
ViewBag.Username = user.username;
if (!user.two_factor_enabled)
{
var secret = Session["Pending2FaSetupSecret"] as string;
if (string.IsNullOrWhiteSpace(secret))
{
secret = TotpHelper.GenerateSecret();
Session["Pending2FaSetupSecret"] = secret;
}
ViewBag.SetupSecret = secret;
ViewBag.OtpAuthUri = TotpHelper.BuildOtpAuthUri("atakanozban.com", user.username, secret);
}
return View();
}
[Authorize]
public ActionResult Security()
{
return RedirectToAction("UserSettings");
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
{
var user = GetCurrentAdmin();
if (user == null)
return RedirectToAction("Index", "login");
if (string.IsNullOrWhiteSpace(currentPassword)
|| string.IsNullOrWhiteSpace(newPassword)
|| string.IsNullOrWhiteSpace(confirmPassword))
{
TempData["pwdError"] = "All password fields are required.";
return RedirectToAction("UserSettings");
}
if (!PasswordHasher.Verify(currentPassword, user.password))
{
TempData["pwdError"] = "Current password is incorrect.";
return RedirectToAction("UserSettings");
}
if (newPassword.Length < 8)
{
TempData["pwdError"] = "New password must be at least 8 characters.";
return RedirectToAction("UserSettings");
}
if (!string.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
{
TempData["pwdError"] = "New password and confirmation do not match.";
return RedirectToAction("UserSettings");
}
user.password = PasswordHasher.Hash(newPassword);
db.SaveChanges();
TempData["pwdOk"] = "Password updated successfully.";
return RedirectToAction("UserSettings");
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EnableTwoFactor(string code)
{
var user = GetCurrentAdmin();
if (user == null)
return RedirectToAction("Index", "login");
var secret = Session["Pending2FaSetupSecret"] as string;
if (string.IsNullOrWhiteSpace(secret))
{
TempData["tfaError"] = "Setup expired. Please try again.";
return RedirectToAction("UserSettings");
}
if (!TotpHelper.VerifyCode(secret, code))
{
TempData["tfaError"] = "Invalid code. Scan the QR again and enter a fresh code.";
return RedirectToAction("UserSettings");
}
user.two_factor_secret = SecretProtector.Protect(secret);
user.two_factor_enabled = true;
db.SaveChanges();
Session.Remove("Pending2FaSetupSecret");
TempData["tfaOk"] = "Two-factor authentication is now enabled.";
return RedirectToAction("UserSettings");
}
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DisableTwoFactor(string code)
{
var user = GetCurrentAdmin();
if (user == null)
return RedirectToAction("Index", "login");
if (!user.two_factor_enabled || string.IsNullOrWhiteSpace(user.two_factor_secret))
{
TempData["tfaError"] = "Two-factor authentication is not enabled.";
return RedirectToAction("UserSettings");
}
var secret = SecretProtector.Unprotect(user.two_factor_secret) ?? user.two_factor_secret;
if (!TotpHelper.VerifyCode(secret, code))
{
TempData["tfaError"] = "Invalid code. 2FA was not disabled.";
return RedirectToAction("UserSettings");
}
user.two_factor_enabled = false;
user.two_factor_secret = null;
db.SaveChanges();
Session.Remove("Pending2FaSetupSecret");
TempData["tfaOk"] = "Two-factor authentication has been disabled.";
return RedirectToAction("UserSettings");
}
private admin GetCurrentAdmin()
{
var username = User?.Identity?.Name;
if (string.IsNullOrWhiteSpace(username))
return null;
return db.admins.FirstOrDefault(x => x.username == username);
}
private static bool IsSafeHttpUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
return false;
return Uri.TryCreate(url.Trim(), UriKind.Absolute, out var uri)
&& (uri.Scheme == Uri.UriSchemeHttps || uri.Scheme == Uri.UriSchemeHttp);
}
// TempData["ok"] = "Project updated.";
// return RedirectToAction("myprojectsget", new { id = p.id });
//}
}
}