mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 22:51:30 -07:00
[auth][api] accept the 20250424.01 client
Version check now answers "current" for a set of builds rather than one: SUPPORTED_GAME_VERSIONS carries 20230414 and 20250424.01. GAME_VERSION is unchanged and still what the server reports for itself (presence, rn.ver). Adds GET /api/versioncheck/islandedversions, always [] — we never island a build off into its own matchmaking pool. The 2025 build POSTs /cachedlogin/forplatformid/:platform/:id with a deviceId/platformAuth/time form body where the 2023 build GETs it, so that route now takes both methods. The body is accepted and ignored for now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -434,7 +434,14 @@ const app = new Hono<App>()
|
||||
// id, so the client can offer them on the login screen (and post one back as a
|
||||
// cached_login grant). No linked account → [], and the client falls back to a
|
||||
// fresh login / create_account.
|
||||
.get(
|
||||
//
|
||||
// GET or POST: the 2023 build asks with a GET, the 2025 build (20250424.01) POSTs
|
||||
// the same path with a form body — `deviceId`, `platformAuth` (a JSON blob holding
|
||||
// the platform's session ticket and app id) and `time`. The body is READ BY NOTHING
|
||||
// here; both methods answer the same list off the path params, so a newer client
|
||||
// gets its picker. Verifying that ticket is the eventual point of the POST.
|
||||
.on(
|
||||
['GET', 'POST'],
|
||||
'/cachedlogin/forplatformid/:platform/:id',
|
||||
describeRoute({
|
||||
tags: ['Cached login'],
|
||||
@@ -449,6 +456,10 @@ const app = new Hono<App>()
|
||||
'APKs: with no Meta SDK they have no real identity to ask about and stall on an',
|
||||
'empty picker. It consults nothing and returns one canned, non-redeemable entry',
|
||||
'with `requirePassword: true`, sending the build to username/password login.',
|
||||
'Older clients GET this; the 20250424.01 build POSTs it with a',
|
||||
'`deviceId` / `platformAuth` / `time` form body attesting the platform session.',
|
||||
'That body is accepted and ignored — both methods answer identically from the',
|
||||
'path params.',
|
||||
].join(' '),
|
||||
parameters: [
|
||||
{
|
||||
|
||||
@@ -420,6 +420,59 @@ describe('auth worker routes', () => {
|
||||
])
|
||||
})
|
||||
|
||||
// The 20250424.01 build POSTs the picker lookup with a platform-attestation form body
|
||||
// instead of GETting it. Nothing reads that body yet, so both methods must answer the
|
||||
// same list — otherwise the newer client's login screen comes up empty.
|
||||
test('POST /cachedlogin/forplatformid answers exactly what the GET answers', async () => {
|
||||
const steamId = '76561197962463211'
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(
|
||||
JSON.stringify({
|
||||
accountId: 31381,
|
||||
username: 'SteamPlayer2025',
|
||||
platform: 0,
|
||||
platformId: steamId,
|
||||
lastLoginTime: '2026-08-13T04:21:34.768Z',
|
||||
})
|
||||
)
|
||||
.run()
|
||||
await linkPlatformIdentity(env.DB, 31381, 0, steamId)
|
||||
|
||||
// The body as the live client sends it: device id, the platform session ticket, a
|
||||
// timestamp. All ignored for now.
|
||||
const body = new URLSearchParams({
|
||||
deviceId: '69640e6ae1b54ae5b0ca8eeb4a8872ec6cf8fd88',
|
||||
platformAuth: JSON.stringify({ Ticket: '140000009C5F501B447424FF', AppId: '471710' }),
|
||||
time: '2026-08-13T04:21:34.7684754Z',
|
||||
}).toString()
|
||||
const posted = await exports.default.fetch(`${ORIGIN}/cachedlogin/forplatformid/0/${steamId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body,
|
||||
})
|
||||
expect(posted.status).toBe(200)
|
||||
const expected = [
|
||||
{
|
||||
platform: 0,
|
||||
platformId: steamId,
|
||||
accountId: 31381,
|
||||
lastLoginTime: '2026-08-13T04:21:34.768Z',
|
||||
requirePassword: false,
|
||||
},
|
||||
]
|
||||
expect(await posted.json()).toEqual(expected)
|
||||
expect(await cachedLogins(0, steamId)).toEqual(expected)
|
||||
})
|
||||
|
||||
// A POST with no body at all still resolves — the client's body is never consulted.
|
||||
test('POST /cachedlogin/forplatformid/1/1 still returns the canned Oculus entry', async () => {
|
||||
const res = await exports.default.fetch(`${ORIGIN}/cachedlogin/forplatformid/1/1`, {
|
||||
method: 'POST',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toMatchObject([{ accountId: 1, requirePassword: true }])
|
||||
})
|
||||
|
||||
test('one account, a Steam and a Meta identity: both pickers offer it', async () => {
|
||||
// The point of the link table. The same account is reachable from the PC and from
|
||||
// the headset, and each picker reports the identity IT was asked about — that's
|
||||
@@ -1122,6 +1175,7 @@ describe('auth worker routes', () => {
|
||||
'GET /role/developer/{id}',
|
||||
'GET /role/moderator/{id}',
|
||||
'POST /account/me/changepassword',
|
||||
'POST /cachedlogin/forplatformid/{platform}/{id}',
|
||||
'POST /cachedlogin/forplatformids',
|
||||
'POST /connect/token',
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user