UNPKG

oauth2-mock-server

Version:
147 lines (142 loc) 4.99 kB
#!/usr/bin/env node import { writeFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; import { s as shift, r as readJsonFromFile, a as OAuth2Server, c as assertIsString } from './shared/oauth2-server.js'; import 'node:http'; import 'node:https'; import 'node:net'; import 'node:crypto'; import 'node:assert'; import 'jose'; import 'node:events'; import 'express'; import 'cors'; import 'basic-auth'; import 'node:fs'; import 'is-plain-obj'; const __filename = fileURLToPath(import.meta.url); const defaultOptions = { port: 8080, keys: [], saveJWK: false, }; async function cli(args) { let options; try { options = parseCliArgs(args); } catch (err) { console.error(err instanceof Error ? err.message : err); process.exitCode = 1; throw err; } if (options === null) { showHelp(); return null; } return await startServer(options); } function parseCliArgs(args) { const opts = { ...defaultOptions }; while (args.length > 0) { const arg = shift(args); switch (arg) { case '-h': case '--help': return null; case '-a': opts.host = shift(args); break; case '-p': opts.port = parsePort(shift(args)); break; case '-c': opts.cert = shift(args); break; case '-k': opts.key = shift(args); break; case '--jwk': opts.keys.push(readJsonFromFile(shift(args))); break; case '--save-jwk': opts.saveJWK = true; break; default: throw new Error(`Unrecognized option '${arg}'.`); } } return opts; } function showHelp() { const scriptName = path.basename(__filename).replace(/\.(ts|js)$/, ''); console.log(`Usage: ${scriptName} [options] ${scriptName} -a localhost -p 8080 Options: -h, --help Shows this help information. -a <address> Address on which the server will listen for connections. If omitted, the server will accept connections on [::] if IPv6 is available, or 0.0.0.0 otherwise. -p <port> TCP port on which the server will listen for connections. If omitted, 8080 will be used. If 0 is provided, the operating system will assign an arbitrary unused port. -c <cert> Optional file path to an SSL cert. Both cert and key need to be supplied to enable SSL. -k <key> Optional file path to an SSL key. Both key and cert need to be supplied to enable SSL. --jwk <filename> Adds a JSON-formatted key to the server's keystore. Can be specified many times. --save-jwk Saves all the keys in the keystore as "{kid}.json". If no keys are added via the --jwk option, a new random RSA key will be generated. This key can then be saved to disk with the --save-jwk for later reuse.`); } function parsePort(portStr) { const port = parseInt(portStr, 10); if (Number.isNaN(port) || port < 0 || port > 65535) { throw new Error('Invalid port number.'); } return port; } async function saveJWK(keys) { for (const key of keys) { const filename = `${key.kid}.json`; await writeFile(filename, JSON.stringify(key, null, 2)); console.log(`JSON web key written to file "${filename}".`); } } async function startServer(opts) { const server = new OAuth2Server(opts.key, opts.cert); await Promise.all(opts.keys.map(async (key) => { const jwk = await server.issuer.keys.add(key); console.log(`Added key with kid "${jwk.kid}"`); })); if (opts.keys.length === 0) { const jwk = await server.issuer.keys.generate('RS256'); console.log(`Generated new RSA key with kid "${jwk.kid}"`); } if (opts.saveJWK) { await saveJWK(server.issuer.keys.toJSON(true)); } await server.start(opts.port, opts.host); const addr = server.address(); const hostname = addr.family === 'IPv6' ? `[${addr.address}]` : addr.address; console.log(`OAuth 2 server listening on http://${hostname}:${addr.port.toString()}`); assertIsString(server.issuer.url, 'Empty host'); console.log(`OAuth 2 issuer is ${server.issuer.url}`); process.once('SIGINT', () => { console.log('OAuth 2 server is stopping...'); const handler = async () => { await server.stop(); }; handler().catch((e) => { throw e; }); console.log('OAuth 2 server has been stopped.'); }); return server; } var oauth2MockServer = cli(process.argv.slice(2)); export { oauth2MockServer as default };