mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-07-06 14:40:28 -04:00
Removed NioTSO client and server
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
This commit is contained in:
parent
f12ba1502b
commit
22191ce648
591 changed files with 53264 additions and 3362 deletions
157
server/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs
Executable file
157
server/FSO.Server.Api.Core/Controllers/Admin/AdminEventsController.cs
Executable file
|
@ -0,0 +1,157 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Api.Core.Models;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Database.DA.DbEvents;
|
||||
using FSO.Server.Database.DA.Tuning;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/events")]
|
||||
[ApiController]
|
||||
public class AdminEventsController : ControllerBase
|
||||
{
|
||||
//List events
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset, string order)
|
||||
{
|
||||
if (limit == 0) limit = 20;
|
||||
if (order == null) order = "start_day";
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Events.All((int)offset, (int)limit, order);
|
||||
return ApiResponse.PagedList<DbEvent>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("presets")]
|
||||
public IActionResult GetPresets()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Tuning.GetAllPresets().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("presets")]
|
||||
public IActionResult CreatePreset([FromBody]PresetCreateModel request)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
//make the preset first
|
||||
var preset_id = da.Tuning.CreatePreset(
|
||||
new DbTuningPreset()
|
||||
{
|
||||
name = request.name,
|
||||
description = request.description,
|
||||
flags = request.flags
|
||||
});
|
||||
|
||||
foreach (var item in request.items)
|
||||
{
|
||||
da.Tuning.CreatePresetItem(new DbTuningPresetItem()
|
||||
{
|
||||
preset_id = preset_id,
|
||||
tuning_type = item.tuning_type,
|
||||
tuning_table = item.tuning_table,
|
||||
tuning_index = item.tuning_index,
|
||||
value = item.value
|
||||
});
|
||||
}
|
||||
return new JsonResult(da.Tuning.GetAllPresets().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("presets/{preset_id}")]
|
||||
public IActionResult GetPresetEntries(int preset_id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Tuning.GetPresetItems(preset_id).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("presets/{preset_id}")]
|
||||
public IActionResult DeletePreset(int preset_id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return da.Tuning.DeletePreset(preset_id) ? (IActionResult)Ok() : NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// POST admin/updates (start update generation)
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromBody]EventCreateModel request)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
DbEventType type;
|
||||
try
|
||||
{
|
||||
type = Enum.Parse<DbEventType>(request.type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BadRequest("Event type must be one of:" + string.Join(", ", Enum.GetNames(typeof(DbEventType))));
|
||||
}
|
||||
var model = new DbEvent()
|
||||
{
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
start_day = request.start_day,
|
||||
end_day = request.end_day,
|
||||
type = type,
|
||||
value = request.value,
|
||||
value2 = request.value2,
|
||||
mail_subject = request.mail_subject,
|
||||
mail_message = request.mail_message,
|
||||
mail_sender = request.mail_sender,
|
||||
mail_sender_name = request.mail_sender_name
|
||||
};
|
||||
return new JsonResult(new { id = da.Events.Add(model) });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[Route("{id}")]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (!da.Events.Delete(id)) return NotFound();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
30
server/FSO.Server.Api.Core/Controllers/Admin/AdminHostsController.cs
Executable file
30
server/FSO.Server.Api.Core/Controllers/Admin/AdminHostsController.cs
Executable file
|
@ -0,0 +1,30 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/hosts")]
|
||||
[ApiController]
|
||||
public class AdminHostsController : ControllerBase
|
||||
{
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var hosts = api.HostPool.GetAll();
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, hosts.Select(x => new {
|
||||
role = x.Role,
|
||||
call_sign = x.CallSign,
|
||||
internal_host = x.InternalHost,
|
||||
public_host = x.PublicHost,
|
||||
connected = x.Connected,
|
||||
time_boot = x.BootTime
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
129
server/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs
Executable file
129
server/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs
Executable file
|
@ -0,0 +1,129 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/oauth/token")]
|
||||
[ApiController]
|
||||
public class AdminOAuthController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromForm] AuthRequest auth)
|
||||
{
|
||||
if (auth == null) Ok();
|
||||
if (auth.grant_type == "password")
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var user = da.Users.GetByUsername(auth.username);
|
||||
if (user == null || user.is_banned || !(user.is_admin || user.is_moderator))
|
||||
{
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "unauthorized_client",
|
||||
error_description = "user_credentials_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
var accLock = da.Users.GetRemainingAuth(user.user_id, ip);
|
||||
if (accLock != null && (accLock.active || accLock.count >= AuthLoginController.LockAttempts) && accLock.expire_time > Epoch.Now)
|
||||
{
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "unauthorized_client",
|
||||
error_description = "account_locked"
|
||||
});
|
||||
}
|
||||
|
||||
var authSettings = da.Users.GetAuthenticationSettings(user.user_id);
|
||||
var isPasswordCorrect = PasswordHasher.Verify(auth.password, new PasswordHash
|
||||
{
|
||||
data = authSettings.data,
|
||||
scheme = authSettings.scheme_class
|
||||
});
|
||||
|
||||
if (!isPasswordCorrect)
|
||||
{
|
||||
var durations = AuthLoginController.LockDuration;
|
||||
var failDelay = 60 * durations[Math.Min(durations.Length - 1, da.Users.FailedConsecutive(user.user_id, ip))];
|
||||
if (accLock == null)
|
||||
{
|
||||
da.Users.NewFailedAuth(user.user_id, ip, (uint)failDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = da.Users.FailedAuth(accLock.attempt_id, (uint)failDelay, AuthLoginController.LockAttempts);
|
||||
}
|
||||
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "unauthorized_client",
|
||||
error_description = "user_credentials_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
da.Users.SuccessfulAuth(user.user_id, ip);
|
||||
|
||||
JWTUser identity = new JWTUser();
|
||||
identity.UserName = user.username;
|
||||
var claims = new List<string>();
|
||||
if (user.is_admin || user.is_moderator)
|
||||
{
|
||||
claims.Add("moderator");
|
||||
}
|
||||
if (user.is_admin)
|
||||
{
|
||||
claims.Add("admin");
|
||||
}
|
||||
|
||||
identity.Claims = claims;
|
||||
identity.UserID = user.user_id;
|
||||
|
||||
var token = api.JWT.CreateToken(identity);
|
||||
|
||||
var response = ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthSuccess
|
||||
{
|
||||
access_token = token.Token,
|
||||
expires_in = token.ExpiresIn
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new OAuthError
|
||||
{
|
||||
error = "invalid_request",
|
||||
error_description = "unknown grant_type"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class OAuthError
|
||||
{
|
||||
public string error_description { get; set; }
|
||||
public string error { get; set; }
|
||||
}
|
||||
|
||||
public class OAuthSuccess
|
||||
{
|
||||
public string access_token { get; set; }
|
||||
public int expires_in { get; set; }
|
||||
}
|
||||
|
||||
public class AuthRequest
|
||||
{
|
||||
public string grant_type { get; set; }
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
}
|
69
server/FSO.Server.Api.Core/Controllers/Admin/AdminShardsController.cs
Executable file
69
server/FSO.Server.Api.Core/Controllers/Admin/AdminShardsController.cs
Executable file
|
@ -0,0 +1,69 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/shards")]
|
||||
[ApiController]
|
||||
public class AdminShardsController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var shards = db.Shards.All();
|
||||
return ApiResponse.Json(HttpStatusCode.OK, shards);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("shutdown")]
|
||||
public IActionResult shutdown(ShutdownModel sd)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
ShutdownType type = ShutdownType.SHUTDOWN;
|
||||
if (sd.update) type = ShutdownType.UPDATE;
|
||||
else if (sd.restart) type = ShutdownType.RESTART;
|
||||
|
||||
api.RequestShutdown((uint)sd.timeout, type);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, true);
|
||||
}
|
||||
|
||||
[HttpPost("announce")]
|
||||
public IActionResult announce(AnnouncementModel an)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
api.BroadcastMessage(an.sender, an.subject, an.message);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class AnnouncementModel
|
||||
{
|
||||
public string sender;
|
||||
public string subject;
|
||||
public string message;
|
||||
public int[] shard_ids;
|
||||
}
|
||||
|
||||
public class ShutdownModel
|
||||
{
|
||||
public int timeout;
|
||||
public bool restart;
|
||||
public bool update;
|
||||
public int[] shard_ids;
|
||||
}
|
||||
}
|
73
server/FSO.Server.Api.Core/Controllers/Admin/AdminTasksController.cs
Executable file
73
server/FSO.Server.Api.Core/Controllers/Admin/AdminTasksController.cs
Executable file
|
@ -0,0 +1,73 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Database.DA.Tasks;
|
||||
using FSO.Server.Protocol.Gluon.Packets;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/tasks")]
|
||||
[ApiController]
|
||||
public class AdminTasksController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Tasks.All((int)offset, (int)limit);
|
||||
return ApiResponse.PagedList<DbTask>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("request")]
|
||||
public IActionResult request([FromBody] TaskRequest task)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
var taskServer = api.HostPool.GetByRole(Database.DA.Hosts.DbHostRole.task).FirstOrDefault();
|
||||
if (taskServer == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = taskServer.Call(new RequestTask()
|
||||
{
|
||||
TaskType = task.task_type.ToString(),
|
||||
ParameterJson = JsonConvert.SerializeObject(task.parameter),
|
||||
ShardId = (task.shard_id == null || !task.shard_id.HasValue) ? -1 : task.shard_id.Value
|
||||
}).Result;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public class TaskRequest
|
||||
{
|
||||
public DbTaskType task_type;
|
||||
public int? shard_id;
|
||||
public dynamic parameter;
|
||||
}
|
||||
}
|
172
server/FSO.Server.Api.Core/Controllers/Admin/AdminUpdatesController.cs
Executable file
172
server/FSO.Server.Api.Core/Controllers/Admin/AdminUpdatesController.cs
Executable file
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using FSO.Server.Api.Core.Models;
|
||||
using FSO.Server.Api.Core.Services;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Database.DA.Updates;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/updates")]
|
||||
public class AdminUpdatesController : ControllerBase
|
||||
{
|
||||
//List updates
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset, string order)
|
||||
{
|
||||
if (limit == 0) limit = 20;
|
||||
if (order == null) order = "date";
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Updates.All((int)offset, (int)limit);
|
||||
return ApiResponse.PagedList<DbUpdate>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GET all branches
|
||||
[HttpGet("branches")]
|
||||
public IActionResult GetBranches()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Updates.GetBranches().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// GET all addons
|
||||
[HttpGet("addons")]
|
||||
public IActionResult GetAddons()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new JsonResult(da.Updates.GetAddons(20).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// POST create a branch.
|
||||
[HttpPost("branches")]
|
||||
public IActionResult AddBranch(DbUpdateBranch branch)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (da.Updates.AddBranch(branch)) return Ok();
|
||||
else return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// POST update a branch.
|
||||
[HttpPost("branches/{id}")]
|
||||
public IActionResult UpdateBranch(DbUpdateBranch branch)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (da.Updates.UpdateBranchInfo(branch)) return Ok();
|
||||
else return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
public class AddonUploadModel
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public IFormFile clientAddon { get; set; }
|
||||
public IFormFile serverAddon { get; set; }
|
||||
}
|
||||
|
||||
static int AddonRequestID = 0;
|
||||
[HttpPost("uploadaddon")]
|
||||
[DisableRequestSizeLimit]
|
||||
[RequestFormLimits(MultipartBodyLengthLimit = 500000000)]
|
||||
public async Task<IActionResult> UploadAddon(AddonUploadModel upload)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var reqID = ++AddonRequestID;
|
||||
|
||||
var info = new DbUpdateAddon();
|
||||
if (upload.name == null || upload.name.Length > 128) return BadRequest("Invalid name.");
|
||||
if (upload.description == null || upload.description.Length > 1024) return BadRequest("Invalid description.");
|
||||
info.name = upload.name;
|
||||
info.description = upload.description;
|
||||
info.date = DateTime.UtcNow;
|
||||
|
||||
if (upload.clientAddon == null && upload.serverAddon == null)
|
||||
return BadRequest("client or server addon binary must be uploaded.");
|
||||
|
||||
var addonID = DateTime.UtcNow.Ticks;
|
||||
Directory.CreateDirectory("updateTemp/addons/");
|
||||
if (upload.clientAddon != null)
|
||||
{
|
||||
using (var file = System.IO.File.Open($"updateTemp/addons/client{reqID}.zip", FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await upload.clientAddon.CopyToAsync(file);
|
||||
}
|
||||
info.addon_zip_url = await api.UpdateUploader.UploadFile($"addons/client{addonID}.zip", $"updateTemp/addons/client{reqID}.zip", $"addon-{addonID}");
|
||||
System.IO.File.Delete($"updateTemp/addons/client{reqID}.zip");
|
||||
}
|
||||
|
||||
if (upload.serverAddon != null)
|
||||
{
|
||||
using (var file = System.IO.File.Open($"updateTemp/addons/server{reqID}.zip", FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await upload.serverAddon.CopyToAsync(file);
|
||||
}
|
||||
info.server_zip_url = await api.UpdateUploader.UploadFile($"addons/server{addonID}.zip", $"updateTemp/addons/server{reqID}.zip", $"addon-{addonID}");
|
||||
System.IO.File.Delete($"updateTemp/addons/server{reqID}.zip");
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
da.Updates.AddAddon(info);
|
||||
return new JsonResult(info);
|
||||
}
|
||||
}
|
||||
|
||||
// GET status for ongoing update generation
|
||||
[HttpGet("updateTask/{id}")]
|
||||
public IActionResult GetTask(int id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var task = GenerateUpdateService.INSTANCE.GetTask(id);
|
||||
if (task == null) return NotFound();
|
||||
else return new JsonResult(task);
|
||||
}
|
||||
|
||||
// POST admin/updates (start update generation)
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromBody]UpdateCreateModel request)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandAdmin(Request);
|
||||
var task = GenerateUpdateService.INSTANCE.CreateTask(request);
|
||||
return new JsonResult(task);
|
||||
}
|
||||
}
|
||||
}
|
334
server/FSO.Server.Api.Core/Controllers/Admin/AdminUsersController.cs
Executable file
334
server/FSO.Server.Api.Core/Controllers/Admin/AdminUsersController.cs
Executable file
|
@ -0,0 +1,334 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.Inbox;
|
||||
using FSO.Server.Database.DA.Users;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.Admin
|
||||
{
|
||||
[EnableCors("AdminAppPolicy")]
|
||||
[Route("admin/users")]
|
||||
[ApiController]
|
||||
public class AdminUsersController : ControllerBase
|
||||
{
|
||||
//Get information about me, useful for the admin user interface to disable UI based on who you login as
|
||||
public IActionResult current()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var user = api.RequireAuthentication(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var userModel = da.Users.GetById(user.UserID);
|
||||
if (userModel == null)
|
||||
{
|
||||
throw new Exception("Unable to find user");
|
||||
}
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Get the attributes of a specific user
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult Get(string id)
|
||||
{
|
||||
if (id == "current") return current();
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var userModel = da.Users.GetById(uint.Parse(id));
|
||||
if (userModel == null) { throw new Exception("Unable to find user"); }
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unbans a user by IP and user.
|
||||
/// </summary>
|
||||
/// <param name="user_id">ID of user to unban.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/unban")]
|
||||
public IActionResult UnbanUser([FromBody] string user_id)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
User userModel = da.Users.GetById(uint.Parse(user_id));
|
||||
|
||||
if(userModel.is_banned)
|
||||
{
|
||||
da.Users.UpdateBanned(uint.Parse(user_id), false);
|
||||
}
|
||||
|
||||
var ban = da.Bans.GetByIP(userModel.last_ip);
|
||||
|
||||
if (ban!=null)
|
||||
{
|
||||
da.Bans.Remove(userModel.user_id);
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an in-game email message to a player.
|
||||
/// </summary>
|
||||
/// <param name="mail"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/mail")]
|
||||
public IActionResult SendMail(MailCreateModel mail)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandAdmin(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
User recipient = da.Users.GetById(uint.Parse(mail.target_id));
|
||||
|
||||
if (recipient == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_target_id"
|
||||
});
|
||||
}
|
||||
|
||||
if (mail.subject.Trim() == "")
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "subject_empty"
|
||||
});
|
||||
}
|
||||
|
||||
if (mail.body.Trim() == "")
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "body_empty"
|
||||
});
|
||||
}
|
||||
|
||||
// Save mail in db
|
||||
int message_id = da.Inbox.CreateMessage(new DbInboxMsg
|
||||
{
|
||||
sender_id = 2147483648,
|
||||
target_id = uint.Parse(mail.target_id),
|
||||
subject = mail.subject,
|
||||
body = mail.body,
|
||||
sender_name = "FreeSO Staff",
|
||||
time = DateTime.UtcNow,
|
||||
msg_type = 4,
|
||||
msg_subtype = 0,
|
||||
read_state = 0,
|
||||
});
|
||||
|
||||
// Try and notify the user ingame
|
||||
api.RequestMailNotify(message_id, mail.subject, mail.body, uint.Parse(mail.target_id));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a user out the current session.
|
||||
/// </summary>
|
||||
/// <param name="kick"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/kick")]
|
||||
public IActionResult KickUser([FromBody] string user_id)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandModerator(Request);
|
||||
|
||||
api.RequestUserDisconnect(uint.Parse(user_id));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new {
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bans a user and kicks them.
|
||||
/// </summary>
|
||||
/// <param name="ban"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("admin/ban")]
|
||||
public IActionResult BanUser(BanCreateModel ban)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
User userModel = da.Users.GetById(uint.Parse(ban.user_id));
|
||||
|
||||
if (userModel == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_id"
|
||||
});
|
||||
}
|
||||
|
||||
if (ban.ban_type == "ip")
|
||||
{
|
||||
if (da.Bans.GetByIP(userModel.last_ip) != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "already_banned"
|
||||
});
|
||||
}
|
||||
|
||||
if (userModel.last_ip == "127.0.0.1")
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_ip"
|
||||
});
|
||||
}
|
||||
|
||||
da.Bans.Add(userModel.last_ip, userModel.user_id, ban.reason, int.Parse(ban.end_date), userModel.client_id);
|
||||
|
||||
api.RequestUserDisconnect(userModel.user_id);
|
||||
|
||||
api.SendBanMail(userModel.username, userModel.email, uint.Parse(ban.end_date));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
else if (ban.ban_type == "user")
|
||||
{
|
||||
if (userModel.is_banned)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.NotFound, new
|
||||
{
|
||||
status = "already_banned"
|
||||
});
|
||||
}
|
||||
|
||||
da.Users.UpdateBanned(userModel.user_id, true);
|
||||
|
||||
api.RequestUserDisconnect(userModel.user_id);
|
||||
|
||||
api.SendBanMail(userModel.username, userModel.email, uint.Parse(ban.end_date));
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "invalid_ban_type"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//List users
|
||||
[HttpGet]
|
||||
public IActionResult Get(int limit, int offset, string order)
|
||||
{
|
||||
if (limit == 0) limit = 20;
|
||||
if (order == null) order = "register_date";
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
if (limit > 100)
|
||||
{
|
||||
limit = 100;
|
||||
}
|
||||
|
||||
var result = da.Users.All((int)offset, (int)limit);
|
||||
return ApiResponse.PagedList<User>(Request, HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new user
|
||||
[HttpPost]
|
||||
public IActionResult Post(UserCreateModel user)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var nuser = api.RequireAuthentication(Request);
|
||||
api.DemandModerator(nuser);
|
||||
|
||||
if (user.is_admin)
|
||||
{
|
||||
//I need admin claim to do this
|
||||
api.DemandAdmin(nuser);
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var userModel = new User();
|
||||
userModel.username = user.username;
|
||||
userModel.email = user.email;
|
||||
userModel.is_admin = user.is_admin;
|
||||
userModel.is_moderator = user.is_moderator;
|
||||
userModel.user_state = UserState.valid;
|
||||
userModel.register_date = Epoch.Now;
|
||||
userModel.is_banned = false;
|
||||
|
||||
var userId = da.Users.Create(userModel);
|
||||
|
||||
userModel = da.Users.GetById(userId);
|
||||
if (userModel == null) { throw new Exception("Unable to find user"); }
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UserCreateModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string email { get; set; }
|
||||
public string password { get; set; }
|
||||
public bool is_admin { get; set; }
|
||||
public bool is_moderator { get; set; }
|
||||
}
|
||||
|
||||
public class BanCreateModel
|
||||
{
|
||||
public string ban_type { get; set; }
|
||||
public string user_id { get; set; }
|
||||
public string reason { get; set; }
|
||||
public string end_date { get; set; }
|
||||
}
|
||||
|
||||
public class MailCreateModel
|
||||
{
|
||||
public string target_id { get; set; }
|
||||
public string subject { get; set; }
|
||||
public string body { get; set; }
|
||||
public string sender_name { get; set; }
|
||||
}
|
||||
}
|
134
server/FSO.Server.Api.Core/Controllers/AuthLoginController.cs
Executable file
134
server/FSO.Server.Api.Core/Controllers/AuthLoginController.cs
Executable file
|
@ -0,0 +1,134 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.AuthTickets;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("AuthLogin")]
|
||||
[ApiController]
|
||||
public class AuthLoginController : ControllerBase
|
||||
{
|
||||
private static Func<IActionResult> ERROR_020 = printError("INV-020", "Please enter your member name and password.");
|
||||
private static Func<IActionResult> ERROR_110 = printError("INV-110", "The member name or password you have entered is incorrect. Please try again.");
|
||||
private static Func<IActionResult> ERROR_302 = printError("INV-302", "The game has experienced an internal error. Please try again.");
|
||||
private static Func<IActionResult> ERROR_160 = printError("INV-160", "The server is currently down for maintainance. Please try again later.");
|
||||
private static Func<IActionResult> ERROR_150 = printError("INV-150", "We're sorry, but your account has been suspended or cancelled.");
|
||||
private static string LOCK_MESSAGE = "Your account has been locked due to too many incorrect login attempts. " +
|
||||
"If you cannot remember your password, it can be reset at https://beta.freeso.org/forgot. Locked for: ";
|
||||
|
||||
public static int LockAttempts = 5;
|
||||
|
||||
public static int[] LockDuration = new int[] {
|
||||
5,
|
||||
15,
|
||||
30,
|
||||
60,
|
||||
120,
|
||||
720,
|
||||
1440
|
||||
};
|
||||
|
||||
// GET api/<controller>
|
||||
[HttpGet]
|
||||
public IActionResult Get(string username, string password, string version, string clientid)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
return ERROR_020();
|
||||
}
|
||||
|
||||
AuthTicket ticket = null;
|
||||
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var user = db.Users.GetByUsername(username);
|
||||
if (user == null)
|
||||
{
|
||||
return ERROR_110();
|
||||
}
|
||||
|
||||
if (user.is_banned)
|
||||
{
|
||||
return ERROR_150();
|
||||
}
|
||||
|
||||
if (api.Config.Maintainance && !(user.is_admin || user.is_moderator))
|
||||
{
|
||||
return ERROR_160();
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
|
||||
var accLock = db.Users.GetRemainingAuth(user.user_id, ip);
|
||||
if (accLock != null && (accLock.active || accLock.count >= LockAttempts) && accLock.expire_time > Epoch.Now)
|
||||
{
|
||||
return printError("INV-170", LOCK_MESSAGE + Epoch.HMSRemaining(accLock.expire_time))();
|
||||
}
|
||||
|
||||
var authSettings = db.Users.GetAuthenticationSettings(user.user_id);
|
||||
var isPasswordCorrect = PasswordHasher.Verify(password, new PasswordHash
|
||||
{
|
||||
data = authSettings.data,
|
||||
scheme = authSettings.scheme_class
|
||||
});
|
||||
|
||||
if (!isPasswordCorrect)
|
||||
{
|
||||
var failDelay = 60 * LockDuration[Math.Min(LockDuration.Length - 1, db.Users.FailedConsecutive(user.user_id, ip))];
|
||||
if (accLock == null)
|
||||
{
|
||||
db.Users.NewFailedAuth(user.user_id, ip, (uint)failDelay);
|
||||
} else
|
||||
{
|
||||
var remaining = db.Users.FailedAuth(accLock.attempt_id, (uint)failDelay, LockAttempts);
|
||||
if (remaining == 0)
|
||||
return printError("INV-170", LOCK_MESSAGE + Epoch.HMSRemaining(Epoch.Now + (uint)failDelay))();
|
||||
}
|
||||
return ERROR_110();
|
||||
}
|
||||
|
||||
var ban = db.Bans.GetByIP(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
return ERROR_110();
|
||||
}
|
||||
|
||||
db.Users.SuccessfulAuth(user.user_id, ip);
|
||||
db.Users.UpdateClientID(user.user_id, clientid ?? "0");
|
||||
|
||||
/** Make a ticket **/
|
||||
ticket = new AuthTicket();
|
||||
ticket.ticket_id = Guid.NewGuid().ToString().Replace("-", "");
|
||||
ticket.user_id = user.user_id;
|
||||
ticket.date = Epoch.Now;
|
||||
ticket.ip = ip;
|
||||
|
||||
db.AuthTickets.Create(ticket);
|
||||
db.Users.UpdateLastLogin(user.user_id, Epoch.Now);
|
||||
}
|
||||
var content = "Valid=TRUE\r\nTicket=" + ticket.ticket_id.ToString() + "\r\n";
|
||||
return ApiResponse.Plain(HttpStatusCode.OK, content);
|
||||
}
|
||||
|
||||
|
||||
public static Func<IActionResult> printError(String code, String message)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.AppendLine("Valid=FALSE");
|
||||
result.AppendLine("Ticket=0");
|
||||
result.AppendLine("reasontext=" + code + ";" + message);
|
||||
result.AppendLine("reasonurl=");
|
||||
|
||||
return () =>
|
||||
{
|
||||
return ApiResponse.Plain(HttpStatusCode.OK, result.ToString());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
48
server/FSO.Server.Api.Core/Controllers/AvatarDataController.cs
Executable file
48
server/FSO.Server.Api.Core/Controllers/AvatarDataController.cs
Executable file
|
@ -0,0 +1,48 @@
|
|||
using FSO.Common.Utils;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[Route("cityselector/app/AvatarDataServlet")]
|
||||
[ApiController]
|
||||
public class AvatarDataController : ControllerBase
|
||||
{
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var user = api.RequireAuthentication(Request);
|
||||
|
||||
var result = new XMLList<AvatarData>("The-Sims-Online");
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var avatars = db.Avatars.GetSummaryByUserId(user.UserID);
|
||||
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
result.Add(new AvatarData
|
||||
{
|
||||
ID = avatar.avatar_id,
|
||||
Name = avatar.name,
|
||||
ShardName = api.Shards.GetById(avatar.shard_id).Name,
|
||||
HeadOutfitID = avatar.head,
|
||||
BodyOutfitID = avatar.body,
|
||||
AppearanceType = (AvatarAppearanceType)Enum.Parse(typeof(AvatarAppearanceType), avatar.skin_tone.ToString()),
|
||||
Description = avatar.description,
|
||||
LotId = avatar.lot_id,
|
||||
LotName = avatar.lot_name,
|
||||
LotLocation = avatar.lot_location
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.Xml(HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
}
|
321
server/FSO.Server.Api.Core/Controllers/AvatarInfoController.cs
Executable file
321
server/FSO.Server.Api.Core/Controllers/AvatarInfoController.cs
Executable file
|
@ -0,0 +1,321 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Database.DA.Avatars;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class AvatarInfoController : ControllerBase
|
||||
{
|
||||
//get the avatars by user_id
|
||||
[Route("userapi/user/avatars")]
|
||||
public IActionResult GetByUser()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var user = api.RequireAuthentication(Request);
|
||||
if (!user.Claims.Contains("userReadPermissions")) return ApiResponse.Json(HttpStatusCode.OK, new JSONAvatarError("No read premissions found."));
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatars = da.Avatars.GetByUserId(user.UserID);
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
}
|
||||
var avatarsJson = new JSONAvatars();
|
||||
avatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarsJson);
|
||||
}
|
||||
}
|
||||
//get the avatar by id
|
||||
[HttpGet]
|
||||
[Route("userapi/avatars/{avartarId}")]
|
||||
public IActionResult GetByID(uint avartarId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatar = da.Avatars.Get(avartarId);
|
||||
if (avatar == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
|
||||
var avatarJson = new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarJson);
|
||||
}
|
||||
}
|
||||
//get the avatars by ids
|
||||
[Route("userapi/avatars")]
|
||||
public IActionResult GetByIDs([FromQuery(Name = "ids")]string idsString)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
try
|
||||
{
|
||||
uint[] ids = Array.ConvertAll(idsString.Split(","), uint.Parse);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatars = da.Avatars.GetMultiple(ids);
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
}
|
||||
var avatarsJson = new JSONAvatars();
|
||||
avatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarsJson);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Error during cast. (invalid_value)"));
|
||||
}
|
||||
}
|
||||
//gets all the avatars from one city
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/avatars/page/{pageNum}")]
|
||||
public IActionResult GetAll(int shardId,int pageNum, [FromQuery(Name = "avatars_on_page")]int perPage)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
if(perPage == 0)
|
||||
{
|
||||
perPage = 100;
|
||||
}
|
||||
if (perPage > 500) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("The max amount of avatars per page is 500"));
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
pageNum = pageNum - 1;
|
||||
|
||||
var avatars = da.Avatars.AllByPage(shardId, pageNum * perPage, perPage,"avatar_id");
|
||||
var avatarCount = avatars.Total;
|
||||
var totalPages = (avatars.Total - 1)/perPage + 1;
|
||||
|
||||
var pageAvatarsJson = new JSONAvatarsPage();
|
||||
pageAvatarsJson.total_avatars = avatarCount;
|
||||
pageAvatarsJson.page = pageNum + 1;
|
||||
pageAvatarsJson.total_pages = (int)totalPages;
|
||||
pageAvatarsJson.avatars_on_page = avatars.Count();
|
||||
|
||||
if (pageNum < 0 || pageNum >= (int)totalPages) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Page not found"));
|
||||
if (avatars == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
pageAvatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, pageAvatarsJson);
|
||||
}
|
||||
}
|
||||
//gets avatar by name
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/avatars/name/{name}")]
|
||||
public IActionResult GetByName(int shardId, string name)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var avatar = da.Avatars.SearchExact(shardId, name, 1).FirstOrDefault();
|
||||
if (avatar == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
|
||||
var avatarJson = new JSONAvatar();
|
||||
var avatarById = da.Avatars.Get(avatar.avatar_id);
|
||||
if (avatarById == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatar not found"));
|
||||
avatarJson = (new JSONAvatar
|
||||
{
|
||||
avatar_id = avatarById.avatar_id,
|
||||
shard_id = avatarById.shard_id,
|
||||
name = avatarById.name,
|
||||
gender = avatarById.gender,
|
||||
date = avatarById.date,
|
||||
description = avatarById.description,
|
||||
current_job = avatarById.current_job,
|
||||
mayor_nhood = avatarById.mayor_nhood
|
||||
});
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarJson);
|
||||
}
|
||||
}
|
||||
//gets all the avatars that live in a specific neighbourhood
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/avatars/neighborhood/{nhoodId}")]
|
||||
public IActionResult GetByNhood(int shardId, uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.All(shardId).Where(x => x.neighborhood_id == nhoodId);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Lots not found"));
|
||||
|
||||
List<JSONAvatar> avatarJson = new List<JSONAvatar>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
if(lot.category != FSO.Common.Enum.LotCategory.community)
|
||||
{
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id);
|
||||
var avatars = da.Avatars.GetMultiple(roomies.ToArray());
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
avatarJson.Add(new JSONAvatar
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
shard_id = avatar.shard_id,
|
||||
name = avatar.name,
|
||||
gender = avatar.gender,
|
||||
date = avatar.date,
|
||||
description = avatar.description,
|
||||
current_job = avatar.current_job,
|
||||
mayor_nhood = avatar.mayor_nhood
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
var avatarsJson = new JSONAvatars();
|
||||
avatarsJson.avatars = avatarJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarsJson);
|
||||
}
|
||||
}
|
||||
//get all online Avatars
|
||||
[HttpGet]
|
||||
[Route("userapi/avatars/online")]
|
||||
public IActionResult GetOnline([FromQuery(Name = "compact")]bool compact)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
List<JSONAvatarSmall> avatarSmallJson = new List<JSONAvatarSmall>();
|
||||
var avatarJson = new JSONAvatarOnline();
|
||||
if (compact)
|
||||
{
|
||||
var avatarStatus = da.AvatarClaims.GetAllActiveAvatarsCount();
|
||||
if (avatarStatus == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("ammount not found"));
|
||||
avatarJson.avatars_online_count = avatarStatus;
|
||||
}
|
||||
|
||||
|
||||
if (!compact)
|
||||
{
|
||||
var avatarStatus = da.AvatarClaims.GetAllActiveAvatars();
|
||||
if (avatarStatus == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONAvatarError("Avatars not found"));
|
||||
|
||||
foreach (var avatar in avatarStatus)
|
||||
{
|
||||
uint location = 0;
|
||||
if (avatar.privacy_mode == 0)
|
||||
{
|
||||
location = avatar.location;
|
||||
}
|
||||
avatarSmallJson.Add(new JSONAvatarSmall
|
||||
{
|
||||
avatar_id = avatar.avatar_id,
|
||||
name = avatar.name,
|
||||
privacy_mode = avatar.privacy_mode,
|
||||
location = location
|
||||
});
|
||||
|
||||
}
|
||||
avatarJson.avatars_online_count = avatarStatus.Count();
|
||||
}
|
||||
|
||||
avatarJson.avatars = avatarSmallJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, avatarJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONAvatarError
|
||||
{
|
||||
public string error;
|
||||
public JSONAvatarError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONAvatarsPage
|
||||
{
|
||||
public int page { get; set; }
|
||||
public int total_pages { get; set; }
|
||||
public int total_avatars { get; set; }
|
||||
public int avatars_on_page { get; set; }
|
||||
public List<JSONAvatar> avatars { get; set; }
|
||||
}
|
||||
public class JSONAvatarOnline
|
||||
{
|
||||
public int? avatars_online_count { get; set; }
|
||||
public List<JSONAvatarSmall> avatars { get; set; }
|
||||
}
|
||||
public class JSONAvatarSmall
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public byte privacy_mode { get; set; }
|
||||
public uint location { get; set; }
|
||||
}
|
||||
public class JSONAvatars
|
||||
{
|
||||
public List<JSONAvatar> avatars { get; set; }
|
||||
}
|
||||
public class JSONAvatar
|
||||
{
|
||||
public uint avatar_id { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public DbAvatarGender gender { get; set; }
|
||||
public uint date { get; set; }
|
||||
public string description { get; set; }
|
||||
public ushort current_job { get; set; }
|
||||
public int? mayor_nhood { get; set; }
|
||||
}
|
||||
}
|
134
server/FSO.Server.Api.Core/Controllers/BulletinInfoController.cs
Executable file
134
server/FSO.Server.Api.Core/Controllers/BulletinInfoController.cs
Executable file
|
@ -0,0 +1,134 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Database.DA.Bulletin;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class BulletinInfoController : ControllerBase
|
||||
{
|
||||
[HttpGet("nhoodId")]
|
||||
[Route("userapi/neighborhood/{nhoodId}/bulletins")]
|
||||
public IActionResult GetByNhoodAndAfter(uint nhoodId, [FromQuery(Name = "after")]uint after)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
if (after == null) after = 0;
|
||||
var bulletins = da.BulletinPosts.GetByNhoodId(nhoodId, after);
|
||||
if (bulletins == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONBulletinError("Bulletins not found"));
|
||||
|
||||
List<JSONBulletin> bulletinJson = new List<JSONBulletin>();
|
||||
foreach (var bulletin in bulletins)
|
||||
{
|
||||
bulletinJson.Add(new JSONBulletin
|
||||
{
|
||||
bulletin_id = bulletin.bulletin_id,
|
||||
neighborhood_id = bulletin.neighborhood_id,
|
||||
avatar_id = bulletin.avatar_id,
|
||||
title = bulletin.title,
|
||||
body = bulletin.body,
|
||||
date = bulletin.date,
|
||||
flags = bulletin.flags,
|
||||
lot_id = bulletin.lot_id,
|
||||
type = bulletin.type
|
||||
});
|
||||
|
||||
}
|
||||
var bulletinsJson = new JSONBulletins();
|
||||
bulletinsJson.bulletins = bulletinJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, bulletinsJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhood/{nhoodid}/bulletins/{bulletinId}")]
|
||||
public IActionResult GetByID(uint bulletinId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var bulletin = da.BulletinPosts.Get(bulletinId);
|
||||
if (bulletin == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONBulletinError("Bulletin not found"));
|
||||
|
||||
var bulletinJson = new JSONBulletin();
|
||||
bulletinJson = new JSONBulletin
|
||||
{
|
||||
bulletin_id = bulletin.bulletin_id,
|
||||
neighborhood_id = bulletin.neighborhood_id,
|
||||
avatar_id = bulletin.avatar_id,
|
||||
title = bulletin.title,
|
||||
body = bulletin.body,
|
||||
date = bulletin.date,
|
||||
flags = bulletin.flags,
|
||||
lot_id = bulletin.lot_id,
|
||||
type = bulletin.type
|
||||
};
|
||||
return ApiResponse.Json(HttpStatusCode.OK, bulletinJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhood/{nhoodId}/bulletins/type/{bulletinType}")]
|
||||
public IActionResult GetByNhoodAndType(uint nhoodId,DbBulletinType bulletinType)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var bulletins = da.BulletinPosts.GetByNhoodId(nhoodId, 0).Where(x => x.type == bulletinType);
|
||||
if (bulletins == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONBulletinError("Bulletins not found"));
|
||||
|
||||
List<JSONBulletin> bulletinJson = new List<JSONBulletin>();
|
||||
foreach (var bulletin in bulletins)
|
||||
{
|
||||
bulletinJson.Add(new JSONBulletin
|
||||
{
|
||||
bulletin_id = bulletin.bulletin_id,
|
||||
neighborhood_id = bulletin.neighborhood_id,
|
||||
avatar_id = bulletin.avatar_id,
|
||||
title = bulletin.title,
|
||||
body = bulletin.body,
|
||||
date = bulletin.date,
|
||||
flags = bulletin.flags,
|
||||
lot_id = bulletin.lot_id,
|
||||
type = bulletin.type
|
||||
});
|
||||
|
||||
}
|
||||
var bulletinsJson = new JSONBulletins();
|
||||
bulletinsJson.bulletins = bulletinJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, bulletinsJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONBulletinError
|
||||
{
|
||||
public string error;
|
||||
public JSONBulletinError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONBulletins
|
||||
{
|
||||
public List<JSONBulletin> bulletins { get; set; }
|
||||
}
|
||||
public class JSONBulletin
|
||||
{
|
||||
public uint bulletin_id { get; set; }
|
||||
public int neighborhood_id { get; set; }
|
||||
public uint? avatar_id { get; set; }
|
||||
public string title { get; set; }
|
||||
public string body { get; set; }
|
||||
public uint date { get; set; }
|
||||
public uint flags { get; set; }
|
||||
public int? lot_id { get; set; }
|
||||
public DbBulletinType type { get; set; }
|
||||
}
|
||||
}
|
73
server/FSO.Server.Api.Core/Controllers/CityJSONController.cs
Executable file
73
server/FSO.Server.Api.Core/Controllers/CityJSONController.cs
Executable file
|
@ -0,0 +1,73 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class CityJSONController : ControllerBase
|
||||
{
|
||||
private static object ModelLock = new object { };
|
||||
private static CityInfoModel LastModel = new CityInfoModel();
|
||||
private static uint LastModelUpdate;
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/city.json")]
|
||||
public IActionResult Get(int shardid)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var now = Epoch.Now;
|
||||
if (LastModelUpdate < now - 15)
|
||||
{
|
||||
LastModelUpdate = now;
|
||||
lock (ModelLock)
|
||||
{
|
||||
LastModel = new CityInfoModel();
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.AllLocations(shardid);
|
||||
var lotstatus = da.LotClaims.AllLocations(shardid);
|
||||
LastModel.reservedLots = lots.ConvertAll(x => x.location).ToArray();
|
||||
LastModel.names = lots.ConvertAll(x => x.name).ToArray();
|
||||
LastModel.activeLots = lotstatus.ConvertAll(x => x.location).ToArray();
|
||||
LastModel.onlineCount = lotstatus.ConvertAll(x => x.active).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
lock (ModelLock)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, LastModel);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/thumbwork.json")]
|
||||
public IActionResult ThumbWork()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var work = da.Lots.Get3DWork();
|
||||
if (work == null) return ApiResponse.Plain(HttpStatusCode.OK, "");
|
||||
else
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, work);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CityInfoModel
|
||||
{
|
||||
public string[] names;
|
||||
public uint[] reservedLots;
|
||||
public uint[] activeLots;
|
||||
public int[] onlineCount;
|
||||
}
|
||||
}
|
79
server/FSO.Server.Api.Core/Controllers/ElectionInfoController.cs
Executable file
79
server/FSO.Server.Api.Core/Controllers/ElectionInfoController.cs
Executable file
|
@ -0,0 +1,79 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using FSO.Server.Database.DA.Elections;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class ElectionController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhood/{nhoodId}/elections")]
|
||||
public IActionResult GetByNhood(uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var nhood = da.Neighborhoods.Get(nhoodId);
|
||||
if (nhood.election_cycle_id == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONElectionError("Election cycle not found"));
|
||||
|
||||
var electionCycle = da.Elections.GetCycle((uint)nhood.election_cycle_id);
|
||||
|
||||
var electionCandidates = new List<DbElectionCandidate>();
|
||||
if (electionCycle.current_state == Database.DA.Elections.DbElectionCycleState.election)
|
||||
electionCandidates = da.Elections.GetCandidates(electionCycle.cycle_id, Database.DA.Elections.DbCandidateState.running);
|
||||
|
||||
if (electionCycle.current_state == Database.DA.Elections.DbElectionCycleState.ended)
|
||||
electionCandidates = da.Elections.GetCandidates(electionCycle.cycle_id, Database.DA.Elections.DbCandidateState.won);
|
||||
|
||||
if (electionCycle == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONElectionError("Election cycle not found"));
|
||||
|
||||
List<JSONCandidates> candidatesJson = new List<JSONCandidates>();
|
||||
foreach (var candidate in electionCandidates)
|
||||
{
|
||||
candidatesJson.Add(new JSONCandidates
|
||||
{
|
||||
candidate_avatar_id = candidate.candidate_avatar_id,
|
||||
comment = candidate.comment,
|
||||
state = candidate.state
|
||||
});
|
||||
|
||||
}
|
||||
var electionJson = new JSONElections();
|
||||
electionJson.candidates = candidatesJson;
|
||||
electionJson.current_state = electionCycle.current_state;
|
||||
electionJson.neighborhood_id = nhood.neighborhood_id;
|
||||
electionJson.start_date = electionCycle.start_date;
|
||||
electionJson.end_date = electionCycle.end_date;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, electionJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONElectionError
|
||||
{
|
||||
public string error;
|
||||
public JSONElectionError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONElections
|
||||
{
|
||||
public DbElectionCycleState current_state { get; set; }
|
||||
public int neighborhood_id { get; set; }
|
||||
public uint start_date { get; set; }
|
||||
public uint end_date { get; set; }
|
||||
public List<JSONCandidates> candidates { get; set; }
|
||||
}
|
||||
public class JSONCandidates
|
||||
{
|
||||
public uint candidate_avatar_id { get; set; }
|
||||
public string comment { get; set; }
|
||||
public DbCandidateState state { get; set; }
|
||||
}
|
||||
}
|
38
server/FSO.Server.Api.Core/Controllers/GameAPI/UpdateController.cs
Executable file
38
server/FSO.Server.Api.Core/Controllers/GameAPI/UpdateController.cs
Executable file
|
@ -0,0 +1,38 @@
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers.GameAPI
|
||||
{
|
||||
[EnableCors]
|
||||
[Route("userapi/update")]
|
||||
public class UpdateController : ControllerBase
|
||||
{
|
||||
|
||||
// GET userapi/update
|
||||
// get recent PUBLISHED updates for the active branch, ordered by publish date
|
||||
[HttpGet()]
|
||||
public IActionResult Get(int id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var recents = da.Updates.GetRecentUpdatesForBranchByName(api.Config.BranchName, 20);
|
||||
return new JsonResult(recents.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
// GET: userapi/update/<branch>
|
||||
// get recent PUBLISHED updates for a specific branch, ordered by publish date
|
||||
[HttpGet("{branch}")]
|
||||
public IActionResult Get(string branch)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var recents = da.Updates.GetRecentUpdatesForBranchByName(branch, 20);
|
||||
return new JsonResult(recents.ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
server/FSO.Server.Api.Core/Controllers/GithubController.cs
Executable file
77
server/FSO.Server.Api.Core/Controllers/GithubController.cs
Executable file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Octokit;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class GithubController : ControllerBase
|
||||
{
|
||||
readonly GitHubClient client =
|
||||
new GitHubClient(new ProductHeaderValue(Api.INSTANCE.Github.AppName), new Uri("https://github.com/"));
|
||||
|
||||
private string StoredToken;
|
||||
private static string CSRF;
|
||||
|
||||
// GET: /<controller>/
|
||||
[HttpGet]
|
||||
[Route("github/")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (Api.INSTANCE.Github == null) return NotFound();
|
||||
if (Api.INSTANCE.Github.AccessToken != null) return NotFound();
|
||||
|
||||
return Redirect(GetOauthLoginUrl());
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("github/callback")]
|
||||
public async Task<IActionResult> Callback(string code, string state)
|
||||
{
|
||||
if (Api.INSTANCE.Github == null) return NotFound();
|
||||
if (Api.INSTANCE.Github.AccessToken != null) return NotFound();
|
||||
|
||||
if (!String.IsNullOrEmpty(code))
|
||||
{
|
||||
var expectedState = CSRF;
|
||||
if (state != expectedState) throw new InvalidOperationException("SECURITY FAIL!");
|
||||
//CSRF = null;
|
||||
|
||||
var token = await client.Oauth.CreateAccessToken(
|
||||
new OauthTokenRequest(Api.INSTANCE.Github.ClientID, Api.INSTANCE.Github.ClientSecret, code)
|
||||
{
|
||||
RedirectUri = new Uri("http://localhost:80/github/callback")
|
||||
});
|
||||
StoredToken = token.AccessToken;
|
||||
}
|
||||
|
||||
return Ok(StoredToken);
|
||||
}
|
||||
|
||||
private string GetOauthLoginUrl()
|
||||
{
|
||||
var rngCsp = new RNGCryptoServiceProvider();
|
||||
string csrf = "";
|
||||
var random = new byte[24];
|
||||
rngCsp.GetBytes(random);
|
||||
for (int i=0; i<24; i++)
|
||||
{
|
||||
csrf += (char)('?' + random[i]/4);
|
||||
}
|
||||
CSRF = csrf;
|
||||
|
||||
// 1. Redirect users to request GitHub access
|
||||
var request = new OauthLoginRequest(Api.INSTANCE.Github.ClientID)
|
||||
{
|
||||
Scopes = { "admin:org", "repo" },
|
||||
State = csrf
|
||||
};
|
||||
var oauthLoginUrl = client.Oauth.GetGitHubLoginUrl(request);
|
||||
return oauthLoginUrl.ToString();
|
||||
}
|
||||
}
|
||||
}
|
93
server/FSO.Server.Api.Core/Controllers/InitialConnectController.cs
Executable file
93
server/FSO.Server.Api.Core/Controllers/InitialConnectController.cs
Executable file
|
@ -0,0 +1,93 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("cityselector/app/InitialConnectServlet")]
|
||||
[ApiController]
|
||||
public class InitialConnectController : ControllerBase
|
||||
{
|
||||
private static Func<IActionResult> ERROR_MISSING_TOKEN = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("501", "Token not found"));
|
||||
private static Func<IActionResult> ERROR_EXPIRED_TOKEN = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("502", "Token has expired"));
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Get(string ticket, string version)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
if (ticket == null || ticket == "" || version == null){
|
||||
return ERROR_MISSING_TOKEN();
|
||||
}
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
var dbTicket = db.AuthTickets.Get(ticket);
|
||||
if (dbTicket == null){
|
||||
return ERROR_MISSING_TOKEN();
|
||||
}
|
||||
|
||||
db.AuthTickets.Delete((string)ticket);
|
||||
if (dbTicket.date + api.Config.AuthTicketDuration < Epoch.Now){
|
||||
return ERROR_EXPIRED_TOKEN();
|
||||
}
|
||||
|
||||
/** Is it a valid account? **/
|
||||
var user = db.Users.GetById(dbTicket.user_id);
|
||||
if (user == null){
|
||||
return ERROR_MISSING_TOKEN();
|
||||
}
|
||||
|
||||
//Use JWT to create and sign an auth cookies
|
||||
var session = new JWTUser()
|
||||
{
|
||||
UserID = user.user_id,
|
||||
UserName = user.username
|
||||
};
|
||||
|
||||
//TODO: This assumes 1 shard, when using multiple need to either have version download occour after
|
||||
//avatar select, or rework the tables
|
||||
var shardOne = api.Shards.GetById(1);
|
||||
|
||||
var token = api.JWT.CreateToken(session);
|
||||
|
||||
IActionResult response;
|
||||
if (shardOne.UpdateID != null)
|
||||
{
|
||||
var update = db.Updates.GetUpdate(shardOne.UpdateID.Value);
|
||||
response = ApiResponse.Xml(HttpStatusCode.OK, new UserAuthorized()
|
||||
{
|
||||
FSOBranch = shardOne.VersionName,
|
||||
FSOVersion = shardOne.VersionNumber,
|
||||
FSOUpdateUrl = update.full_zip,
|
||||
FSOCDNUrl = api.Config.CDNUrl
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
response = ApiResponse.Xml(HttpStatusCode.OK, new UserAuthorized()
|
||||
{
|
||||
FSOBranch = shardOne.VersionName,
|
||||
FSOVersion = shardOne.VersionNumber,
|
||||
FSOUpdateUrl = api.Config.UpdateUrl,
|
||||
FSOCDNUrl = api.Config.CDNUrl
|
||||
});
|
||||
}
|
||||
Response.Cookies.Append("fso", token.Token, new Microsoft.AspNetCore.Http.CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.Now.AddDays(1),
|
||||
Domain = Request.Host.Host,
|
||||
Path = "/"
|
||||
});
|
||||
//HttpContext.Current.Response.SetCookie(new HttpCookie("fso", token.Token));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
705
server/FSO.Server.Api.Core/Controllers/LotInfoController.cs
Executable file
705
server/FSO.Server.Api.Core/Controllers/LotInfoController.cs
Executable file
|
@ -0,0 +1,705 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Common.Enum;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
|
||||
public static class MemoryCacher
|
||||
{
|
||||
public static MemoryCache Default = new MemoryCache(new MemoryCacheOptions());
|
||||
public static object GetValue(string key)
|
||||
{
|
||||
MemoryCache memoryCache = Default;
|
||||
return memoryCache.Get(key);
|
||||
}
|
||||
|
||||
public static bool Add(string key, object value, DateTimeOffset absExpiration)
|
||||
{
|
||||
MemoryCache memoryCache = Default;
|
||||
return memoryCache.Set(key, value, absExpiration) == value;
|
||||
}
|
||||
|
||||
public static void Delete(string key)
|
||||
{
|
||||
MemoryCache memoryCache = Default;
|
||||
memoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class LotInfoController : ControllerBase
|
||||
{
|
||||
public static ConcurrentDictionary<int, ShardLocationCache> LotLocationCache = new ConcurrentDictionary<int, ShardLocationCache>();
|
||||
|
||||
public static int? IDForLocation(int shardid, uint loc)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
var locToID = LotLocationCache.GetOrAdd(shardid, (ikey) =>
|
||||
{
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return new ShardLocationCache(
|
||||
new ConcurrentDictionary<uint, int>(da.Lots.All(ikey).Select(x => new KeyValuePair<uint, int>(x.location, x.lot_id)))
|
||||
);
|
||||
}
|
||||
});
|
||||
if (DateTime.UtcNow - locToID.CreateTime > TimeSpan.FromMinutes(15))
|
||||
{
|
||||
ShardLocationCache removed;
|
||||
LotLocationCache.TryRemove(shardid, out removed);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return locToID.Dict.GetOrAdd(loc, (ikey) =>
|
||||
{
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
return da.Lots.GetByLocation(shardid, ikey).lot_id;
|
||||
}
|
||||
});
|
||||
} catch (NullReferenceException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/{id}.png")]
|
||||
[ResponseCache(Duration = 60 * 60, Location = ResponseCacheLocation.Any)]
|
||||
public IActionResult Get(int shardid, uint id)
|
||||
{
|
||||
var dat = (byte[])MemoryCacher.GetValue("lt" + shardid + ":" + id);
|
||||
if (dat != null)
|
||||
{
|
||||
return File(dat, "image/png");
|
||||
}
|
||||
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = IDForLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var ndat = System.IO.File.ReadAllBytes(Path.Combine(api.Config.NFSdir, "Lots/" + lot.Value.ToString("x8") + "/thumb.png"));
|
||||
MemoryCacher.Add("lt" + shardid + ":" + id, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0)));
|
||||
|
||||
return File(ndat, "image/png");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/i{id}.json")]
|
||||
public IActionResult GetJSON(int shardid, uint id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var jlot = new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, jlot);
|
||||
}
|
||||
}
|
||||
|
||||
//New user API calls might replace old once later
|
||||
//get lot information by location
|
||||
[HttpGet]
|
||||
[Route("userapi/lots/{lotId}")]
|
||||
public IActionResult GetByID(int lotId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.Get(lotId);
|
||||
if (lot == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var lotJson = new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies,
|
||||
lot_id = lot.lot_id
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotJson);
|
||||
}
|
||||
}
|
||||
//get the lots by ids
|
||||
[Route("userapi/lots")]
|
||||
public IActionResult GetByIDs([FromQuery(Name = "ids")]string idsString)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
try
|
||||
{
|
||||
int[] ids = Array.ConvertAll(idsString.Split(","), int.Parse);
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.GetMultiple(ids);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
|
||||
List<JSONLot> lotJson = new List<JSONLot>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
lotJson.Add(new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
}
|
||||
var lotsJson = new JSONLots();
|
||||
lotsJson.lots = lotJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotsJson);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Error during cast. (invalid_value)"));
|
||||
}
|
||||
}
|
||||
//gets all the lots from one city
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/page/{pageNum}")]
|
||||
public IActionResult GetAll(int shardId, int pageNum, [FromQuery(Name = "lots_on_page")]int perPage)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
if (perPage == 0)
|
||||
{
|
||||
perPage = 100;
|
||||
}
|
||||
if (perPage > 500) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("The max amount of lots per page is 500"));
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
pageNum = pageNum - 1;
|
||||
|
||||
var lots = da.Lots.AllByPage(shardId, pageNum * perPage, perPage,"lot_id");
|
||||
var lotCount = lots.Total;
|
||||
var totalPages = (lots.Total - 1) / perPage + 1;
|
||||
|
||||
var pageLotsJson = new JSONLotsPage();
|
||||
pageLotsJson.total_lots = lotCount;
|
||||
pageLotsJson.page = pageNum + 1;
|
||||
pageLotsJson.total_pages = (int)totalPages;
|
||||
pageLotsJson.lots_on_page = lots.Count();
|
||||
|
||||
if (pageNum < 0 || pageNum >= (int)totalPages) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Page not found"));
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
|
||||
List<JSONLotSmall> lotJson = new List<JSONLotSmall>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
lotJson.Add(new JSONLotSmall
|
||||
{
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
description = lot.description,
|
||||
category = lot.category,
|
||||
admit_mode = lot.admit_mode,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
}
|
||||
|
||||
pageLotsJson.lots = lotJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, pageLotsJson);
|
||||
}
|
||||
}
|
||||
//get lot information by location
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/location/{locationId}")]
|
||||
public IActionResult GetByLocation(int shardId, uint locationId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardId, locationId);
|
||||
if (lot == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var LotJSON = new JSONLot
|
||||
{
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies,
|
||||
lot_id = lot.lot_id
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, LotJSON);
|
||||
}
|
||||
}
|
||||
//get lot information By neighbourhood
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/neighborhood/{nhoodId}")]
|
||||
public IActionResult GetByNhood(int shardId, uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.Lots.All(shardId).Where(x => x.neighborhood_id == nhoodId);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("lots not found"));
|
||||
List<JSONLotSmall> lotJson = new List<JSONLotSmall>();
|
||||
foreach (var lot in lots)
|
||||
{
|
||||
lotJson.Add(new JSONLotSmall
|
||||
{
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
description = lot.description,
|
||||
category = lot.category,
|
||||
admit_mode = lot.admit_mode,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
}
|
||||
var lotsJson = new JSONLotsSmall();
|
||||
lotsJson.lots = lotJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotsJson);
|
||||
}
|
||||
}
|
||||
//get lot information by name
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/name/{lotName}")]
|
||||
public IActionResult GetByName(int shardId, string lotName)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByName(shardId, lotName);
|
||||
if (lot == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lot not found"));
|
||||
|
||||
var roomies = da.Roommates.GetLotRoommates(lot.lot_id).Where(x => x.is_pending == 0).Select(x => x.avatar_id).ToArray();
|
||||
|
||||
var lotJson = new JSONLot
|
||||
{
|
||||
lot_id = lot.lot_id,
|
||||
admit_mode = lot.admit_mode,
|
||||
category = lot.category,
|
||||
created_date = lot.created_date,
|
||||
description = lot.description,
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
owner_id = lot.owner_id,
|
||||
shard_id = lot.shard_id,
|
||||
skill_mode = lot.skill_mode,
|
||||
roommates = roomies
|
||||
};
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotJson);
|
||||
}
|
||||
}
|
||||
//get online lots
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/online")]
|
||||
public IActionResult GetOnline(int shardId, [FromQuery(Name = "compact")]bool compact)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
List<JSONLotSmall> lotSmallJson = new List<JSONLotSmall>();
|
||||
var lotsOnlineJson = new JSONLotsOnline();
|
||||
|
||||
if (!compact)
|
||||
{
|
||||
var activeLots = da.LotClaims.AllActiveLots(shardId);
|
||||
if (activeLots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
var totalAvatars = 0;
|
||||
foreach (var lot in activeLots)
|
||||
{
|
||||
lotSmallJson.Add(new JSONLotSmall
|
||||
{
|
||||
location = lot.location,
|
||||
name = lot.name,
|
||||
description = lot.description,
|
||||
category = lot.category,
|
||||
admit_mode = lot.admit_mode,
|
||||
neighborhood_id = lot.neighborhood_id,
|
||||
avatars_in_lot = lot.active,
|
||||
lot_id = lot.lot_id
|
||||
});
|
||||
totalAvatars += lot.active;
|
||||
}
|
||||
lotsOnlineJson.total_lots_online = activeLots.Count();
|
||||
lotsOnlineJson.total_avatars_in_lots_online = totalAvatars;
|
||||
}
|
||||
else
|
||||
{
|
||||
var activeLots = da.LotClaims.AllLocations(shardId);
|
||||
if (activeLots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
var totalAvatars = 0;
|
||||
foreach (var lot in activeLots)
|
||||
{
|
||||
totalAvatars += lot.active;
|
||||
}
|
||||
|
||||
lotsOnlineJson.total_lots_online = activeLots.Count();
|
||||
lotsOnlineJson.total_avatars_in_lots_online = totalAvatars;
|
||||
}
|
||||
|
||||
lotsOnlineJson.lots = lotSmallJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, lotsOnlineJson);
|
||||
}
|
||||
}
|
||||
//get Top-100 lots by category
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/top100/category/{lotCategory}")]
|
||||
public IActionResult GetTop100ByCategory(int shardId, LotCategory lotCategory)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.LotTop100.GetByCategory(shardId, lotCategory).Take(100);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Top100 lots not found"));
|
||||
|
||||
List<JSONTop100Lot> top100Lots = new List<JSONTop100Lot>();
|
||||
foreach (var top100Lot in lots)
|
||||
{
|
||||
top100Lots.Add(new JSONTop100Lot
|
||||
{
|
||||
category = top100Lot.category,
|
||||
rank = top100Lot.rank,
|
||||
shard_id = top100Lot.shard_id,
|
||||
lot_location = top100Lot.lot_location,
|
||||
lot_name = top100Lot.lot_name,
|
||||
lot_id = top100Lot.lot_id
|
||||
});
|
||||
}
|
||||
var top100Json = new JSONTop100Lots();
|
||||
top100Json.lots = top100Lots;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, top100Json);
|
||||
}
|
||||
}
|
||||
//get Top-100 lots by shard
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/lots/top100/all")]
|
||||
public IActionResult GetTop100ByShard(int shardId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lots = da.LotTop100.GetAllByShard(shardId);
|
||||
if (lots == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONLotError("Lots not found"));
|
||||
|
||||
List<JSONTop100Lot> top100Lots = new List<JSONTop100Lot>();
|
||||
foreach (var top100Lot in lots)
|
||||
{
|
||||
top100Lots.Add(new JSONTop100Lot
|
||||
{
|
||||
category = top100Lot.category,
|
||||
rank = top100Lot.rank,
|
||||
shard_id = top100Lot.shard_id,
|
||||
lot_location = top100Lot.lot_location,
|
||||
lot_name = top100Lot.lot_name,
|
||||
lot_id = top100Lot.lot_id
|
||||
});
|
||||
}
|
||||
var top100Json = new JSONTop100Lots();
|
||||
top100Json.lots = top100Lots;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, top100Json);
|
||||
}
|
||||
}
|
||||
|
||||
//moderation only functions, such as downloading lot state
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/{id}.fsov")]
|
||||
public IActionResult GetFSOV(int shardid, uint id)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/state_" + lot.ring_backup_num.ToString() + ".fsov");
|
||||
|
||||
|
||||
stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return File(stream, "application/octet-stream");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardid}/{id}.fsof")]
|
||||
[ResponseCache(Duration = 60 * 60, Location = ResponseCacheLocation.Any)]
|
||||
public IActionResult GetFSOF(int shardid, uint id)
|
||||
{
|
||||
var dat = (byte[])MemoryCacher.GetValue("lf" + shardid + ":" + id);
|
||||
if (dat != null)
|
||||
{
|
||||
return File(dat, "application/octet-stream");
|
||||
}
|
||||
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = IDForLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.Value.ToString("x8") + "/thumb.fsof");
|
||||
|
||||
var ndat = System.IO.File.ReadAllBytes(path);
|
||||
MemoryCacher.Add("lf" + shardid + ":" + id, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0)));
|
||||
return File(ndat, "application/octet-stream");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("userapi/city/{shardid}/uploadfacade/{id}")]
|
||||
public IActionResult UploadFacade(int shardid, uint id, List<IFormFile> files)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
if (files == null)
|
||||
return NotFound();
|
||||
|
||||
byte[] data = null;
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filename = file.FileName.Trim('\"');
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
file.CopyTo(memoryStream);
|
||||
data = memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (data == null) return NotFound();
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return NotFound();
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/thumb.fsof");
|
||||
stream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||
stream.Write(data, 0, data.Length);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
var api = Api.INSTANCE;
|
||||
api.DemandModerator(Request);
|
||||
|
||||
if (!Request.Content.IsMimeMultipartContent())
|
||||
return new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType);
|
||||
|
||||
var provider = new MultipartMemoryStreamProvider();
|
||||
var files = Request.Content.ReadAsMultipartAsync(provider).Result;
|
||||
|
||||
byte[] data = null;
|
||||
foreach (var file in provider.Contents)
|
||||
{
|
||||
var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
|
||||
data = file.ReadAsByteArrayAsync().Result;
|
||||
}
|
||||
|
||||
if (data == null) return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var lot = da.Lots.GetByLocation(shardid, id);
|
||||
if (lot == null) return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
|
||||
FileStream stream;
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/thumb.fsof");
|
||||
stream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
response.Content = new StringContent("", Encoding.UTF8, "text/plain");
|
||||
return response;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ShardLocationCache
|
||||
{
|
||||
public ConcurrentDictionary<uint, int> Dict = new ConcurrentDictionary<uint, int>();
|
||||
public DateTime CreateTime = DateTime.UtcNow;
|
||||
|
||||
public ShardLocationCache(ConcurrentDictionary<uint, int> dict)
|
||||
{
|
||||
Dict = dict;
|
||||
}
|
||||
}
|
||||
public class JSONLotError
|
||||
{
|
||||
public string error;
|
||||
public JSONLotError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONLotsSmall
|
||||
{
|
||||
public List<JSONLotSmall> lots { get; set; }
|
||||
}
|
||||
public class JSONLots
|
||||
{
|
||||
public List<JSONLot> lots { get; set; }
|
||||
}
|
||||
public class JSONLotsPage
|
||||
{
|
||||
public int page { get; set; }
|
||||
public int total_pages { get; set; }
|
||||
public int total_lots { get; set; }
|
||||
public int lots_on_page { get; set; }
|
||||
public List<JSONLotSmall> lots { get; set; }
|
||||
}
|
||||
public class JSONLotsOnline
|
||||
{
|
||||
public int total_lots_online { get; set; }
|
||||
public int total_avatars_in_lots_online { get; set; }
|
||||
public List<JSONLotSmall> lots { get; set; }
|
||||
}
|
||||
public class JSONLotSmall
|
||||
{
|
||||
public int lot_id { get; set; }
|
||||
public uint location { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public LotCategory category { get; set; }
|
||||
public uint admit_mode { get; set; }
|
||||
public uint neighborhood_id { get; set; }
|
||||
public int avatars_in_lot { get; set; }
|
||||
}
|
||||
public class JSONLot
|
||||
{
|
||||
public int lot_id { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public uint? owner_id { get; set; }
|
||||
public uint[] roommates { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public uint location { get; set; }
|
||||
public uint neighborhood_id { get; set; }
|
||||
public uint created_date { get; set; }
|
||||
public LotCategory category { get; set; }
|
||||
public byte skill_mode { get; set; }
|
||||
public byte admit_mode { get; set; }
|
||||
}
|
||||
public class JSONTop100Lots
|
||||
{
|
||||
public List<JSONTop100Lot> lots { get; set; }
|
||||
}
|
||||
public class JSONTop100Lot
|
||||
{
|
||||
public LotCategory category { get; set; }
|
||||
public byte rank { get; set; }
|
||||
public int shard_id { get; set; }
|
||||
public string lot_name { get; set; }
|
||||
public uint? lot_location { get; set; }
|
||||
public int? lot_id { get; set; }
|
||||
}
|
||||
}
|
128
server/FSO.Server.Api.Core/Controllers/NhoodInfoController.cs
Executable file
128
server/FSO.Server.Api.Core/Controllers/NhoodInfoController.cs
Executable file
|
@ -0,0 +1,128 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[ApiController]
|
||||
public class NhoodInfoController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/neighborhoods/all")]
|
||||
public IActionResult GetAll(int shardId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var nhoods = da.Neighborhoods.All(shardId);
|
||||
if (nhoods == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhoods not found"));
|
||||
|
||||
List<JSONNhood> nhoodJson = new List<JSONNhood>();
|
||||
foreach (var nhood in nhoods)
|
||||
{
|
||||
nhoodJson.Add(new JSONNhood
|
||||
{
|
||||
neighborhood_id = nhood.neighborhood_id,
|
||||
name = nhood.name,
|
||||
description = nhood.description,
|
||||
color = nhood.color,
|
||||
town_hall_id = nhood.town_hall_id,
|
||||
icon_url = nhood.icon_url,
|
||||
mayor_id = nhood.mayor_id,
|
||||
mayor_elected_date = nhood.mayor_elected_date,
|
||||
election_cycle_id = nhood.election_cycle_id
|
||||
});
|
||||
|
||||
}
|
||||
var nhoodsJson = new JSONNhoods();
|
||||
nhoodsJson.neighborhoods = nhoodJson;
|
||||
return ApiResponse.Json(HttpStatusCode.OK, nhoodsJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/neighborhoods/{nhoodId}")]
|
||||
public IActionResult GetByID(uint nhoodId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var nhood = da.Neighborhoods.Get(nhoodId);
|
||||
if (nhood == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhood not found"));
|
||||
|
||||
var nhoodJson = new JSONNhood
|
||||
{
|
||||
neighborhood_id = nhood.neighborhood_id,
|
||||
name = nhood.name,
|
||||
description = nhood.description,
|
||||
color = nhood.color,
|
||||
town_hall_id = nhood.town_hall_id,
|
||||
icon_url = nhood.icon_url,
|
||||
mayor_id = nhood.mayor_id,
|
||||
mayor_elected_date = nhood.mayor_elected_date,
|
||||
election_cycle_id = nhood.election_cycle_id
|
||||
};
|
||||
return ApiResponse.Json(HttpStatusCode.OK, nhoodJson);
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("userapi/city/{shardId}/neighborhoods/name/{nhoodName}")]
|
||||
public IActionResult GetByName(int shardId, string nhoodName)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var searchNhood = da.Neighborhoods.SearchExact(shardId, nhoodName, 1).FirstOrDefault();
|
||||
if (searchNhood == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhood not found"));
|
||||
|
||||
var nhood = da.Neighborhoods.Get((uint)searchNhood.neighborhood_id);
|
||||
if (nhood == null) return ApiResponse.Json(HttpStatusCode.NotFound, new JSONNhoodError("Neighborhood not found"));
|
||||
|
||||
var nhoodJson = new JSONNhood
|
||||
{
|
||||
neighborhood_id = nhood.neighborhood_id,
|
||||
name = nhood.name,
|
||||
description = nhood.description,
|
||||
color = nhood.color,
|
||||
town_hall_id = nhood.town_hall_id,
|
||||
icon_url = nhood.icon_url,
|
||||
mayor_id = nhood.mayor_id,
|
||||
mayor_elected_date = nhood.mayor_elected_date,
|
||||
election_cycle_id = nhood.election_cycle_id
|
||||
};
|
||||
return ApiResponse.Json(HttpStatusCode.OK, nhoodJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class JSONNhoodError
|
||||
{
|
||||
public string error;
|
||||
public JSONNhoodError(string errorString)
|
||||
{
|
||||
error = errorString;
|
||||
}
|
||||
}
|
||||
public class JSONNhoods
|
||||
{
|
||||
public List<JSONNhood> neighborhoods { get; set; }
|
||||
}
|
||||
public class JSONNhood
|
||||
{
|
||||
public int neighborhood_id { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public uint color { get; set; }
|
||||
public int? town_hall_id { get; set; }
|
||||
public string icon_url { get; set; }
|
||||
public uint? mayor_id { get; set; }
|
||||
public uint mayor_elected_date { get; set; }
|
||||
public uint? election_cycle_id { get; set; }
|
||||
|
||||
}
|
||||
}
|
616
server/FSO.Server.Api.Core/Controllers/RegistrationController.cs
Executable file
616
server/FSO.Server.Api.Core/Controllers/RegistrationController.cs
Executable file
|
@ -0,0 +1,616 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.EmailConfirmation;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Controller for user registrations.
|
||||
/// Supports email confirmation if enabled in config.json.
|
||||
/// </summary>
|
||||
|
||||
[EnableCors]
|
||||
[Route("userapi/registration")]
|
||||
[ApiController]
|
||||
public class RegistrationController : ControllerBase
|
||||
{
|
||||
private const int REGISTER_THROTTLE_SECS = 60;
|
||||
private const int EMAIL_CONFIRMATION_EXPIRE = 2 * 60 * 60; // 2 hrs
|
||||
|
||||
/// <summary>
|
||||
/// Alphanumeric (lowercase), no whitespace or special chars, cannot start with an underscore.
|
||||
/// </summary>
|
||||
private static Regex USERNAME_VALIDATION = new Regex("^([a-z0-9]){1}([a-z0-9_]){2,23}$");
|
||||
|
||||
#region Registration
|
||||
[HttpPost]
|
||||
public IActionResult CreateUser([FromForm] RegistrationModel user)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
if(api.Config.SmtpEnabled)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "missing_confirmation_token"
|
||||
});
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
|
||||
user.username = user.username ?? "";
|
||||
user.username = user.username.ToLowerInvariant();
|
||||
user.email = user.email ?? "";
|
||||
//user.key = user.key ?? "";
|
||||
|
||||
string failReason = null;
|
||||
if (user.username.Length < 3) failReason = "user_short";
|
||||
else if (user.username.Length > 24) failReason = "user_long";
|
||||
else if (!USERNAME_VALIDATION.IsMatch(user.username ?? "")) failReason = "user_invalid";
|
||||
else if ((user.password?.Length ?? 0) == 0) failReason = "pass_required";
|
||||
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(user.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
failReason = "email_invalid";
|
||||
}
|
||||
|
||||
if (failReason != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "bad_request",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
if (!string.IsNullOrEmpty(api.Config.Regkey) && api.Config.Regkey != user.key)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "key_wrong",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
//has this ip been banned?
|
||||
var ban = da.Bans.GetByIP(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "ip_banned"
|
||||
});
|
||||
}
|
||||
|
||||
//has this user registered a new account too soon after their last?
|
||||
var now = Epoch.Now;
|
||||
var prev = da.Users.GetByRegisterIP(ip);
|
||||
if (now - (prev.FirstOrDefault()?.register_date ?? 0) < REGISTER_THROTTLE_SECS)
|
||||
{
|
||||
//cannot create a new account this soon.
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "registrations_too_frequent"
|
||||
});
|
||||
}
|
||||
|
||||
var userModel = api.CreateUser(user.username, user.email, user.password, ip);
|
||||
|
||||
if(userModel==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "user_exists"
|
||||
});
|
||||
} else {
|
||||
api.SendEmailConfirmationOKMail(user.username, user.email);
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a confirmation token and send email.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/registration/request")]
|
||||
public IActionResult CreateToken(ConfirmationCreateTokenModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
// smtp needs to be configured for this
|
||||
if(!api.Config.SmtpEnabled)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "smtp_disabled"
|
||||
});
|
||||
}
|
||||
|
||||
if(model.confirmation_url==null||model.email==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
// verify email syntax
|
||||
// To do: check if email address is disposable.
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(model.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "email_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
// email is taken
|
||||
if(da.Users.GetByEmail(model.email)!=null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "email_taken"
|
||||
});
|
||||
}
|
||||
|
||||
EmailConfirmation confirm = da.EmailConfirmations.GetByEmail(model.email, ConfirmationType.email);
|
||||
|
||||
// already waiting for confirmation
|
||||
if(confirm!=null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "confirmation_pending"
|
||||
});
|
||||
}
|
||||
|
||||
uint expires = Epoch.Now + EMAIL_CONFIRMATION_EXPIRE;
|
||||
|
||||
// create new email confirmation
|
||||
string token = da.EmailConfirmations.Create(new EmailConfirmation
|
||||
{
|
||||
type = ConfirmationType.email,
|
||||
email = model.email,
|
||||
expires = expires
|
||||
});
|
||||
|
||||
// send email with recently generated token
|
||||
bool sent = api.SendEmailConfirmationMail(model.email, token, model.confirmation_url, expires);
|
||||
|
||||
if(sent)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "email_failed"
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a user with a valid email confirmation token.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/registration/confirm")]
|
||||
public IActionResult CreateUserWithToken(RegistrationUseTokenModel user)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "invalid_token"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
EmailConfirmation confirmation = da.EmailConfirmations.GetByToken(user.token);
|
||||
|
||||
if(confirmation == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "invalid_token"
|
||||
});
|
||||
}
|
||||
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
|
||||
user.username = user.username ?? "";
|
||||
user.username = user.username.ToLowerInvariant();
|
||||
user.email = user.email ?? "";
|
||||
user.key = user.key ?? "";
|
||||
|
||||
string failReason = null;
|
||||
if (user.username.Length < 3) failReason = "user_short";
|
||||
else if (user.username.Length > 24) failReason = "user_long";
|
||||
else if (!USERNAME_VALIDATION.IsMatch(user.username ?? "")) failReason = "user_invalid";
|
||||
else if ((user.password?.Length ?? 0) == 0) failReason = "pass_required";
|
||||
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(user.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
failReason = "email_invalid";
|
||||
}
|
||||
|
||||
if (failReason != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "bad_request",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(api.Config.Regkey) && api.Config.Regkey != user.key)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "key_wrong",
|
||||
error_description = failReason
|
||||
});
|
||||
}
|
||||
|
||||
//has this ip been banned?
|
||||
var ban = da.Bans.GetByIP(ip);
|
||||
if (ban != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "ip_banned"
|
||||
});
|
||||
}
|
||||
|
||||
//has this user registered a new account too soon after their last?
|
||||
var prev = da.Users.GetByRegisterIP(ip);
|
||||
if (Epoch.Now - (prev.FirstOrDefault()?.register_date ?? 0) < REGISTER_THROTTLE_SECS)
|
||||
{
|
||||
//cannot create a new account this soon.
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "registrations_too_frequent"
|
||||
});
|
||||
}
|
||||
|
||||
//create user in db
|
||||
var userModel = api.CreateUser(user.username, user.email, user.password, ip);
|
||||
|
||||
if (userModel == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "user_exists"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//send OK email
|
||||
api.SendEmailConfirmationOKMail(user.username, user.email);
|
||||
da.EmailConfirmations.Remove(user.token);
|
||||
return ApiResponse.Json(HttpStatusCode.OK, userModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Password reset
|
||||
[HttpPost]
|
||||
[Route("userapi/password")]
|
||||
public IActionResult ChangePassword(PasswordResetModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if (api.Config.SmtpEnabled)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_confirmation_token"
|
||||
});
|
||||
}
|
||||
|
||||
// No empty fields
|
||||
if (model.username==null||model.new_password==null||model.old_password==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var user = da.Users.GetByUsername(model.username);
|
||||
|
||||
if(user==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "user_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
var authSettings = da.Users.GetAuthenticationSettings(user.user_id);
|
||||
var correct = PasswordHasher.Verify(model.old_password, new PasswordHash
|
||||
{
|
||||
data = authSettings.data,
|
||||
scheme = authSettings.scheme_class
|
||||
});
|
||||
|
||||
if (!correct)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "incorrect_password"
|
||||
});
|
||||
}
|
||||
|
||||
api.ChangePassword(user.user_id, model.new_password);
|
||||
api.SendPasswordResetOKMail(user.email, user.username);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets a user's password using a confirmation token.
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/password/confirm")]
|
||||
public IActionResult ConfirmPwd(PasswordResetUseTokenModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if(model.token==null||model.new_password==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
EmailConfirmation confirmation = da.EmailConfirmations.GetByToken(model.token);
|
||||
|
||||
if(confirmation==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "invalid_token"
|
||||
});
|
||||
}
|
||||
|
||||
var user = da.Users.GetByEmail(confirmation.email);
|
||||
|
||||
api.ChangePassword(user.user_id, model.new_password);
|
||||
api.SendPasswordResetOKMail(user.email, user.username);
|
||||
da.EmailConfirmations.Remove(model.token);
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a password reset token and mails it to the user.
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("userapi/password/request")]
|
||||
public IActionResult CreatePwdToken(ConfirmationCreateTokenModel model)
|
||||
{
|
||||
Api api = Api.INSTANCE;
|
||||
|
||||
if (model.confirmation_url == null || model.email == null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "missing_fields"
|
||||
});
|
||||
}
|
||||
|
||||
// verify email syntax
|
||||
// To do: check if email address is disposable.
|
||||
try
|
||||
{
|
||||
var addr = new System.Net.Mail.MailAddress(model.email);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "email_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
|
||||
var user = da.Users.GetByEmail(model.email);
|
||||
|
||||
if(user==null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "password_reset_failed",
|
||||
error_description = "email_invalid"
|
||||
});
|
||||
}
|
||||
|
||||
EmailConfirmation confirm = da.EmailConfirmations.GetByEmail(model.email, ConfirmationType.password);
|
||||
|
||||
// already awaiting a confirmation
|
||||
// to-do: resend?
|
||||
if (confirm != null)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new RegistrationError()
|
||||
{
|
||||
error = "registration_failed",
|
||||
error_description = "confirmation_pending"
|
||||
});
|
||||
}
|
||||
|
||||
uint expires = Epoch.Now + EMAIL_CONFIRMATION_EXPIRE;
|
||||
|
||||
// create new email confirmation
|
||||
string token = da.EmailConfirmations.Create(new EmailConfirmation
|
||||
{
|
||||
type = ConfirmationType.password,
|
||||
email = model.email,
|
||||
expires = expires
|
||||
});
|
||||
|
||||
// send confirmation email with generated token
|
||||
bool sent = api.SendPasswordResetMail(model.email, user.username, token, model.confirmation_url, expires);
|
||||
|
||||
if (sent)
|
||||
{
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
status = "success"
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResponse.Json(HttpStatusCode.OK, new
|
||||
{
|
||||
// success but email shitfaced
|
||||
status = "email_failed"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Models
|
||||
public class RegistrationError
|
||||
{
|
||||
public string error_description { get; set; }
|
||||
public string error { get; set; }
|
||||
}
|
||||
|
||||
public class RegistrationModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string email { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expected request data when trying to create a token to register.
|
||||
/// </summary>
|
||||
public class ConfirmationCreateTokenModel
|
||||
{
|
||||
public string email { get; set; }
|
||||
/// <summary>
|
||||
/// The link the user will have to go to in order to confirm their token.
|
||||
/// If %token% is present in the url, it will be replaced with the user's token.
|
||||
/// </summary>
|
||||
public string confirmation_url { get; set; }
|
||||
}
|
||||
|
||||
public class PasswordResetModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string old_password { get; set; }
|
||||
public string new_password { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expected request data when trying to register with a token.
|
||||
/// </summary>
|
||||
public class RegistrationUseTokenModel
|
||||
{
|
||||
public string username { get; set; }
|
||||
/// <summary>
|
||||
/// User email.
|
||||
/// </summary>
|
||||
public string email { get; set; }
|
||||
/// <summary>
|
||||
/// User password.
|
||||
/// </summary>
|
||||
public string password { get; set; }
|
||||
/// <summary>
|
||||
/// Registration key.
|
||||
/// </summary>
|
||||
public string key { get; set; }
|
||||
/// <summary>
|
||||
/// The unique GUID.
|
||||
/// </summary>
|
||||
public string token { get; set; }
|
||||
}
|
||||
|
||||
public class PasswordResetUseTokenModel
|
||||
{
|
||||
public string token { get; set; }
|
||||
public string new_password { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
98
server/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs
Executable file
98
server/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs
Executable file
|
@ -0,0 +1,98 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Database.DA.Shards;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("cityselector/app/ShardSelectorServlet")]
|
||||
[ApiController]
|
||||
public class ShardSelectorController : ControllerBase
|
||||
{
|
||||
private static Func<IActionResult> ERROR_SHARD_NOT_FOUND = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("503", "Shard not found"));
|
||||
private static Func<IActionResult> ERROR_AVATAR_NOT_FOUND = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("504", "Avatar not found"));
|
||||
private static Func<IActionResult> ERROR_AVATAR_NOT_YOURS = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("505", "You do not own this avatar!"));
|
||||
private static Func<IActionResult> ERROR_BANNED = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("506", "Your account has been banned."));
|
||||
private static Func<IActionResult> ERROR_MAINTAINANCE = ApiResponse.XmlFuture(HttpStatusCode.OK, new XMLErrorMessage("507", "The server is currently undergoing maintainance. Please try again later."));
|
||||
|
||||
public IActionResult Get(string shardName, string avatarId)
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var user = api.RequireAuthentication(Request);
|
||||
if (avatarId == null){
|
||||
//Using 0 to mean no avatar for CAS
|
||||
avatarId = "0";
|
||||
}
|
||||
|
||||
using (var db = api.DAFactory.Get())
|
||||
{
|
||||
ShardStatusItem shard = api.Shards.GetByName(shardName);
|
||||
if (shard != null)
|
||||
{
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
uint avatarDBID = uint.Parse(avatarId);
|
||||
|
||||
if (avatarDBID != 0)
|
||||
{
|
||||
var avatar = db.Avatars.Get(avatarDBID);
|
||||
if (avatar == null)
|
||||
{
|
||||
//can't join server with an avatar that doesn't exist
|
||||
return ERROR_AVATAR_NOT_FOUND();
|
||||
}
|
||||
if (avatar.user_id != user.UserID || avatar.shard_id != shard.Id)
|
||||
{
|
||||
//make sure we own the avatar we're trying to connect with
|
||||
return ERROR_AVATAR_NOT_YOURS();
|
||||
}
|
||||
}
|
||||
|
||||
var ban = db.Bans.GetByIP(ip);
|
||||
var dbuser = db.Users.GetById(user.UserID);
|
||||
if (dbuser == null || ban != null || dbuser.is_banned != false)
|
||||
{
|
||||
return ERROR_BANNED();
|
||||
}
|
||||
|
||||
if (api.Config.Maintainance && !(dbuser.is_admin || dbuser.is_moderator))
|
||||
{
|
||||
return ERROR_MAINTAINANCE();
|
||||
}
|
||||
|
||||
/** Make an auth ticket **/
|
||||
var ticket = new ShardTicket
|
||||
{
|
||||
ticket_id = Guid.NewGuid().ToString().Replace("-", ""),
|
||||
user_id = user.UserID,
|
||||
avatar_id = avatarDBID,
|
||||
date = Epoch.Now,
|
||||
ip = ip
|
||||
};
|
||||
|
||||
db.Users.UpdateConnectIP(ticket.user_id, ip);
|
||||
db.Shards.CreateTicket(ticket);
|
||||
|
||||
var result = new ShardSelectorServletResponse();
|
||||
result.PreAlpha = false;
|
||||
|
||||
result.Address = shard.PublicHost;
|
||||
result.PlayerID = user.UserID;
|
||||
result.AvatarID = avatarId;
|
||||
result.Ticket = ticket.ticket_id;
|
||||
result.ConnectionID = ticket.ticket_id;
|
||||
result.AvatarID = avatarId;
|
||||
|
||||
return ApiResponse.Xml(HttpStatusCode.OK, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ERROR_SHARD_NOT_FOUND();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
server/FSO.Server.Api.Core/Controllers/ShardStatusController.cs
Executable file
29
server/FSO.Server.Api.Core/Controllers/ShardStatusController.cs
Executable file
|
@ -0,0 +1,29 @@
|
|||
using FSO.Common.Utils;
|
||||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Protocol.CitySelector;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[EnableCors]
|
||||
[Route("cityselector/shard-status.jsp")]
|
||||
[ApiController]
|
||||
public class ShardStatusController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
var api = Api.INSTANCE;
|
||||
|
||||
var result = new XMLList<ShardStatusItem>("Shard-Status-List");
|
||||
var shards = api.Shards.All;
|
||||
foreach (var shard in shards)
|
||||
{
|
||||
result.Add(shard);
|
||||
}
|
||||
return ApiResponse.Xml(HttpStatusCode.OK, result);
|
||||
}
|
||||
}
|
||||
}
|
122
server/FSO.Server.Api.Core/Controllers/UserOAuthController.cs
Executable file
122
server/FSO.Server.Api.Core/Controllers/UserOAuthController.cs
Executable file
|
@ -0,0 +1,122 @@
|
|||
using FSO.Server.Api.Core.Utils;
|
||||
using FSO.Server.Common;
|
||||
using FSO.Server.Servers.Api.JsonWebToken;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Server.Api.Core.Controllers
|
||||
{
|
||||
[Route("userapi/oauth/token")]
|
||||
[ApiController]
|
||||
public class UserOAuthController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult CreateToken([FromForm] UserOAuthRequest userAuthRequest)
|
||||
{
|
||||
if (userAuthRequest == null) BadRequest();
|
||||
var api = Api.INSTANCE;
|
||||
using (var da = api.DAFactory.Get())
|
||||
{
|
||||
var user = da.Users.GetByUsername(userAuthRequest.username);
|
||||
if (user == null || user.is_banned) return ApiResponse.Json(System.Net.HttpStatusCode.Unauthorized, new UserOAuthError("unauthorized_client", "user_credentials_invalid"));
|
||||
var ip = ApiUtils.GetIP(Request);
|
||||
var hashSettings = da.Users.GetAuthenticationSettings(user.user_id);
|
||||
var isPasswordCorrect = PasswordHasher.Verify(userAuthRequest.password, new PasswordHash
|
||||
{
|
||||
data = hashSettings.data,
|
||||
scheme = hashSettings.scheme_class
|
||||
});
|
||||
//check if account is locked due to failed attempts
|
||||
var accLock = da.Users.GetRemainingAuth(user.user_id, ip);
|
||||
if (accLock != null && (accLock.active || accLock.count >= AuthLoginController.LockAttempts) && accLock.expire_time > Epoch.Now)
|
||||
{
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new UserOAuthError("unauthorized_client", "account_locked"));
|
||||
}
|
||||
//if the password is incorrect and check if user failed muli times and set a time out till next try.
|
||||
if (!isPasswordCorrect)
|
||||
{
|
||||
var durations = AuthLoginController.LockDuration;
|
||||
var failDelay = 60 * durations[Math.Min(durations.Length - 1, da.Users.FailedConsecutive(user.user_id, ip))];
|
||||
if (accLock == null)
|
||||
{
|
||||
da.Users.NewFailedAuth(user.user_id, ip, (uint)failDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = da.Users.FailedAuth(accLock.attempt_id, (uint)failDelay, AuthLoginController.LockAttempts);
|
||||
}
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new UserOAuthError("unauthorized_client", "user_credentials_invalid"));
|
||||
}
|
||||
|
||||
//user passed the password check, and now creates the claim/token
|
||||
da.Users.SuccessfulAuth(user.user_id, ip);
|
||||
var claims = new List<string>();
|
||||
|
||||
//set the permission level in the claim
|
||||
switch (userAuthRequest.permission_level)
|
||||
{
|
||||
case 1:
|
||||
claims.Add("userReadPermissions");
|
||||
break;
|
||||
case 2:
|
||||
claims.Add("userReadPermissions");
|
||||
claims.Add("userWritePermissions");
|
||||
break;
|
||||
case 3:
|
||||
claims.Add("userReadPermissions");
|
||||
claims.Add("userWritePermissions");
|
||||
claims.Add("userUpdatePermissions");
|
||||
break;
|
||||
case 4:
|
||||
claims.Add("userReadPermissions");
|
||||
claims.Add("userWritePermissions");
|
||||
claims.Add("userUpdatePermissions");
|
||||
claims.Add("userDeletePermissions");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//set the user identity
|
||||
JWTUser identity = new JWTUser
|
||||
{
|
||||
UserID = user.user_id,
|
||||
UserName = user.username,
|
||||
Claims = claims
|
||||
};
|
||||
|
||||
//generate the the tokenen and send it in a JSON format as response
|
||||
var generatedToken = api.JWT.CreateToken(identity);
|
||||
return ApiResponse.Json(System.Net.HttpStatusCode.OK, new UserOAuthSuccess
|
||||
{
|
||||
access_token = generatedToken.Token,
|
||||
expires_in = generatedToken.ExpiresIn
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public class UserOAuthRequest
|
||||
{
|
||||
public int permission_level { get; set; }
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
}
|
||||
public class UserOAuthError
|
||||
{
|
||||
public string error;
|
||||
public string error_description;
|
||||
public UserOAuthError(string errorString,string errorDescriptionString)
|
||||
{
|
||||
error = errorString;
|
||||
error_description = errorDescriptionString;
|
||||
}
|
||||
}
|
||||
public class UserOAuthSuccess
|
||||
{
|
||||
public string access_token { get; set; }
|
||||
public int expires_in { get; set; }
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue