using System; using System.Collections.Generic; using System.Net; using System.Text; using System.Text.RegularExpressions; using JetBrains.Annotations; using Utf8Json; using Webserver.Permissions; using Webserver.UrlHandlers; namespace Webserver.WebAPI.APIs.Permissions { [UsedImplicitly] public class RegisterUser : AbsRestApi { private static readonly byte[] jsonPlayerNameKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("playerName"); private static readonly byte[] jsonExpirationKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("expirationSeconds"); // TODO: Rate-limiting private static readonly Regex userValidationRegex = new Regex ("^\\w{4,16}$", RegexOptions.ECMAScript | RegexOptions.Compiled); private static readonly Regex passValidationRegex = new Regex ("^\\w{4,16}$", RegexOptions.ECMAScript | RegexOptions.Compiled); public RegisterUser (Web _parentWeb) : base (_parentWeb) { } protected override void HandleRestGet (RequestContext _context) { string token = _context.RequestPath; if (string.IsNullOrEmpty (token)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, null, "NO_TOKEN"); return; } if (!UserRegistrationTokens.TryValidate (token, out UserRegistrationTokens.RegistrationData regData)) { SendEmptyResponse (_context, HttpStatusCode.NotFound, null, "INVALID_OR_EXPIRED_TOKEN"); return; } PrepareEnvelopedResult (out JsonWriter writer); writer.WriteRaw (jsonPlayerNameKey); writer.WriteString (regData.PlayerName); writer.WriteRaw (jsonExpirationKey); writer.WriteDouble ((regData.ExpiryTime - DateTime.Now).TotalSeconds); writer.WriteEndObject (); SendEnvelopedResult (_context, ref writer); } protected override void HandleRestPost (RequestContext _context, IDictionary _jsonInput, byte[] _jsonInputData) { if (!JsonCommons.TryGetJsonField (_jsonInput, "token", out string token)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_TOKEN"); return; } if (!JsonCommons.TryGetJsonField (_jsonInput, "username", out string username)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_USERNAME"); return; } if (!JsonCommons.TryGetJsonField (_jsonInput, "password", out string password)) { SendEmptyResponse (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_PASSWORD"); return; } if (!UserRegistrationTokens.TryValidate (token, out UserRegistrationTokens.RegistrationData regData)) { SendEmptyResponse (_context, HttpStatusCode.Unauthorized, null, "INVALID_OR_EXPIRED_TOKEN"); return; } if (!userValidationRegex.IsMatch (username)) { SendEmptyResponse (_context, HttpStatusCode.Unauthorized, _jsonInputData, "INVALID_USERNAME"); return; } if (!passValidationRegex.IsMatch (password)) { SendEmptyResponse (_context, HttpStatusCode.Unauthorized, _jsonInputData, "INVALID_PASSWORD"); return; } if (AdminWebUsers.Instance.GetUsers ().TryGetValue (username, out AdminWebUsers.WebUser existingMapping)) { // Username already exists if (!PlatformUserIdentifierAbs.Equals (existingMapping.PlatformUser, regData.PlatformUserId) || !PlatformUserIdentifierAbs.Equals (existingMapping.CrossPlatformUser, regData.CrossPlatformUserId)) { // Username already in use by another player SendEmptyResponse (_context, HttpStatusCode.Unauthorized, _jsonInputData, "DUPLICATE_USERNAME"); return; } // Username used by the same player, allow overwriting his existing login } // Log info string crossplatformidString = regData.CrossPlatformUserId == null ? "" : $", crossplatform ID {regData.CrossPlatformUserId.CombinedString}"; global::Log.Out ($"[Web] User registered: Username '{username}' for platform ID {regData.PlatformUserId.CombinedString}{crossplatformidString}"); if (AdminWebUsers.Instance.HasUser (regData.PlatformUserId, regData.CrossPlatformUserId, out AdminWebUsers.WebUser existingUser)) { // Remove existing username of player, only allowing one user per player global::Log.Out ($"[Web] Re-registration, replacing existing username '{existingUser.Name}'"); AdminWebUsers.Instance.RemoveUser (existingUser.Name); } // Add new user AdminWebUsers.Instance.AddUser (username, password, regData.PlatformUserId, regData.CrossPlatformUserId); // Login with new user and return response string remoteEndpointString = _context.Request.RemoteEndPoint!.ToString (); SessionHandler.HandleUserIdLogin (ParentWeb.ConnectionHandler, _context, remoteEndpointString, SessionHandler.userPassLoginName, username, regData.PlatformUserId, regData.CrossPlatformUserId); _context.Response.StatusCode = (int)HttpStatusCode.Created; _context.Response.ContentType = WebUtils.MimePlain; _context.Response.ContentEncoding = Encoding.UTF8; _context.Response.ContentLength64 = 0; } public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest; } }