credential broker: Play tokens from POST /api/google/token (cached, single flight, local key fallback), grants and APIs per package in validate, Play write preflight, apps check and the desktop Accounts view; Google 403 names the API or grant, no retry

This commit is contained in:
CRTkafa 2026-10-25 22:50:52 +07:00
parent 304407e523
commit 2c5e30090f
27 changed files with 1145 additions and 43 deletions

View file

@ -9,6 +9,7 @@ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSyn
import { homedir } from 'node:os';
import { isAbsolute, join, resolve } from 'node:path';
import { maskUser, PROFILE_KEYS, PROFILE_RE, profileDir, profileEnv, profileNames } from './credentials';
import { brokerConfig, profileBrokerOff } from './broker';
export type Kind = 'apple' | 'play';
/** The store keys that move to a profile (one developer account's), never the app's own ones. */
@ -36,12 +37,19 @@ export function profileIdentity(name: string): Identity {
}
/** One line per profile for `storebound accounts`: stores, masked Play e-mail. No key ids, no values. */
export function describeProfiles(): { profile: string; apple: boolean; play: boolean; playAccount: string | null; vendor: boolean; playDeveloperId?: string | null; error?: string }[] {
export function describeProfiles(): { profile: string; apple: boolean; play: boolean; playAccount: string | null; vendor: boolean; playDeveloperId?: string | null;
/** Where Play tokens come from: the credential broker (~/.storebound/broker.json), the profile's key file, or none. */
playSource: 'broker' | 'file' | null; error?: string }[] {
let broker = false;
try { broker = !!brokerConfig(); } catch { /* a broken broker.json: doctor reports it */ }
return profileNames().map(profile => {
try {
const id = profileIdentity(profile);
return { profile, apple: !!id.appleKeyId, play: !!id.playEmail, playAccount: id.playEmail ? maskUser(id.playEmail) : null, vendor: id.vendor, playDeveloperId: id.playDeveloperId };
} catch (e) { return { profile, apple: false, play: false, playAccount: null, vendor: false, error: (e as Error).message }; }
const email = id.playEmail ?? profileEnv(profile).PLAY_SERVICE_ACCOUNT_EMAIL ?? null;
const viaBroker = broker && !profileBrokerOff(profile) && !!email;
const playSource = viaBroker ? 'broker' as const : id.playEmail ? 'file' as const : null;
return { profile, apple: !!id.appleKeyId, play: !!playSource, playAccount: email && playSource ? maskUser(email) : null, vendor: id.vendor, playDeveloperId: id.playDeveloperId, playSource };
} catch (e) { return { profile, apple: false, play: false, playAccount: null, vendor: false, playSource: null, error: (e as Error).message }; }
});
}