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);
}
}
+11
View File
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.21.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>
+24
View File
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameAuthApi", "GameAuthApi.csproj", "{349B73A5-7632-5ADF-4645-BB2EF73FA948}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{349B73A5-7632-5ADF-4645-BB2EF73FA948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{349B73A5-7632-5ADF-4645-BB2EF73FA948}.Debug|Any CPU.Build.0 = Debug|Any CPU
{349B73A5-7632-5ADF-4645-BB2EF73FA948}.Release|Any CPU.ActiveCfg = Release|Any CPU
{349B73A5-7632-5ADF-4645-BB2EF73FA948}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7EFA1A51-A917-4BB3-9C99-E90800825E9F}
EndGlobalSection
EndGlobal
+33
View File
@@ -0,0 +1,33 @@
using System.Linq;
using System.Threading.Tasks;
using GameAuthApi.Services;
using Microsoft.AspNetCore.Http;
namespace GameAuthApi.Middleware;
public class TokenBlacklistMiddleware
{
private readonly RequestDelegate _next;
public TokenBlacklistMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, BlacklistService blacklistService)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
if (!string.IsNullOrWhiteSpace(token))
{
if (await blacklistService.IsTokenBlacklistedAsync(token))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Token has been revoked");
return;
}
}
await _next(context);
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace GameAuthApi.Models;
public class BlacklistedToken
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Token { get; set; }
public DateTime Expiry { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace GameAuthApi.Models;
public class ChangeRoleRequest
{
public string Username { get; set; }
public string NewRole { get; set; }
}
+8
View File
@@ -0,0 +1,8 @@
namespace GameAuthApi.Models;
public class CreateItemRequest
{
public string LocationId { get; set; }
public string Name { get; set; }
}
+17
View File
@@ -0,0 +1,17 @@
namespace GameAuthApi.Models;
public class CreateLocationRequest
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
public string N { get; set; }
public string NE { get; set; }
public string E { get; set; }
public string SE { get; set; }
public string S { get; set; }
public string SW { get; set; }
public string W { get; set; }
public string NW { get; set; }
}
+8
View File
@@ -0,0 +1,8 @@
// Models/CreatePlayerRequest.cs
namespace GameAuthApi.Models;
public class CreatePlayerRequest
{
public string Name { get; set; }
public string LocationId { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace GameAuthApi.Models;
public class CreateSpawnableRequest
{
public string Name { get; set; }
public int SpawnRate { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace GameAuthApi.Models;
public class LoginRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
+6
View File
@@ -0,0 +1,6 @@
namespace GameAuthApi.Models;
public class MoveRequest
{
public string Direction { get; set; } // N, NE, E, SE, S, SW, W, NW
}
+8
View File
@@ -0,0 +1,8 @@
namespace GameAuthApi.Models;
public class RegisterRequest
{
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace GameAuthApi.Models;
public class TeleportRequest
{
public string PlayerId { get; set; }
public string LocationId { get; set; }
}
+16
View File
@@ -0,0 +1,16 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace GameAuthApi.Models;
public class Item
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string LocationId { get; set; }
public string Name { get; set; }
}
+40
View File
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace GameAuthApi.Models;
public class Location
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string LocationType { get; set; } = "World";
public int? X { get; set; }
public int? Y { get; set; }
public int? Z { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string N { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string NE { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string E { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string SE { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string S { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string SW { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string W { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string NW { get; set; }
public List<string> Players { get; set; } = [];
[BsonRepresentation(BsonType.ObjectId)]
public List<string> Content { get; set; } = [];
}
+18
View File
@@ -0,0 +1,18 @@
// Models/Player.cs
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace GameAuthApi.Models;
public class Player
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string UserId { get; set; } // Linked to user
public string Name { get; set; } // In-game player name
public string LocationId { get; set; } // Current location (nullable)
public string InventoryLocationId { get; set; } // Inventory location (nullable)
public int Climbing { get; set; } = 3; // Climbing skill level (default 3)
}
+14
View File
@@ -0,0 +1,14 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace GameAuthApi.Models;
public class Spawnable
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
public int SpawnRate { get; set; }
}
+19
View File
@@ -0,0 +1,19 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace GameAuthApi.Models;
public class User
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public string Role { get; set; } = "player";
public string RefreshToken { get; set; }
public DateTime RefreshTokenExpiry { get; set; }
public string Email { get; set; }
}
+83
View File
@@ -0,0 +1,83 @@
using GameAuthApi.Services;
using GameAuthApi.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "GameAuthApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new()
{
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Description = "Enter: `Bearer <your JWT token>`"
});
c.AddSecurityRequirement(new()
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
builder.Services.AddSingleton<UserService>();
builder.Services.AddSingleton<LocationService>();
builder.Services.AddSingleton<PlayerService>();
builder.Services.AddSingleton<SpawnableService>();
builder.Services.AddSingleton<ItemService>();
builder.Services.AddSingleton<BlacklistService>();
var jwtKey = builder.Configuration["Jwt:Key"];
var keyBytes = Encoding.UTF8.GetBytes(jwtKey);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Issuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(keyBytes)
};
});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseMiddleware<GameAuthApi.Middleware.TokenBlacklistMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
+31
View File
@@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using GameAuthApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace GameAuthApi.Services;
public class BlacklistService
{
private readonly IMongoCollection<BlacklistedToken> _blacklist;
public BlacklistService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var db = client.GetDatabase(config["MongoDB:DatabaseName"]);
_blacklist = db.GetCollection<BlacklistedToken>("BlacklistedTokens");
}
public async Task AddToBlacklistAsync(string token, DateTime expiry)
{
var entry = new BlacklistedToken { Token = token, Expiry = expiry };
await _blacklist.InsertOneAsync(entry);
}
public async Task<bool> IsTokenBlacklistedAsync(string token)
{
var exists = await _blacklist.Find(x => x.Token == token && x.Expiry > DateTime.UtcNow).FirstOrDefaultAsync();
return exists != null;
}
}
+39
View File
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GameAuthApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace GameAuthApi.Services;
public class ItemService
{
private readonly IMongoCollection<Item> _items;
public ItemService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var db = client.GetDatabase(config["MongoDB:DatabaseName"]);
_items = db.GetCollection<Item>("Items");
}
public async Task CreateAsync(Item item)
{
await _items.InsertOneAsync(item);
}
public async Task<List<Item>> GetByLocationAsync(string locationId)
{
return await _items.Find(i => i.LocationId == locationId).ToListAsync();
}
public async Task<Item> GetByIdAsync(string id)
{
return await _items.Find(i => i.Id == id).FirstOrDefaultAsync();
}
public async Task UpdateAsync(Item item)
{
await _items.ReplaceOneAsync(i => i.Id == item.Id, item);
}
}
+69
View File
@@ -0,0 +1,69 @@
using System.Threading.Tasks;
using GameAuthApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Bson;
using MongoDB.Driver;
namespace GameAuthApi.Services;
public class LocationService
{
private readonly IMongoCollection<Location> _locations;
public LocationService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var db = client.GetDatabase(config["MongoDB:DatabaseName"]);
_locations = db.GetCollection<Location>("Locations");
}
public async Task<Location> GetByIdAsync(string id)
{
if (!ObjectId.TryParse(id, out _))
return null; // prevent crash if id is invalid
return await _locations.Find(x => x.Id == id).FirstOrDefaultAsync();
}
public async Task CreateAsync(Location loc) =>
await _locations.InsertOneAsync(loc);
public async Task DeleteAsync(string id) =>
await _locations.DeleteOneAsync(loc => loc.Id == id);
public async Task UpdateAsync(Location loc) =>
await _locations.ReplaceOneAsync(l => l.Id == loc.Id, loc);
public async Task AddPlayerToLocationAsync(string locationId, string playerId)
{
var update = Builders<Location>.Update.AddToSet(l => l.Players, playerId);
await _locations.UpdateOneAsync(l => l.Id == locationId, update);
}
public async Task<Location> GetByCoordinatesAsync(int x, int y, int z)
{
return await _locations.Find(loc => loc.X == x && loc.Y == y && loc.Z == z).FirstOrDefaultAsync();
}
public async Task<Location> GetSpawnLocationAsync()
{
return await _locations.Find(loc => loc.X == 0 && loc.Y == 0).FirstOrDefaultAsync();
}
public async Task RemovePlayerFromLocationAsync(string locationId, string playerId)
{
var update = Builders<Location>.Update.Pull(l => l.Players, playerId);
await _locations.UpdateOneAsync(l => l.Id == locationId, update);
}
public async Task AddItemToLocationAsync(string locationId, string itemId)
{
var update = Builders<Location>.Update.Push(l => l.Content, itemId);
await _locations.UpdateOneAsync(l => l.Id == locationId, update);
}
public async Task RemoveItemFromLocationAsync(string locationId, string itemId)
{
var update = Builders<Location>.Update.Pull(l => l.Content, itemId);
await _locations.UpdateOneAsync(l => l.Id == locationId, update);
}
}
+44
View File
@@ -0,0 +1,44 @@
// Services/PlayerService.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using GameAuthApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace GameAuthApi.Services;
public class PlayerService
{
private readonly IMongoCollection<Player> _players;
public PlayerService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var db = client.GetDatabase(config["MongoDB:DatabaseName"]);
_players = db.GetCollection<Player>("Players");
}
public async Task<Player> GetByUserIdAsync(string userId) =>
await _players.Find(p => p.UserId == userId).FirstOrDefaultAsync();
public async Task<List<Player>> GetByLocationIdAsync(string locationId)
{
return await _players.Find(p => p.LocationId == locationId).ToListAsync();
}
public async Task<Player> GetByIdAsync(string id) =>
await _players.Find(p => p.Id == id).FirstOrDefaultAsync();
public async Task CreateAsync(Player player) =>
await _players.InsertOneAsync(player);
public async Task UpdateAsync(Player player)
{
await _players.ReplaceOneAsync(p => p.Id == player.Id, player);
}
public async Task DeleteAsync(string id)
{
await _players.DeleteOneAsync(p => p.Id == id);
}
}
+29
View File
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GameAuthApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace GameAuthApi.Services;
public class SpawnableService
{
private readonly IMongoCollection<Spawnable> _spawnables;
public SpawnableService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var db = client.GetDatabase(config["MongoDB:DatabaseName"]);
_spawnables = db.GetCollection<Spawnable>("Spawnables");
}
public async Task CreateAsync(Spawnable spawnable)
{
await _spawnables.InsertOneAsync(spawnable);
}
public async Task<List<Spawnable>> GetAllAsync()
{
return await _spawnables.Find(_ => true).ToListAsync();
}
}
+27
View File
@@ -0,0 +1,27 @@
using System.Threading.Tasks;
using GameAuthApi.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace GameAuthApi.Services;
public class UserService
{
private readonly IMongoCollection<User> _users;
public UserService(IConfiguration config)
{
var client = new MongoClient(config["MongoDB:ConnectionString"]);
var database = client.GetDatabase(config["MongoDB:DatabaseName"]);
_users = database.GetCollection<User>("Users");
}
public async Task<User> GetByUsernameAsync(string username) =>
await _users.Find(u => u.Username == username).FirstOrDefaultAsync();
public async Task CreateAsync(User user) =>
await _users.InsertOneAsync(user);
public async Task UpdateAsync(User user) =>
await _users.ReplaceOneAsync(u => u.Id == user.Id, user);
}
+16
View File
@@ -0,0 +1,16 @@
{
"MongoDB": {
"ConnectionString": "mongodb://192.168.86.50:27017",
"DatabaseName": "GameDb"
},
"Jwt": {
"Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!",
"Issuer": "GameAuthApi"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"AllowedHosts": "*"
}