Merge 0d8b96adeae27e05fe963f922c1575022a3d1c44 into 4290ea753912378913b1f3f54e0fc5720afeda5d
This commit is contained in:
commit
54a9e53de4
82
.github/workflows/awaiting-response-label.yml
vendored
Normal file
82
.github/workflows/awaiting-response-label.yml
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
name: Awaiting Response Label Sync
|
||||
|
||||
on:
|
||||
# Fires when someone finishes reviewing a PR (e.g. picks "Request changes")
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
# Fires when the PR author pushes new commits
|
||||
pull_request_target:
|
||||
types: [synchronize]
|
||||
# Fires when someone comments on a PR (also fires for plain issues, filtered out below)
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync-label:
|
||||
# issue_comment fires for issues too, so only run it for PR comments
|
||||
if: >-
|
||||
github.event_name != 'issue_comment' ||
|
||||
github.event.issue.pull_request != null
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Sync "awaiting response" label
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0
|
||||
env:
|
||||
AWAITING_LABEL: 'awaiting response'
|
||||
with:
|
||||
script: |
|
||||
const awaitingLabel = process.env.AWAITING_LABEL;
|
||||
// Resolve the PR number for whichever event triggered this run
|
||||
const prNumber = context.eventName === 'issue_comment'
|
||||
? context.payload.issue.number
|
||||
: context.payload.pull_request.number;
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
// Check whether the label is already on the PR, so we don't add it twice or
|
||||
// try to remove something that isn't there
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner, repo, issue_number: prNumber,
|
||||
});
|
||||
const hasLabel = issue.labels.some(l =>
|
||||
(typeof l === 'string' ? l : l.name) === awaitingLabel
|
||||
);
|
||||
|
||||
// A reviewer chose "Request changes" -> the author needs to respond
|
||||
if (context.eventName === 'pull_request_review' && context.payload.review.state === 'changes_requested') {
|
||||
if (!hasLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: prNumber, labels: [awaitingLabel],
|
||||
});
|
||||
core.info(`Added "${awaitingLabel}".`);
|
||||
} else {
|
||||
core.info('Label already applied.');
|
||||
}
|
||||
// The author pushed new commits -> treat that as their response and clear the label
|
||||
} else if (context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') {
|
||||
if (hasLabel) {
|
||||
// If the label was already gone for some reason, that's fine, not an error
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: prNumber, name: awaitingLabel,
|
||||
}).catch(e => core.warning(`removeLabel failed: ${e.message}`));
|
||||
core.info(`Removed "${awaitingLabel}".`);
|
||||
} else {
|
||||
core.info('Label not present; nothing to do.');
|
||||
}
|
||||
// The PR author left a comment -> treat any reply from them as a response
|
||||
} else if (context.eventName === 'issue_comment' && context.payload.comment.user.login === context.payload.issue.user.login) {
|
||||
if (hasLabel) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: prNumber, name: awaitingLabel,
|
||||
}).catch(e => core.warning(`removeLabel failed: ${e.message}`));
|
||||
core.info(`Removed "${awaitingLabel}".`);
|
||||
} else {
|
||||
core.info('Label not present; nothing to do.');
|
||||
}
|
||||
} else {
|
||||
core.info('Event does not require a label change.');
|
||||
}
|
||||
119
package-lock.json
generated
119
package-lock.json
generated
@ -53,6 +53,7 @@
|
||||
"highlight.js": "^11.11.1",
|
||||
"ieee754": "^1.2.1",
|
||||
"jimp": "1.6.0",
|
||||
"jose": "^6.2.3",
|
||||
"jq-web": "^0.5.1",
|
||||
"jquery": "3.7.1",
|
||||
"js-ascon": "^1.3.0",
|
||||
@ -62,7 +63,6 @@
|
||||
"json5": "^2.2.3",
|
||||
"jsonata": "^2.2.2",
|
||||
"jsonpath-plus": "^10.4.0",
|
||||
"jsonwebtoken": "9.0.3",
|
||||
"jsqr": "^1.4.0",
|
||||
"jsrsasign": "^11.1.3",
|
||||
"kbpgp": "^2.1.17",
|
||||
@ -6486,12 +6486,6 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -8833,15 +8827,6 @@
|
||||
"stream-shift": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -12496,6 +12481,15 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
|
||||
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/jpeg-js": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
||||
@ -12696,40 +12690,6 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^4.0.1",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jsqr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
|
||||
@ -12755,27 +12715,6 @@
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kbpgp": {
|
||||
"version": "2.1.17",
|
||||
"resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.1.17.tgz",
|
||||
@ -13046,18 +12985,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isfinite": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
|
||||
@ -13065,28 +12992,11 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
@ -13096,12 +13006,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.union": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||
@ -13828,6 +13732,7 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multicast-dns": {
|
||||
|
||||
@ -138,6 +138,7 @@
|
||||
"highlight.js": "^11.11.1",
|
||||
"ieee754": "^1.2.1",
|
||||
"jimp": "1.6.0",
|
||||
"jose": "^6.2.3",
|
||||
"jq-web": "^0.5.1",
|
||||
"jquery": "3.7.1",
|
||||
"js-ascon": "^1.3.0",
|
||||
@ -147,7 +148,6 @@
|
||||
"json5": "^2.2.3",
|
||||
"jsonata": "^2.2.2",
|
||||
"jsonpath-plus": "^10.4.0",
|
||||
"jsonwebtoken": "9.0.3",
|
||||
"jsqr": "^1.4.0",
|
||||
"jsrsasign": "^11.1.3",
|
||||
"kbpgp": "^2.1.17",
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import forge from "node-forge";
|
||||
import * as asn1js from "asn1js";
|
||||
|
||||
export const MD_ALGORITHMS = {
|
||||
"SHA-1": forge.md.sha1,
|
||||
@ -15,3 +16,48 @@ export const MD_ALGORITHMS = {
|
||||
"SHA-384": forge.md.sha384,
|
||||
"SHA-512": forge.md.sha512,
|
||||
};
|
||||
|
||||
const rsaEncryptionOID = "1.2.840.113549.1.1.1";
|
||||
|
||||
/**
|
||||
* Convert PKCS#1 RSA public key (PEM) to SPKI PEM
|
||||
* @param {string} originalPem
|
||||
* @returns {string}
|
||||
*/
|
||||
export function pkcs1ToSpki(originalPem) {
|
||||
// remove PEM headers
|
||||
const b64 = originalPem
|
||||
.replace(/-----BEGIN RSA PUBLIC KEY-----/g, "")
|
||||
.replace(/-----END RSA PUBLIC KEY-----/g, "")
|
||||
.replace(/\s+/g, "");
|
||||
|
||||
const pkcs1Der = Uint8Array.from(atob(b64), c => c.charCodeAt(0)).buffer;
|
||||
|
||||
// SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
// algorithm AlgorithmIdentifier,
|
||||
// subjectPublicKey BIT STRING
|
||||
// }
|
||||
|
||||
const spkiSchema = new asn1js.Sequence({
|
||||
value: [
|
||||
new asn1js.Sequence({
|
||||
value: [
|
||||
// rsaEncryption OID
|
||||
new asn1js.ObjectIdentifier({ value: rsaEncryptionOID }),
|
||||
new asn1js.Null()
|
||||
]
|
||||
}),
|
||||
new asn1js.BitString({ valueHex: pkcs1Der })
|
||||
]
|
||||
});
|
||||
|
||||
const spkiDer = spkiSchema.toBER(false);
|
||||
|
||||
const spkiB64 = btoa(
|
||||
String.fromCharCode(...new Uint8Array(spkiDer))
|
||||
);
|
||||
|
||||
const lines = spkiB64.match(/.{1,64}/g).join("\n");
|
||||
|
||||
return `-----BEGIN PUBLIC KEY-----\n${lines}\n-----END PUBLIC KEY-----`;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { decodeJwt } from "jose";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
@ -42,14 +42,11 @@ class JWTDecode extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
try {
|
||||
const decoded = jwt.decode(input, {
|
||||
json: true,
|
||||
complete: true
|
||||
});
|
||||
|
||||
return decoded.payload;
|
||||
return decodeJwt(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
throw new OperationError(`Invalid JWT: could not decode token.
|
||||
|
||||
${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Operation from "../Operation.mjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { jwtVerify, importSPKI, importX509, decodeProtectedHeader } from "jose";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import {JWT_ALGORITHMS} from "../lib/JWT.mjs";
|
||||
import {pkcs1ToSpki} from "../lib/RSA.mjs";
|
||||
|
||||
|
||||
/**
|
||||
@ -22,7 +23,7 @@ class JWTVerify extends Operation {
|
||||
|
||||
this.name = "JWT Verify";
|
||||
this.module = "Crypto";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded public key for RSA and ECDSA.";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / public key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded public key (or certificate) for RSA and ECDSA.<br><br>Expiry (<code>exp</code>) and not-before (<code>nbf</code>) claims are validated if present. Unsigned tokens (<code>alg: none</code>) are rejected; use 'JWT Decode' to view their payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
@ -38,23 +39,64 @@ class JWTVerify extends Operation {
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
async run(input, args) {
|
||||
const [key] = args;
|
||||
const algos = JWT_ALGORITHMS;
|
||||
algos[algos.indexOf("None")] = "none";
|
||||
const algorithms = JWT_ALGORITHMS.filter(a => a !== "None");
|
||||
|
||||
let header;
|
||||
try {
|
||||
header = decodeProtectedHeader(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid JWT format.
|
||||
|
||||
${err}`);
|
||||
}
|
||||
|
||||
if (!header.alg || header.alg === "none") {
|
||||
throw new OperationError("This token is unsigned (\"alg\": \"none\") and cannot be verified. Use the 'JWT Decode' operation to view its payload.");
|
||||
}
|
||||
|
||||
if (!algorithms.includes(header.alg)) {
|
||||
throw new OperationError(`The token's algorithm "${header.alg}" is not supported. Supported algorithms are: ${algorithms.join(", ")}.`);
|
||||
}
|
||||
|
||||
let secret;
|
||||
try {
|
||||
if (key.startsWith("-----BEGIN PUBLIC KEY-----")) {
|
||||
secret = await importSPKI(key, header.alg);
|
||||
} else if (key.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
||||
secret = await importSPKI(pkcs1ToSpki(key), header.alg);
|
||||
} else if (key.startsWith("-----BEGIN CERTIFICATE-----")) {
|
||||
secret = await importX509(key, header.alg);
|
||||
} else {
|
||||
secret = new TextEncoder().encode(key);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded public key for RSA and ECDSA.
|
||||
|
||||
${err}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const verified = jwt.verify(input, key, { algorithms: algos });
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(verified, "name") && verified.name === "JsonWebTokenError") {
|
||||
throw new OperationError(verified.message);
|
||||
}
|
||||
|
||||
return verified;
|
||||
const { payload } = await jwtVerify(input, secret, { algorithms });
|
||||
return payload;
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
switch (err.code) {
|
||||
case "ERR_JWT_EXPIRED":
|
||||
throw new OperationError(`The token has expired.
|
||||
|
||||
${err.message}`);
|
||||
case "ERR_JWS_SIGNATURE_VERIFICATION_FAILED":
|
||||
throw new OperationError("Invalid signature. Have you entered the correct key?");
|
||||
case "ERR_JWT_CLAIM_VALIDATION_FAILED":
|
||||
throw new OperationError(`Token claim validation failed.
|
||||
|
||||
${err.message}`);
|
||||
default:
|
||||
throw new OperationError(err.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -219,9 +219,9 @@ module.exports = {
|
||||
// testOp(browser, "JSON Minify", "test input", "test_output");
|
||||
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
||||
testOp(browser, "Jsonata Query", '{"a": "SGVsbG8gV29ybGQh"}', '"Hello World!"', ["$base64decode($.a)"]);
|
||||
// testOp(browser, "JWT Decode", "test input", "test_output");
|
||||
testOp(browser, "JWT Decode", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", '{\n "String": "SomeString",\n "Number": 42,\n "iat": 1\n}');
|
||||
// testOp(browser, "JWT Sign", "test input", "test_output");
|
||||
// testOp(browser, "JWT Verify", "test input", "test_output");
|
||||
testOp(browser, "JWT Verify", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", '{\n "String": "SomeString",\n "Number": 42,\n "iat": 1\n}', ["secret_cat"]);
|
||||
// testOp(browser, "JavaScript Beautify", "test input", "test_output");
|
||||
// testOp(browser, "JavaScript Minify", "test input", "test_output");
|
||||
// testOp(browser, "JavaScript Parser", "test input", "test_output");
|
||||
|
||||
@ -47,5 +47,18 @@ TestRegister.addTests([
|
||||
args: [],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Decode: malformed token",
|
||||
input: "not a jwt",
|
||||
expectedOutput: `Invalid JWT: could not decode token.
|
||||
|
||||
JWTInvalid: Invalid JWT`,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Decode",
|
||||
args: [],
|
||||
}
|
||||
],
|
||||
}
|
||||
]);
|
||||
|
||||
@ -16,23 +16,55 @@ const outputObject = JSON.stringify({
|
||||
|
||||
const hsKey = "secret_cat";
|
||||
/* Retaining private key as a comment
|
||||
const rsPriv = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
|
||||
33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
|
||||
+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
|
||||
AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS
|
||||
3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp
|
||||
uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE
|
||||
2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0
|
||||
GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K
|
||||
Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY
|
||||
6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5
|
||||
fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
|
||||
Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
|
||||
FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
|
||||
-----END RSA PRIVATE KEY-----`;
|
||||
const rsPriv = `-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDlhnQq4PFKzMOV
|
||||
qM1FyJkLv2oadstOD+3jxzokG2n48HdE7rSnBR1bbu4peSjvMK4Pg++qwwi/Q2V5
|
||||
J/o97utd92P7+Qa/pz7JXZnNU6xKo7snYF3oKZXt63B75ia7/Gz7P5fC+KoBNlIu
|
||||
4dFTdb0Mp/Ld8QvXuOrqMZBUbsYpxW/4FY0hWhimCXsItHogX41X7/AaXVLL/5fO
|
||||
3o2yi78S3kbimqBAk6BQHFUQwl2Ktf432pWG7o6PvcMZHu5XKvphUYNItOc7KGuY
|
||||
Qf83AYWfwttWO7pVw9wIQLsNQOg4dF2exNWOguZiJl5ri0E1sHOwm/3uZWGZc41G
|
||||
qVNz6BfXAgMBAAECggEABTXLy0a3PiXEcXrsdfS7bKXYCGkWl7C1i2HJXb/K5F4T
|
||||
sa/iz+vGIn4zz83dR64I4CH0PcxsQBM36XZ72ri4BQu0QahXJotAWfJ9pvM1n610
|
||||
dndcilmZ+qrz2bbRaEZZnvZu69EYjYB91cHwg37SX3LXM4Hg7UZYUXVLO8TBS3oR
|
||||
2spOUdC+jWZlK/irnu9SVqCYxPJ4GlTCVEtw81arbhVtVmSzYxoEJJWPRHLEOPK8
|
||||
vwPDjzOzuQeDtHWuEriDsXe1iiVZA+06QfiI3YL60kdZqCNQeAr4AyXEZ4KHDuPZ
|
||||
XKO8WE7zIJR/KOdFmQnMSjyVSgI9ZSNROr/n8FphYQKBgQD9k9gb5gzUL6UhRjpg
|
||||
JnQxENGfZX2IE2ftqIlRqgquiv+haEmSuK9uFOksBPmuHL12xD6E29HWZ5eRZinw
|
||||
j1MTSPn9YSBMSId3ZCx7fMWulI9GkD14ERCrwPTasqj0q2+qbLavf6DEkvAYgGs1
|
||||
j5hBexw3Cs0MsAupK8uel9GapwKBgQDnt8liEU2MXiM0lZdA4JNv52UWVawLSTjZ
|
||||
SnPD9Uvfs7e9qFAsQLHoBKTRKxjRNOduUCFjC0LDKK6V+D1Ykhw0iO2/v7v79F13
|
||||
WlJm+11Wrw0LxRSVF8TJ7WSeV3NQoeneiInDY/SkVAJKuananAuxQa5vbVdjL9Yw
|
||||
f2VNkPKvUQKBgDQPeWvo5DNgAMf/EhcRbgNwz4ipHoekbE7nriGdBoplMSxjK8+6
|
||||
qRGxq1mFP/mWHvPWQHuoYcFiDUUls3CXDFhGQETb/vSKY32IjHVh3XgFOhEjpesT
|
||||
ndeVSu/nhDaPR+K2ZR9M1aXmAPMW0mvxqjGvY9CNICH9trcpralGxBq7AoGBALtl
|
||||
BedaiPWxGBWpuOYN+Q6x+y2h4X5LHD+Wl6LtmQv0Iu+t1BDFzVsPjl9e1DY068lp
|
||||
4mZgMAD2wKKmXK5pk4flCSlenV+5E3G3yRZhyO2Wqh1PqEKfM1X+t7XakXavSxze
|
||||
HVO4XZwcGLJoDnauZkJXFQQmUFr1z+nuzXV6eNnRAoGAYIpPHO5B6UtCPdK8p3jk
|
||||
WYA790gGN/t6vMRw7qtFGBKt3DmlcTp7GHZxbF5p6yGKFPIo/mHGebdibTF4qamP
|
||||
Rkqrshupx/m+4udn6kDNyi1mHJeWjoLod6qA8BaJIh2NqEcim6FQp6MfYVC84m91
|
||||
KedDUWDtk6YFQKGxIDOrz/k=
|
||||
-----END PRIVATE KEY-----`;
|
||||
*/
|
||||
const rsPub = `-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5YZ0KuDxSszDlajNRciZ
|
||||
C79qGnbLTg/t48c6JBtp+PB3RO60pwUdW27uKXko7zCuD4PvqsMIv0NleSf6Pe7r
|
||||
Xfdj+/kGv6c+yV2ZzVOsSqO7J2Bd6CmV7etwe+Ymu/xs+z+XwviqATZSLuHRU3W9
|
||||
DKfy3fEL17jq6jGQVG7GKcVv+BWNIVoYpgl7CLR6IF+NV+/wGl1Sy/+Xzt6Nsou/
|
||||
Et5G4pqgQJOgUBxVEMJdirX+N9qVhu6Oj73DGR7uVyr6YVGDSLTnOyhrmEH/NwGF
|
||||
n8LbVju6VcPcCEC7DUDoOHRdnsTVjoLmYiZea4tBNbBzsJv97mVhmXONRqlTc+gX
|
||||
1wIDAQAB
|
||||
-----END PUBLIC KEY-----`;
|
||||
// Same public key as rsPub, in PKCS#1 format
|
||||
const rsPubPkcs1 = `-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEA5YZ0KuDxSszDlajNRciZC79qGnbLTg/t48c6JBtp+PB3RO60pwUd
|
||||
W27uKXko7zCuD4PvqsMIv0NleSf6Pe7rXfdj+/kGv6c+yV2ZzVOsSqO7J2Bd6CmV
|
||||
7etwe+Ymu/xs+z+XwviqATZSLuHRU3W9DKfy3fEL17jq6jGQVG7GKcVv+BWNIVoY
|
||||
pgl7CLR6IF+NV+/wGl1Sy/+Xzt6Nsou/Et5G4pqgQJOgUBxVEMJdirX+N9qVhu6O
|
||||
j73DGR7uVyr6YVGDSLTnOyhrmEH/NwGFn8LbVju6VcPcCEC7DUDoOHRdnsTVjoLm
|
||||
YiZea4tBNbBzsJv97mVhmXONRqlTc+gX1wIDAQAB
|
||||
-----END RSA PUBLIC KEY-----`;
|
||||
// 1024-bit key: jose refuses RSA keys below 2048 bits
|
||||
const rsPubWeak = `-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
|
||||
UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
|
||||
HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
|
||||
@ -50,10 +82,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
|
||||
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
|
||||
-----END PUBLIC KEY-----`;
|
||||
|
||||
const hsToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk";
|
||||
const rsToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.GbJLourP2hTpBMm0mPrDOKSjsOnVU_teH0h7CCW333uT-56axQLR8pKcLyTBYt8wx5cZVk4lunqA95GCmz8upGN9U_Hzc6aCBaoXzbb9i0TGp-7kLOUP_UN0_5f96SWDHF4hb2P38Q1ONXMbI7hTbkphc4RlDpqSjNeFvzMgJpNjAVeIpzLoyhDqJbx_D6-iGQCXdHRgEpF8xNjZHHVADv00G0riIQvRsvJ3Q4XUiQDbtXCHfOWMIUWobLEFfI9I4se0RMRditm5UytOyrAuUl4QhskU3bofD8p7UiM9BjBHmTMepCJM__D1qrWjv5ugcYV7yVgerBKAoYF2LSAS2A";
|
||||
// Same claims as rsToken, signed with the 1024-bit key pair
|
||||
const rsTokenWeak = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y";
|
||||
const esToken = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA";
|
||||
// Same claims as hsToken plus "exp": 1, signed with hsKey
|
||||
const hsTokenExpired = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxLCJleHAiOjF9.ZKrXhOiSg4rsEY77HurSJY1i4-cpFYkr6m_TTA2ChFM";
|
||||
// Unsigned token: header {"alg":"none"}
|
||||
const noneToken = "eyJhbGciOiJub25lIn0.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "JWT Verify: HS",
|
||||
input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk",
|
||||
input: hsToken,
|
||||
expectedOutput: outputObject,
|
||||
recipeConfig: [
|
||||
{
|
||||
@ -64,7 +106,7 @@ TestRegister.addTests([
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: RS",
|
||||
input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y",
|
||||
input: rsToken,
|
||||
expectedOutput: outputObject,
|
||||
recipeConfig: [
|
||||
{
|
||||
@ -73,9 +115,31 @@ TestRegister.addTests([
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: RS with PKCS#1 public key",
|
||||
input: rsToken,
|
||||
expectedOutput: outputObject,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Verify",
|
||||
args: [rsPubPkcs1],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: RS with key shorter than 2048 bits",
|
||||
input: rsTokenWeak,
|
||||
expectedOutput: "TypeError: RS256 requires key modulusLength to be 2048 bits or larger",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Verify",
|
||||
args: [rsPubWeak],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: ES",
|
||||
input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA",
|
||||
input: esToken,
|
||||
expectedOutput: outputObject,
|
||||
recipeConfig: [
|
||||
{
|
||||
@ -83,5 +147,40 @@ TestRegister.addTests([
|
||||
args: [esPub],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: wrong key",
|
||||
input: hsToken,
|
||||
expectedOutput: "Invalid signature. Have you entered the correct key?",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Verify",
|
||||
args: ["wrong_secret"],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: expired token",
|
||||
input: hsTokenExpired,
|
||||
expectedOutput: `The token has expired.
|
||||
|
||||
"exp" claim timestamp check failed`,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Verify",
|
||||
args: [hsKey],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Verify: unsigned token",
|
||||
input: noneToken,
|
||||
expectedOutput: "This token is unsigned (\"alg\": \"none\") and cannot be verified. Use the 'JWT Decode' operation to view its payload.",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Verify",
|
||||
args: [hsKey],
|
||||
}
|
||||
],
|
||||
}
|
||||
]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user