mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
feat: Add new API controllers for progression events, room consumables, currencies, earnings distributions, screen share reports, subscription seasons, and UGC purchasables
- Implemented ProgressionEventsController to handle progression event retrieval and active events. - Created RoomConsumablesController for listing and purchasing room consumables. - Developed RoomCurrenciesController for managing room currencies, including creation, updates, and balance retrieval. - Added RoomEarningsDistributionsController to fetch earnings distribution data for rooms. - Introduced ScreenShareReportsController for reporting screen share incidents. - Established SubscriptionSeasonsController to provide current subscription season details and rewards. - Launched UgcPurchasablesController for managing user-generated content purchasables, including bulk operations. - Added new data entities for customization and room economy, including custom avatar items, room currencies, and UGC purchasables.
This commit is contained in:
@@ -31,6 +31,42 @@ public class AuthController(
|
||||
if (lastSeen.Date < DateTime.UtcNow.Date)
|
||||
await level.AwardXpAsync(playerId, LevelService.FirstLoginXp, "first_login_today");
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> BuildTokenResponse(string accessToken, string refreshToken) => new()
|
||||
{
|
||||
["access_token"] = accessToken,
|
||||
["accessToken"] = accessToken,
|
||||
["AccessToken"] = accessToken,
|
||||
["token"] = accessToken,
|
||||
["Token"] = accessToken,
|
||||
["token_type"] = "Bearer",
|
||||
["tokenType"] = "Bearer",
|
||||
["TokenType"] = "Bearer",
|
||||
["expires_in"] = 43200,
|
||||
["expiresIn"] = 43200,
|
||||
["ExpiresIn"] = 43200,
|
||||
["refresh_token"] = refreshToken,
|
||||
["refreshToken"] = refreshToken,
|
||||
["RefreshToken"] = refreshToken,
|
||||
["scope"] = "openid profile",
|
||||
};
|
||||
|
||||
private IActionResult TokenOk(string accessToken, string refreshToken)
|
||||
{
|
||||
var cookie = new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = string.Equals(domain.Scheme, "https", StringComparison.OrdinalIgnoreCase),
|
||||
SameSite = SameSiteMode.None,
|
||||
Expires = DateTimeOffset.UtcNow.AddHours(12),
|
||||
IsEssential = true,
|
||||
};
|
||||
if (domain.Apex.Contains('.'))
|
||||
cookie.Domain = "." + domain.Apex;
|
||||
Response.Cookies.Append(AuthService.AccessCookieName, accessToken, cookie);
|
||||
return Ok(BuildTokenResponse(accessToken, refreshToken));
|
||||
}
|
||||
|
||||
// OAuth token endpoint. The 2020 client uses three grant types here:
|
||||
// - grant_type=cached_login → user clicked a remembered account on the
|
||||
// account-selection screen. Body includes `account_id` (and platform
|
||||
@@ -90,14 +126,7 @@ public class AuthController(
|
||||
await TryAwardLoginXpAsync(picked.Id);
|
||||
await playerService.TagPlatformAsync(picked.Id, platform, effPlatformId);
|
||||
var (acc1, refr1) = authService.GenerateTokenPair(picked.Id);
|
||||
return Ok(new
|
||||
{
|
||||
access_token = acc1,
|
||||
token_type = "Bearer",
|
||||
expires_in = 43200,
|
||||
refresh_token = refr1,
|
||||
scope = "openid profile",
|
||||
});
|
||||
return TokenOk(acc1, refr1);
|
||||
}
|
||||
|
||||
if (string.Equals(grant_type, "refresh_token", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -111,14 +140,7 @@ public class AuthController(
|
||||
if (existing is null)
|
||||
return Unauthorized(new { error = "invalid_grant" });
|
||||
var (acc, refr) = authService.GenerateTokenPair(existing.Id);
|
||||
return Ok(new
|
||||
{
|
||||
access_token = acc,
|
||||
token_type = "Bearer",
|
||||
expires_in = 43200,
|
||||
refresh_token = refr,
|
||||
scope = "openid profile",
|
||||
});
|
||||
return TokenOk(acc, refr);
|
||||
}
|
||||
|
||||
// password grant: two paths.
|
||||
@@ -150,14 +172,7 @@ public class AuthController(
|
||||
await playerService.TagPlatformAsync(byName.Id, platform, effPlatformId);
|
||||
await TryAwardLoginXpAsync(byName.Id);
|
||||
var pair = authService.GenerateTokenPair(byName.Id);
|
||||
return Ok(new
|
||||
{
|
||||
access_token = pair.AccessToken,
|
||||
token_type = "Bearer",
|
||||
expires_in = 43200,
|
||||
refresh_token = pair.RefreshToken,
|
||||
scope = "openid profile",
|
||||
});
|
||||
return TokenOk(pair.AccessToken, pair.RefreshToken);
|
||||
}
|
||||
|
||||
// Device-id fallback path. Prefer the real deviceId; fall back
|
||||
@@ -196,14 +211,7 @@ public class AuthController(
|
||||
await TryAwardLoginXpAsync(player.Id);
|
||||
var (access, refresh) = authService.GenerateTokenPair(player.Id);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
access_token = access,
|
||||
token_type = "Bearer",
|
||||
expires_in = 43200,
|
||||
refresh_token = refresh,
|
||||
scope = "openid profile",
|
||||
});
|
||||
return TokenOk(access, refresh);
|
||||
}
|
||||
|
||||
[HttpGet("/connect/userinfo")]
|
||||
@@ -307,6 +315,62 @@ public class AuthController(
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
[HttpGet("/cachedlogin/current")]
|
||||
public async Task<IActionResult> CurrentCachedLogin()
|
||||
{
|
||||
var auth = Request.Headers.Authorization.ToString();
|
||||
if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
return Unauthorized();
|
||||
var token = auth["Bearer ".Length..].Trim();
|
||||
var id = authService.ValidateToken(token);
|
||||
if (id is not long playerId) return Unauthorized();
|
||||
var player = await playerService.GetByIdAsync(playerId);
|
||||
if (player is null) return Unauthorized();
|
||||
return Ok(ToCachedLogin(player, player.LastPlatform, player.LastPlatformId));
|
||||
}
|
||||
|
||||
[HttpPost("/cachedlogin/migrate")]
|
||||
public async Task<IActionResult> MigrateCachedLogin(
|
||||
[FromForm] int platform = 0,
|
||||
[FromForm] string? platformId = null)
|
||||
{
|
||||
var auth = Request.Headers.Authorization.ToString();
|
||||
if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
return Unauthorized();
|
||||
var token = auth["Bearer ".Length..].Trim();
|
||||
var id = authService.ValidateToken(token);
|
||||
if (id is not long playerId) return Unauthorized();
|
||||
await playerService.TagPlatformAsync(playerId, platform, platformId);
|
||||
var player = await playerService.GetByIdAsync(playerId);
|
||||
return player is null
|
||||
? Unauthorized()
|
||||
: Ok(ToCachedLogin(player, platform, platformId ?? string.Empty));
|
||||
}
|
||||
|
||||
[HttpPost("/connect/deviceauthorization")]
|
||||
[HttpGet("/connect/deviceauthorization")]
|
||||
public async Task<IActionResult> DeviceAuthorization()
|
||||
{
|
||||
var deviceCode = Guid.NewGuid().ToString("N");
|
||||
var userCode = deviceCode[..8].ToUpperInvariant();
|
||||
db.PlayerSettings.Add(new PlayerSettingEntity
|
||||
{
|
||||
PlayerId = RelationshipQueries.SystemAccountId,
|
||||
Key = $"deviceauthorization:{deviceCode}",
|
||||
Value = $"{userCode}|{DateTime.UtcNow.AddMinutes(15):O}",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return Ok(new
|
||||
{
|
||||
device_code = deviceCode,
|
||||
user_code = userCode,
|
||||
verification_uri = $"https://{domain.Sub("auth")}/device",
|
||||
verification_uri_complete = $"https://{domain.Sub("auth")}/device?user_code={userCode}",
|
||||
expires_in = 900,
|
||||
interval = 5,
|
||||
});
|
||||
}
|
||||
|
||||
private static CachedLogin ToCachedLogin(PlayerEntity p, int platform, string platformId) => new()
|
||||
{
|
||||
Platform = platform,
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DorkNet.Server.Controllers.Auth;
|
||||
|
||||
/// <summary>The boot-time client-version gate the 2020.12 watch hits
|
||||
/// <summary>The boot-time client-version gate the 2023.03.21 client hits
|
||||
/// at startup as <c>GET api/versioncheck/v4?v={build}&p={platform}</c>.
|
||||
///
|
||||
/// <para>Verified against the watch's <c>KMDHPCHFADM.VerifyGameVersion</c>
|
||||
@@ -20,10 +20,9 @@ namespace DorkNet.Server.Controllers.Auth;
|
||||
/// front door — the watch has dedicated UX for it, no custom client
|
||||
/// mod work needed.</para>
|
||||
///
|
||||
/// <para>The <c>v</c> query param is the raw build identifier the watch
|
||||
/// reads from <c>KMJJGELKNAP.MCFMPCNNEBH</c> — for the December build
|
||||
/// that's the literal string <c>"20201210"</c>. We map raw build IDs
|
||||
/// to our internal <see cref="ClientVersion.Key"/>s via the
|
||||
/// <para>The <c>v</c> query param is the raw build identifier supplied
|
||||
/// by the client. We map raw build IDs to our internal
|
||||
/// <see cref="ClientVersion.Key"/>s via the
|
||||
/// <c>DorkNet:BuildIdToVersionKey</c> config map; any build ID we
|
||||
/// don't recognise OR that maps to a key not in
|
||||
/// <see cref="VersionRegistry.SupportedVersionKeys"/> gets
|
||||
@@ -49,7 +48,8 @@ public class VersionCheckController(
|
||||
private static readonly IReadOnlyDictionary<string, string> BuiltInBuildIdToVersionKey =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["20201210"] = "december_2020_12_18",
|
||||
["20230317"] = "march_2023_03_21",
|
||||
["20230321"] = "march_2023_03_21",
|
||||
};
|
||||
|
||||
[HttpGet("/api/versioncheck/v4")]
|
||||
@@ -66,8 +66,7 @@ public class VersionCheckController(
|
||||
// Map raw build ID → ClientVersion key via config. Format:
|
||||
// "DorkNet": {
|
||||
// "BuildIdToVersionKey": {
|
||||
// "20201210": "december_2020_12_18",
|
||||
// "20200310": "march_2020_03_10"
|
||||
// "20230321": "march_2023_03_21"
|
||||
// }
|
||||
// }
|
||||
// Mapping ABSENT → unknown build → UpdateRequired.
|
||||
|
||||
Reference in New Issue
Block a user