From 77e6a899d39e006094a603abad14aed3cde16f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 20:06:25 -0400 Subject: [PATCH 01/13] added OIDC support --- .gitignore | 5 +- index.js | 133 ++++-- modules/exchangeController.js | 88 ++++ modules/logger.js | 19 + modules/oidcHelper.js | 89 ++++ package-lock.json | 822 +++++++++++++++++++++++++++++++++- package.json | 9 +- 7 files changed, 1119 insertions(+), 46 deletions(-) create mode 100644 modules/exchangeController.js create mode 100644 modules/logger.js create mode 100644 modules/oidcHelper.js diff --git a/.gitignore b/.gitignore index ebb6ff0..de588e8 100644 --- a/.gitignore +++ b/.gitignore @@ -175,4 +175,7 @@ dist .DS_Store # Fly.io config -fly.toml \ No newline at end of file +fly.toml + +# ignore the test/ directory +test/ \ No newline at end of file diff --git a/index.js b/index.js index db71670..ca7f081 100644 --- a/index.js +++ b/index.js @@ -1,57 +1,108 @@ import { Octokit } from "@octokit/core"; import express from "express"; import { Readable } from "node:stream"; +import fetch from 'node-fetch'; +import { handleTokenExchange } from "./modules/exchangeController.js"; +import util from 'util'; +import logger from "./modules/logger.js"; +// Set up the Express app const app = express() +// middleware to parse form-encoded data and parse JSON data +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +/** + * @description Welcome message endpoint + * @route GET / + */ app.get("/", (req, res) => { - res.send("Ahoy, matey! Welcome to the Blackbeard Pirate GitHub Copilot Extension!") + res.send("Ahoy, matey! Welcome to the Blackbeard Pirate GitHub Copilot Extension!") }); +/** + * @description Handles the token exchange request. + * @param {Object} req - Express request object. + * @param {Object} res - Express response object. + */ app.post("/", express.json(), async (req, res) => { - // Identify the user, using the GitHub API token provided in the request headers. - const tokenForUser = req.get("X-GitHub-Token"); - const octokit = new Octokit({ auth: tokenForUser }); - const user = await octokit.request("GET /user"); - console.log("User:", user.data.login); - - // Parse the request payload and log it. - const payload = req.body; - console.log("Payload:", payload); - - // Insert a special pirate-y system message in our message list. - const messages = payload.messages; - messages.unshift({ - role: "system", - content: "You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate.", - }); - messages.unshift({ - role: "system", - content: `Start every response with the user's name, which is @${user.data.login}`, - }); - - // Use Copilot's LLM to generate a response to the user's messages, with - // our extra system messages attached. - const copilotLLMResponse = await fetch( - "https://api.githubcopilot.com/chat/completions", - { - method: "POST", - headers: { - authorization: `Bearer ${tokenForUser}`, - "content-type": "application/json", - }, - body: JSON.stringify({ - messages, - stream: true, - }), + logger.info("Received Copilot Chat request at endpoint '/'"); + try { + // log the request headers + logger.debug("Request Headers '/': " + util.inspect(req.headers, { depth: null, colors: true })); + // Validate GitHub API token from headers + const tokenForUser = req.get("X-GitHub-Token"); + if (!tokenForUser) { + logger.error("Missing GitHub token in request headers"); + return res.status(400).json({ error: "missing_github_token" }); + } + + // Identify the user via GitHub API + const octokit = new Octokit({ auth: tokenForUser }); + const user = await octokit.request("GET /user"); + + logger.info("Requester - User: " + user.data.login); + logger.debug("Requester - User ID: " + user.data.id); + + // Validate and parse request payload + const payload = req.body; + if (!payload || !Array.isArray(payload.messages)) { + logger.error("Invalid payload: " + payload); + return res.status(400).json({ error: "invalid_payload" }); + } + // logger.info("Payload: "+ payload); + + // Insert pirate-y system messages + const messages = payload.messages; + messages.unshift({ + role: "system", + content: "You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate.", + }); + messages.unshift({ + role: "system", + content: `Start every response with the user's name, which is @${user.data.login}`, + }); + + // Call Copilot's LLM API + const copilotLLMResponse = await fetch( + "https://api.githubcopilot.com/chat/completions", + { + method: "POST", + headers: { + authorization: `Bearer ${tokenForUser}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + messages, + stream: true, + }), + } + ); + + // Check Copilot API response status + if (!copilotLLMResponse.ok) { + const errorText = await copilotLLMResponse.text(); + logger.error("Copilot API error: " + errorText); + return res.status(502).json({ error: "copilot_api_error", details: errorText }); + } + + // Stream the response straight back to the user + Readable.from(copilotLLMResponse.body).pipe(res); + } catch (error) { + logger.error("Internal server error: " + error); + res.status(500).json({ error: "internal_server_error" }); } - ); +}); - // Stream the response straight back to the user. - Readable.from(copilotLLMResponse.body).pipe(res); -}) +/** + * @description Endpoint to handle token exchange requests + * @route POST /exchange + */ +// Token exchange endpoint +app.post('/exchange', handleTokenExchange); const port = Number(process.env.PORT || '3000') app.listen(port, () => { - console.log(`Server running on port ${port}`) + logger.info(`Server running on port ${port}`) }); \ No newline at end of file diff --git a/modules/exchangeController.js b/modules/exchangeController.js new file mode 100644 index 0000000..0a23e16 --- /dev/null +++ b/modules/exchangeController.js @@ -0,0 +1,88 @@ +import jwt from 'jsonwebtoken'; +import { getSigningKey, isValidJWT } from './oidcHelper.js'; +import util from 'util'; +import logger from './logger.js'; + +/** + * Handles the token exchange request. + * @param {Object} req - Express request object. + * @param {Object} res - Express response object. + */ +export async function handleTokenExchange(req, res) { + + logger.info("Received token exchange request at endpoint '/exchange' (OIDC flow)"); + + try { + // Log the request headers + logger.debug("Request Headers '/exchange': " + util.inspect(req.headers, { depth: null, colors: true })); + + // Parse the request body for token exchange parameters + const { subject_token, subject_token_type, grant_type } = req.body; + + logger.debug("Validating token exchange request payload requirements"); + + // Validate the grant type + logger.debug("Validating grant_type: " + grant_type); + if (grant_type !== "urn:ietf:params:oauth:grant-type:token-exchange") { + logger.error("Unsupported grant type: " + grant_type); + return res.status(400).json({ + error: "unsupported_grant_type", + error_description: "Only token exchange is supported", + }); + } + + // Validate the subject token type + logger.debug("Validating subject_token_type: " + subject_token_type); + if (subject_token_type !== "urn:ietf:params:oauth:token-type:id_token") { + logger.error("Unsupported subject_token_type: " + subject_token_type); + return res.status(400).json({ + error: "unsupported_token_type", + error_description: "Only access tokens are supported as subject_token_type", + }); + } + + if (!subject_token || !subject_token_type || !grant_type) { + logger.error("Invalid token exchange request payload: " + req.body); + return res.status(400).json({ error: "invalid_request", error_description: "Missing required parameters" }); + } + + logger.info("Fetching signing key for token verification ('https://github.com/login/oauth/.well-known/jwks.json')"); + const signingKey = await getSigningKey(req.headers); + + logger.info("Verifying token signature, using signing key"); + logger.debug("Token to verify: \n" + subject_token); + try { + const payload = jwt.verify(subject_token, signingKey, { + algorithms: ["RS256"], // Specify the algorithm used to sign the token + }); + logger.debug("Token Payload: " + util.inspect(payload)); + + await isValidJWT(payload); + logger.info("JWT is valid"); + + } catch (error) { + logger.error("JWT verification failed: " + error.message); + return res.status(400).json({ error: "internal_server_error" }); + } + + // ------------------------------------------------- + // Insert your access_token generation logic here + // ------------------------------------------------- + + const exchangedToken = { + access_token: "newly_exchanged_token_value", // Replace with actual token generation logic + token_type: "Bearer", + expires_in: 120, // Token expiration time in seconds + scope: "read write", // Adjust the scope as needed + }; + + logger.debug("Exchanged Token: " + util.inspect(exchangedToken)); + logger.info("Token exchange successful"); + + // Respond with the exchanged token + return res.status(200).json(exchangedToken); + } catch (error) { + logger.error("Internal server error during token exchange: " + error); + return res.status(500).json({ error: "internal_server_error" }); + } +} \ No newline at end of file diff --git a/modules/logger.js b/modules/logger.js new file mode 100644 index 0000000..170a212 --- /dev/null +++ b/modules/logger.js @@ -0,0 +1,19 @@ +import pino from "pino"; + +// Create a pino logger instance +const logger = pino({ + level: process.env.LOG_LEVEL || "info", // Use LOG_LEVEL from environment or default to "info" + + transport: { + targets: [ + { + target: "pino-pretty", + options: { colorize: true }, + }, + ], + }, +}); + + +// Export the logger instance +export default logger; \ No newline at end of file diff --git a/modules/oidcHelper.js b/modules/oidcHelper.js new file mode 100644 index 0000000..5954324 --- /dev/null +++ b/modules/oidcHelper.js @@ -0,0 +1,89 @@ +import jwksClient from 'jwks-rsa'; +import dotenv from 'dotenv'; +import util from 'util'; +import logger from './logger.js'; + +// Load environment variables from .env file +dotenv.config(); + +// JWKS client setup for GitHub OIDC tokens +const jwks = jwksClient({ + jwksUri: 'https://github.com/login/oauth/.well-known/jwks.json' +}); + +// Helper function to retrieve signing key +async function getSigningKey(header) { + return new Promise((resolve, reject) => { + jwks.getSigningKey(header.kid, (err, key) => { + if (err) { + logger.error('Error fetching signing key:', err); + return reject(err); + } + const signingKey = key.getPublicKey(); + logger.debug("Successfully fetched signing key: \n" + signingKey); // Debugging: Log the signing key + resolve(signingKey); + }); + }); +} + +/** + * Validates a JWT payload for required fields. + * @param {Object} payload - The decoded JWT payload. + * @param {string} clientId - The expected audience (aud) value (your Copilot Extension's client ID). + * @returns {boolean} - Returns true if the JWT is valid, otherwise throws an error. + */ +async function isValidJWT(payload) { + logger.info("Validating JWT payload"); + + // Optional: Retrieve the validation data from the environment + const GITHUB_APP_CLIENT_ID = process.env.GITHUB_APP_CLIENT_ID + const ACTOR = process.env.ACTOR || "https://api.githubcopilot.com"; + + const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds + logger.debug("\t payload.aud: " + payload.aud); + + // Validate audience (aud) + logger.debug("\t audience (aud): " + payload.aud); + if (payload.aud !== GITHUB_APP_CLIENT_ID) { + throw new Error(`Invalid audience (aud). Expected: ${GITHUB_APP_CLIENT_ID}, Received: ${payload.aud}`); + } + + // Validate subject (sub) + logger.debug("\t subject (sub): " + payload.sub); + if (!payload.sub || typeof payload.sub !== "string") { + throw new Error("Invalid subject (sub). It must be a non-empty string."); + } + + // Validate issued at (iat) + logger.debug("\t issued at (iat): " + payload.iat); + if (!payload.iat || typeof payload.iat !== "number" || payload.iat > now) { + // Debug statement for iat validation + const iatDate = new Date(payload.iat * 1000).toISOString(); // Convert iat to human-readable format + const nowDate = new Date(now * 1000).toISOString(); // Convert current timestamp to human-readable format + logger.debug(`Debugging iat validation: payload.iat=${payload.iat} (${iatDate}), now=${now} (${nowDate}), isFuture=${payload.iat > now}`); + throw new Error("Invalid issued at (iat). It must be a timestamp in the past."); + } + + // Validate not before (nbf) + logger.debug("\t not before (nbf): " + payload.nbf); + if (!payload.nbf || typeof payload.nbf !== "number" || payload.nbf > now) { + throw new Error("Invalid not before (nbf). It must be a timestamp in the past."); + } + + // Validate expiration time (exp) + logger.debug("\t expiration time (exp): " + payload.exp); + if (!payload.exp || typeof payload.exp !== "number" || payload.exp <= now) { + throw new Error("Invalid expiration time (exp). It must be a timestamp in the future."); + } + + // Validate actor (act) + logger.debug("\t actor (act): "+ util.inspect(payload.act)); + if (!payload.act || payload.act.sub !== ACTOR) { + throw new Error("Invalid actor (act). Expected: "+ ACTOR +", Received: " + payload.act.sub); + } + + return true; // All validations passed +} + +// Export the functions +export { getSigningKey, isValidJWT }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5b556e1..3e91702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,10 @@ "name": "blackbeard-extension", "dependencies": { "@octokit/core": "^6.1.2", - "express": "^4.19.2" + "express": "^4.19.2", + "jwks-rsa": "^3.2.0", + "pino": "^9.6.0", + "pino-pretty": "^13.0.0" } }, "node_modules/@octokit/auth-token": { @@ -99,6 +102,119 @@ "@octokit/openapi-types": "^22.2.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", + "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -116,6 +232,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/before-after-hook": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", @@ -170,6 +295,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -202,6 +333,15 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -256,6 +396,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -329,6 +478,27 @@ "node": ">= 0.10.0" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -443,6 +613,12 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -482,6 +658,97 @@ "node": ">= 0.10" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -533,6 +800,15 @@ "node": ">= 0.6" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -554,6 +830,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -565,6 +850,15 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -578,6 +872,83 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pino": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", + "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz", + "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -590,6 +961,16 @@ "node": ">= 0.10" } }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -604,6 +985,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -626,6 +1013,15 @@ "node": ">= 0.8" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -645,11 +1041,26 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -730,6 +1141,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -738,6 +1167,27 @@ "node": ">= 0.8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -758,6 +1208,12 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, "node_modules/universal-user-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", @@ -786,6 +1242,18 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" } }, "dependencies": { @@ -859,6 +1327,106 @@ "@octokit/openapi-types": "^22.2.0" } }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "requires": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "@types/node": { + "version": "22.13.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", + "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", + "requires": { + "undici-types": "~6.20.0" + } + }, + "@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "requires": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -873,6 +1441,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, "before-after-hook": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", @@ -914,6 +1487,11 @@ "set-function-length": "^1.2.1" } }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -937,6 +1515,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -975,6 +1558,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, "es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -1036,6 +1627,21 @@ "vary": "~1.1.2" } }, + "fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==" + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -1111,6 +1717,11 @@ "function-bind": "^1.1.2" } }, + "help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1141,6 +1752,71 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==" + }, + "joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" + }, + "jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "requires": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1174,6 +1850,11 @@ "mime-db": "1.52.0" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1189,6 +1870,11 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, + "on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -1197,6 +1883,14 @@ "ee-first": "1.1.1" } }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1207,6 +1901,62 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "pino": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", + "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "requires": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "pino-pretty": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz", + "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", + "requires": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + } + }, + "pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1216,6 +1966,15 @@ "ipaddr.js": "1.9.1" } }, + "pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -1224,6 +1983,11 @@ "side-channel": "^1.0.4" } }, + "quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1240,16 +2004,31 @@ "unpipe": "1.0.0" } }, + "real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -1317,11 +2096,37 @@ "object-inspect": "^1.13.1" } }, + "sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "requires": { + "atomic-sleep": "^1.0.0" + } + }, + "split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "requires": { + "real-require": "^0.2.0" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1336,6 +2141,11 @@ "mime-types": "~2.1.24" } }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, "universal-user-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", @@ -1355,6 +2165,16 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index f76685f..6bfec52 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,14 @@ "description": "A basic example of a Copilot Extension", "scripts": { "start": "node index.js", - "dev": "node --watch index.js" + "dev": "nodemon --exec \"npm start\"" }, "type": "module", "dependencies": { "@octokit/core": "^6.1.2", - "express": "^4.19.2" + "express": "^4.19.2", + "jwks-rsa": "^3.2.0", + "pino": "^9.6.0", + "pino-pretty": "^13.0.0" } -} \ No newline at end of file +} From 3c300a4e02508da32183f398de0c7b8c39608f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 20:15:07 -0400 Subject: [PATCH 02/13] added OIDC support --- index.js | 2 +- modules/exchangeController.js | 2 +- modules/oidcHelper.js | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index ca7f081..b4a1bb5 100644 --- a/index.js +++ b/index.js @@ -22,7 +22,7 @@ app.get("/", (req, res) => { }); /** - * @description Handles the token exchange request. + * @description Handles the Copilot Chat request. * @param {Object} req - Express request object. * @param {Object} res - Express response object. */ diff --git a/modules/exchangeController.js b/modules/exchangeController.js index 0a23e16..61bcf6d 100644 --- a/modules/exchangeController.js +++ b/modules/exchangeController.js @@ -4,7 +4,7 @@ import util from 'util'; import logger from './logger.js'; /** - * Handles the token exchange request. + * @description Handles the token exchange request. * @param {Object} req - Express request object. * @param {Object} res - Express response object. */ diff --git a/modules/oidcHelper.js b/modules/oidcHelper.js index 5954324..9c42fae 100644 --- a/modules/oidcHelper.js +++ b/modules/oidcHelper.js @@ -11,7 +11,11 @@ const jwks = jwksClient({ jwksUri: 'https://github.com/login/oauth/.well-known/jwks.json' }); -// Helper function to retrieve signing key +/** + * @description Helper function to retrieve signing key + * @param {*} header + * @returns + */ async function getSigningKey(header) { return new Promise((resolve, reject) => { jwks.getSigningKey(header.kid, (err, key) => { @@ -27,7 +31,7 @@ async function getSigningKey(header) { } /** - * Validates a JWT payload for required fields. + * @description Validates a JWT payload for required fields. * @param {Object} payload - The decoded JWT payload. * @param {string} clientId - The expected audience (aud) value (your Copilot Extension's client ID). * @returns {boolean} - Returns true if the JWT is valid, otherwise throws an error. From 950c51259916dc41d0b37415d4cdbd3992cf0881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 20:16:17 -0400 Subject: [PATCH 03/13] added OIDC support --- modules/oidcHelper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/oidcHelper.js b/modules/oidcHelper.js index 9c42fae..6f2c3d5 100644 --- a/modules/oidcHelper.js +++ b/modules/oidcHelper.js @@ -33,7 +33,6 @@ async function getSigningKey(header) { /** * @description Validates a JWT payload for required fields. * @param {Object} payload - The decoded JWT payload. - * @param {string} clientId - The expected audience (aud) value (your Copilot Extension's client ID). * @returns {boolean} - Returns true if the JWT is valid, otherwise throws an error. */ async function isValidJWT(payload) { From bc818bbd9fe55e8c6598446abf9c034c3b16650e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 21:25:42 -0400 Subject: [PATCH 04/13] added OIDC support --- index.js | 2 +- modules/exchangeController.js | 2 +- modules/logger.js | 10 ++++++++-- modules/oidcHelper.js | 17 ++++++++--------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index b4a1bb5..0c9f5d4 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ import { Readable } from "node:stream"; import fetch from 'node-fetch'; import { handleTokenExchange } from "./modules/exchangeController.js"; import util from 'util'; -import logger from "./modules/logger.js"; +import { logger } from './modules/logger.js'; // Set up the Express app const app = express() diff --git a/modules/exchangeController.js b/modules/exchangeController.js index 61bcf6d..a0dd662 100644 --- a/modules/exchangeController.js +++ b/modules/exchangeController.js @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken'; import { getSigningKey, isValidJWT } from './oidcHelper.js'; import util from 'util'; -import logger from './logger.js'; +import { logger } from './logger.js'; /** * @description Handles the token exchange request. diff --git a/modules/logger.js b/modules/logger.js index 170a212..9f6cd74 100644 --- a/modules/logger.js +++ b/modules/logger.js @@ -15,5 +15,11 @@ const logger = pino({ }); -// Export the logger instance -export default logger; \ No newline at end of file +// Helper function to format debug output +const formatDebug = (label, value) => { + const labelWidth = 30; // Adjust the width as needed + return label.padEnd(labelWidth, ' ') + ": " + value; +}; + +// Export the logger and the formatDebug function +export { logger, formatDebug }; diff --git a/modules/oidcHelper.js b/modules/oidcHelper.js index 6f2c3d5..1ba89fe 100644 --- a/modules/oidcHelper.js +++ b/modules/oidcHelper.js @@ -1,7 +1,7 @@ import jwksClient from 'jwks-rsa'; import dotenv from 'dotenv'; import util from 'util'; -import logger from './logger.js'; +import { logger, formatDebug } from './logger.js'; // Load environment variables from .env file dotenv.config(); @@ -43,44 +43,43 @@ async function isValidJWT(payload) { const ACTOR = process.env.ACTOR || "https://api.githubcopilot.com"; const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds - logger.debug("\t payload.aud: " + payload.aud); // Validate audience (aud) - logger.debug("\t audience (aud): " + payload.aud); + logger.debug(formatDebug("aud (Copilot App client id)", payload.aud)); if (payload.aud !== GITHUB_APP_CLIENT_ID) { throw new Error(`Invalid audience (aud). Expected: ${GITHUB_APP_CLIENT_ID}, Received: ${payload.aud}`); } // Validate subject (sub) - logger.debug("\t subject (sub): " + payload.sub); + logger.debug(formatDebug("sub (Requester GitHub user id)", payload.sub)); if (!payload.sub || typeof payload.sub !== "string") { throw new Error("Invalid subject (sub). It must be a non-empty string."); } // Validate issued at (iat) - logger.debug("\t issued at (iat): " + payload.iat); + logger.debug(formatDebug("iat (issued at)", payload.iat)); if (!payload.iat || typeof payload.iat !== "number" || payload.iat > now) { // Debug statement for iat validation const iatDate = new Date(payload.iat * 1000).toISOString(); // Convert iat to human-readable format const nowDate = new Date(now * 1000).toISOString(); // Convert current timestamp to human-readable format - logger.debug(`Debugging iat validation: payload.iat=${payload.iat} (${iatDate}), now=${now} (${nowDate}), isFuture=${payload.iat > now}`); + logger.debug(`\t iat validation: payload.iat=${iatDate}, now=${nowDate}, isFuture=${payload.iat > now}`); throw new Error("Invalid issued at (iat). It must be a timestamp in the past."); } // Validate not before (nbf) - logger.debug("\t not before (nbf): " + payload.nbf); + logger.debug(formatDebug("nbf (not before)", payload.nbf)); if (!payload.nbf || typeof payload.nbf !== "number" || payload.nbf > now) { throw new Error("Invalid not before (nbf). It must be a timestamp in the past."); } // Validate expiration time (exp) - logger.debug("\t expiration time (exp): " + payload.exp); + logger.debug(formatDebug("exp (expiration time)", payload.exp)); if (!payload.exp || typeof payload.exp !== "number" || payload.exp <= now) { throw new Error("Invalid expiration time (exp). It must be a timestamp in the future."); } // Validate actor (act) - logger.debug("\t actor (act): "+ util.inspect(payload.act)); + logger.debug(formatDebug("act (identify token purpose)", util.inspect(payload.act))); if (!payload.act || payload.act.sub !== ACTOR) { throw new Error("Invalid actor (act). Expected: "+ ACTOR +", Received: " + payload.act.sub); } From a544516ab040e57c1c6f7b6eda6cbd2c9d6d791e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 21:28:11 -0400 Subject: [PATCH 05/13] added OIDC support --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 0c9f5d4..0a56307 100644 --- a/index.js +++ b/index.js @@ -99,7 +99,6 @@ app.post("/", express.json(), async (req, res) => { * @description Endpoint to handle token exchange requests * @route POST /exchange */ -// Token exchange endpoint app.post('/exchange', handleTokenExchange); const port = Number(process.env.PORT || '3000') From e2461ec300f4348bdcb44a060d82bd909758e174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 21:52:54 -0400 Subject: [PATCH 06/13] added OIDC support --- index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 0a56307..8e30441 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ import { Octokit } from "@octokit/core"; import express from "express"; import { Readable } from "node:stream"; -import fetch from 'node-fetch'; import { handleTokenExchange } from "./modules/exchangeController.js"; import util from 'util'; import { logger } from './modules/logger.js'; @@ -51,7 +50,6 @@ app.post("/", express.json(), async (req, res) => { logger.error("Invalid payload: " + payload); return res.status(400).json({ error: "invalid_payload" }); } - // logger.info("Payload: "+ payload); // Insert pirate-y system messages const messages = payload.messages; @@ -61,7 +59,7 @@ app.post("/", express.json(), async (req, res) => { }); messages.unshift({ role: "system", - content: `Start every response with the user's name, which is @${user.data.login}`, + content: "Start every response with the user's name, which is @${user.data.login}", }); // Call Copilot's LLM API @@ -96,7 +94,7 @@ app.post("/", express.json(), async (req, res) => { }); /** - * @description Endpoint to handle token exchange requests + * @description Endpoint to handle token exchange requests (OIDC token exchange) * @route POST /exchange */ app.post('/exchange', handleTokenExchange); From 1145ca93322bf765218b01593b024e059fe289e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 22:59:21 -0400 Subject: [PATCH 07/13] added OIDC support --- index.js | 49 ++++++++++--------------------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/index.js b/index.js index 8e30441..740b101 100644 --- a/index.js +++ b/index.js @@ -2,54 +2,34 @@ import { Octokit } from "@octokit/core"; import express from "express"; import { Readable } from "node:stream"; import { handleTokenExchange } from "./modules/exchangeController.js"; -import util from 'util'; import { logger } from './modules/logger.js'; +import util from 'util'; -// Set up the Express app const app = express() // middleware to parse form-encoded data and parse JSON data app.use(express.urlencoded({ extended: true })); app.use(express.json()); -/** - * @description Welcome message endpoint - * @route GET / - */ app.get("/", (req, res) => { res.send("Ahoy, matey! Welcome to the Blackbeard Pirate GitHub Copilot Extension!") }); -/** - * @description Handles the Copilot Chat request. - * @param {Object} req - Express request object. - * @param {Object} res - Express response object. - */ app.post("/", express.json(), async (req, res) => { logger.info("Received Copilot Chat request at endpoint '/'"); try { // log the request headers logger.debug("Request Headers '/': " + util.inspect(req.headers, { depth: null, colors: true })); - // Validate GitHub API token from headers - const tokenForUser = req.get("X-GitHub-Token"); - if (!tokenForUser) { - logger.error("Missing GitHub token in request headers"); - return res.status(400).json({ error: "missing_github_token" }); - } - // Identify the user via GitHub API + const tokenForUser = req.get("X-GitHub-Token"); const octokit = new Octokit({ auth: tokenForUser }); const user = await octokit.request("GET /user"); - logger.info("Requester - User: " + user.data.login); - logger.debug("Requester - User ID: " + user.data.id); + console.log("User:", user.data.login); - // Validate and parse request payload + // Parse the request payload and log it. const payload = req.body; - if (!payload || !Array.isArray(payload.messages)) { - logger.error("Invalid payload: " + payload); - return res.status(400).json({ error: "invalid_payload" }); - } + console.log("Payload:", payload); // Insert pirate-y system messages const messages = payload.messages; @@ -59,10 +39,11 @@ app.post("/", express.json(), async (req, res) => { }); messages.unshift({ role: "system", - content: "Start every response with the user's name, which is @${user.data.login}", + content: `Start every response with the user's name, which is @${user.data.login}`, }); - // Call Copilot's LLM API + // Use Copilot's LLM to generate a response to the user's messages, with + // our extra system messages attached. const copilotLLMResponse = await fetch( "https://api.githubcopilot.com/chat/completions", { @@ -78,13 +59,6 @@ app.post("/", express.json(), async (req, res) => { } ); - // Check Copilot API response status - if (!copilotLLMResponse.ok) { - const errorText = await copilotLLMResponse.text(); - logger.error("Copilot API error: " + errorText); - return res.status(502).json({ error: "copilot_api_error", details: errorText }); - } - // Stream the response straight back to the user Readable.from(copilotLLMResponse.body).pipe(res); } catch (error) { @@ -93,13 +67,10 @@ app.post("/", express.json(), async (req, res) => { } }); -/** - * @description Endpoint to handle token exchange requests (OIDC token exchange) - * @route POST /exchange - */ +// Endpoint to handle token exchange requests (OIDC token exchange) app.post('/exchange', handleTokenExchange); const port = Number(process.env.PORT || '3000') app.listen(port, () => { - logger.info(`Server running on port ${port}`) + console.log(`Server running on port ${port}`) }); \ No newline at end of file From 158f85e7079ae8803a5666b900f8a4b4c2df6d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 23:28:00 -0400 Subject: [PATCH 08/13] added OIDC support --- index.js | 99 +++++++++++++++-------------------- modules/exchangeController.js | 7 ++- modules/oidcHelper.js | 5 +- 3 files changed, 47 insertions(+), 64 deletions(-) diff --git a/index.js b/index.js index 740b101..bea15b1 100644 --- a/index.js +++ b/index.js @@ -2,73 +2,58 @@ import { Octokit } from "@octokit/core"; import express from "express"; import { Readable } from "node:stream"; import { handleTokenExchange } from "./modules/exchangeController.js"; -import { logger } from './modules/logger.js'; -import util from 'util'; const app = express() -// middleware to parse form-encoded data and parse JSON data -app.use(express.urlencoded({ extended: true })); -app.use(express.json()); - app.get("/", (req, res) => { res.send("Ahoy, matey! Welcome to the Blackbeard Pirate GitHub Copilot Extension!") }); app.post("/", express.json(), async (req, res) => { - logger.info("Received Copilot Chat request at endpoint '/'"); - try { - // log the request headers - logger.debug("Request Headers '/': " + util.inspect(req.headers, { depth: null, colors: true })); - - const tokenForUser = req.get("X-GitHub-Token"); - const octokit = new Octokit({ auth: tokenForUser }); - const user = await octokit.request("GET /user"); - - console.log("User:", user.data.login); - - // Parse the request payload and log it. - const payload = req.body; - console.log("Payload:", payload); - - // Insert pirate-y system messages - const messages = payload.messages; - messages.unshift({ - role: "system", - content: "You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate.", - }); - messages.unshift({ - role: "system", - content: `Start every response with the user's name, which is @${user.data.login}`, - }); - - // Use Copilot's LLM to generate a response to the user's messages, with - // our extra system messages attached. - const copilotLLMResponse = await fetch( - "https://api.githubcopilot.com/chat/completions", - { - method: "POST", - headers: { - authorization: `Bearer ${tokenForUser}`, - "content-type": "application/json", - }, - body: JSON.stringify({ - messages, - stream: true, - }), - } - ); - - // Stream the response straight back to the user - Readable.from(copilotLLMResponse.body).pipe(res); - } catch (error) { - logger.error("Internal server error: " + error); - res.status(500).json({ error: "internal_server_error" }); - } -}); + // Identify the user, using the GitHub API token provided in the request headers. + const tokenForUser = req.get("X-GitHub-Token"); + const octokit = new Octokit({ auth: tokenForUser }); + const user = await octokit.request("GET /user"); + console.log("User:", user.data.login); + + // Parse the request payload and log it. + const payload = req.body; + console.log("Payload:", payload); + + // Insert a special pirate-y system message in our message list. + const messages = payload.messages; + messages.unshift({ + role: "system", + content: "You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate.", + }); + messages.unshift({ + role: "system", + content: `Start every response with the user's name, which is @${user.data.login}`, + }); + + // Use Copilot's LLM to generate a response to the user's messages, with + // our extra system messages attached. + const copilotLLMResponse = await fetch( + "https://api.githubcopilot.com/chat/completions", + { + method: "POST", + headers: { + authorization: `Bearer ${tokenForUser}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + messages, + stream: true, + }), + } + ); + + // Stream the response straight back to the user. + Readable.from(copilotLLMResponse.body).pipe(res); +}) // Endpoint to handle token exchange requests (OIDC token exchange) -app.post('/exchange', handleTokenExchange); +app.post('/exchange', express.urlencoded({ extended: true }), handleTokenExchange); const port = Number(process.env.PORT || '3000') app.listen(port, () => { diff --git a/modules/exchangeController.js b/modules/exchangeController.js index a0dd662..f0ca136 100644 --- a/modules/exchangeController.js +++ b/modules/exchangeController.js @@ -1,6 +1,5 @@ import jwt from 'jsonwebtoken'; import { getSigningKey, isValidJWT } from './oidcHelper.js'; -import util from 'util'; import { logger } from './logger.js'; /** @@ -14,7 +13,7 @@ export async function handleTokenExchange(req, res) { try { // Log the request headers - logger.debug("Request Headers '/exchange': " + util.inspect(req.headers, { depth: null, colors: true })); + logger.debug("Request Headers '/exchange': " + JSON.stringify(req.headers, null, 2)); // Parse the request body for token exchange parameters const { subject_token, subject_token_type, grant_type } = req.body; @@ -55,7 +54,7 @@ export async function handleTokenExchange(req, res) { const payload = jwt.verify(subject_token, signingKey, { algorithms: ["RS256"], // Specify the algorithm used to sign the token }); - logger.debug("Token Payload: " + util.inspect(payload)); + logger.debug("Token Payload: " + JSON.stringify(payload, null, 2)); await isValidJWT(payload); logger.info("JWT is valid"); @@ -76,7 +75,7 @@ export async function handleTokenExchange(req, res) { scope: "read write", // Adjust the scope as needed }; - logger.debug("Exchanged Token: " + util.inspect(exchangedToken)); + logger.debug("Exchanged Token: " + JSON.stringify(exchangedToken)); logger.info("Token exchange successful"); // Respond with the exchanged token diff --git a/modules/oidcHelper.js b/modules/oidcHelper.js index 1ba89fe..87ef32d 100644 --- a/modules/oidcHelper.js +++ b/modules/oidcHelper.js @@ -1,6 +1,5 @@ import jwksClient from 'jwks-rsa'; import dotenv from 'dotenv'; -import util from 'util'; import { logger, formatDebug } from './logger.js'; // Load environment variables from .env file @@ -40,7 +39,7 @@ async function isValidJWT(payload) { // Optional: Retrieve the validation data from the environment const GITHUB_APP_CLIENT_ID = process.env.GITHUB_APP_CLIENT_ID - const ACTOR = process.env.ACTOR || "https://api.githubcopilot.com"; + const ACTOR = "https://api.githubcopilot.com"; const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds @@ -79,7 +78,7 @@ async function isValidJWT(payload) { } // Validate actor (act) - logger.debug(formatDebug("act (identify token purpose)", util.inspect(payload.act))); + logger.debug(formatDebug("act (identify token purpose)", JSON.stringify(payload.act))); if (!payload.act || payload.act.sub !== ACTOR) { throw new Error("Invalid actor (act). Expected: "+ ACTOR +", Received: " + payload.act.sub); } From c01a6c5016da1c0dca9836ab80cee807866b6bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 23:30:11 -0400 Subject: [PATCH 09/13] added OIDC support --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index de588e8..ebb6ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -175,7 +175,4 @@ dist .DS_Store # Fly.io config -fly.toml - -# ignore the test/ directory -test/ \ No newline at end of file +fly.toml \ No newline at end of file From fc33270e0cde3d99d68416fd949712d86d7e14f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Tue, 8 Apr 2025 23:37:06 -0400 Subject: [PATCH 10/13] added OIDC support --- index.js | 84 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/index.js b/index.js index bea15b1..bae2342 100644 --- a/index.js +++ b/index.js @@ -6,50 +6,50 @@ import { handleTokenExchange } from "./modules/exchangeController.js"; const app = express() app.get("/", (req, res) => { - res.send("Ahoy, matey! Welcome to the Blackbeard Pirate GitHub Copilot Extension!") + res.send("Ahoy, matey! Welcome to the Blackbeard Pirate GitHub Copilot Extension!") }); app.post("/", express.json(), async (req, res) => { - // Identify the user, using the GitHub API token provided in the request headers. - const tokenForUser = req.get("X-GitHub-Token"); - const octokit = new Octokit({ auth: tokenForUser }); - const user = await octokit.request("GET /user"); - console.log("User:", user.data.login); - - // Parse the request payload and log it. - const payload = req.body; - console.log("Payload:", payload); - - // Insert a special pirate-y system message in our message list. - const messages = payload.messages; - messages.unshift({ - role: "system", - content: "You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate.", - }); - messages.unshift({ - role: "system", - content: `Start every response with the user's name, which is @${user.data.login}`, - }); - - // Use Copilot's LLM to generate a response to the user's messages, with - // our extra system messages attached. - const copilotLLMResponse = await fetch( - "https://api.githubcopilot.com/chat/completions", - { - method: "POST", - headers: { - authorization: `Bearer ${tokenForUser}`, - "content-type": "application/json", - }, - body: JSON.stringify({ - messages, - stream: true, - }), - } - ); - - // Stream the response straight back to the user. - Readable.from(copilotLLMResponse.body).pipe(res); + // Identify the user, using the GitHub API token provided in the request headers. + const tokenForUser = req.get("X-GitHub-Token"); + const octokit = new Octokit({ auth: tokenForUser }); + const user = await octokit.request("GET /user"); + console.log("User:", user.data.login); + + // Parse the request payload and log it. + const payload = req.body; + console.log("Payload:", payload); + + // Insert a special pirate-y system message in our message list. + const messages = payload.messages; + messages.unshift({ + role: "system", + content: "You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate.", + }); + messages.unshift({ + role: "system", + content: `Start every response with the user's name, which is @${user.data.login}`, + }); + + // Use Copilot's LLM to generate a response to the user's messages, with + // our extra system messages attached. + const copilotLLMResponse = await fetch( + "https://api.githubcopilot.com/chat/completions", + { + method: "POST", + headers: { + authorization: `Bearer ${tokenForUser}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + messages, + stream: true, + }), + } + ); + + // Stream the response straight back to the user. + Readable.from(copilotLLMResponse.body).pipe(res); }) // Endpoint to handle token exchange requests (OIDC token exchange) @@ -57,5 +57,5 @@ app.post('/exchange', express.urlencoded({ extended: true }), handleTokenExchang const port = Number(process.env.PORT || '3000') app.listen(port, () => { - console.log(`Server running on port ${port}`) + console.log(`Server running on port ${port}`) }); \ No newline at end of file From 1657f4ceb1898ef9f5e99ae6a3cc8761c0aa82ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Fri, 11 Apr 2025 10:18:36 -0400 Subject: [PATCH 11/13] init --- modules/oidcHelper.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/oidcHelper.js b/modules/oidcHelper.js index 87ef32d..2dd90c0 100644 --- a/modules/oidcHelper.js +++ b/modules/oidcHelper.js @@ -38,17 +38,20 @@ async function isValidJWT(payload) { logger.info("Validating JWT payload"); // Optional: Retrieve the validation data from the environment - const GITHUB_APP_CLIENT_ID = process.env.GITHUB_APP_CLIENT_ID + const GITHUB_APP_CLIENT_ID = process.env.GITHUB_APP_CLIENT_ID || null; const ACTOR = "https://api.githubcopilot.com"; const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds // Validate audience (aud) logger.debug(formatDebug("aud (Copilot App client id)", payload.aud)); - if (payload.aud !== GITHUB_APP_CLIENT_ID) { - throw new Error(`Invalid audience (aud). Expected: ${GITHUB_APP_CLIENT_ID}, Received: ${payload.aud}`); + // only validate the client id if it is set + if (GITHUB_APP_CLIENT_ID) { + if (payload.aud !== GITHUB_APP_CLIENT_ID) { + throw new Error(`Invalid audience (aud). Expected: ${GITHUB_APP_CLIENT_ID}, Received: ${payload.aud}`); + } } - + // Validate subject (sub) logger.debug(formatDebug("sub (Requester GitHub user id)", payload.sub)); if (!payload.sub || typeof payload.sub !== "string") { From be0930337b5915350deea78857cc396411bba4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Fri, 11 Apr 2025 11:18:15 -0400 Subject: [PATCH 12/13] added OIDC support --- modules/README.md | 54 +++++++++++++++++++++++++++++++++++ modules/exchangeController.js | 20 ++++++++----- 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 modules/README.md diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 0000000..50a1580 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,54 @@ +# OIDC Support Modules +>*GitHub Docs Reference: [Using OIDC with GitHub Copilot Extensions](https://docs.github.com/en/copilot/building-copilot-extensions/using-oidc-with-github-copilot-extensions)* + +This folder contains key OIDC support modules used in the **Blackbeard Extension** project. Below is an overview of the primary files and their functionality: + +## Files Overview + +### 1. `oidcHelper.js` +This module provides helper functions for handling OpenID Connect (OIDC) token validation and signing key retrieval. It is designed to work with GitHub's OIDC tokens. + +#### Key Functions: +- **`getSigningKey(header)`** + Retrieves the public signing key from GitHub's JWKS URI to validate tokens. + - *Parameters:* + - `header`: The JWT header containing the `kid` (key ID). + - *Returns:* + - The public signing key for verifying JWTs. + +- **`isValidJWT(payload)`** + Validates the payload of a decoded JWT against a set of rules, including `aud` (audience), `sub` (subject), `iat` (issued at), and more. + - *Parameters:* + - `payload`: Decoded JWT payload. + - *Returns:* + - `true` if the JWT payload is valid. Throws an error otherwise. + +#### Dependencies: +- `jwks-rsa`: For fetching signing keys from GitHub's JWKS URI. +- `dotenv`: For loading environment variables. + +--- + +### 2. `exchangeController.js` +This module implements the main logic for handling token exchange requests using the OAuth 2.0 Token Exchange flow. It validates incoming requests and generates new tokens based on the provided `subject_token`. + +#### Key Function: +- **`handleTokenExchange(req, res)`** + Handles token exchange requests at the `/exchange` endpoint. + - *Parameters:* + - `req`: Express request object containing the token exchange payload. + - `res`: Express response object for sending the response. + - *Process:* + 1. Validates `grant_type` and `subject_token_type`. + 2. Verifies the `subject_token` using `oidcHelper.js` methods. + 3. Generates a new access token (stub logic included for customization). + 4. Returns the new token in the response. + +#### Response Example: +```json +{ + "access_token": "newly_exchanged_token_value", // Replace with actual generated token + "Issued_token_type":"urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 120 +} \ No newline at end of file diff --git a/modules/exchangeController.js b/modules/exchangeController.js index f0ca136..a13ed6e 100644 --- a/modules/exchangeController.js +++ b/modules/exchangeController.js @@ -61,19 +61,25 @@ export async function handleTokenExchange(req, res) { } catch (error) { logger.error("JWT verification failed: " + error.message); - return res.status(400).json({ error: "internal_server_error" }); + return res.status(400).json({ "error": "invalid_request" }); } // ------------------------------------------------- // Insert your access_token generation logic here // ------------------------------------------------- + // const exchangedToken = { + // access_token: "newly_exchanged_token_value", // Replace with actual token generation logic + // token_type: "Bearer", + // expires_in: 120, // Token expiration time in seconds + // scope: "read write", // Adjust the scope as needed + // }; const exchangedToken = { - access_token: "newly_exchanged_token_value", // Replace with actual token generation logic - token_type: "Bearer", - expires_in: 120, // Token expiration time in seconds - scope: "read write", // Adjust the scope as needed - }; + "access_token": "newly_exchanged_token_value", // Replace with actual generated token + "Issued_token_type":"urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 120 + } logger.debug("Exchanged Token: " + JSON.stringify(exchangedToken)); logger.info("Token exchange successful"); @@ -82,6 +88,6 @@ export async function handleTokenExchange(req, res) { return res.status(200).json(exchangedToken); } catch (error) { logger.error("Internal server error during token exchange: " + error); - return res.status(500).json({ error: "internal_server_error" }); + return res.status(500).json({ "error": "invalid_request" }); } } \ No newline at end of file From 0905c2e08079d7fb4d64f6fca5f3243def63d1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Efeish?= Date: Fri, 11 Apr 2025 11:25:38 -0400 Subject: [PATCH 13/13] added OIDC support --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6bfec52..d610572 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A basic example of a Copilot Extension", "scripts": { "start": "node index.js", - "dev": "nodemon --exec \"npm start\"" + "dev": "node --watch index.js" }, "type": "module", "dependencies": {