312 lines
9.2 KiB
C#
312 lines
9.2 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Security.Cryptography;
|
|
using watcher_monitoring.Data;
|
|
using watcher_monitoring.Models;
|
|
using BCrypt.Net;
|
|
|
|
namespace watcher_monitoring.Controllers;
|
|
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
public class UserController : Controller
|
|
{
|
|
private readonly WatcherDbContext _context;
|
|
private readonly ILogger<UserController> _logger;
|
|
|
|
public UserController(WatcherDbContext context, ILogger<UserController> logger)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
}
|
|
|
|
// GET: /User
|
|
[HttpGet]
|
|
public async Task<IActionResult> Index()
|
|
{
|
|
var users = await _context.Users
|
|
.Include(u => u.ApiKeys)
|
|
.OrderByDescending(u => u.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
return View(users);
|
|
}
|
|
|
|
// GET: /User/Details/{id}
|
|
[HttpGet("Details/{id}")]
|
|
public async Task<IActionResult> Details(int id)
|
|
{
|
|
var user = await _context.Users
|
|
.Include(u => u.ApiKeys)
|
|
.FirstOrDefaultAsync(u => u.Id == id);
|
|
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return View(user);
|
|
}
|
|
|
|
// GET: /User/Create
|
|
[HttpGet("Create")]
|
|
public IActionResult Create()
|
|
{
|
|
return View();
|
|
}
|
|
|
|
// POST: /User/Create
|
|
[HttpPost("Create")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Create([Bind("Username,Email,Password")] User user)
|
|
{
|
|
if (ModelState.IsValid)
|
|
{
|
|
// Prüfen, ob Username oder Email bereits existiert
|
|
var existingUser = await _context.Users
|
|
.FirstOrDefaultAsync(u => u.Username == user.Username || u.Email == user.Email);
|
|
|
|
if (existingUser != null)
|
|
{
|
|
if (existingUser.Username == user.Username)
|
|
{
|
|
ModelState.AddModelError("Username", "Benutzername ist bereits vergeben");
|
|
}
|
|
if (existingUser.Email == user.Email)
|
|
{
|
|
ModelState.AddModelError("Email", "E-Mail-Adresse ist bereits registriert");
|
|
}
|
|
return View(user);
|
|
}
|
|
|
|
// Passwort hashen mit BCrypt
|
|
user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password);
|
|
user.CreatedAt = DateTime.UtcNow;
|
|
user.LastLogin = DateTime.UtcNow;
|
|
user.IsActive = true;
|
|
|
|
_context.Users.Add(user);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Neuer User erstellt: {Username}", user.Username);
|
|
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
return View(user);
|
|
}
|
|
|
|
// GET: /User/Edit/{id}
|
|
[HttpGet("Edit/{id}")]
|
|
public async Task<IActionResult> Edit(int id)
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return View(user);
|
|
}
|
|
|
|
// POST: /User/Edit/{id}
|
|
[HttpPost("Edit/{id}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Edit(int id, [Bind("Id,Username,Email,IsActive")] User updatedUser)
|
|
{
|
|
if (id != updatedUser.Id)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (ModelState.IsValid)
|
|
{
|
|
try
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
// Prüfen, ob neuer Username oder Email bereits von anderem User verwendet wird
|
|
var duplicate = await _context.Users
|
|
.FirstOrDefaultAsync(u => u.Id != id && (u.Username == updatedUser.Username || u.Email == updatedUser.Email));
|
|
|
|
if (duplicate != null)
|
|
{
|
|
if (duplicate.Username == updatedUser.Username)
|
|
{
|
|
ModelState.AddModelError("Username", "Benutzername ist bereits vergeben");
|
|
}
|
|
if (duplicate.Email == updatedUser.Email)
|
|
{
|
|
ModelState.AddModelError("Email", "E-Mail-Adresse ist bereits registriert");
|
|
}
|
|
return View(updatedUser);
|
|
}
|
|
|
|
user.Username = updatedUser.Username;
|
|
user.Email = updatedUser.Email;
|
|
user.IsActive = updatedUser.IsActive;
|
|
|
|
_context.Update(user);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("User aktualisiert: {Username}", user.Username);
|
|
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
if (!await UserExists(updatedUser.Id))
|
|
{
|
|
return NotFound();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
return View(updatedUser);
|
|
}
|
|
|
|
// POST: /User/Delete/{id}
|
|
[HttpPost("Delete/{id}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Delete(int id)
|
|
{
|
|
var user = await _context.Users
|
|
.Include(u => u.ApiKeys)
|
|
.FirstOrDefaultAsync(u => u.Id == id);
|
|
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
// Alle API-Keys des Users löschen
|
|
_context.ApiKeys.RemoveRange(user.ApiKeys);
|
|
_context.Users.Remove(user);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("User gelöscht: {Username}", user.Username);
|
|
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
// POST: /User/ToggleActive/{id}
|
|
[HttpPost("ToggleActive/{id}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ToggleActive(int id)
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
user.IsActive = !user.IsActive;
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("User {Username} wurde {Status}", user.Username, user.IsActive ? "aktiviert" : "deaktiviert");
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// POST: /User/GenerateApiKey/{id}
|
|
[HttpPost("GenerateApiKey/{id}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> GenerateApiKey(int id, string keyName, string? description, DateTime? expiresAt)
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(keyName))
|
|
{
|
|
TempData["Error"] = "API-Key-Name ist erforderlich";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
var apiKey = new ApiKey
|
|
{
|
|
Key = GenerateApiKeyString(),
|
|
Name = keyName,
|
|
Description = description,
|
|
ExpiresAt = expiresAt,
|
|
IsActive = true,
|
|
UserId = user.Id
|
|
};
|
|
|
|
_context.ApiKeys.Add(apiKey);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Neuer API-Key für User {Username} erstellt: {KeyName}", user.Username, keyName);
|
|
|
|
TempData["Success"] = $"API-Key erstellt: {apiKey.Key}";
|
|
TempData["NewApiKey"] = apiKey.Key;
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// POST: /User/DeleteApiKey/{userId}/{keyId}
|
|
[HttpPost("DeleteApiKey/{userId}/{keyId}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> DeleteApiKey(int userId, int keyId)
|
|
{
|
|
var apiKey = await _context.ApiKeys
|
|
.FirstOrDefaultAsync(k => k.Id == keyId && k.UserId == userId);
|
|
|
|
if (apiKey == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
_context.ApiKeys.Remove(apiKey);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("API-Key gelöscht: {KeyName}", apiKey.Name);
|
|
|
|
TempData["Success"] = "API-Key erfolgreich gelöscht";
|
|
|
|
return RedirectToAction(nameof(Details), new { id = userId });
|
|
}
|
|
|
|
// POST: /User/ToggleApiKey/{userId}/{keyId}
|
|
[HttpPost("ToggleApiKey/{userId}/{keyId}")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ToggleApiKey(int userId, int keyId)
|
|
{
|
|
var apiKey = await _context.ApiKeys
|
|
.FirstOrDefaultAsync(k => k.Id == keyId && k.UserId == userId);
|
|
|
|
if (apiKey == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
apiKey.IsActive = !apiKey.IsActive;
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("API-Key {KeyName} wurde {Status}", apiKey.Name, apiKey.IsActive ? "aktiviert" : "deaktiviert");
|
|
|
|
return RedirectToAction(nameof(Details), new { id = userId });
|
|
}
|
|
|
|
private async Task<bool> UserExists(int id)
|
|
{
|
|
return await _context.Users.AnyAsync(u => u.Id == id);
|
|
}
|
|
|
|
private static string GenerateApiKeyString()
|
|
{
|
|
var randomBytes = new byte[32];
|
|
using var rng = RandomNumberGenerator.Create();
|
|
rng.GetBytes(randomBytes);
|
|
return Convert.ToBase64String(randomBytes).Replace("+", "").Replace("/", "").Replace("=", "");
|
|
}
|
|
}
|