Skip to content

Commit

Permalink
🐛 Fix broken AJV constructor (#231)
Browse files Browse the repository at this point in the history
- depending on the bundler, it seems to be either default or not???
- Notably, in protect-and-track, this causes it to fail with `Uncaught TypeError: n.default is not a constructor`
- Found an issue where a mock TDF.create was escaping its test harness and breaking other tests
- This would have shown up earlier, but we've been ignoring mocha's return value in CI 😬. This changes the CI step to use a trap to close the server
  • Loading branch information
dmihalcik-virtru authored Aug 16, 2023
1 parent 893788d commit 459cb42
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 54 deletions.
4 changes: 2 additions & 2 deletions lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@
"license-check": "license-checker-rseidelsohn --production --onlyAllow 'Apache-2.0; BSD; CC-BY-4.0; ISC; MIT'",
"lint": "eslint ./src/**/*.ts ./tdf3/**/*.ts ./tests/**/*.ts",
"prepack": "npm run build",
"test": "npm run build && npm run test:wtr && npm run test:mocha && npm run test:browser && npm run coverage:merge",
"test": "npm run build && npm run test:mocha && npm run test:wtr && npm run test:browser && npm run coverage:merge",
"test:browser": "rm -rf tests/mocha/dist && node tests/server.cjs & npx webpack --config webpack.test.config.cjs && npx karma start karma.conf.cjs; node tests/stopServer.cjs",
"test:mocha": "node tests/server.cjs & c8 --exclude=\"dist/web/tests/\" --exclude=\"dist/web/tdf3/src/utils/aws-lib-storage/\" --exclude=\"dist/web/tests/**/*\" --report-dir=./coverage/mocha mocha 'dist/web/tests/mocha/**/*.spec.js' --file tests/mocha/setup.js ; npx c8 report --reporter=json --report-dir=./coverage/mocha; node tests/stopServer.cjs",
"test:mocha": "node tests/server.cjs & trap \"node tests/stopServer.cjs\" EXIT; c8 --exclude=\"dist/web/tests/\" --exclude=\"dist/web/tdf3/src/utils/aws-lib-storage/\" --exclude=\"dist/web/tests/**/*\" --report-dir=./coverage/mocha mocha 'dist/web/tests/mocha/**/*.spec.js' --file tests/mocha/setup.js && npx c8 report --reporter=json --report-dir=./coverage/mocha",
"test:wtr": "web-test-runner",
"watch": "(trap 'kill 0' SIGINT; npm run build && (npm run build:watch & npm run test -- --watch))"
},
Expand Down
18 changes: 14 additions & 4 deletions lib/tdf3/src/models/attribute-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ const ATTRIBUTE_OBJECT_SCHEMA: JSONSchemaType<AttributeObject> = {
additionalProperties: false,
};

const validator = new Ajv.default();
const validator = (() => {
try {
//@ts-expect-error: Ajv not a constructor
return new Ajv();
} catch (e) {
console.log(e);
}
return new Ajv.default();
})();

