Skip to content

Instantly share code, notes, and snippets.

@MegaManSec
Last active September 1, 2024 20:53
Show Gist options
  • Save MegaManSec/2b0f430337a25b8ddd7218c84b88a472 to your computer and use it in GitHub Desktop.
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).
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