Created
January 21, 2024 08:59
-
-
Save nyamadan/1c39a1b1bfdf80b5083b3ec070fcdd5c to your computer and use it in GitHub Desktop.
Revisions
-
nyamadan created this gist
Jan 21, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,110 @@ // https://cloud.google.com/run/docs/authenticating/service-to-service export interface IdTokenError { error: string; error_description: string; } export interface IdTokenSuccess extends IdToken { error?: never; } export interface IdToken { id_token: string; } export interface AccountCredential { client_email: string; private_key: string; } function arrayBufferToBase64(buffer: ArrayBuffer) { let binary = ""; const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } function arrayBufferToBase64Url(buffer: ArrayBuffer) { const base64 = arrayBufferToBase64(buffer); return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } function base64ToArrayBuffer(base64: string) { while (base64.length % 4) { base64 += "="; } const binary = atob(base64); const length = binary.length; const buffer = new ArrayBuffer(length); const bytes = new Uint8Array(buffer); for (let i = 0; i < length; i++) { bytes[i] = binary.charCodeAt(i); } return buffer; } export function base64UrlToArrayBuffer(base64url: string) { const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); return base64ToArrayBuffer(base64); } export async function getIdToken( target_audience: string, credential: AccountCredential, ): Promise<IdTokenSuccess | IdTokenError> { const privateKeyDER = base64ToArrayBuffer( credential.private_key.trim().split("\n").slice(1, -1).join(""), ); const algorithm = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" }, } as const; const privateKey = await crypto.subtle.importKey( "pkcs8", privateKeyDER, algorithm, false, ["sign"], ); const header = { alg: "RS256", typ: "JWT" } as const; const clientEmail = credential.client_email; const aud = "https://www.googleapis.com/oauth2/v4/token"; const iss = clientEmail; const iat = (Date.now() / 1000) | 0; const exp = iat + 3600; const sub = clientEmail; const payload = { target_audience, aud, iss, sub, iat, exp } as const; const encoder = new TextEncoder(); const encodedHeader = arrayBufferToBase64Url( encoder.encode(JSON.stringify(header)), ); const encodedPayload = arrayBufferToBase64Url( encoder.encode(JSON.stringify(payload)), ); const encodedMessage = `${encodedHeader}.${encodedPayload}`; const encodedMessageArrBuf = encoder.encode(encodedMessage); const signatureArrBuf = await crypto.subtle.sign( algorithm, privateKey, encodedMessageArrBuf, ); const encodedSignature = arrayBufferToBase64Url(signatureArrBuf); const jwtToken = `${encodedMessage}.${encodedSignature}`; const response = await fetch("https://www.googleapis.com/oauth2/v4/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwtToken, }), }); return await response.json(); }