mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
put back stubbed meta auth for sideloaded apks
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
|||||||
CachedLogin,
|
CachedLogin,
|
||||||
ChangePasswordRequest,
|
ChangePasswordRequest,
|
||||||
ChangePasswordResponse,
|
ChangePasswordResponse,
|
||||||
|
FakeCachedLogin,
|
||||||
form,
|
form,
|
||||||
json,
|
json,
|
||||||
OAuthError,
|
OAuthError,
|
||||||
@@ -57,6 +58,19 @@ import type { PlatformLink } from './platform-db'
|
|||||||
const TOKEN_SCOPE =
|
const TOKEN_SCOPE =
|
||||||
'offline_access profile rn rn.accounts rn.accounts.gc rn.api rn.chat rn.clubs rn.commerce rn.match.read rn.match.write rn.notify rn.rooms rn.storage'
|
'offline_access profile rn rn.accounts rn.accounts.gc rn.api rn.chat rn.clubs rn.commerce rn.match.read rn.match.write rn.notify rn.rooms rn.storage'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The canned entry served for the one Oculus cached-login lookup below — the sideloaded
|
||||||
|
* APK's way onto the password login screen. Not backed by a link, an account or a
|
||||||
|
* platform proof, hence `requirePassword: true`.
|
||||||
|
*/
|
||||||
|
const FAKE_OCULUS_CACHED_LOGIN = {
|
||||||
|
platform: PlatformType.Oculus,
|
||||||
|
platformId: '1',
|
||||||
|
accountId: 1,
|
||||||
|
lastLoginTime: '2026-07-19T17:13:29.225Z',
|
||||||
|
requirePassword: true,
|
||||||
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signup caps, enforced on create_account only (never on login — an existing account
|
* Signup caps, enforced on create_account only (never on login — an existing account
|
||||||
* always stays reachable, however many accounts its owner has since accumulated).
|
* always stays reachable, however many accounts its owner has since accumulated).
|
||||||
@@ -361,6 +375,10 @@ const app = new Hono<App>()
|
|||||||
'`cached_login` grant (both read the same table). An account linked to several',
|
'`cached_login` grant (both read the same table). An account linked to several',
|
||||||
'platforms appears in each of their pickers. An unknown id yields `[]` (not a 404)',
|
'platforms appears in each of their pickers. An unknown id yields `[]` (not a 404)',
|
||||||
'and the client falls back to a fresh login or create_account.',
|
'and the client falls back to a fresh login or create_account.',
|
||||||
|
'EXCEPT the exact identity `1/1` (Oculus, id `1`), which is stubbed for SIDELOADED',
|
||||||
|
'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.',
|
||||||
].join(' '),
|
].join(' '),
|
||||||
parameters: [
|
parameters: [
|
||||||
{
|
{
|
||||||
@@ -379,13 +397,30 @@ const app = new Hono<App>()
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
200: json(CachedLogin.array(), 'Matching accounts; `[]` if none'),
|
200: json(
|
||||||
|
CachedLogin.or(FakeCachedLogin).array(),
|
||||||
|
'Matching accounts; `[]` if none. The canned entry for `1/1`.'
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { platform, id } = c.req.param()
|
const { platform, id } = c.req.param()
|
||||||
logger.info('cached login lookup', { platform, id })
|
logger.info('cached login lookup', { platform, id })
|
||||||
const platformInt = Number.parseInt(platform, 10)
|
const platformInt = Number.parseInt(platform, 10)
|
||||||
|
// SIDELOADED APKs ONLY. A sideloaded build has no Meta SDK behind it, so it can't
|
||||||
|
// produce a real Meta identity or a nonce to prove one with — it asks about the
|
||||||
|
// placeholder identity `1/1`, and an empty picker leaves it stuck on the platform
|
||||||
|
// login screen with nothing to do. Hand back one canned entry to push it onto the
|
||||||
|
// username/password login instead, which is the only flow such a build can finish.
|
||||||
|
// `requirePassword` is true for exactly that reason: there's no platform proof here,
|
||||||
|
// and the `cached_login` grant would (correctly) refuse this entry.
|
||||||
|
//
|
||||||
|
// Scoped to that ONE identity rather than to all of platform 1 — store builds do
|
||||||
|
// real Meta logins, and shadowing the whole platform would hide genuine links from
|
||||||
|
// their pickers.
|
||||||
|
if (platformInt === PlatformType.Oculus && id === '1') {
|
||||||
|
return c.json([FAKE_OCULUS_CACHED_LOGIN])
|
||||||
|
}
|
||||||
// Listed straight from the link table, which is also what the `cached_login`
|
// Listed straight from the link table, which is also what the `cached_login`
|
||||||
// grant authorizes against — so the picker can't offer an account the grant
|
// grant authorizes against — so the picker can't offer an account the grant
|
||||||
// then refuses.
|
// then refuses.
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ export const CachedLogin = z.object({
|
|||||||
.describe('Always false — platform ownership is the credential for a cached login'),
|
.describe('Always false — platform ownership is the credential for a cached login'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stubbed Oculus cached login served to sideloaded APKs. Same shape as `CachedLogin`,
|
||||||
|
* but `requirePassword` is true — with no Meta SDK there is nothing to prove platform
|
||||||
|
* ownership with, so the client falls through to username/password.
|
||||||
|
*/
|
||||||
|
export const FakeCachedLogin = CachedLogin.extend({
|
||||||
|
requirePassword: z.literal(true).describe('Always true — the entry is not platform-backed'),
|
||||||
|
})
|
||||||
|
|
||||||
/** OAuth-shaped error body. Always HTTP 400 except `server_error` (500). */
|
/** OAuth-shaped error body. Always HTTP 400 except `server_error` (500). */
|
||||||
export const OAuthError = z.object({
|
export const OAuthError = z.object({
|
||||||
error: z.enum(['invalid_grant', 'invalid_request', 'server_error']),
|
error: z.enum(['invalid_grant', 'invalid_request', 'server_error']),
|
||||||
|
|||||||
@@ -198,6 +198,23 @@ describe('auth worker routes', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The one stubbed identity: `1/1` consults nothing and always answers the canned
|
||||||
|
// entry, which is how a sideloaded APK (no Meta SDK, so no real identity) gets off
|
||||||
|
// the platform login screen and onto username/password.
|
||||||
|
test('GET /cachedlogin/forplatformid/1/1 returns the canned Oculus entry', async () => {
|
||||||
|
const res = await exports.default.fetch(`${ORIGIN}/cachedlogin/forplatformid/1/1`)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.json()).toEqual([
|
||||||
|
{
|
||||||
|
platform: 1,
|
||||||
|
platformId: '1',
|
||||||
|
accountId: 1,
|
||||||
|
lastLoginTime: '2026-07-19T17:13:29.225Z',
|
||||||
|
requirePassword: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
// Only Steam (0) and Meta (1) can be verified — Steam by its signed platform_auth
|
// Only Steam (0) and Meta (1) can be verified — Steam by its signed platform_auth
|
||||||
// ticket, Meta by validating its nonce with Meta. Every OTHER platform is rejected
|
// ticket, Meta by validating its nonce with Meta. Every OTHER platform is rejected
|
||||||
// on the platform-authenticated grants: we won't bind or authorize an identity we
|
// on the platform-authenticated grants: we won't bind or authorize an identity we
|
||||||
|
|||||||
Reference in New Issue
Block a user