/** * Registering an OAuth client the owner just handed over, and getting the * consent link back in the same breath. * * ── Why this is not the setup-flow runner ────────────────────────────────── * * `oauthAuthorizeRunner` awaits the redirect: it blocks for up to five minutes * while a person visits Google. That is right for a command someone is * watching, and wrong for a conversational turn, which has to answer NOW. A * turn that blocks for five minutes is a turn that looks broken. * * So the consent is split in two. `beginGoogleConsent` binds the listener, * builds the URL and returns immediately; the exchange finishes on the * `completed` promise whenever the person actually approves. The caller hands * back the link in its reply and the refresh token lands in the encrypted store * on its own, without anybody being told to run anything afterwards. * * ── The rule about the secret ───────────────────────────────────────────── * * A client secret arrives here because the owner pasted it into a conversation. * It goes into the encrypted store and it is never returned, never echoed and * never put in a detail line. `registerGoogleClient` hands back the client id's * last few characters and nothing else, which is enough for a person to confirm * the right client was registered and useless to anyone reading the transcript * later. Google shows the full secret exactly once, at creation, so the value * arriving here is often the only copy, losing it means making a new client, * and echoing it means it lives in a log. */ import { type GoogleClientCredentials } from './client-intake.js'; import { type GoogleFetchPort, type GoogleLoopbackListenerFactory } from './oauth-loopback.js'; import type { GoogleConfigPort, GoogleSecretPort } from './types.js'; /** How long the listener waits for the person before giving up. */ export declare const CONSENT_WAIT_MS = 600000; /** Safe-to-display confirmation that a client was registered. Carries no secret. */ export interface GoogleClientRegistration { readonly ok: true; /** The last characters of the client id. Never the secret, in any form. */ readonly clientIdTail: string; } export interface GoogleClientRegistrationFailed { readonly ok: false; readonly problem: string; readonly fix: string; } export type GoogleClientRegistrationResult = GoogleClientRegistration | GoogleClientRegistrationFailed; /** The tail of a client id, for a confirmation line. */ export declare function clientIdTail(clientId: string): string; /** * Store a client id and secret the owner supplied. * * The id goes to config, the secret to the encrypted store, and the reply * carries neither in full. Validation is `clientCredentialsFromInput`, the same * check every other intake route uses, so a mistyped pair is refused here * rather than at the consent screen. */ export declare function registerGoogleClient(deps: { readonly config: GoogleConfigPort; readonly secrets: GoogleSecretPort; }, input: { readonly clientId: string; readonly clientSecret: string; }): Promise; /** Shared by both intake routes: config half, secret half, safe confirmation. */ export declare function storeClientCredentials(deps: { readonly config: GoogleConfigPort; readonly secrets: GoogleSecretPort; }, credentials: GoogleClientCredentials): Promise; /** How the consent ended. Safe to display; never carries a token. */ export interface GoogleConsentCompletion { readonly ok: boolean; readonly detail: string; readonly problem?: string; readonly fix?: string; } export interface GoogleConsentSession { /** The link to hand the person. This is the one action the flow asks of them. */ readonly consentUrl: string; /** * Resolves when the person approves, or when the wait ends without them. * * Never rejects: a consent nobody completed is an ordinary outcome, not a * fault, and a rejected promise nobody awaited would surface as an unhandled * rejection in whatever process happened to be running. */ readonly completed: Promise; /** Release the port without waiting. Safe to call more than once. */ cancel(): void; } export interface BeginGoogleConsentDeps { readonly clientId: string; readonly clientSecret: string; readonly config: GoogleConfigPort; readonly secrets: GoogleSecretPort; readonly loopback: GoogleLoopbackListenerFactory; readonly fetchPort: GoogleFetchPort; /** The account to preselect on the consent screen, when anything knows it. */ readonly loginHint?: string | undefined; readonly timeoutMs?: number | undefined; /** Injected so the CSRF token is deterministic in tests. */ readonly generateState?: (() => string) | undefined; } /** * Start a consent and return its link straight away. * * The caller gets the URL synchronously and can answer with it in the same * reply. Everything after the person clicks, the redirect, the state check, * the code exchange, storing the refresh token, happens on `completed`. */ export declare function beginGoogleConsent(deps: BeginGoogleConsentDeps): GoogleConsentSession; //# sourceMappingURL=consent-session.d.ts.map