mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[auth] record duid for now
This commit is contained in:
@@ -864,6 +864,74 @@ describe('auth worker routes', () => {
|
|||||||
expect(row!.data).not.toContain('2.0')
|
expect(row!.data).not.toContain('2.0')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// The grant the live client actually logs in with, and the one path whose device
|
||||||
|
// recording had no test — `create_account` and the password grant were both pinned,
|
||||||
|
// cached_login only happened to work. The body is the shape the 2025 client posts (see
|
||||||
|
// the capture the device id below comes from): device_id alongside the platform
|
||||||
|
// attestation, on every sign-in.
|
||||||
|
//
|
||||||
|
// Nothing verifies a device id — the client picks it — so it is recorded and never
|
||||||
|
// authorized on. It is stored for ban EVASION to read later: an account logging in from
|
||||||
|
// the same install as a banned one. See `getAccountsByDeviceId`.
|
||||||
|
test('POST /connect/token cached_login stores the login device on the account', async () => {
|
||||||
|
const metaId = '27061366730201234'
|
||||||
|
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||||
|
.bind(
|
||||||
|
JSON.stringify({
|
||||||
|
accountId: 5151,
|
||||||
|
username: 'CachedDevicePlayer',
|
||||||
|
platform: 1,
|
||||||
|
platformId: metaId,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.run()
|
||||||
|
await linkPlatformIdentity(env.DB, 5151, 1, metaId)
|
||||||
|
|
||||||
|
const deviceId = '69640e6ae1b54ae5b0ca8eeb4a8872ec6cf8fd88'
|
||||||
|
const res = await metaLogin(
|
||||||
|
`grant_type=cached_login&account_id=5151&platform=1&platform_id=${metaId}` +
|
||||||
|
`&platform_auth=${encodeURIComponent(metaPlatformAuth())}` +
|
||||||
|
`&device_id=${deviceId}&device_class=2&isInitialLogin=true&locale=en`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
|
||||||
|
const row = await env.DB.prepare('SELECT data FROM account WHERE account_id = ?1')
|
||||||
|
.bind(5151)
|
||||||
|
.first<{ data: string }>()
|
||||||
|
const account = JSON.parse(row!.data) as { deviceId: string; deviceClass: number }
|
||||||
|
expect(account.deviceId).toBe(deviceId)
|
||||||
|
expect(account.deviceClass).toBe(2)
|
||||||
|
// And findable by it, which is the whole reason it is kept.
|
||||||
|
expect((await getAccountsByDeviceId(env.DB, deviceId)).map((a) => a.accountId)).toContain(5151)
|
||||||
|
})
|
||||||
|
|
||||||
|
// A refresh is NOT a login: the client redeems a stored token and re-attests nothing,
|
||||||
|
// so there is no device to believe and the grant records none. Pinned because the
|
||||||
|
// alternative is worse than it looks — trusting a device_id posted alongside a refresh
|
||||||
|
// would let a client move an account's recorded device without ever proving anything,
|
||||||
|
// which is exactly the signal ban evasion is meant to read.
|
||||||
|
test('POST /connect/token refresh_token leaves the recorded device alone', async () => {
|
||||||
|
const first = await postToken(
|
||||||
|
`grant_type=password&username=Player77&password=${LOGIN_PASSWORD}` +
|
||||||
|
'&device_id=dev-77-real&device_class=2'
|
||||||
|
)
|
||||||
|
expect(first.status).toBe(200)
|
||||||
|
|
||||||
|
const refreshed = await postToken(
|
||||||
|
`grant_type=refresh_token&refresh_token=${first.json.refresh_token as string}` +
|
||||||
|
'&device_id=dev-77-spoofed&device_class=9'
|
||||||
|
)
|
||||||
|
expect(refreshed.status).toBe(200)
|
||||||
|
|
||||||
|
const row = await env.DB.prepare('SELECT data FROM account WHERE account_id = ?1')
|
||||||
|
.bind(77)
|
||||||
|
.first<{ data: string }>()
|
||||||
|
const account = JSON.parse(row!.data) as { deviceId: string; deviceClass: number }
|
||||||
|
expect(account.deviceId).toBe('dev-77-real')
|
||||||
|
expect(account.deviceClass).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
test('POST /connect/token refreshes the stored device on a credential login', async () => {
|
test('POST /connect/token refreshes the stored device on a credential login', async () => {
|
||||||
// Account 42 was seeded with no device; a later login records the one it came from.
|
// Account 42 was seeded with no device; a later login records the one it came from.
|
||||||
const res = await postToken(
|
const res = await postToken(
|
||||||
|
|||||||
@@ -67,10 +67,23 @@ export interface Account {
|
|||||||
/** ISO-8601 time of the account's most recent successful login. */
|
/** ISO-8601 time of the account's most recent successful login. */
|
||||||
lastLoginTime?: string
|
lastLoginTime?: string
|
||||||
/**
|
/**
|
||||||
* The client's `device_id` from its most recent login (a stable per-install hash
|
* The client's `device_id` from its most recent login — a stable per-install hash the
|
||||||
* the client sends on every /connect/token). Not a credential — the client picks
|
* client sends on every /connect/token, on the account-creating grant and on both
|
||||||
* it and nothing verifies it — so never authorize on it alone. Kept so accounts
|
* login grants (but NOT on a refresh, which re-attests nothing).
|
||||||
* sharing a device can be found later, e.g. for account linkup.
|
*
|
||||||
|
* Not a credential. The client picks it and nothing verifies it, so never authorize on
|
||||||
|
* it alone; it is a SIGNAL, recorded and read later.
|
||||||
|
*
|
||||||
|
* Kept for ban EVASION: an account logging in from the same install as a banned one is
|
||||||
|
* the sharpest of the linked arms after a proven platform identity, and much narrower
|
||||||
|
* than the IP arm (a household shares an address, not an install). Nothing consumes it
|
||||||
|
* yet — see `getAccountsByDeviceId`, and the arms in the api worker's bans-db, which
|
||||||
|
* this is not one of yet.
|
||||||
|
*
|
||||||
|
* ONE value, overwritten each login: this is the account's LAST-SEEN device, not a
|
||||||
|
* history of every device it has used. So evasion matching on it will catch a player
|
||||||
|
* who evades from the install they were last banned on, and miss one who has since
|
||||||
|
* logged the banned account in somewhere else.
|
||||||
*/
|
*/
|
||||||
deviceId?: string
|
deviceId?: string
|
||||||
/** DeviceClass int (2 = PC/standalone) that `deviceId` was last seen on. */
|
/** DeviceClass int (2 = PC/standalone) that `deviceId` was last seen on. */
|
||||||
|
|||||||
Reference in New Issue
Block a user