CreateCharacterAsync([FromBody] RPGCharacterCreationRequest <TRaceType, TClassType> request, CancellationToken token = default) { //TODO: Fix GetAccountId API int accountId = ClaimsReader.GetAccountId <int>(User); if (!ModelState.IsValid) { return(Failure <RPGCharacterCreationResult, CharacterCreationResponseCode>(CharacterCreationResponseCode.GeneralError)); } //TODO: Add validation pipeline. if (String.IsNullOrWhiteSpace(request.Name)) { return(Failure <RPGCharacterCreationResult, CharacterCreationResponseCode>(CharacterCreationResponseCode.InvalidName)); } try { DBRPGCharacter character = await CharacterRepository.CreateCharacterAsync(accountId, request.Name, request.Race, request.ClassType, token); return(Success <RPGCharacterCreationResult, CharacterCreationResponseCode>(new RPGCharacterCreationResult(character.Id))); } catch (Exception e) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Failed to create Character: {request} Reason: {e}"); } return(Failure <RPGCharacterCreationResult, CharacterCreationResponseCode>(CharacterCreationResponseCode.GeneralError)); } }
public async Task <RPGCharacterData <TRaceType, TClassType>[]> RetrieveCharactersDataAsync(CancellationToken token = default) { //TODO: Fix GetAccountId API int accountId = ClaimsReader.GetAccountId <int>(User); return((await CharacterRepository .RetrieveOwnedCharactersAsync(accountId, token)) .Select(ConvertDbToTransit) .ToArray()); }
public async Task <int[]> RetrieveCharacterBasicListAsync(CancellationToken token = default) { //TODO: Properly handle failure and return correct response codes. int accountId = ClaimsReader.GetAccountId <int>(User); return((await CharacterRepository .RetrieveOwnedCharactersAsync(accountId, token)) .Select(d => d.Character.Id) .OrderBy(i => i) .ToArray()); }
public async Task <CharacterDataQueryResponseCode> CreateCharacterAppearanceAsync([FromRoute(Name = "id")] int characterId, [FromBody] RPGCharacterCustomizationData <TCustomizableSlotType, TColorStructureType, TProportionSlotType, TProportionStructureType> data, CancellationToken token = default) { if (!ModelState.IsValid) { return(CharacterDataQueryResponseCode.GeneralError); } int accountId = ClaimsReader.GetAccountId <int>(User); try { if (!await CharacterRepository.ContainsAsync(characterId, token)) { return(CharacterDataQueryResponseCode.CharacterDoesNotExist); } //Only allow users who own the character to create its appearance if (!await CharacterRepository.AccountOwnsCharacterAsync(accountId, characterId, token)) { return(CharacterDataQueryResponseCode.NotAuthorized); } //We scope the appearance persistence in a transaction because we don't want a HALF customized character. await using IDbContextTransaction transaction = await AppearanceRepository.CreateTransactionAsync(token); if (data.ProportionData.Count > 0) { await AppearanceRepository.CreateSlotsAsync(data.ProportionData.Select(p => new DBRPGCharacterProportionSlot <TProportionSlotType, TProportionStructureType>(characterId, p.Key, p.Value)).ToArray(), token); } if (data.SlotData.Count > 0) { await AppearanceRepository.CreateSlotsAsync(ConvertToCustomizableSlotData(characterId, data), token); } await transaction.CommitAsync(token); return(CharacterDataQueryResponseCode.Success); } catch (Exception e) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Failed to create character appearance for Character: {characterId} Account: {accountId}. Reason: {e}"); } return(CharacterDataQueryResponseCode.GeneralError); } }
private int RetrieveOwnershipId(ConfigurationSourceType source) { return(source == ConfigurationSourceType.Account ? ClaimsReader.GetAccountId <int>(User) : ClaimsReader.GetSubAccountId <int>(User)); }
public async Task <IActionResult> CreateZoneServerAccount([FromBody][JetBrains.Annotations.NotNull] ZoneServerAccountRegistrationRequest request) { if (request == null) { throw new ArgumentNullException(nameof(request)); } //We want to log this out for information purposes whenever an auth request begins if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Zone Register Request by UserAccount: {ClaimsReader.GetAccountName(this.User)}:{ClaimsReader.GetAccountIdInt(this.User)} {HttpContext.Connection.RemoteIpAddress}:{HttpContext.Connection.RemotePort}"); } //TODO: Create cryptographically secure random bytes for password and user. string zoneUserName = Guid.NewGuid().ToString(); string zonePassword = Guid.NewGuid().ToString(); ZoneServerApplicationUser user = new ZoneServerApplicationUser(ClaimsReader.GetAccountIdInt(this.User)) { UserName = zoneUserName, Email = "*****@*****.**", //TODO: Real email??? }; IdentityResult identityResult = await UserManager.CreateAsync(user, zonePassword); if (identityResult.Succeeded) { //Adds the vrgid account owner claim. await UserManager.AddClaimAsync(user, new Claim(GladMMOAuthConstants.ACCOUNT_ID_OWNER_CLAIM_NAME, ClaimsReader.GetAccountId(this.User))); //Here we inherit each role the user account has to the zoneserver account foreach (var claim in User.Claims) { if (claim.Type == "role") //TODO: Is there a constant for this?? { if (claim.Value.ToLower() != GladMMOAuthConstants.PLAYERACCOUNT_AUTHORIZATION_ROLE) //DO NOT add the player role. { await UserManager.AddToRoleAsync(user, claim.Value); } } } //Also add the ZoneServer role await UserManager.AddToRoleAsync(user, GladMMOAuthConstants.ZONESERVER_AUTHORIZATION_ROLE); //At this point, the account has the PlayFab id claim so it's ready for use. return(Json(new ZoneServerAccountRegistrationResponse(user.Id, zoneUserName, zonePassword))); } else { return(BadRequest(identityResult.Errors.Aggregate("", (s, error) => $"{s} {error.Code}:{error.Description}"))); } }
public async Task <IActionResult> TryRegisterZoneServer([FromBody] ZoneServerRegistrationRequest request, [FromServices][NotNull] IWorldDataServiceClient worldDataClient) { if (worldDataClient == null) { throw new ArgumentNullException(nameof(worldDataClient)); } if (!ModelState.IsValid) { return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.GeneralServerError)); } //This should really ever happen in normal circumstances. if (await ZoneRepository.ContainsAsync(ClaimsReader.GetAccountIdInt(User))) { if (Logger.IsEnabled(LogLevel.Warning)) { Logger.LogWarning($"UserId: {ClaimsReader.GetPlayerAccountId(User)} attempted to register ZoneId: {ClaimsReader.GetAccountId(User)} multiple times."); } return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.ZoneAlreadyRegistered)); } //Check world exists if (!await worldDataClient.CheckWorldExistsAsync(request.WorldId)) { return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.WorldRequestedNotFound)); } //TODO: Should we check world rights ownership here? bool registerResult = await ZoneRepository.TryCreateAsync(new ZoneInstanceEntryModel(ClaimsReader.GetAccountIdInt(User), HttpContext.Connection.RemoteIpAddress.ToString(), request.NetworkPort, request.WorldId)); if (!registerResult) { return(BuildFailedResponseModel(ZoneServerRegistrationResponseCode.GeneralServerError)); } return(BuildSuccessfulResponseModel(new ZoneServerRegistrationResponse(ClaimsReader.GetAccountIdInt(User)))); }
public async Task <IActionResult> RegisteredZoneCheckin([FromBody] ZoneServerCheckinRequestModel zoneCheckinRequest) { //This is ok, it means the zone was unregistered before the checkin message was recieved. //It doesn't alone indicate a failure. if (!await ZoneRepository.ContainsAsync(ClaimsReader.GetAccountIdInt(User))) { if (Logger.IsEnabled(LogLevel.Warning)) { Logger.LogWarning($"UserId: {ClaimsReader.GetPlayerAccountId(User)} attempted to checkin ZoneId: {ClaimsReader.GetAccountId(User)} but the zone was not registered."); } return(Ok()); } ZoneInstanceEntryModel model = await ZoneRepository.RetrieveAsync(ClaimsReader.GetAccountIdInt(User)); //We don't REMOVE if expired. This should only update checkin time //something else should be responsible for removal if expired at some point. model.UpdateCheckinTime(); await ZoneRepository.UpdateAsync(model.ZoneId, model); //We don't really have anything to reply to, this shouldn't be called externally. //It's basically handling a proxied message queue message. return(Ok()); }
public async Task <IActionResult> RequestContentDownloadURL( [FromRoute(Name = "id")] long contentId, [FromServices] ICustomContentRepository <TContentType> contentEntryRepository, [FromServices] IStorageUrlBuilder urlBuilder, [FromServices] IContentDownloadAuthroizationValidator downloadAuthorizer) { if (contentEntryRepository == null) { throw new ArgumentNullException(nameof(contentEntryRepository)); } //TODO: We want to rate limit access to this API //TODO: We should use both app logging but also another logging service that always gets hit //TODO: Consolidate this shared logic between controllers if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Recieved {nameof(RequestContentDownloadURL)} request from {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)}."); } //TODO: We should probably check the flags of content to see if it's private (IE hidden from user). Or if it's unlisted or removed. //It's possible a user is requesting a content that doesn't exist //Could be malicious or it could have been deleted for whatever reason if (!await contentEntryRepository.ContainsAsync(contentId).ConfigureAwaitFalse()) { return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.NoContentId))); } //TODO: Refactor this into a validation dependency //Now we need to do some validation to determine if they should even be downloading this content //we do not want people downloading a content they have no business of going to int userId = ClaimsReader.GetAccountIdInt(User); //TODO: Need to NOT call it world content if (!await downloadAuthorizer.CanUserAccessWorldContet(userId, contentId)) { return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.AuthorizationFailed))); } TContentType contentEntry = await contentEntryRepository.RetrieveAsync(contentId); //We can get the URL from the urlbuilder if we provide the content storage GUID string downloadUrl = await urlBuilder.BuildRetrivalUrl(ContentType, contentEntry.StorageGuid); //TODO: Should we be validating S3 availability? if (String.IsNullOrEmpty(downloadUrl)) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Failed to create content upload URL for {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)} with ID: {contentId}."); } return(Json(new ContentDownloadURLResponse(ContentDownloadURLResponseCode.ContentDownloadServiceUnavailable))); } if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Success. Sending {ClaimsReader.GetAccountName(User)} URL: {downloadUrl}"); } return(Json(new ContentDownloadURLResponse(downloadUrl, contentEntry.Version))); }
private async Task <IActionResult> GenerateUploadTokenResponse(IStorageUrlBuilder urlBuilder, TContentType content) { string uploadUrl = await urlBuilder.BuildUploadUrl(ContentType, content.StorageGuid); if (String.IsNullOrEmpty(uploadUrl)) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Failed to create content upload URL for {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)} with GUID: {content.StorageGuid}."); } return(BuildFailedResponseModel(ContentUploadResponseCode.ServiceUnavailable)); } if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Success. Sending {ClaimsReader.GetAccountName(User)} URL: {uploadUrl}"); } return(BuildSuccessfulResponseModel(new ContentUploadToken(uploadUrl, content.ContentId, content.StorageGuid))); }
public async Task <IActionResult> RequestContentUploadUrl( [FromServices] ICustomContentRepository <TContentType> contentEntryRepository, [FromServices] IStorageUrlBuilder urlBuilder) { if (contentEntryRepository == null) { throw new ArgumentNullException(nameof(contentEntryRepository)); } //TODO: We want to rate limit access to this API //TODO: We should use both app logging but also another logging service that always gets hit if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Recieved {nameof(RequestContentUploadUrl)} request from {ClaimsReader.GetAccountName(User)}:{ClaimsReader.GetAccountId(User)}."); } //TODO: Check if the result is valid? We should maybe return bool from this API (we do return bool from this API now) //The idea is to create an entry which will contain a GUID. From that GUID we can then generate the upload URL TContentType content = GenerateNewModel(); bool result = await contentEntryRepository.TryCreateAsync(content); return(await GenerateUploadTokenResponse(urlBuilder, content)); }
/// <inheritdoc /> public override async Task OnConnectedAsync() { await base.OnConnectedAsync() .ConfigureAwaitFalseVoid(); if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation($"Account Connected: {ClaimsReader.GetAccountName(Context.User)}:{ClaimsReader.GetAccountId(Context.User)} with SignalR UserId: {Context.UserIdentifier}"); } try { foreach (var listener in OnConnectionHubListeners) { HubOnConnectionState connectionState = await listener.OnConnected(this).ConfigureAwaitFalse(); //if the listener indicated we need to abort for whatever reason we //should believe it and just abort. if (connectionState == HubOnConnectionState.Abort) { Context.Abort(); break; } } } catch (Exception e) { if (Logger.IsEnabled(LogLevel.Error)) { Logger.LogError($"Account: {ClaimsReader.GetAccountName(Context.User)}:{ClaimsReader.GetAccountId(Context.User)} failed to properly connect to hub. Error: {e.ToString()}\n\nStack: {e.StackTrace}"); } Context.Abort(); } }