mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 22:51:30 -07:00
more stubs
This commit is contained in:
@@ -662,6 +662,19 @@ const app = new Hono<App>()
|
||||
typeof body.device_class === 'string' ? Number.parseInt(body.device_class, 10) : NaN
|
||||
const deviceClass = Number.isNaN(deviceClassInt) ? 0 : deviceClassInt
|
||||
|
||||
// The client's own build (`ver`, e.g. `20250718.01`), stamped into the token's
|
||||
// `rn.ver` claim so everything downstream reports the build the player is ACTUALLY
|
||||
// running rather than this server's GAME_VERSION — `match` reads it back off the
|
||||
// token when it writes presence. Unverified like the device fields, and only ever
|
||||
// echoed, never trusted for a decision (the version CHECK is `api`'s
|
||||
// `/api/versioncheck/v4`, against its own list).
|
||||
//
|
||||
// A grant that posts none — a refresh, or a caller that isn't the game — leaves it
|
||||
// undefined and generateToken falls back to GAME_VERSION. An empty string is
|
||||
// treated as absent for the same reason: presence must never carry an empty
|
||||
// version, which breaks the client's handling of it.
|
||||
const version = typeof body.ver === 'string' && body.ver !== '' ? body.ver : undefined
|
||||
|
||||
// The client's real IP, per Cloudflare (the client can't spoof CF-Connecting-IP —
|
||||
// the edge sets it — unlike X-Forwarded-For, which is why we don't read that).
|
||||
// Recorded as the immutable `signupIp` at creation and as `lastLoginIp` on every
|
||||
@@ -1033,7 +1046,8 @@ const app = new Hono<App>()
|
||||
platform,
|
||||
jwtSecret,
|
||||
accountRoles(roleAccount),
|
||||
accountPrivileges(roleAccount)
|
||||
accountPrivileges(roleAccount),
|
||||
version
|
||||
)
|
||||
// Issue a fresh, persisted refresh token (single-use; the client redeems it via
|
||||
// grant_type=refresh_token). A refresh grant thus rotates its token.
|
||||
|
||||
@@ -170,6 +170,14 @@ export const TokenRequest = z.object({
|
||||
.optional()
|
||||
.describe('Client-chosen, unverified. Recorded on the account, never trusted'),
|
||||
device_class: z.string().optional().describe('Integer string; defaults to 0'),
|
||||
ver: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The client’s build, e.g. `20250718.01`. Stamped into the token’s `rn.ver` claim and ' +
|
||||
'read back by `match` when it writes presence, so a player reports the build they ' +
|
||||
'are running. Absent (or empty) falls back to the server’s GAME_VERSION'
|
||||
),
|
||||
})
|
||||
|
||||
/** `POST /account/me/changepassword` form body. */
|
||||
|
||||
@@ -5,6 +5,7 @@ import { beforeAll, describe, expect, test } from 'vitest'
|
||||
import '../../auth.app'
|
||||
|
||||
import {
|
||||
GAME_VERSION,
|
||||
getAccountsByDeviceId,
|
||||
hashPassword,
|
||||
PRESENCE_SCHEMA_DDL,
|
||||
@@ -579,6 +580,25 @@ describe('auth worker routes', () => {
|
||||
expect(payload.scope).toContain('rn.api')
|
||||
})
|
||||
|
||||
// `rn.ver` is the CLIENT's build, from the `ver` it posts here — presence in `match`
|
||||
// reads it back off the token, so this is what a player is reported as running.
|
||||
test('POST /connect/token stamps the posted ver into rn.ver', async () => {
|
||||
const payload = await tokenFor(`account_id=42&password=${LOGIN_PASSWORD}&ver=20250718.01`)
|
||||
expect(payload['rn.ver']).toBe('20250718.01')
|
||||
})
|
||||
|
||||
// A grant that names no build — a refresh, or a caller that isn't the game — falls
|
||||
// back to the server's GAME_VERSION rather than stamping an empty claim, which would
|
||||
// leave presence carrying an empty version.
|
||||
test('POST /connect/token falls back to GAME_VERSION with no ver', async () => {
|
||||
expect((await tokenFor(`account_id=42&password=${LOGIN_PASSWORD}`))['rn.ver']).toBe(
|
||||
GAME_VERSION
|
||||
)
|
||||
expect((await tokenFor(`account_id=42&password=${LOGIN_PASSWORD}&ver=`))['rn.ver']).toBe(
|
||||
GAME_VERSION
|
||||
)
|
||||
})
|
||||
|
||||
test('POST /connect/token stamps developer/moderator roles into the token', async () => {
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(
|
||||
|
||||
Reference in New Issue
Block a user