export class AttributeSet {
attributes: AttributeObject[];
Expand Down Expand Up @@ -148,11 +156,13 @@ export class AttributeSet {
* @return {Object} - Decrypted and added attribute object
*/
addJwtAttribute(jwtAttribute: { jwt: string }) {
const { jwt: attrJwt } = jwtAttribute;
const attrJwt = jwtAttribute?.jwt;
// Can't verify the JWT because the client does not have the easPublicKey,
// but the contents of the JWT can be decoded.
const attrObjPayload = decodeJwt(attrJwt);
if (!attrObjPayload) return null;
const attrObjPayload = attrJwt && decodeJwt(attrJwt);
if (!attrObjPayload) {
return null;
}
// JWT payloads contain many things, incluing .iat and .exp. This
// extraneous material should be stripped away before adding the
// attribute to the attributeSet.
Expand Down
97 changes: 50 additions & 47 deletions lib/tests/mocha/encrypt-decrypt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,61 @@ describe('encrypt decrypt test', async function () {
const kasUrl = `http://localhost:4000/`;
const kasPublicKey = await TDF.extractPemFromKeyString(Mocks.kasPublicKey);

const sandbox = createSandbox();
const tdf1 = TDF.create({ cryptoService });
sandbox.replace(
TDF,
'create',
sandbox.fake(() => tdf1)
);
sandbox.spy(tdf1, '_generateManifest');
sandbox.stub(tdf1, 'unwrapKey').callsFake(async () => {
// @ts-ignore
const keyInfo = tdf1._generateManifest.lastCall.args[0];
return {
reconstructedKeyBinary: keyInfo.unwrappedKeyBinary as Binary,
metadata: undefined,
};
});

it('encrypt-decrypt stream source happy path', async function () {
const client = new Client.Client({
kasEndpoint: kasUrl,
kasPublicKey,
keypair: { publicKey: Mocks.entityPublicKey, privateKey: Mocks.entityPrivateKey },
clientId: 'id',
authProvider,
});
const sandbox = createSandbox();
try {
const tdf1 = TDF.create({ cryptoService });
sandbox.replace(
TDF,
'create',
sandbox.fake(() => tdf1)
);
sandbox.spy(tdf1, '_generateManifest');
sandbox.stub(tdf1, 'unwrapKey').callsFake(async () => {
// @ts-ignore
const keyInfo = tdf1._generateManifest.lastCall.args[0];
return {
reconstructedKeyBinary: keyInfo.unwrappedKeyBinary as Binary,
metadata: undefined,
};
});
const client = new Client.Client({
kasEndpoint: kasUrl,
kasPublicKey,
keypair: { publicKey: Mocks.entityPublicKey, privateKey: Mocks.entityPrivateKey },
clientId: 'id',
authProvider,
});

const eo = await Mocks.getEntityObject();
const scope = Mocks.getScope();
const eo = await Mocks.getEntityObject();
const scope = Mocks.getScope();

const encryptedStream = await client.encrypt({
eo,
metadata: Mocks.getMetadataObject(),
offline: true,
scope,
source: new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(expectedVal));
controller.close();
},
}),
});
const encryptedStream = await client.encrypt({
eo,
metadata: Mocks.getMetadataObject(),
offline: true,
scope,
source: new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(expectedVal));
controller.close();
},
}),
});

const decryptStream = await client.decrypt({
eo,
source: {
type: 'stream',
location: encryptedStream.stream,
},
});
const decryptStream = await client.decrypt({
eo,
source: {
type: 'stream',
location: encryptedStream.stream,
},
});

const { value: decryptedText } = await decryptStream.stream.getReader().read();
const { value: decryptedText } = await decryptStream.stream.getReader().read();

assert.equal(new TextDecoder().decode(decryptedText), expectedVal);
assert.equal(new TextDecoder().decode(decryptedText), expectedVal);
} finally {
sandbox.restore();
}
});
});
18 changes: 18 additions & 0 deletions lib/tests/mocha/unit/attribute-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,22 @@ describe('AttributeSet', function () {
const aSet = new AttributeSet();
assert.deepEqual(aSet.get('unknown URL'), null);
});

it('empty jwt attribute', async () => {
const aSet = new AttributeSet();
// @ts-expect-error invalid value
aSet.addJwtAttribute({});
assert.equal(aSet.attributes.length, 0);
assert.deepEqual(aSet.getDefault(), null);
});

it('simplest jwt attribute', async () => {
const aSet = new AttributeSet();
const attribute = 'https://example.com/attr/test-case/value/bar';
const jwtAttr = await Mocks.createJwtAttribute({ attribute });
aSet.addJwtAttribute(jwtAttr);

const expected = Mocks.createAttribute({ attribute });
assert.include(aSet.get(attribute), expected);
});
});
3 changes: 2 additions & 1 deletion lib/tests/mocha/unit/tdf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ describe('TDF', () => {
});

it('allowedKases', () => {
const actual = TDF.create({ allowedKases: ['https://local.virtru.com'], cryptoService });
const cfg = { allowedKases: ['https://local.virtru.com'], cryptoService };
const actual = TDF.create(cfg);
expect(actual.allowedKases).to.contain('https://local.virtru.com');
});

Expand Down

0 comments on commit 459cb42

Please sign in to comment.