Skip to content

Commit

Permalink
Handle user without Dawnshard identity in transition_by_n_account (#1139
Browse files Browse the repository at this point in the history
)

This has been spotted in the logs. Presumably this can happen for a new
account that immediately links a Nintendo account without first going to
/tool/signup.

Plus miscalleneous refactoring to bring more stuff into Features/
  • Loading branch information
SapiensAnatis authored Nov 1, 2024
1 parent 290835c commit bd774ee
Show file tree
Hide file tree
Showing 13 changed files with 94 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DragaliaAPI.Models;
using MessagePack;
using Microsoft.EntityFrameworkCore;
using static DragaliaAPI.Infrastructure.DragaliaHttpConstants;

namespace DragaliaAPI.Integration.Test.Features.Tool;

Expand All @@ -11,8 +12,6 @@ namespace DragaliaAPI.Integration.Test.Features.Tool;
/// </summary>
public class ToolTest : TestFixture
{
private const string IdTokenHeader = "ID-TOKEN";

public ToolTest(CustomWebApplicationFactory factory, ITestOutputHelper outputHelper)
: base(factory, outputHelper)
{
Expand Down Expand Up @@ -40,7 +39,7 @@ public async Task Auth_CorrectIdToken_ReturnsOKResponse(string endpoint)
DeviceAccountId,
DateTime.UtcNow + TimeSpan.FromMinutes(5)
);
this.Client.DefaultRequestHeaders.Add(IdTokenHeader, token);
this.Client.DefaultRequestHeaders.Add(Headers.IdToken, token);

ToolAuthResponse response = (
await this.Client.PostMsgpack<ToolAuthResponse>(endpoint)
Expand All @@ -58,7 +57,7 @@ public async Task Auth_NoAccount_CreatesNewUser(string endpoint)
$"new account {Guid.NewGuid()}",
DateTime.UtcNow + TimeSpan.FromMinutes(5)
);
this.Client.DefaultRequestHeaders.Add(IdTokenHeader, token);
this.Client.DefaultRequestHeaders.Add(Headers.IdToken, token);

ToolAuthResponse response = (
await this.Client.PostMsgpack<ToolAuthResponse>(endpoint)
Expand All @@ -81,7 +80,7 @@ public async Task Auth_PendingImport_ImportsSave()
savefileTime: DateTimeOffset.UtcNow
);

this.Client.DefaultRequestHeaders.Add(IdTokenHeader, token);
this.Client.DefaultRequestHeaders.Add(Headers.IdToken, token);

await this.Client.PostMsgpack<ToolAuthResponse>("tool/auth");

Expand All @@ -104,7 +103,7 @@ public async Task Auth_ExpiredIdToken_ReturnsRefreshRequest()
DateTime.UtcNow - TimeSpan.FromHours(5)
);

this.Client.DefaultRequestHeaders.Add(IdTokenHeader, token);
this.Client.DefaultRequestHeaders.Add(Headers.IdToken, token);
this.Client.DefaultRequestHeaders.Add("DeviceId", "id");

HttpResponseMessage response = await this.Client.PostMsgpackBasic(
Expand All @@ -128,7 +127,7 @@ public async Task Auth_RepeatedExpiredIdToken_ReturnsAuthError()
DateTime.UtcNow - TimeSpan.FromHours(5)
);

this.Client.DefaultRequestHeaders.Add(IdTokenHeader, token);
this.Client.DefaultRequestHeaders.Add(Headers.IdToken, token);
this.Client.DefaultRequestHeaders.Add("DeviceId", "id");

