Merge 0d8b96adeae27e05fe963f922c1575022a3d1c44 into 4290ea753912378913b1f3f54e0fc5720afeda5d

This commit is contained in:
alleria173 2026-08-10 03:41:48 -07:00 committed by GitHub
commit 54a9e53de4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 334 additions and 150 deletions

View 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
View File

@ -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": {

View File

@ -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",

View File

@ -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-----`;
}

View File

@ -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}`);
}
}

View File

@ -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());
}
}
}

View File

@ -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");

View File

@ -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: [],
}
],
}
]);

View File

@ -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],
}
],
}
]);