Last active
September 1, 2024 20:53
-
-
Save MegaManSec/2b0f430337a25b8ddd7218c84b88a472 to your computer and use it in GitHub Desktop.
Normal (pure?) javascript. Useful for performing actions in GCP with a service account using pure JS, especially if using Cloudflare workers (which is what the "new Response" is about).
This file contains 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 characters
const FIREBASE_SERVICE_ACCOUNT = { | |
"type": "service_account", | |
"project_id": "....etc...", | |
}; | |
function base64EncodeWebSafe(input) { | |
let base64 = btoa(input); | |
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); | |
} | |
async function computeRsaSha256Signature(data) { | |
// Convert PEM-encoded private key to a CryptoKey object | |
const privateKey = await importRsaPrivateKey(); | |
// Encode the data to Uint8Array | |
const encoder = new TextEncoder(); | |
const encodedData = encoder.encode(data); | |
// Sign the data | |
const signature = await crypto.subtle.sign( | |
{ | |
name: "RSASSA-PKCS1-v1_5", | |
hash: "SHA-256" | |
}, | |
privateKey, | |
encodedData | |
); | |
// Convert the signature to Base64 for easy display | |
return arrayBufferToBase64(signature); | |
} | |
async function importRsaPrivateKey() { | |
// Remove PEM headers and footers, and replace newlines with empty string | |
const pemContents = FIREBASE_SERVICE_ACCOUNT.private_key | |
.replace(/-----BEGIN PRIVATE KEY-----/g, "") | |
.replace(/-----END PRIVATE KEY-----/g, "") | |
.replace(/\n/g, ""); | |
const binaryDerString = atob(pemContents); | |
const binaryDer = str2ab(binaryDerString); | |
// Import the key | |
return crypto.subtle.importKey( | |
"pkcs8", | |
binaryDer, | |
{ | |
name: "RSASSA-PKCS1-v1_5", | |
hash: "SHA-256" | |
}, | |
false, | |
["sign"] | |
); | |
} | |
function str2ab(str) { | |
const buf = new ArrayBuffer(str.length); | |
const view = new Uint8Array(buf); | |
for (let i = 0; i < str.length; i++) { | |
view[i] = str.charCodeAt(i); | |
} | |
return buf; | |
} | |
function arrayBufferToBase64(buffer) { | |
let binary = ''; | |
const bytes = new Uint8Array(buffer); | |
const len = bytes.byteLength; | |
for (let i = 0; i < len; i++) { | |
binary += String.fromCharCode(bytes[i]); | |
} | |
return base64EncodeWebSafe(binary); | |
} | |
async function getAccessToken() { | |
var privateKey = await importRsaPrivateKey(); | |
var CLIENT_EMAIL = FIREBASE_SERVICE_ACCOUNT.client_email; | |
var TOKEN_URL = FIREBASE_SERVICE_ACCOUNT.token_uri; | |
var SCOPES = [ | |
"https://www.googleapis.com/auth/cloud-platform", | |
]; | |
var header = { | |
"alg": "RS256", | |
"typ": "JWT" | |
}; | |
var claimSet = { | |
"iss": CLIENT_EMAIL, | |
"scope": SCOPES.join(' '), | |
"aud": TOKEN_URL, | |
"exp": Math.floor(Date.now() / 1000) + 360, | |
"iat": Math.floor((new Date().getTime()) / 1000) | |
}; | |
const headerBase64 = base64EncodeWebSafe(JSON.stringify(header)); | |
const jwtClaimBase64 = base64EncodeWebSafe(JSON.stringify(claimSet)); | |
const signatureInput = `${headerBase64}.${jwtClaimBase64}`; | |
const signature = await computeRsaSha256Signature(signatureInput); | |
const jwt = `${signatureInput}.${signature}`; | |
var options = { | |
"method": "POST", | |
"headers": { "Content-Type": "application/x-www-form-urlencoded" }, | |
"body": new URLSearchParams({ | |
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", | |
"assertion": jwt | |
}), | |
}; | |
const response = await fetch(TOKEN_URL, options); | |
if (!response.ok) { | |
const errorData = response.json(); | |
throw new Error(`Error updating ?`); | |
} | |
const data = response.json(); | |
return data; | |
} | |
async function updateUser() { | |
try { | |
const jwt = await getAccessToken(); | |
const idToken = jwt.access_token; | |
return new Response(idToken, {status:200}); | |
} catch (error) { | |
return new Response("Error: " + error.message, { status: 500 }); | |
} | |
} | |
// Test the Worker by updating a specific user's attribute | |
addEventListener("fetch", (event) => { | |
event.respondWith(updateUser()); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment