oauth2-mock-server
Advanced tools
Comparing version 5.0.2 to 6.0.0
@@ -7,4 +7,13 @@ # Changelog | ||
## [5.0.2](https://github.com/axa-group/oauth2-mock-server/compare/v5.0.1...v5.0.2) — 2022-02-20 | ||
## [6.0.0](https://github.com/axa-group/oauth2-mock-server/compare/v5.0.2...v6.0.0) — 2023-06-19 | ||
### Changed | ||
- **Breaking:** No longer support Node.js 14 | ||
- Fix authorize endpoint compliance (remove scope requirement, make state optional) (by [jirutka](https://github.com/jirutka)) | ||
- Add support for Node.js 20 | ||
- Update dependencies | ||
## [5.0.2](https://github.com/axa-group/oauth2-mock-server/compare/v5.0.1...v5.0.2) — 2023-02-20 | ||
### Security | ||
@@ -11,0 +20,0 @@ |
@@ -0,0 +0,0 @@ export { JWKStore } from './lib/jwk-store'; |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -6,2 +6,3 @@ /// <reference types="node" /> | ||
export declare function assertIsString(input: unknown, errorMessage: string): asserts input is string; | ||
export declare function assertIsStringOrUndefined(input: unknown, errorMessage: string): asserts input is string | undefined; | ||
export declare function assertIsAddressInfo(input: string | null | AddressInfo): asserts input is AddressInfo; | ||
@@ -8,0 +9,0 @@ export declare function assertIsPlainObject(obj: unknown, errMessage: string): asserts obj is Record<string, unknown>; |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.readJsonFromFile = exports.shift = exports.assertIsValidTokenRequest = exports.assertIsPlainObject = exports.assertIsAddressInfo = exports.assertIsString = exports.defaultTokenTtl = void 0; | ||
exports.readJsonFromFile = exports.shift = exports.assertIsValidTokenRequest = exports.assertIsPlainObject = exports.assertIsAddressInfo = exports.assertIsStringOrUndefined = exports.assertIsString = exports.defaultTokenTtl = void 0; | ||
const assert_1 = require("assert"); | ||
@@ -18,2 +18,8 @@ const fs_1 = require("fs"); | ||
exports.assertIsString = assertIsString; | ||
function assertIsStringOrUndefined(input, errorMessage) { | ||
if (typeof input !== 'string' && input !== undefined) { | ||
throw new assert_1.AssertionError({ message: errorMessage }); | ||
} | ||
} | ||
exports.assertIsStringOrUndefined = assertIsStringOrUndefined; | ||
function assertIsAddressInfo(input) { | ||
@@ -34,7 +40,7 @@ if (input === null || typeof input === 'string') { | ||
if ('scope' in body) { | ||
assertIsString(body.scope, "Invalid 'scope' type"); | ||
assertIsString(body['scope'], "Invalid 'scope' type"); | ||
} | ||
assertIsString(body.grant_type, "Invalid 'grant_type' type"); | ||
assertIsString(body['grant_type'], "Invalid 'grant_type' type"); | ||
if ('code' in body) { | ||
assertIsString(body.code, "Invalid 'code' type"); | ||
assertIsString(body['code'], "Invalid 'code' type"); | ||
} | ||
@@ -41,0 +47,0 @@ } |
@@ -0,0 +0,0 @@ /// <reference types="node" /> |
@@ -26,3 +26,3 @@ "use strict"; | ||
__classPrivateFieldSet(this, _HttpServer_isSecured, false, "f"); | ||
if ((options === null || options === void 0 ? void 0 : options.key) && (options === null || options === void 0 ? void 0 : options.cert)) { | ||
if (options?.key && options?.cert) { | ||
__classPrivateFieldSet(this, _HttpServer_server, (0, https_1.createServer)(options, requestListener), "f"); | ||
@@ -29,0 +29,0 @@ __classPrivateFieldSet(this, _HttpServer_isSecured, true, "f"); |
@@ -0,0 +0,0 @@ import { JWK } from './types'; |
@@ -18,2 +18,3 @@ "use strict"; | ||
const jose_1 = require("jose"); | ||
const assert_1 = require("assert"); | ||
const generateRandomKid = () => { | ||
@@ -57,10 +58,10 @@ return (0, crypto_1.randomBytes)(40).toString('hex'); | ||
function normalizeKeyKid(jwk, opts) { | ||
if (jwk.kid !== undefined) { | ||
if (jwk['kid'] !== undefined) { | ||
return; | ||
} | ||
if (opts !== undefined && opts.kid !== undefined) { | ||
jwk.kid = opts.kid; | ||
jwk['kid'] = opts.kid; | ||
} | ||
else { | ||
jwk.kid = generateRandomKid(); | ||
jwk['kid'] = generateRandomKid(); | ||
} | ||
@@ -135,2 +136,5 @@ } | ||
const cleaner = privateToPublicTransformerMap[key.alg]; | ||
if (cleaner === undefined) { | ||
throw new Error(`Unsupported algo '{key.alg}'`); | ||
} | ||
keys.push(cleaner(key)); | ||
@@ -151,2 +155,7 @@ } | ||
const [key] = __classPrivateFieldGet(this, _KeyRotator_keys, "f").splice(i, 1); | ||
if (key === undefined) { | ||
throw new assert_1.AssertionError({ | ||
message: 'Unexpected error. key is supposed to exist', | ||
}); | ||
} | ||
__classPrivateFieldGet(this, _KeyRotator_keys, "f").push(key); | ||
@@ -153,0 +162,0 @@ return key; |
@@ -0,0 +0,0 @@ /// <reference types="node" /> |
@@ -32,4 +32,3 @@ "use strict"; | ||
async buildToken(opts) { | ||
var _a; | ||
const key = this.keys.get(opts === null || opts === void 0 ? void 0 : opts.kid); | ||
const key = this.keys.get(opts?.kid); | ||
if (key === undefined) { | ||
@@ -46,12 +45,12 @@ throw new Error('Cannot build token: Unknown key.'); | ||
iat: timestamp, | ||
exp: timestamp + ((_a = opts === null || opts === void 0 ? void 0 : opts.expiresIn) !== null && _a !== void 0 ? _a : helpers_1.defaultTokenTtl), | ||
exp: timestamp + (opts?.expiresIn ?? helpers_1.defaultTokenTtl), | ||
nbf: timestamp - 10, | ||
}; | ||
if ((opts === null || opts === void 0 ? void 0 : opts.scopesOrTransform) !== undefined) { | ||
if (opts?.scopesOrTransform !== undefined) { | ||
const scopesOrTransform = opts.scopesOrTransform; | ||
if (typeof scopesOrTransform === 'string') { | ||
payload.scope = scopesOrTransform; | ||
payload['scope'] = scopesOrTransform; | ||
} | ||
else if (Array.isArray(scopesOrTransform)) { | ||
payload.scope = scopesOrTransform.join(' '); | ||
payload['scope'] = scopesOrTransform.join(' '); | ||
} | ||
@@ -58,0 +57,0 @@ else if (typeof scopesOrTransform === 'function') { |
@@ -0,0 +0,0 @@ /// <reference types="node" /> |
@@ -15,3 +15,3 @@ "use strict"; | ||
const iss = new oauth2_issuer_1.OAuth2Issuer(); | ||
const serv = new oauth2_service_1.OAuth2Service(iss, oauth2Options === null || oauth2Options === void 0 ? void 0 : oauth2Options.endpoints); | ||
const serv = new oauth2_service_1.OAuth2Service(iss, oauth2Options?.endpoints); | ||
let options = undefined; | ||
@@ -18,0 +18,0 @@ if (key && cert) { |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { IncomingMessage } from 'http'; | ||
import { Express } from 'express'; | ||
import { IncomingMessage, type RequestListener } from 'http'; | ||
import { EventEmitter } from 'events'; | ||
@@ -13,3 +12,3 @@ import { OAuth2Issuer } from './oauth2-issuer'; | ||
buildToken(req: IncomingMessage, expiresIn: number, scopesOrTransform: ScopesOrTransform | undefined): Promise<string>; | ||
get requestHandler(): Express; | ||
get requestHandler(): RequestListener; | ||
private buildRequestHandler; | ||
@@ -16,0 +15,0 @@ private openidConfigurationHandler; |
@@ -25,3 +25,2 @@ "use strict"; | ||
const uuid_1 = require("uuid"); | ||
const body_parser_1 = require("body-parser"); | ||
const helpers_1 = require("./helpers"); | ||
@@ -50,3 +49,3 @@ const types_1 = require("./types"); | ||
app.disable('x-powered-by'); | ||
app.use((0, body_parser_1.json)()); | ||
app.use(express_1.default.json()); | ||
app.use((0, cors_1.default)()); | ||
@@ -89,3 +88,3 @@ app.get(__classPrivateFieldGet(this, _OAuth2Service_endpoints, "f").wellKnownDocument, this.openidConfigurationHandler); | ||
this.jwksHandler = (_req, res) => { | ||
res.json({ keys: this.issuer.keys.toJSON() }); | ||
return res.json({ keys: this.issuer.keys.toJSON() }); | ||
}; | ||
@@ -163,4 +162,4 @@ this.tokenHandler = async (req, res, next) => { | ||
}; | ||
body.id_token = await this.buildToken(req, tokenTtl, xfn); | ||
body.refresh_token = (0, uuid_1.v4)(); | ||
body['id_token'] = await this.buildToken(req, tokenTtl, xfn); | ||
body['refresh_token'] = (0, uuid_1.v4)(); | ||
} | ||
@@ -181,22 +180,14 @@ const tokenEndpointResponse = { | ||
this.authorizeHandler = (req, res) => { | ||
const { scope, state } = req.query; | ||
const responseType = req.query.response_type; | ||
const redirectUri = req.query.redirect_uri; | ||
const code = (0, uuid_1.v4)(); | ||
let queryNonce; | ||
if ('nonce' in req.query) { | ||
(0, helpers_1.assertIsString)(req.query.nonce, 'Invalid nonce type'); | ||
queryNonce = req.query.nonce; | ||
} | ||
const { nonce, scope, redirect_uri: redirectUri, response_type: responseType, state, } = req.query; | ||
(0, helpers_1.assertIsString)(redirectUri, 'Invalid redirectUri type'); | ||
(0, helpers_1.assertIsString)(scope, 'Invalid scope type'); | ||
(0, helpers_1.assertIsString)(state, 'Invalid state type'); | ||
(0, helpers_1.assertIsStringOrUndefined)(nonce, 'Invalid nonce type'); | ||
(0, helpers_1.assertIsStringOrUndefined)(scope, 'Invalid scope type'); | ||
(0, helpers_1.assertIsStringOrUndefined)(state, 'Invalid state type'); | ||
const url = new url_1.URL(redirectUri); | ||
if (responseType === 'code') { | ||
if (queryNonce !== undefined) { | ||
__classPrivateFieldGet(this, _OAuth2Service_nonce, "f")[code] = queryNonce; | ||
if (nonce !== undefined) { | ||
__classPrivateFieldGet(this, _OAuth2Service_nonce, "f")[code] = nonce; | ||
} | ||
url.searchParams.set('code', code); | ||
url.searchParams.set('scope', scope); | ||
url.searchParams.set('state', state); | ||
} | ||
@@ -206,2 +197,4 @@ else { | ||
url.searchParams.set('error_description', 'The authorization server does not support obtaining an access token using this response_type.'); | ||
} | ||
if (state) { | ||
url.searchParams.set('state', state); | ||
@@ -211,3 +204,3 @@ } | ||
this.emit(types_1.Events.BeforeAuthorizeRedirect, authorizeRedirectUri, req); | ||
res.redirect(url.href); | ||
return res.redirect(url.href); | ||
}; | ||
@@ -222,3 +215,3 @@ this.userInfoHandler = (req, res) => { | ||
this.emit(types_1.Events.BeforeUserinfo, userInfoResponse, req); | ||
res.status(userInfoResponse.statusCode).json(userInfoResponse.body); | ||
return res.status(userInfoResponse.statusCode).json(userInfoResponse.body); | ||
}; | ||
@@ -233,8 +226,8 @@ this.revokeHandler = (req, res) => { | ||
this.endSessionHandler = (req, res) => { | ||
(0, helpers_1.assertIsString)(req.query.post_logout_redirect_uri, 'Invalid post_logout_redirect_uri type'); | ||
(0, helpers_1.assertIsString)(req.query['post_logout_redirect_uri'], 'Invalid post_logout_redirect_uri type'); | ||
const postLogoutRedirectUri = { | ||
url: new url_1.URL(req.query.post_logout_redirect_uri), | ||
url: new url_1.URL(req.query['post_logout_redirect_uri']), | ||
}; | ||
this.emit(types_1.Events.BeforePostLogoutRedirect, postLogoutRedirectUri, req); | ||
res.redirect(postLogoutRedirectUri.url.href); | ||
return res.redirect(postLogoutRedirectUri.url.href); | ||
}; | ||
@@ -249,3 +242,5 @@ this.introspectHandler = (req, res) => { | ||
this.emit(types_1.Events.BeforeIntrospect, introspectResponse, req); | ||
res.status(introspectResponse.statusCode).json(introspectResponse.body); | ||
return res | ||
.status(introspectResponse.statusCode) | ||
.json(introspectResponse.body); | ||
}; | ||
@@ -252,0 +247,0 @@ __classPrivateFieldSet(this, _OAuth2Service_issuer, oauth2Issuer, "f"); |
@@ -0,0 +0,0 @@ import { JWK as JoseJWK } from 'jose'; |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -58,5 +58,5 @@ /// <reference types="node" /> | ||
export interface TokenBuildOptions { | ||
kid?: string; | ||
scopesOrTransform?: ScopesOrTransform; | ||
expiresIn?: number; | ||
kid?: string | undefined; | ||
scopesOrTransform?: ScopesOrTransform | undefined; | ||
expiresIn?: number | undefined; | ||
} | ||
@@ -63,0 +63,0 @@ export interface JWK extends JWKWithKid { |
@@ -0,0 +0,0 @@ "use strict"; |
#!/usr/bin/env node | ||
export {}; | ||
import { OAuth2Server } from './index'; | ||
declare const _default: Promise<OAuth2Server | null>; | ||
export default _default; |
@@ -7,8 +7,6 @@ #!/usr/bin/env node | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs_1 = require("fs"); | ||
const util_1 = require("util"); | ||
const promises_1 = require("fs/promises"); | ||
const path_1 = __importDefault(require("path")); | ||
const index_1 = require("./index"); | ||
const helpers_1 = require("./lib/helpers"); | ||
const writeFileAsync = (0, util_1.promisify)(fs_1.writeFile); | ||
const defaultOptions = { | ||
@@ -19,3 +17,2 @@ port: 8080, | ||
}; | ||
module.exports = cli(process.argv.slice(2)); | ||
async function cli(args) { | ||
@@ -29,7 +26,7 @@ let options; | ||
process.exitCode = 1; | ||
return Promise.reject(err); | ||
throw err; | ||
} | ||
if (!options) { | ||
process.exitCode = 0; | ||
return Promise.resolve(null); | ||
if (options === null) { | ||
showHelp(); | ||
return null; | ||
} | ||
@@ -45,3 +42,2 @@ return await startServer(options); | ||
case '--help': | ||
showHelp(); | ||
return null; | ||
@@ -108,3 +104,3 @@ case '-a': | ||
const filename = `${key.kid}.json`; | ||
await writeFileAsync(filename, JSON.stringify(key, null, 2)); | ||
await (0, promises_1.writeFile)(filename, JSON.stringify(key, null, 2)); | ||
console.log(`JSON web key written to file "${filename}".`); | ||
@@ -132,5 +128,10 @@ } | ||
console.log(`OAuth 2 issuer is ${server.issuer.url}`); | ||
process.once('SIGINT', async () => { | ||
process.once('SIGINT', () => { | ||
console.log('OAuth 2 server is stopping...'); | ||
await server.stop(); | ||
const handler = async () => { | ||
await server.stop(); | ||
}; | ||
handler().catch((e) => { | ||
throw e; | ||
}); | ||
console.log('OAuth 2 server has been stopped.'); | ||
@@ -140,1 +141,2 @@ }); | ||
} | ||
exports.default = cli(process.argv.slice(2)); |
{ | ||
"name": "oauth2-mock-server", | ||
"version": "5.0.2", | ||
"version": "6.0.0", | ||
"description": "OAuth 2 mock server", | ||
@@ -22,3 +22,3 @@ "keywords": [ | ||
"engines": { | ||
"node": "^14.15 || ^16.13 || ^18", | ||
"node": "^16.13 || ^18.12 || ^20", | ||
"yarn": "^1.15.2" | ||
@@ -43,18 +43,15 @@ }, | ||
"scripts": { | ||
"build:clean": "rimraf ./dist", | ||
"prebuild": "yarn build:clean", | ||
"prebuild": "rimraf ./dist ./.cache", | ||
"build": "tsc -p ./tsconfig.build.json", | ||
"cleanup:testresults": "rimraf TestResults", | ||
"prelint": "tsc --noEmit", | ||
"lint": "eslint --cache --cache-location .cache/ --ext=.js,.ts src test --max-warnings 0", | ||
"lint": "eslint --cache --cache-location .cache/ --ext=.ts src test --max-warnings 0", | ||
"prepack": "yarn build --tsBuildInfoFile null --incremental false", | ||
"pretest": "yarn cleanup:testresults && yarn lint", | ||
"test": "yarn jest" | ||
"pretest": "yarn lint", | ||
"test": "yarn vitest --run --coverage" | ||
}, | ||
"dependencies": { | ||
"basic-auth": "^2.0.1", | ||
"body-parser": "^1.20.1", | ||
"cors": "^2.8.5", | ||
"express": "^4.18.2", | ||
"jose": "^4.12.0", | ||
"jose": "^4.14.4", | ||
"lodash.isplainobject": "^4.0.6", | ||
@@ -67,27 +64,24 @@ "uuid": "^9.0.0" | ||
"@types/express": "^4.17.17", | ||
"@types/jest": "^29.4.0", | ||
"@types/lodash.isplainobject": "^4.0.7", | ||
"@types/node": "^14.18.36", | ||
"@types/node": "^16.18.28", | ||
"@types/supertest": "^2.0.12", | ||
"@types/uuid": "^9.0.0", | ||
"@typescript-eslint/eslint-plugin": "^5.52.0", | ||
"@typescript-eslint/parser": "^5.52.0", | ||
"eslint": "^8.34.0", | ||
"eslint-config-prettier": "^8.6.0", | ||
"@types/uuid": "^9.0.1", | ||
"@typescript-eslint/eslint-plugin": "^5.59.7", | ||
"@typescript-eslint/parser": "^5.59.7", | ||
"@vitest/coverage-c8": "^0.31.1", | ||
"eslint": "^8.41.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-jest": "^27.2.1", | ||
"eslint-plugin-jsdoc": "^40.0.0", | ||
"eslint-plugin-jsdoc": "^45.0.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"jest": "^29.4.3", | ||
"jest-junit": "^14.0.0", | ||
"prettier": "^2.8.4", | ||
"rimraf": "^4.1.2", | ||
"eslint-plugin-vitest": "^0.2.3", | ||
"prettier": "^2.8.8", | ||
"rimraf": "^5.0.1", | ||
"supertest": "^6.3.3", | ||
"ts-jest": "^29.0.5", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.5" | ||
"typescript": "^5.0.4", | ||
"vitest": "^0.31.1" | ||
}, | ||
"resolutions": { | ||
"@types/node": "^14" | ||
"@types/node": "^16" | ||
} | ||
} |
@@ -16,3 +16,3 @@ # `oauth2-mock-server` | ||
- [Node.js 14+](https://nodejs.org/) | ||
- [Node.js 16+](https://nodejs.org/) | ||
- [Yarn 1.15.2+](https://classic.yarnpkg.com/lang/en/) | ||
@@ -19,0 +19,0 @@ |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
6
21
1052
66369
- Removedbody-parser@^1.20.1
Updatedjose@^4.14.4