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:
parent
304407e523
commit
2c5e30090f
27 changed files with 1145 additions and 43 deletions
34
cli/lib/google-access.ts
Normal file
34
cli/lib/google-access.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// A Google 401 / 403 that says what is missing (SERVICE_DISABLED, PERMISSION_DENIED, a refused key) becomes one
|
||||
// GoogleAccessError with the exact API or grant and the link to fix it. It is never retried: the same call fails the
|
||||
// same way until the owner (or the credential broker) changes something. With the broker as the token source the text
|
||||
// also names the Play permissions the broker lists for the package and the broker-side fix.
|
||||
import { brokerConfig, brokerPlayInfo, grantsOf, redact } from './broker';
|
||||
import { findingText, type GoogleFinding, parseGoogleError } from './google-errors';
|
||||
|
||||
export class GoogleAccessError extends Error {
|
||||
/** Never sent again: retry loops and the upsert layer check this. */
|
||||
readonly noRetry = true;
|
||||
constructor(msg: string, readonly finding: GoogleFinding) { super(msg); }
|
||||
}
|
||||
|
||||
/** The GoogleAccessError for a failed call's text, or null when the text is another error. */
|
||||
export async function accessError(text: string, o: { app?: string | null; root?: string; source?: 'broker' | 'file' } = {}): Promise<GoogleAccessError | null> {
|
||||
const f = parseGoogleError(text, { app: o.app ?? null });
|
||||
if (!f || f.kind === 'quota') return null;
|
||||
const lines = [redact(text), findingText(f, { withUrl: true })];
|
||||
if (o.source === 'broker') {
|
||||
if (f.kind === 'auth-revoked') lines.push('The token came from the credential broker: its key for this service account was revoked or deleted at Google; rotate the key on the broker.');
|
||||
if (f.kind === 'api-disabled') lines.push(`Or enable it on the credential broker (google_api_enable ${f.projectId ?? '<project>'} ${f.service}).`);
|
||||
if (f.kind === 'no-app-access' && o.root && o.app) {
|
||||
try {
|
||||
const cfg = brokerConfig(o.root);
|
||||
const r = cfg ? await brokerPlayInfo(cfg, o.app) : null;
|
||||
if (r?.kind === 'ok') {
|
||||
const g = grantsOf(r.info);
|
||||
lines.push(`The credential broker lists ${g ? (g.length ? g.join(', ') : 'no Play permissions') : 'no permission data'} for ${r.info.account ?? 'the service account'} on ${o.app}: grant the missing one there (google_play_grant).`);
|
||||
} else if (r) lines.push(r.message);
|
||||
} catch { /* the Google error stands on its own */ }
|
||||
}
|
||||
}
|
||||
return new GoogleAccessError(lines.join('\n'), f);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue