This commit is contained in:
JiriUhlir
2026-06-15 16:00:12 +02:00
parent d648589ac7
commit 8f7f8404c3
3 changed files with 325 additions and 7 deletions
+26
View File
@@ -52,6 +52,7 @@ Env promenne lze nahradit primo v requestu:
- `GOOGLE_SERVICE_ACCOUNT_EMAIL`: `serviceAccountEmail` nebo `X-Google-Service-Account-Email` - `GOOGLE_SERVICE_ACCOUNT_EMAIL`: `serviceAccountEmail` nebo `X-Google-Service-Account-Email`
- `GOOGLE_PRIVATE_KEY`: `privateKey` nebo `X-Google-Private-Key` - `GOOGLE_PRIVATE_KEY`: `privateKey` nebo `X-Google-Private-Key`
- `GOOGLE_SCOPES`: `scope`, `scopes` nebo `X-Google-Scopes` - `GOOGLE_SCOPES`: `scope`, `scopes` nebo `X-Google-Scopes`
- `GOOGLE_SERVICE_ACCOUNT_JSON`: `serviceAccountJson`, `serviceAccountJsonEnv`, `X-Google-Service-Account-Json`, `X-Google-Service-Account-Json-Env`
- `GOOGLE_ACCESS_TOKEN`: `Authorization: Bearer <token>`, `accessToken`, `accessTokenEnv`, `X-Google-Access-Token`, `X-Google-Access-Token-Env` - `GOOGLE_ACCESS_TOKEN`: `Authorization: Bearer <token>`, `accessToken`, `accessTokenEnv`, `X-Google-Access-Token`, `X-Google-Access-Token-Env`
- `GOOGLE_API_KEY`: `apiKey`, `apiKeyEnv`, `X-Google-Api-Key`, `X-Google-Api-Key-Env` - `GOOGLE_API_KEY`: `apiKey`, `apiKeyEnv`, `X-Google-Api-Key`, `X-Google-Api-Key-Env`
@@ -143,6 +144,31 @@ POST /google/sheets/write-by-url
`mode=append` prida radky pod existujici data. `mode=update` prepise bunky od `startCell`. `mode=append` prida radky pod existujici data. `mode=update` prepise bunky od `startCell`.
Stejny endpoint lze volat primo se service account JSON. V tom pripade se neposila `Authorization` header; sluzba si z `client_email` a `private_key` sama vystavi access token:
```json
{
"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1abcDEFghiJKLmnopQRstuVWXyz/edit#gid=0",
"sheetName": "Objednavky",
"mode": "append",
"values": [
["2026-06-15", "ACME", 1234]
],
"serviceAccountScopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"serviceAccountJson": {
"type": "service_account",
"project_id": "project-id",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "service-account@project-id.iam.gserviceaccount.com",
"client_id": "123456789",
"token_uri": "https://oauth2.googleapis.com/token"
}
}
```
## OAuth postup ## OAuth postup
1. Zavolej `GET /google/oauth/scopes` a vyber scopes podle sluzeb. 1. Zavolej `GET /google/oauth/scopes` a vyber scopes podle sluzeb.
+42
View File
@@ -36,9 +36,51 @@ Swagger obsahuje `GET /google/configuration`, ktery vypise podporovane env prome
- `GOOGLE_SERVICE_ACCOUNT_EMAIL`: `serviceAccountEmail`, `X-Google-Service-Account-Email` - `GOOGLE_SERVICE_ACCOUNT_EMAIL`: `serviceAccountEmail`, `X-Google-Service-Account-Email`
- `GOOGLE_PRIVATE_KEY`: `privateKey`, `X-Google-Private-Key` - `GOOGLE_PRIVATE_KEY`: `privateKey`, `X-Google-Private-Key`
- `GOOGLE_SCOPES`: `scope`, `scopes`, `X-Google-Scopes` - `GOOGLE_SCOPES`: `scope`, `scopes`, `X-Google-Scopes`
- `GOOGLE_SERVICE_ACCOUNT_JSON`: `serviceAccountJson`, `serviceAccountJsonEnv`, `X-Google-Service-Account-Json`, `X-Google-Service-Account-Json-Env`
- `GOOGLE_ACCESS_TOKEN`: `Authorization: Bearer <token>`, `accessToken`, `accessTokenEnv`, `X-Google-Access-Token`, `X-Google-Access-Token-Env` - `GOOGLE_ACCESS_TOKEN`: `Authorization: Bearer <token>`, `accessToken`, `accessTokenEnv`, `X-Google-Access-Token`, `X-Google-Access-Token-Env`
- `GOOGLE_API_KEY`: `apiKey`, `apiKeyEnv`, `X-Google-Api-Key`, `X-Google-Api-Key-Env` - `GOOGLE_API_KEY`: `apiKey`, `apiKeyEnv`, `X-Google-Api-Key`, `X-Google-Api-Key-Env`
### Service account JSON
Google service account JSON neobsahuje `access_token`. Obsahuje hlavne `client_email`, `private_key` a `token_uri`. Sluzba z techto poli sama vytvori JWT assertion, zavola Google OAuth token endpoint a ziska docasny `access_token`.
Produktove endpointy proto lze volat primo s:
- `serviceAccountJson` v body
- `X-Google-Service-Account-Json` header
- `serviceAccountJsonEnv` nebo `X-Google-Service-Account-Json-Env`, napr. `GOOGLE_SERVICE_ACCOUNT_JSON`
Je nutne dodat scopes:
- `serviceAccountScopes` v body
- `X-Google-Service-Account-Scopes` header
- nebo `GOOGLE_SCOPES`
Priklad pro zapis do Sheets:
```json
{
"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1abcDEFghiJKLmnopQRstuVWXyz/edit#gid=0",
"sheetName": "Objednavky",
"mode": "append",
"values": [
["2026-06-15", "ACME", 1234]
],
"serviceAccountScopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"serviceAccountJson": {
"type": "service_account",
"project_id": "project-id",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "service-account@project-id.iam.gserviceaccount.com",
"client_id": "123456789",
"token_uri": "https://oauth2.googleapis.com/token"
}
}
```
### OAuth krok za krokem ### OAuth krok za krokem
1. Zavolej `GET /google/oauth/scopes`. 1. Zavolej `GET /google/oauth/scopes`.
+257 -7
View File
@@ -15,6 +15,10 @@ type GoogleRequestBody = {
accessTokenEnv?: string; accessTokenEnv?: string;
apiKey?: string; apiKey?: string;
apiKeyEnv?: string; apiKeyEnv?: string;
serviceAccountJson?: GoogleServiceAccountJson | string;
serviceAccountJsonEnv?: string;
serviceAccountScopes?: string[] | string;
serviceAccountSubject?: string;
}; };
type TokenExchangeBody = { type TokenExchangeBody = {
@@ -33,6 +37,18 @@ type ServiceAccountTokenBody = {
subject?: string; subject?: string;
serviceAccountEmail?: string; serviceAccountEmail?: string;
privateKey?: string; privateKey?: string;
serviceAccountJson?: GoogleServiceAccountJson | string;
serviceAccountJsonEnv?: string;
};
type GoogleServiceAccountJson = {
type?: string;
project_id?: string;
private_key_id?: string;
private_key?: string;
client_email?: string;
client_id?: string;
token_uri?: string;
}; };
type SheetWriteByUrlBody = { type SheetWriteByUrlBody = {
@@ -238,8 +254,17 @@ app.post("/google/oauth/token", async (req, res) => {
app.post("/google/oauth/service-account-token", async (req, res) => { app.post("/google/oauth/service-account-token", async (req, res) => {
try { try {
const body = req.body as ServiceAccountTokenBody; const body = req.body as ServiceAccountTokenBody;
const email = body.serviceAccountEmail || req.header("x-google-service-account-email") || process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL; const serviceAccountJson = readServiceAccountJson(req, body);
const rawPrivateKey = body.privateKey || req.header("x-google-private-key") || process.env.GOOGLE_PRIVATE_KEY; const email =
body.serviceAccountEmail ||
serviceAccountJson?.client_email ||
req.header("x-google-service-account-email") ||
process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL;
const rawPrivateKey =
body.privateKey ||
serviceAccountJson?.private_key ||
req.header("x-google-private-key") ||
process.env.GOOGLE_PRIVATE_KEY;
const privateKey = rawPrivateKey?.replace(/\\n/g, "\n"); const privateKey = rawPrivateKey?.replace(/\\n/g, "\n");
const scope = body.scope || body.scopes?.join(" ") || req.header("x-google-scopes") || process.env.GOOGLE_SCOPES; const scope = body.scope || body.scopes?.join(" ") || req.header("x-google-scopes") || process.env.GOOGLE_SCOPES;
@@ -254,7 +279,7 @@ app.post("/google/oauth/service-account-token", async (req, res) => {
const claimSet: Record<string, string | number> = { const claimSet: Record<string, string | number> = {
iss: email, iss: email,
scope, scope,
aud: "https://oauth2.googleapis.com/token", aud: serviceAccountJson?.token_uri || "https://oauth2.googleapis.com/token",
iat: now, iat: now,
exp: now + 3600 exp: now + 3600
}; };
@@ -269,7 +294,7 @@ app.post("/google/oauth/service-account-token", async (req, res) => {
assertion assertion
}); });
await pipeGoogleForm(res, "https://oauth2.googleapis.com/token", params); await pipeGoogleForm(res, serviceAccountJson?.token_uri || "https://oauth2.googleapis.com/token", params);
} catch (error) { } catch (error) {
sendError(res, error); sendError(res, error);
} }
@@ -320,6 +345,7 @@ app.get("/google/configuration", (_req, res) => {
{ name: "GOOGLE_SERVICE_ACCOUNT_EMAIL", usedBy: ["/google/oauth/service-account-token"], alternatives: ["serviceAccountEmail", "X-Google-Service-Account-Email"] }, { name: "GOOGLE_SERVICE_ACCOUNT_EMAIL", usedBy: ["/google/oauth/service-account-token"], alternatives: ["serviceAccountEmail", "X-Google-Service-Account-Email"] },
{ name: "GOOGLE_PRIVATE_KEY", usedBy: ["/google/oauth/service-account-token"], alternatives: ["privateKey", "X-Google-Private-Key"] }, { name: "GOOGLE_PRIVATE_KEY", usedBy: ["/google/oauth/service-account-token"], alternatives: ["privateKey", "X-Google-Private-Key"] },
{ name: "GOOGLE_SCOPES", usedBy: ["/google/oauth/service-account-token"], alternatives: ["scope", "scopes", "X-Google-Scopes"] }, { name: "GOOGLE_SCOPES", usedBy: ["/google/oauth/service-account-token"], alternatives: ["scope", "scopes", "X-Google-Scopes"] },
{ name: "GOOGLE_SERVICE_ACCOUNT_JSON", usedBy: ["/google/oauth/service-account-token", "Google product endpoints", "/google/request"], alternatives: ["serviceAccountJson", "serviceAccountJsonEnv", "X-Google-Service-Account-Json", "X-Google-Service-Account-Json-Env"] },
{ name: "GOOGLE_ACCESS_TOKEN", usedBy: ["Google product endpoints", "/google/request"], alternatives: ["Authorization: Bearer", "accessToken", "accessTokenEnv", "X-Google-Access-Token", "X-Google-Access-Token-Env"] }, { name: "GOOGLE_ACCESS_TOKEN", usedBy: ["Google product endpoints", "/google/request"], alternatives: ["Authorization: Bearer", "accessToken", "accessTokenEnv", "X-Google-Access-Token", "X-Google-Access-Token-Env"] },
{ name: "GOOGLE_API_KEY", usedBy: ["Google product endpoints", "/google/request"], alternatives: ["apiKey", "apiKeyEnv", "X-Google-Api-Key", "X-Google-Api-Key-Env"] } { name: "GOOGLE_API_KEY", usedBy: ["Google product endpoints", "/google/request"], alternatives: ["apiKey", "apiKeyEnv", "X-Google-Api-Key", "X-Google-Api-Key-Env"] }
], ],
@@ -863,6 +889,7 @@ app.post("/google/request", async (req, res) => {
try { try {
const requestBody = req.body as GoogleRequestBody; const requestBody = req.body as GoogleRequestBody;
applyCredentialHeaders(req, requestBody); applyCredentialHeaders(req, requestBody);
await applyServiceAccountAuthorization(req, requestBody);
const targetUrl = buildGoogleUrl(requestBody); const targetUrl = buildGoogleUrl(requestBody);
const headers = buildGoogleHeaders(req, requestBody); const headers = buildGoogleHeaders(req, requestBody);
const method = (requestBody.method || "GET").toUpperCase(); const method = (requestBody.method || "GET").toUpperCase();
@@ -919,13 +946,14 @@ async function callGoogle(req: Request, res: Response, options: GoogleRouteOptio
}; };
applyCredentialHeaders(req, requestBody); applyCredentialHeaders(req, requestBody);
await applyServiceAccountAuthorization(req, requestBody);
const targetUrl = buildGoogleUrl(requestBody); const targetUrl = buildGoogleUrl(requestBody);
const headers = buildGoogleHeaders(req, requestBody); const headers = buildGoogleHeaders(req, requestBody);
const method = (requestBody.method || "GET").toUpperCase(); const method = (requestBody.method || "GET").toUpperCase();
const init: RequestInit = { method, headers }; const init: RequestInit = { method, headers };
if (!["GET", "HEAD"].includes(method) && options.body !== undefined) { if (!["GET", "HEAD"].includes(method) && options.body !== undefined) {
init.body = JSON.stringify(options.body); init.body = JSON.stringify(stripCredentialFields(options.body));
} }
const response = await fetch(targetUrl, init); const response = await fetch(targetUrl, init);
@@ -958,11 +986,102 @@ function readBodyString(body: unknown, key: string): string | undefined {
return typeof value === "string" ? value : undefined; return typeof value === "string" ? value : undefined;
} }
function stripCredentialFields(body: JsonValue): JsonValue {
if (!body || typeof body !== "object" || Array.isArray(body)) {
return body;
}
const credentialFields = new Set([
"accessToken",
"accessTokenEnv",
"apiKey",
"apiKeyEnv",
"serviceAccountJson",
"serviceAccountJsonEnv",
"serviceAccountScopes",
"serviceAccountSubject"
]);
const result: Record<string, JsonValue> = {};
for (const [key, value] of Object.entries(body)) {
if (!credentialFields.has(key)) {
result[key] = value;
}
}
return result;
}
function applyCredentialHeaders(req: Request, body: GoogleRequestBody): void { function applyCredentialHeaders(req: Request, body: GoogleRequestBody): void {
body.accessToken ||= req.header("x-google-access-token"); body.accessToken ||= req.header("x-google-access-token");
body.accessTokenEnv ||= req.header("x-google-access-token-env"); body.accessTokenEnv ||= req.header("x-google-access-token-env");
body.apiKey ||= req.header("x-google-api-key"); body.apiKey ||= req.header("x-google-api-key");
body.apiKeyEnv ||= req.header("x-google-api-key-env"); body.apiKeyEnv ||= req.header("x-google-api-key-env");
body.serviceAccountJson ||= req.header("x-google-service-account-json");
body.serviceAccountJsonEnv ||= req.header("x-google-service-account-json-env");
body.serviceAccountScopes ||= req.header("x-google-service-account-scopes");
body.serviceAccountSubject ||= req.header("x-google-service-account-subject");
}
async function applyServiceAccountAuthorization(req: Request, body: GoogleRequestBody): Promise<void> {
if (body.accessToken || body.accessTokenEnv || readBearerToken(req)) {
return;
}
const serviceAccountJson = readServiceAccountJson(req, body);
if (!serviceAccountJson) {
return;
}
const scopes = readServiceAccountScopes(req, body);
if (scopes.length === 0) {
throw new Error("Service account authentication requires serviceAccountScopes, X-Google-Service-Account-Scopes, or GOOGLE_SCOPES.");
}
body.accessToken = await createServiceAccountAccessToken(
serviceAccountJson,
scopes.join(" "),
body.serviceAccountSubject || req.header("x-google-service-account-subject") || undefined
);
}
function readServiceAccountJson(req: Request, body?: GoogleRequestBody | ServiceAccountTokenBody): GoogleServiceAccountJson | undefined {
const bodyValue = body && "serviceAccountJson" in body ? body.serviceAccountJson : undefined;
const envName = body && "serviceAccountJsonEnv" in body ? body.serviceAccountJsonEnv : undefined;
const raw =
bodyValue ||
req.header("x-google-service-account-json") ||
readEnvValue(envName) ||
readEnvValue(req.header("x-google-service-account-json-env")) ||
process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
if (!raw) {
return undefined;
}
if (typeof raw === "object") {
return raw;
}
try {
return JSON.parse(raw) as GoogleServiceAccountJson;
} catch {
throw new Error("serviceAccountJson must be a valid Google service account JSON object or JSON string.");
}
}
function readServiceAccountScopes(req: Request, body: GoogleRequestBody): string[] {
const value =
body.serviceAccountScopes ||
readBodyString(req.body, "serviceAccountScopes") ||
req.header("x-google-service-account-scopes") ||
process.env.GOOGLE_SCOPES;
if (Array.isArray(value)) {
return value.map((item) => String(item)).filter(Boolean);
}
return parseScopes(typeof value === "string" ? value : undefined) || [];
} }
function requireQueryString(req: Request, res: Response, key: string): string | undefined { function requireQueryString(req: Request, res: Response, key: string): string | undefined {
@@ -1102,6 +1221,69 @@ async function pipeGoogleForm(res: Response, url: string, params: URLSearchParam
await relayResponse(res, response); await relayResponse(res, response);
} }
async function createServiceAccountAccessToken(
serviceAccountJson: GoogleServiceAccountJson,
scope: string,
subject?: string
): Promise<string> {
const email = serviceAccountJson.client_email;
const rawPrivateKey = serviceAccountJson.private_key;
const privateKey = rawPrivateKey?.replace(/\\n/g, "\n");
const tokenUri = serviceAccountJson.token_uri || "https://oauth2.googleapis.com/token";
if (!email || !privateKey) {
throw new Error("Google service account JSON must contain client_email and private_key.");
}
const now = Math.floor(Date.now() / 1000);
const claimSet: Record<string, string | number> = {
iss: email,
scope,
aud: tokenUri,
iat: now,
exp: now + 3600
};
if (subject) {
claimSet.sub = subject;
}
const assertion = signJwt({ alg: "RS256", typ: "JWT" }, claimSet, privateKey);
const response = await fetch(tokenUri, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion
})
});
const text = await response.text();
let payload: unknown;
try {
payload = text ? JSON.parse(text) : {};
} catch {
payload = {};
}
if (!response.ok) {
throw new Error(`Service account token request failed with HTTP ${response.status}: ${text}`);
}
const accessToken = typeof payload === "object" && payload && "access_token" in payload
? (payload as Record<string, unknown>).access_token
: undefined;
if (typeof accessToken !== "string" || !accessToken) {
throw new Error("Service account token response did not contain access_token.");
}
return accessToken;
}
async function relayResponse(res: Response, response: globalThis.Response): Promise<void> { async function relayResponse(res: Response, response: globalThis.Response): Promise<void> {
const contentType = response.headers.get("content-type") || "application/json"; const contentType = response.headers.get("content-type") || "application/json";
const text = await response.text(); const text = await response.text();
@@ -1438,6 +1620,8 @@ function buildOpenApiSchemas(): Record<string, unknown> {
properties: { properties: {
serviceAccountEmail: { type: "string", description: "Alternativa k X-Google-Service-Account-Email nebo env GOOGLE_SERVICE_ACCOUNT_EMAIL." }, serviceAccountEmail: { type: "string", description: "Alternativa k X-Google-Service-Account-Email nebo env GOOGLE_SERVICE_ACCOUNT_EMAIL." },
privateKey: { type: "string", description: "Alternativa k X-Google-Private-Key nebo env GOOGLE_PRIVATE_KEY. Podporuje \\n escapovani." }, privateKey: { type: "string", description: "Alternativa k X-Google-Private-Key nebo env GOOGLE_PRIVATE_KEY. Podporuje \\n escapovani." },
serviceAccountJson: { $ref: "#/components/schemas/GoogleServiceAccountJson" },
serviceAccountJsonEnv: { type: "string", example: "GOOGLE_SERVICE_ACCOUNT_JSON" },
scopes: { type: "array", items: { type: "string" }, description: "Alternativa k X-Google-Scopes nebo env GOOGLE_SCOPES." }, scopes: { type: "array", items: { type: "string" }, description: "Alternativa k X-Google-Scopes nebo env GOOGLE_SCOPES." },
scope: { type: "string" }, scope: { type: "string" },
subject: { type: "string", description: "Workspace user pro domain-wide delegation." } subject: { type: "string", description: "Workspace user pro domain-wide delegation." }
@@ -1456,7 +1640,31 @@ function buildOpenApiSchemas(): Record<string, unknown> {
accessToken: { type: "string", description: "Alternativa k Authorization Bearer nebo X-Google-Access-Token." }, accessToken: { type: "string", description: "Alternativa k Authorization Bearer nebo X-Google-Access-Token." },
accessTokenEnv: { type: "string", example: "GOOGLE_ACCESS_TOKEN", description: "Jmeno env promenne s access tokenem." }, accessTokenEnv: { type: "string", example: "GOOGLE_ACCESS_TOKEN", description: "Jmeno env promenne s access tokenem." },
apiKey: { type: "string", description: "Alternativa k X-Google-Api-Key nebo GOOGLE_API_KEY." }, apiKey: { type: "string", description: "Alternativa k X-Google-Api-Key nebo GOOGLE_API_KEY." },
apiKeyEnv: { type: "string", example: "GOOGLE_API_KEY", description: "Jmeno env promenne s API key." } apiKeyEnv: { type: "string", example: "GOOGLE_API_KEY", description: "Jmeno env promenne s API key." },
serviceAccountJson: { $ref: "#/components/schemas/GoogleServiceAccountJson" },
serviceAccountJsonEnv: { type: "string", example: "GOOGLE_SERVICE_ACCOUNT_JSON" },
serviceAccountScopes: {
oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
description: "Scopes pro automaticke vystaveni tokenu ze service account JSON."
},
serviceAccountSubject: { type: "string", description: "Volitelny Workspace uzivatel pro domain-wide delegation." }
}
},
GoogleServiceAccountJson: {
type: "object",
required: ["client_email", "private_key"],
properties: {
type: { type: "string", example: "service_account" },
project_id: { type: "string" },
private_key_id: { type: "string" },
private_key: { type: "string", description: "Private key ze service account JSON. Secret se nevraci ve vystupu." },
client_email: { type: "string", description: "Service account email pouzity jako JWT iss." },
client_id: { type: "string" },
auth_uri: { type: "string" },
token_uri: { type: "string", example: "https://oauth2.googleapis.com/token" },
auth_provider_x509_cert_url: { type: "string" },
client_x509_cert_url: { type: "string" },
universe_domain: { type: "string", example: "googleapis.com" }
} }
}, },
Calendar: { Calendar: {
@@ -1566,7 +1774,14 @@ function buildOpenApiSchemas(): Record<string, unknown> {
example: [["Datum", "Castka"], ["2026-06-15", 1234]] example: [["Datum", "Castka"], ["2026-06-15", 1234]]
}, },
accessToken: { type: "string", description: "Volitelne. Lepsi je poslat Authorization: Bearer <token>." }, accessToken: { type: "string", description: "Volitelne. Lepsi je poslat Authorization: Bearer <token>." },
accessTokenEnv: { type: "string", description: "Volitelne jmeno env var s access tokenem." } accessTokenEnv: { type: "string", description: "Volitelne jmeno env var s access tokenem." },
serviceAccountJson: { $ref: "#/components/schemas/GoogleServiceAccountJson" },
serviceAccountJsonEnv: { type: "string", example: "GOOGLE_SERVICE_ACCOUNT_JSON" },
serviceAccountScopes: {
oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
example: ["https://www.googleapis.com/auth/spreadsheets"],
description: "Scopes pro automaticke vystaveni tokenu ze service account JSON."
}
} }
}, },
SheetWriteResponse: { SheetWriteResponse: {
@@ -1832,6 +2047,34 @@ function googleCredentialHeaderParameters(): Record<string, unknown>[] {
required: false, required: false,
schema: { type: "string", example: "GOOGLE_API_KEY" }, schema: { type: "string", example: "GOOGLE_API_KEY" },
description: "Jmeno env promenne obsahujici Google API key. Hodnota env se nevraci ve vystupu." description: "Jmeno env promenne obsahujici Google API key. Hodnota env se nevraci ve vystupu."
},
{
name: "X-Google-Service-Account-Json",
in: "header",
required: false,
schema: { type: "string" },
description: "Cely Google service account JSON jako string. Sluzba z nej vystavi access token automaticky. Secret se nevraci ve vystupu."
},
{
name: "X-Google-Service-Account-Json-Env",
in: "header",
required: false,
schema: { type: "string", example: "GOOGLE_SERVICE_ACCOUNT_JSON" },
description: "Jmeno env promenne obsahujici cely service account JSON."
},
{
name: "X-Google-Service-Account-Scopes",
in: "header",
required: false,
schema: { type: "string", example: "https://www.googleapis.com/auth/spreadsheets" },
description: "Scopes pro automaticke vystaveni tokenu ze service account JSON."
},
{
name: "X-Google-Service-Account-Subject",
in: "header",
required: false,
schema: { type: "string" },
description: "Volitelny Workspace uzivatel pro domain-wide delegation."
} }
]; ];
} }
@@ -1896,6 +2139,13 @@ function serviceAccountHeaderParameters(): Record<string, unknown>[] {
required: false, required: false,
schema: { type: "string" }, schema: { type: "string" },
description: "Alternativa k body.scope/body.scopes nebo env GOOGLE_SCOPES." description: "Alternativa k body.scope/body.scopes nebo env GOOGLE_SCOPES."
},
{
name: "X-Google-Service-Account-Json",
in: "header",
required: false,
schema: { type: "string" },
description: "Cely Google service account JSON jako string. Alternativa k body.serviceAccountJson nebo env GOOGLE_SERVICE_ACCOUNT_JSON."
} }
]; ];
} }