await this.Client.PostMsgpackBasic("/tool/auth", new ToolAuthRequest() { });
Expand Down Expand Up @@ -161,7 +160,7 @@ public async Task Auth_RepeatedExpiredIdToken_ReturnsAuthError()
public async Task Auth_InvalidIdToken_ReturnsIdTokenError(string endpoint)
{
string token = "im blue dabba dee dabba doo";
this.Client.DefaultRequestHeaders.Add(IdTokenHeader, token);
this.Client.DefaultRequestHeaders.Add(Headers.IdToken, token);

DragaliaResponse<ResultCodeResponse> response =
await this.Client.PostMsgpack<ResultCodeResponse>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using DragaliaAPI.Infrastructure;

namespace DragaliaAPI.Integration.Test.Features.Tool;

public class TransitionTest : TestFixture
{
public TransitionTest(CustomWebApplicationFactory factory, ITestOutputHelper testOutputHelper)
: base(factory, testOutputHelper) { }

[Fact]
public async Task Transition_CorrectIdToken_ReturnsOKResponse()
{
string token = TokenHelper.GetToken(
DeviceAccountId,
DateTime.UtcNow + TimeSpan.FromMinutes(5)
);
this.Client.DefaultRequestHeaders.Add(DragaliaHttpConstants.Headers.IdToken, token);

TransitionTransitionByNAccountResponse response = (
await this.Client.PostMsgpack<TransitionTransitionByNAccountResponse>(
"/transition/transition_by_n_account"
)
).Data;

response.TransitionResultData.LinkedViewerId.Should().Be((ulong)this.ViewerId);
response.TransitionResultData.AbolishedViewerId.Should().Be(0);
}

[Fact]
public async Task Transition_NewUser_CorrectIdToken_CreatesAccount_ReturnsOKResponse()
{
string token = TokenHelper.GetToken(
$"new account {Guid.NewGuid()}",
DateTime.UtcNow + TimeSpan.FromMinutes(5)
);
this.Client.DefaultRequestHeaders.Add(DragaliaHttpConstants.Headers.IdToken, token);

TransitionTransitionByNAccountResponse response = (
await this.Client.PostMsgpack<TransitionTransitionByNAccountResponse>(
"/transition/transition_by_n_account"
)
).Data;

response.TransitionResultData.LinkedViewerId.Should().Be((ulong)this.ViewerId + 1);
response.TransitionResultData.AbolishedViewerId.Should().Be(0);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DragaliaAPI.Models;
using DragaliaAPI.Features.Tool;
using DragaliaAPI.Models;
using DragaliaAPI.Models.Options;
using DragaliaAPI.Services.Game;
using DragaliaAPI.Shared;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using AutoMapper;
using DragaliaAPI.Database.Entities;
using DragaliaAPI.Database.Repositories;
using DragaliaAPI.Features.Tool;
using DragaliaAPI.Infrastructure;
using DragaliaAPI.Models.Generated;
using DragaliaAPI.Services;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq.Expressions;
using DragaliaAPI.Database;
using DragaliaAPI.Database.Entities;
using DragaliaAPI.Features.Tool;
using DragaliaAPI.Services;
using DragaliaAPI.Shared.PlayerDetails;
using EntityGraphQL.Schema;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using DragaliaAPI.Models;
using DragaliaAPI.Shared;
using DragaliaAPI.Services.Exceptions;

namespace DragaliaAPI.Services;
namespace DragaliaAPI.Features.Tool;

public interface ISessionService
{
Expand All @@ -25,15 +25,15 @@ DateTimeOffset loginTime
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <returns>The session object.</returns>
/// <exception cref="Exceptions.SessionException">A matching key was not found in the cache.</exception>
/// <exception cref="SessionException">A matching key was not found in the cache.</exception>
Task<Session> LoadSessionSessionId(string sessionId);

/// <summary>
/// Get a session from an id token.
/// </summary>
/// <param name="idToken">The ID token.</param>
/// <returns>The session object.</returns>
/// <exception cref="Exceptions.SessionException">A matching key was not found in the cache.</exception>
/// <exception cref="SessionException">A matching key was not found in the cache.</exception>
Task<Session> LoadSessionIdToken(string idToken);
Task StartUserImpersonation(string targetAccountId, long targetViewerId);
Task ClearUserImpersonation();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using DragaliaAPI.Models;
using DragaliaAPI.Models.Options;
using DragaliaAPI.Services;
using DragaliaAPI.Services.Exceptions;
using DragaliaAPI.Shared;
using DragaliaAPI.Shared.PlayerDetails;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;

namespace DragaliaAPI.Services.Game;
namespace DragaliaAPI.Features.Tool;

/// <summary>
/// SessionService interfaces with Redis to store the information about current sessions in-memory.
Expand All @@ -29,7 +29,9 @@ public class SessionService : ISessionService
private DistributedCacheEntryOptions CacheOptions =>
new()
{
SlidingExpiration = TimeSpan.FromMinutes(options.CurrentValue.SessionExpiryTimeMinutes),
SlidingExpiration = TimeSpan.FromMinutes(
this.options.CurrentValue.SessionExpiryTimeMinutes
),
};

public SessionService(
Expand Down Expand Up @@ -81,17 +83,17 @@ DateTimeOffset loginTime
}

// Register in sessions by id token (for reauth)
await cache.SetStringAsync(
await this.cache.SetStringAsync(
Schema.Session_IdToken(idToken),
JsonSerializer.Serialize(session),
CacheOptions
this.CacheOptions
);

// Register in sessions by session id (for all other endpoints)
await cache.SetStringAsync(
await this.cache.SetStringAsync(
Schema.Session_SessionId(session.SessionId),
JsonSerializer.Serialize(session),
CacheOptions
this.CacheOptions
);

return session.SessionId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using DragaliaAPI.Features.Tool;
using DragaliaAPI.Controllers;
using DragaliaAPI.Database.Entities;
using DragaliaAPI.Infrastructure.Authentication;
using DragaliaAPI.Models.Generated;
using DragaliaAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace DragaliaAPI.Controllers.Dragalia;
namespace DragaliaAPI.Features.Tool;

[Route("transition")]
internal sealed class TransitionController : DragaliaControllerBaseCore
Expand All @@ -19,10 +19,15 @@ public TransitionController(IAuthService authService)

[HttpPost("transition_by_n_account")]
[Authorize(AuthenticationSchemes = AuthConstants.SchemeNames.GameJwt)]
public async Task<DragaliaResult> TransitionByNAccount(
TransitionTransitionByNAccountRequest request
)
public async Task<DragaliaResult> TransitionByNAccount()
{
if (!this.User.HasDawnshardIdentity())
{
// This too can apparently be called before /tool/signup
DbPlayer player = await this.authService.DoSignup(this.User);
this.User.InitializeDawnshardIdentity(player.AccountId, player.ViewerId);
}

(long viewerId, _) = await this.authService.DoLogin(this.User);

return this.Ok(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ public static Task OnMessageReceived(MessageReceivedContext context)
{
// Use ID-TOKEN from header rather than id_token in body - the header is updated
// on refreshes and generally seems to be the more accurate source of truth
if (context.Request.Headers.TryGetValue("ID-TOKEN", out StringValues idToken))
if (
context.Request.Headers.TryGetValue(
DragaliaHttpConstants.Headers.IdToken,
out StringValues idToken
)
)
{
context.Token = idToken.FirstOrDefault();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using DragaliaAPI.Database;
using DragaliaAPI.Features.Tool;
using DragaliaAPI.Models;
using DragaliaAPI.Services;
using DragaliaAPI.Services.Exceptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public static class RoutePrefixes

public static class Headers
{
/// <summary>
/// Header containing the JWT from BaaS.
/// </summary>
public const string IdToken = "ID-TOKEN";

/// <summary>
/// Header containing the current session ID.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ public static async Task HandleAsync(HttpContext context)
? dragaliaException.Code
: ResultCode.CommonServerError;

logger.LogError(
exception,
"Encountered unhandled exception. Returning result_code {Code}",
code
);

await context.WriteResultCodeResponse(code);
}
}
Expand Down

0 comments on commit bd774ee

Please sign in to comment.