Last active
September 4, 2025 15:25
-
-
Save fadlee/0ab1f679565ebd82463f38f426428121 to your computer and use it in GitHub Desktop.
Configure private and public key for Convex Auth, submit directly to Convex backend configured in .env.local
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 characters
// run this using: curl -s https://rawurl... | node | |
import { generateKeyPair, createPublicKey } from 'crypto'; | |
import { readFile } from 'fs/promises'; | |
import { promisify } from 'util'; | |
import path from 'path'; | |
import { fileURLToPath } from 'url'; | |
import https from 'https'; | |
import http from 'http'; | |
// Get the directory of the current module | |
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
// Function to load environment variables from a file | |
async function loadEnvFile(filePath) { | |
try { | |
const content = await readFile(filePath, 'utf8'); | |
const lines = content.split('\n'); | |
for (const line of lines) { | |
// Skip comments and empty lines | |
if (!line || line.startsWith('#')) continue; | |
const [key, ...valueParts] = line.split('='); | |
const value = valueParts.join('='); // Handle values that might contain = characters | |
if (key && value) { | |
process.env[key.trim()] = value.trim(); | |
} | |
} | |
} catch (error) { | |
console.error(`Error loading env file: ${error.message}`); | |
} | |
} | |
// Load environment variables from .env.local | |
await loadEnvFile(path.join(__dirname, '.env.local')); | |
// Promisify the generateKeyPair function from crypto | |
const generateKeyPairAsync = promisify(generateKeyPair); | |
// Generate RSA key pair | |
const { privateKey, publicKey } = await generateKeyPairAsync('rsa', { | |
modulusLength: 2048, | |
publicKeyEncoding: { | |
type: 'spki', | |
format: 'pem' | |
}, | |
privateKeyEncoding: { | |
type: 'pkcs8', | |
format: 'pem' | |
} | |
}); | |
// Convert public key to JWK format using Node.js crypto module | |
// Parse the PEM public key | |
const pubKeyObj = createPublicKey(publicKey); | |
// Export as JWK | |
const jwk = pubKeyObj.export({ format: 'jwk' }); | |
// Add the 'use' property for JWT signing | |
jwk.use = 'sig'; | |
// Create the JWKS | |
const jwks = JSON.stringify({ keys: [jwk] }); | |
// Format the private key for storage | |
const formattedPrivateKey = privateKey.trimEnd().replace(/\n/g, " "); | |
// Get environment variables | |
const baseUrl = process.env.CONVEX_SELF_HOSTED_URL; | |
const adminKey = process.env.CONVEX_SELF_HOSTED_ADMIN_KEY; | |
if (!baseUrl || !adminKey) { | |
console.error('Missing required environment variables. Please check your .env.local file.'); | |
process.exit(1); | |
} | |
// Prepare the payload | |
const payload = { | |
changes: [ | |
{ | |
name: "JWT_PRIVATE_KEY", | |
value: formattedPrivateKey | |
}, | |
{ | |
name: "JWKS", | |
value: jwks | |
} | |
] | |
}; | |
// Log what we're about to do | |
console.log(`Submitting keys to ${baseUrl}/api/update_environment_variables`); | |
// Send the request | |
try { | |
console.log('Request payload:', JSON.stringify(payload, null, 2)); | |
// Function to make HTTP/HTTPS requests | |
function makeRequest(url, options, data) { | |
return new Promise((resolve, reject) => { | |
const isHttps = url.startsWith('https'); | |
const requestLib = isHttps ? https : http; | |
const urlObj = new URL(url); | |
const requestOptions = { | |
hostname: urlObj.hostname, | |
port: urlObj.port || (isHttps ? 443 : 80), | |
path: urlObj.pathname + urlObj.search, | |
method: options.method || 'GET', | |
headers: options.headers || {} | |
}; | |
const req = requestLib.request(requestOptions, (res) => { | |
let responseData = ''; | |
res.on('data', (chunk) => { | |
responseData += chunk; | |
}); | |
res.on('end', () => { | |
resolve({ | |
status: res.statusCode, | |
headers: res.headers, | |
text: () => Promise.resolve(responseData), | |
json: () => { | |
try { | |
return Promise.resolve(JSON.parse(responseData)); | |
} catch (error) { | |
return Promise.reject(new Error('Invalid JSON response')); | |
} | |
}, | |
ok: res.statusCode >= 200 && res.statusCode < 300 | |
}); | |
}); | |
}); | |
req.on('error', (error) => { | |
reject(error); | |
}); | |
if (data) { | |
req.write(data); | |
} | |
req.end(); | |
}); | |
} | |
// Make the request | |
const response = await makeRequest(`${baseUrl}/api/update_environment_variables`, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Convex ${adminKey}` | |
} | |
}, JSON.stringify(payload)); | |
console.log('Response status:', response.status); | |
console.log('Response headers:', JSON.stringify(response.headers, null, 2)); | |
// Get the response as text first | |
const responseText = await response.text(); | |
console.log('Response body (raw):', responseText); | |
if (!response.ok) { | |
throw new Error(`API request failed with status ${response.status}: ${responseText}`); | |
} | |
// Try to parse as JSON if possible | |
let data; | |
try { | |
if (responseText.trim()) { | |
data = JSON.parse(responseText); | |
console.log('Response data (parsed):', data); | |
} else { | |
console.log('Response was empty but status was OK'); | |
} | |
} catch (parseError) { | |
console.log('Could not parse response as JSON, but status was OK'); | |
} | |
console.log('Keys successfully submitted to Convex API'); | |
} catch (error) { | |
console.error('Error submitting keys to Convex API:', error.message); | |
process.exit(1); | |
} | |
// Also output to console for reference | |
console.log('\nGenerated keys:'); | |
console.log(`JWT_PRIVATE_KEY="${formattedPrivateKey}"`); | |
console.log(`JWKS=${jwks}`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment