This commit is contained in:
hz
2025-05-08 09:29:20 -05:00
parent 4f67e2b56f
commit c0a94f26d6
31 changed files with 1181 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
using GameAuthApi.Models;
using GameAuthApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace GameAuthApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly UserService _userService;
private readonly IConfiguration _config;
private readonly BlacklistService _blacklistService;
public AuthController(UserService userService, IConfiguration config, BlacklistService blacklistService)
{
_userService = userService;
_config = config;
_blacklistService = blacklistService;
}
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterRequest credentials)
{
if (await _userService.GetByUsernameAsync(credentials.Username) != null)
return BadRequest("User already exists");
var hash = BCrypt.Net.BCrypt.HashPassword(credentials.Password);
var user = new User
{
Username = credentials.Username,
PasswordHash = hash,
Role = "USER",
Email = credentials.Email
};
await _userService.CreateAsync(user);
return Ok("User created");
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest credentials)
{
var user = await _userService.GetByUsernameAsync(credentials.Username);
if (user == null || !BCrypt.Net.BCrypt.Verify(credentials.Password, user.PasswordHash))
return Unauthorized();
var accessToken = GenerateJwtToken(user);
var refreshToken = Guid.NewGuid().ToString();
var refreshExpiry = DateTime.UtcNow.AddDays(7);
user.RefreshToken = refreshToken;
user.RefreshTokenExpiry = refreshExpiry;
await _userService.UpdateAsync(user);
return Ok(new { accessToken, refreshToken });
}
[HttpPost("refresh")]
public async Task<IActionResult> Refresh([FromBody] RefreshRequest req)
{
var user = await _userService.GetByUsernameAsync(req.Username);
if (user == null || user.RefreshToken != req.RefreshToken || user.RefreshTokenExpiry < DateTime.UtcNow)
return Unauthorized("Invalid or expired refresh token");
var newAccessToken = GenerateJwtToken(user);
return Ok(new { accessToken = newAccessToken });
}
[HttpPost("logout")]
[Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Logout()
{
var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
if (string.IsNullOrWhiteSpace(token))
return BadRequest("Token missing");
var jwtToken = new JwtSecurityTokenHandler().ReadToken(token) as JwtSecurityToken;
var expiry = jwtToken.ValidTo;
await _blacklistService.AddToBlacklistAsync(token, expiry);
return Ok("Token blacklisted, user logged out.");
}
[HttpPost("role")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> ChangeUserRole([FromBody] ChangeRoleRequest request)
{
if (request.NewRole != "USER" && request.NewRole != "SUPER")
return BadRequest("Role must be 'USER' or 'SUPER'");
var user = await _userService.GetByUsernameAsync(request.Username);
if (user == null)
return NotFound("User not found");
user.Role = request.NewRole;
await _userService.UpdateAsync(user);
return Ok($"{request.Username}'s role updated to {request.NewRole}");
}
private string GenerateJwtToken(User user)
{
var key = Encoding.UTF8.GetBytes(_config["Jwt:Key"]);
var creds = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Role, user.Role)
};
var token = new JwtSecurityToken(
_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.UtcNow.AddMinutes(15),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
public class RefreshRequest
{
public string Username { get; set; }
public string RefreshToken { get; set; }
}
}
+39
View File
@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using GameAuthApi.Models;
using GameAuthApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GameAuthApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ItemController : ControllerBase
{
private readonly ItemService _itemService;
private readonly LocationService _locationService;
public ItemController(ItemService itemService, LocationService locationService)
{
_itemService = itemService;
_locationService = locationService;
}
[HttpPost]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> Create([FromBody] CreateItemRequest req)
{
var location = await _locationService.GetByIdAsync(req.LocationId);
if (location == null)
return BadRequest("Invalid location ID.");
var item = new Item
{
Name = req.Name,
LocationId = req.LocationId
};
await _itemService.CreateAsync(item);
return Ok(item);
}
}
+78
View File
@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GameAuthApi.Models;
using GameAuthApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GameAuthApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class LocationController : ControllerBase
{
private readonly LocationService _locationService;
private readonly PlayerService _playerService;
public LocationController(LocationService locationService, PlayerService playerService)
{
_locationService = locationService;
_playerService = playerService;
}
[HttpPost]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> Create([FromBody] CreateLocationRequest req)
{
var newLoc = new Location
{
X = req.X,
Y = req.Y,
Z = req.Z,
N = req.N,
NE = req.NE,
E = req.E,
SE = req.SE,
S = req.S,
SW = req.SW,
W = req.W,
NW = req.NW
};
await _locationService.CreateAsync(newLoc);
return Ok(newLoc);
}
[HttpDelete("{id}")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> Delete(string id)
{
var location = await _locationService.GetByIdAsync(id);
if (location == null)
return NotFound("Location not found.");
// Null out all players at this location
var affectedPlayers = await _playerService.GetByLocationIdAsync(id);
foreach (var player in affectedPlayers)
{
player.LocationId = null;
await _playerService.UpdateAsync(player);
}
await _locationService.DeleteAsync(id);
return Ok($"Location deleted and {affectedPlayers.Count} player(s) unlinked.");
}
[HttpGet("{id}")]
[Authorize(Roles = "USER")]
public async Task<IActionResult> GetById(string id)
{
var location = await _locationService.GetByIdAsync(id);
if (location == null)
return NotFound("Location not found");
return Ok(location);
}
}
+282
View File
@@ -0,0 +1,282 @@
// Controllers/PlayerController.cs
using GameAuthApi.Models;
using GameAuthApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GameAuthApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class PlayerController : ControllerBase
{
private readonly PlayerService _playerService;
private readonly LocationService _locationService;
private readonly SpawnableService _spawnableService;
private readonly ItemService _itemService;
public PlayerController(PlayerService playerService, LocationService locationService, SpawnableService spawnableService, ItemService itemService)
{
_playerService = playerService;
_locationService = locationService;
_spawnableService = spawnableService;
_itemService = itemService;
}
[HttpPost]
[Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Create([FromBody] CreatePlayerRequest req)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(userId))
return Unauthorized("User ID not found in token.");
var existing = await _playerService.GetByUserIdAsync(userId);
if (existing != null)
return BadRequest("Player already exists for this user.");
Location spawnLocation = null;
if (!string.IsNullOrWhiteSpace(req.LocationId))
{
spawnLocation = await _locationService.GetByIdAsync(req.LocationId);
if (spawnLocation == null)
return BadRequest("Specified location does not exist.");
}
else
{
spawnLocation = await _locationService.GetSpawnLocationAsync();
// If not found, spawnLocation remains null
}
// create the inventory location
var inventoryLocation = new Location
{
LocationType = "Inventory",
X = null,
Y = null,
Z = null,
N = null,
NE = null,
E = null,
SE = null,
S = null,
SW = null,
W = null,
NW = null,
Players = new List<string>() // optional
};
await _locationService.CreateAsync(inventoryLocation);
var player = new Player
{
UserId = userId,
Name = req.Name,
LocationId = spawnLocation?.Id,
InventoryLocationId = inventoryLocation.Id,
Climbing = 3
};
await _playerService.CreateAsync(player);
if (spawnLocation != null)
{
await _locationService.AddPlayerToLocationAsync(spawnLocation.Id, player.Id);
}
return Ok(player);
}
[HttpDelete]
[Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Delete()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var role = User.FindFirstValue(ClaimTypes.Role);
var player = await _playerService.GetByUserIdAsync(userId);
if (player == null)
return NotFound("No player found for this user.");
// Remove from current location if set
if (!string.IsNullOrEmpty(player.LocationId))
{
await _locationService.RemovePlayerFromLocationAsync(player.LocationId, player.Id);
}
await _playerService.DeleteAsync(player.Id);
return Ok($"Player '{player.Name}' deleted.");
}
[HttpPost("teleport")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> AdminTeleport([FromBody] TeleportRequest req)
{
var player = await _playerService.GetByIdAsync(req.PlayerId);
if (player == null)
return NotFound("Player not found.");
var newLocation = await _locationService.GetByIdAsync(req.LocationId);
if (newLocation == null)
return BadRequest("Target location does not exist.");
if (!string.IsNullOrEmpty(player.LocationId))
{
await _locationService.RemovePlayerFromLocationAsync(player.LocationId, player.Id);
}
player.LocationId = newLocation.Id;
await _playerService.UpdateAsync(player);
await _locationService.AddPlayerToLocationAsync(newLocation.Id, player.Id);
return Ok($"Player '{player.Name}' teleported to '{newLocation.X}, {newLocation.Y}'.");
}
[HttpPost("move")]
[Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Move([FromBody] MoveRequest req)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var player = await _playerService.GetByUserIdAsync(userId);
if (player == null)
return NotFound("Player not found.");
if (string.IsNullOrWhiteSpace(player.LocationId))
return BadRequest("Player is not currently in a location.");
var currentLocation = await _locationService.GetByIdAsync(player.LocationId);
if (currentLocation == null)
return BadRequest("Current location not found.");
var direction = req.Direction.ToUpper();
var directionMap = new Dictionary<string, (int dx, int dy, string reverse)>
{
{ "N", (0, 1, "S") }, { "NE", (1, 1, "SW") }, { "E", (1, 0, "W") }, { "SE", (1, -1, "NW") },
{ "S", (0, -1, "N") }, { "SW", (-1, -1, "NE") }, { "W", (-1, 0, "E") }, { "NW", (-1, 1, "SE") }
};
if (!directionMap.ContainsKey(direction))
return BadRequest("Invalid direction. Use N, NE, E, SE, S, SW, W, or NW.");
var (dx, dy, reverseDir) = directionMap[direction];
var targetId = typeof(Location).GetProperty(direction)?.GetValue(currentLocation)?.ToString();
Location targetLocation;
if (!string.IsNullOrWhiteSpace(targetId))
{
targetLocation = await _locationService.GetByIdAsync(targetId);
if (targetLocation == null)
return BadRequest("Linked location ID is invalid.");
var z1 = currentLocation.Z ?? 0;
var z2 = targetLocation.Z ?? 0;
var zDiff = Math.Abs(z1 - z2);
if (zDiff > player.Climbing)
{
return BadRequest($"Cannot move {req.Direction.ToUpper()}: elevation difference ({zDiff}) exceeds your climbing ability ({player.Climbing}).");
}
}
else
{
// Generate new location
var newX = currentLocation.X + dx;
var newY = currentLocation.Y + dy;
var random = new Random();
var newZ = currentLocation.Z + random.Next(-1, 2); // -1, 0, or +1
string itemId = null;
var spawnables = await _spawnableService.GetAllAsync();
if (spawnables.Any())
{
var rng = new Random();
var roll = rng.Next(0, 100); // 0–99
foreach (var s in spawnables.OrderBy(s => s.SpawnRate))
{
if (roll < s.SpawnRate)
{
var tempItem = new Item
{
Name = s.Name,
LocationId = null
};
await _itemService.CreateAsync(tempItem);
itemId = tempItem.Id;
break;
}
}
}
targetLocation = new Location
{
LocationType = "World",
X = newX,
Y = newY,
Z = newZ,
Content = itemId != null ? [itemId] : []
};
await _locationService.CreateAsync(targetLocation);
if (itemId != null)
{
var createdItem = await _itemService.GetByIdAsync(itemId);
createdItem.LocationId = targetLocation.Id;
await _itemService.UpdateAsync(createdItem);
}
// Link current → new
typeof(Location).GetProperty(direction)?.SetValue(currentLocation, targetLocation.Id);
// Link new → current
typeof(Location).GetProperty(reverseDir)?.SetValue(targetLocation, currentLocation.Id);
await _locationService.UpdateAsync(currentLocation);
await _locationService.UpdateAsync(targetLocation);
// Link additional neighbors
foreach (var entry in directionMap)
{
var dir = entry.Key;
var (nx, ny, rev) = entry.Value;
var neighbor = await _locationService.GetByCoordinatesAsync(
(targetLocation.X ?? 0) + nx,
(targetLocation.Y ?? 0) + ny,
targetLocation.Z ?? 0);
if (neighbor != null)
{
typeof(Location).GetProperty(dir)?.SetValue(targetLocation, neighbor.Id);
typeof(Location).GetProperty(rev)?.SetValue(neighbor, targetLocation.Id);
await _locationService.UpdateAsync(neighbor);
}
}
await _locationService.UpdateAsync(targetLocation);
}
// Move the player
await _locationService.RemovePlayerFromLocationAsync(currentLocation.Id, player.Id);
await _locationService.AddPlayerToLocationAsync(targetLocation.Id, player.Id);
player.LocationId = targetLocation.Id;
await _playerService.UpdateAsync(player);
return Ok($"Player '{player.Name}' moved {direction} to '{targetLocation.X}, {targetLocation.Y}'.");
}
private static readonly Dictionary<string, (int dx, int dy, string reverse)> DirectionData = new()
{
{ "N", (0, 1, "S") }, { "NE", (1, 1, "SW") }, { "E", (1, 0, "W") }, { "SE", (1, -1, "NW") },
{ "S", (0, -1, "N") }, { "SW", (-1, -1, "NE") }, { "W", (-1, 0, "E") }, { "NW", (-1, 1, "SE") }
};
}
+41
View File
@@ -0,0 +1,41 @@
using System.Threading.Tasks;
using GameAuthApi.Models;
using GameAuthApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GameAuthApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class SpawnableController : ControllerBase
{
private readonly SpawnableService _spawnableService;
public SpawnableController(SpawnableService spawnableService)
{
_spawnableService = spawnableService;
}
[HttpPost]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> Create([FromBody] CreateSpawnableRequest req)
{
var spawnable = new Spawnable
{
Name = req.Name.ToLower(),
SpawnRate = req.SpawnRate
};
await _spawnableService.CreateAsync(spawnable);
return Ok(spawnable);
}
[HttpGet]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> GetAll()
{
var all = await _spawnableService.GetAllAsync();
return Ok(all);
}
}