@raytio/core 11.1.0 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -2
- package/README.md +4 -3
- package/dist/crypto/getAADecryptor.js +32 -4
- package/dist/schema/expandSchema/__tests__/expandSchema.test.js +6 -5
- package/dist/schema/expandSchema/__tests__/processSchema.test.js +4 -4
- package/dist/schema/expandSchema/processSchema.d.ts +2 -3
- package/dist/schema/expandSchema/processSchema.js +2 -2
- package/dist/schema/expandSchema/removePrivateFields.d.ts +4 -2
- package/dist/schema/expandSchema/unwrapSchema.d.ts +3 -3
- package/dist/schema/expandSchema/unwrapSchema.js +1 -1
- package/dist/testHelpers.d.ts +9 -0
- package/dist/testHelpers.js +9 -0
- package/dist/verifications/getPOVerification.js +6 -2
- package/dist/verifications/getVerifiedBy.js +6 -1
- package/dist/verifications/verifyCheck/__tests__/getOwnRealVerifications.test.js +72 -7
- package/dist/verifications/verifyCheck/getOwnRealVerifications.js +3 -1
- package/dist/verifications/verifyCheck/operations/__tests__/checkOwnVerification.test.js +45 -5
- package/dist/verifications/verifyCheck/operations/__tests__/sampleBundle.json +1 -0
- package/dist/verifications/verifyCheck/operations/checkOwnVerification.d.ts +3 -2
- package/dist/verifications/verifyCheck/operations/checkOwnVerification.js +15 -8
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,13 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
<!--
|
|
9
|
+
If you think there are missing entries, go to:
|
|
10
|
+
https://gitlab.com/raytio/raytio-client-v2/-/commits/master/packages/core
|
|
11
|
+
to see all commits that touched this package.
|
|
12
|
+
-->
|
|
13
|
+
|
|
8
14
|
## [Unreleased]
|
|
9
15
|
|
|
10
|
-
## 11.
|
|
16
|
+
## 11.2.0 (2023-07-03)
|
|
17
|
+
|
|
18
|
+
- Support key rotation
|
|
19
|
+
- Support the new Schema API format
|
|
20
|
+
|
|
21
|
+
## 11.1.0 (2023-04-20)
|
|
11
22
|
|
|
12
23
|
- Move `expandSchema` into core
|
|
13
24
|
|
|
14
|
-
## 11.0.0 (
|
|
25
|
+
## 11.0.0 (2023-04-20)
|
|
15
26
|
|
|
16
27
|
- 💥 BREAKING CHANGE: Changed the input arguments of `convertInstanceToRuleInput` to only require a list of `ProfileObject`s, instead of the whole `Instance` object.
|
|
17
28
|
- Any profile object with an expired field is now considered `Expired`, even if other fields are verified.
|
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ ___
|
|
|
138
138
|
|
|
139
139
|
### checkJsonSignature
|
|
140
140
|
|
|
141
|
-
▸ **checkJsonSignature**(`data`, `signature`): `Promise`<`boolean`\>
|
|
141
|
+
▸ **checkJsonSignature**(`data`, `signature`, `keyId`): `Promise`<`boolean`\>
|
|
142
142
|
|
|
143
143
|
checks that a json object was signed by the provided signature. Unless you're
|
|
144
144
|
dealing with bundled verifications, you should use `getOwnRealVerifications`
|
|
@@ -150,6 +150,7 @@ or `getSomeoneElsesRealVerifications` instead.
|
|
|
150
150
|
| :------ | :------ |
|
|
151
151
|
| `data` | `unknown` |
|
|
152
152
|
| `signature` | `string` |
|
|
153
|
+
| `keyId` | `undefined` \| `string` |
|
|
153
154
|
|
|
154
155
|
#### Returns
|
|
155
156
|
|
|
@@ -393,7 +394,7 @@ two overloads - if you provide undefined, you might get undefined back
|
|
|
393
394
|
|
|
394
395
|
| Name | Type |
|
|
395
396
|
| :------ | :------ |
|
|
396
|
-
| `urn` |
|
|
397
|
+
| `urn` | `Urn` |
|
|
397
398
|
|
|
398
399
|
#### Returns
|
|
399
400
|
|
|
@@ -407,7 +408,7 @@ two overloads - if you provide undefined, you might get undefined back
|
|
|
407
408
|
|
|
408
409
|
| Name | Type |
|
|
409
410
|
| :------ | :------ |
|
|
410
|
-
| `urn` | `undefined` \|
|
|
411
|
+
| `urn` | `undefined` \| `Urn` |
|
|
411
412
|
|
|
412
413
|
#### Returns
|
|
413
414
|
|
|
@@ -2,16 +2,44 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getAADecryptor = void 0;
|
|
4
4
|
const util_1 = require("../util");
|
|
5
|
+
/**
|
|
6
|
+
* Fetches the Public Key Information for an Access Application
|
|
7
|
+
* @returns the id and Key information of the Applications Public Key
|
|
8
|
+
*/
|
|
9
|
+
async function fetchPublicKey({ apiUrl, apiToken, aId, }) {
|
|
10
|
+
const [publicKey] = await fetch(`${apiUrl}/db/v1/dsm_access_application_public_keys?aa_id=eq.${aId}`, {
|
|
11
|
+
headers: { Authorization: `Bearer ${apiToken}` },
|
|
12
|
+
}).then(util_1.handleResponse);
|
|
13
|
+
if (!publicKey) {
|
|
14
|
+
throw new Error("Could not Find Encryption Key");
|
|
15
|
+
}
|
|
16
|
+
const { id, public_key } = publicKey;
|
|
17
|
+
return { n_id: id, properties: public_key };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Fetches the Private Key Information from a Public Key
|
|
21
|
+
* @returns An encrypted Private Key
|
|
22
|
+
*/
|
|
23
|
+
async function fetchPrivateKey({ apiUrl, apiToken, publicKeyNId, }) {
|
|
24
|
+
const [privateKey] = await fetch(`${apiUrl}/db/v1/dsm_access_application_private_keys?aack_id=eq.${publicKeyNId}`, { headers: { Authorization: `Bearer ${apiToken}` } }).then(util_1.handleResponse);
|
|
25
|
+
return privateKey.encrypted_private_key;
|
|
26
|
+
}
|
|
5
27
|
/**
|
|
6
28
|
* Fetchs the public and private keys for an Access Application, then initializes
|
|
7
29
|
* the {@link https://npm.im/@raytio/maxcryptor|Maxcryptor}'s `ApplicationEncryptor`.
|
|
8
30
|
* @returns an `ApplicationEncryptor` and the public key of the Access Application
|
|
9
31
|
*/
|
|
10
32
|
async function getAADecryptor({ aId, apiUrl, maxcryptor, apiToken, }) {
|
|
11
|
-
const { n_id: publicKeyNId, properties: publicKey } = (await
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
33
|
+
const { n_id: publicKeyNId, properties: publicKey } = (await fetchPublicKey({
|
|
34
|
+
apiUrl,
|
|
35
|
+
apiToken,
|
|
36
|
+
aId,
|
|
37
|
+
}));
|
|
38
|
+
const privateKey = await fetchPrivateKey({
|
|
39
|
+
apiUrl,
|
|
40
|
+
apiToken,
|
|
41
|
+
publicKeyNId,
|
|
42
|
+
});
|
|
15
43
|
return {
|
|
16
44
|
decryptor: await maxcryptor.loadApplicationEncryptorForDecryption(publicKey, privateKey),
|
|
17
45
|
publicKeyNId,
|
|
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const expandSchema_1 = require("../expandSchema");
|
|
4
4
|
const fromAPI = [
|
|
5
5
|
{
|
|
6
|
-
|
|
6
|
+
schema_name: "ps_Vessel",
|
|
7
7
|
start_date: "1970-01-01T00:00:00+00:00",
|
|
8
8
|
end_date: "2222-12-31T23:59:59+00:00",
|
|
9
9
|
active: true,
|
|
10
10
|
version_current: true,
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
schema_version: "0.0.7",
|
|
12
|
+
schema_type: "ps",
|
|
13
13
|
schema: {
|
|
14
14
|
$id: "https://api.rayt.io/graph/v1/schema/ps/ps_Vessel",
|
|
15
15
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
@@ -38,8 +38,8 @@ const fromAPI = [
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
schema_name: "ss_birth_year_integer",
|
|
42
|
+
schema_version: "0.0.1",
|
|
43
43
|
schema: {
|
|
44
44
|
$id: "this field often has typos. in this case its blatantly wrong",
|
|
45
45
|
$schema: "https://whatever",
|
|
@@ -61,6 +61,7 @@ describe("expandSchema", () => {
|
|
|
61
61
|
start_date: "1970-01-01T00:00:00+00:00",
|
|
62
62
|
end_date: "2222-12-31T23:59:59+00:00",
|
|
63
63
|
version: "0.0.7",
|
|
64
|
+
schema_type: "ps",
|
|
64
65
|
i18n: fromAPI[0].schema.i18n,
|
|
65
66
|
isProfileSchema: true,
|
|
66
67
|
isSpSchema: false,
|
|
@@ -25,7 +25,7 @@ describe("processSchema", () => {
|
|
|
25
25
|
const allUnexpandedSchema = [
|
|
26
26
|
mock,
|
|
27
27
|
{
|
|
28
|
-
|
|
28
|
+
schema_name: "ss_first_name_01",
|
|
29
29
|
schema: {
|
|
30
30
|
properties: {},
|
|
31
31
|
tags: ["a tag from the child"],
|
|
@@ -95,14 +95,14 @@ describe("processSchema", () => {
|
|
|
95
95
|
const allUnexpandedSchema = [
|
|
96
96
|
mock,
|
|
97
97
|
{
|
|
98
|
-
|
|
98
|
+
schema_name: "ss_first_name_01",
|
|
99
99
|
schema: {
|
|
100
100
|
required: ["c", "d"],
|
|
101
101
|
properties: { first_name: { type: "string" } },
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
|
-
|
|
105
|
+
schema_name: "ss_last_name_01",
|
|
106
106
|
schema: {
|
|
107
107
|
required: ["e", "f"],
|
|
108
108
|
properties: { last_name: { type: "string" } },
|
|
@@ -297,7 +297,7 @@ describe("processSchema", () => {
|
|
|
297
297
|
],
|
|
298
298
|
};
|
|
299
299
|
const moeSubSchema = {
|
|
300
|
-
|
|
300
|
+
schema_name: "ss_MOE_ID",
|
|
301
301
|
schema: {
|
|
302
302
|
type: "number",
|
|
303
303
|
properties: {},
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { Schema, WrappedSchema } from "@raytio/types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}, allUnexpandedSchemas: WrappedSchema[]) => Schema;
|
|
2
|
+
import type { unwrapSchema } from "./unwrapSchema";
|
|
3
|
+
export declare const processSchema: (schema: ReturnType<typeof unwrapSchema>, allUnexpandedSchemas: WrappedSchema[]) => Schema;
|
|
@@ -19,7 +19,7 @@ const processSchema = (schema, allUnexpandedSchemas) => {
|
|
|
19
19
|
const subSchemaName = (0, sortSchemaProperties_1.getNidFromUrn)((_a = schema.definitions) === null || _a === void 0 ? void 0 : _a[$ref.split("/")[2]].$ref);
|
|
20
20
|
if (!subSchemaName)
|
|
21
21
|
throw new Error("Invalid schema");
|
|
22
|
-
const subSchema = (_b = allUnexpandedSchemas.find(s => s.
|
|
22
|
+
const subSchema = (_b = allUnexpandedSchemas.find(s => s.schema_name === subSchemaName)) === null || _b === void 0 ? void 0 : _b.schema;
|
|
23
23
|
if (!subSchema) {
|
|
24
24
|
throw new Error(`Could not resolve subschema '${subSchemaName}'`);
|
|
25
25
|
}
|
|
@@ -44,7 +44,7 @@ const processSchema = (schema, allUnexpandedSchemas) => {
|
|
|
44
44
|
const subSchemaName = (0, sortSchemaProperties_1.getNidFromUrn)((_a = schema.definitions) === null || _a === void 0 ? void 0 : _a[$ref.split("/")[2]].$ref);
|
|
45
45
|
if (!subSchemaName)
|
|
46
46
|
throw new Error("Invalid schema");
|
|
47
|
-
const subSchema = (_b = allUnexpandedSchemas.find(s => s.
|
|
47
|
+
const subSchema = (_b = allUnexpandedSchemas.find(s => s.schema_name === subSchemaName)) === null || _b === void 0 ? void 0 : _b.schema;
|
|
48
48
|
if (!subSchema) {
|
|
49
49
|
throw new Error(`Could not resolve subschema '${subSchemaName}'`);
|
|
50
50
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Schema } from "@raytio/types";
|
|
2
2
|
export declare const removePrivateFields: (schema: Schema) => {
|
|
3
3
|
properties: Record<string, Omit<import("@raytio/types").SchemaField, "$id" | "allOf" | "$schema" | "definitions">>;
|
|
4
|
-
type?: import("@raytio/types").DataTypes | undefined;
|
|
5
|
-
name: string;
|
|
6
4
|
title: string;
|
|
7
5
|
description: string;
|
|
6
|
+
description_decorator?: "md" | undefined;
|
|
7
|
+
schema_type?: import("@raytio/types").SchemaType | undefined;
|
|
8
8
|
title_plural?: string | undefined;
|
|
9
9
|
schema_group?: string | undefined;
|
|
10
10
|
tags?: import("@raytio/types").SchemaTag[] | undefined;
|
|
@@ -104,6 +104,8 @@ export declare const removePrivateFields: (schema: Schema) => {
|
|
|
104
104
|
}[] | undefined;
|
|
105
105
|
return_to?: string | undefined;
|
|
106
106
|
} | undefined;
|
|
107
|
+
name: string;
|
|
108
|
+
type?: import("@raytio/types").DataTypes | undefined;
|
|
107
109
|
group_title?: string | undefined;
|
|
108
110
|
verified_fields?: (string | import("@raytio/types").ConditionallyRequired)[] | undefined;
|
|
109
111
|
required?: (string | import("@raytio/types").ConditionallyRequired)[] | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WrappedSchema } from "@raytio/types";
|
|
2
|
-
export
|
|
2
|
+
export type ClientFields = {
|
|
3
3
|
version: string;
|
|
4
|
-
|
|
5
|
-
end_date?: string;
|
|
4
|
+
name: string;
|
|
6
5
|
};
|
|
6
|
+
export declare function unwrapSchema(wrapped: WrappedSchema): WrappedSchema["schema"] & Pick<WrappedSchema, "start_date" | "end_date" | "schema_type"> & ClientFields;
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.unwrapSchema = void 0;
|
|
4
4
|
function unwrapSchema(wrapped) {
|
|
5
|
-
return Object.assign(Object.assign({}, wrapped.schema), { name: wrapped.
|
|
5
|
+
return Object.assign(Object.assign({}, wrapped.schema), { name: wrapped.schema_name, start_date: wrapped.start_date, end_date: wrapped.end_date, version: wrapped.schema_version, schema_type: wrapped.schema_type });
|
|
6
6
|
}
|
|
7
7
|
exports.unwrapSchema = unwrapSchema;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
/** like {@link Partial} but applies to all deep properties */
|
|
3
|
+
export type DeepPartial<T> = T | (T extends object ? {
|
|
4
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
5
|
+
} : T extends (infer U)[] ? DeepPartial<U>[] : T);
|
|
6
|
+
/** tells typescript that this function is mocked */
|
|
7
|
+
export declare const m: (func: unknown) => jest.Mock<any, any, any>;
|
|
8
|
+
/** helper to let us define type-safe partial mocks */
|
|
9
|
+
export declare const deepPartial: <T>(partial: DeepPartial<T>) => T;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deepPartial = exports.m = void 0;
|
|
4
|
+
/** tells typescript that this function is mocked */
|
|
5
|
+
const m = (func) => func;
|
|
6
|
+
exports.m = m;
|
|
7
|
+
/** helper to let us define type-safe partial mocks */
|
|
8
|
+
const deepPartial = (partial) => partial;
|
|
9
|
+
exports.deepPartial = deepPartial;
|
|
@@ -55,10 +55,13 @@ function getPOVerification({ PO, schema, realVers, }) {
|
|
|
55
55
|
if (pertainingVers.length && pertainingVers.every(x => x.expired)) {
|
|
56
56
|
return types_1.FieldVerification.Expired;
|
|
57
57
|
}
|
|
58
|
-
if
|
|
58
|
+
// the if statement above checks if every ver is expired. It is possible
|
|
59
|
+
// that only some are expired, so filter out those ones.
|
|
60
|
+
const nonExpiredPertainingVers = pertainingVers.filter(x => !x.expired);
|
|
61
|
+
if (nonExpiredPertainingVers.some(x => !x.verified)) {
|
|
59
62
|
return types_1.FieldVerification.VerifiedFalse;
|
|
60
63
|
}
|
|
61
|
-
return
|
|
64
|
+
return nonExpiredPertainingVers.length
|
|
62
65
|
? types_1.FieldVerification.Verified
|
|
63
66
|
: types_1.FieldVerification.NotVerified;
|
|
64
67
|
}, shouldBeVerifiedProps);
|
|
@@ -89,6 +92,7 @@ function getPOVerification({ PO, schema, realVers, }) {
|
|
|
89
92
|
nId: PO.n_id,
|
|
90
93
|
realVers,
|
|
91
94
|
shouldBeVerifiedProps,
|
|
95
|
+
verificationStatus: status,
|
|
92
96
|
});
|
|
93
97
|
// find the earliest expiry date if there is one
|
|
94
98
|
const maybeExpiryDate = ((_c = (0, ramda_1.sortBy)(ver => +ver.expired, realVers.filter(ver => ver.belongsToNId === PO.n_id && ver.expired))[0]) === null || _c === void 0 ? void 0 : _c.expired) || undefined;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getVerifiedBy = void 0;
|
|
4
|
+
const types_1 = require("@raytio/types");
|
|
4
5
|
const ramda_1 = require("ramda");
|
|
5
6
|
const maybeRereference_1 = require("./maybeRereference");
|
|
6
7
|
// we should probably document this
|
|
7
8
|
/** @internal */
|
|
8
|
-
const getVerifiedBy = ({ nId, realVers, shouldBeVerifiedProps, }) => {
|
|
9
|
+
const getVerifiedBy = ({ nId, realVers, shouldBeVerifiedProps, verificationStatus, }) => {
|
|
9
10
|
const mayBeVerifiedFields = Object.keys(shouldBeVerifiedProps);
|
|
10
11
|
const pertainingVers = realVers
|
|
12
|
+
// for valid verifications, we filter out the information that came from
|
|
13
|
+
// now expired sources. We only ever consider expired source if the verification
|
|
14
|
+
// itself is expired.
|
|
15
|
+
.filter(realVer => realVer.expired ? verificationStatus === types_1.POVerification.Expired : true)
|
|
11
16
|
.filter(x => mayBeVerifiedFields.includes(x.fieldName) &&
|
|
12
17
|
// using ramda's `equals` because this needs to work for objects/arrays
|
|
13
18
|
(0, ramda_1.equals)((0, maybeRereference_1.maybeRereference)(shouldBeVerifiedProps[x.fieldName]), (0, maybeRereference_1.maybeRereference)(x.value)) &&
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const __1 = require("..");
|
|
4
4
|
const operations_1 = require("../operations");
|
|
5
|
+
const testHelpers_1 = require("../../../testHelpers");
|
|
5
6
|
jest.mock("../operations");
|
|
6
7
|
/**
|
|
7
8
|
* in this test case:
|
|
@@ -16,18 +17,21 @@ jest.mock("../operations");
|
|
|
16
17
|
*/
|
|
17
18
|
describe("getOwnRealVerifications", () => {
|
|
18
19
|
beforeAll(() => {
|
|
19
|
-
|
|
20
|
+
jest.fn(() => 1).mockImplementation(() => 123);
|
|
21
|
+
(0, testHelpers_1.m)(operations_1.checkOwnVerification).mockImplementation(async ({ signature, keyId }) => {
|
|
20
22
|
if (signature === "signatureForLastNameMustermannFromN2")
|
|
21
23
|
return false;
|
|
24
|
+
if (keyId === "thisOneIsInvalid")
|
|
25
|
+
return false;
|
|
22
26
|
return true;
|
|
23
27
|
});
|
|
24
28
|
});
|
|
25
29
|
it("calls the verifyCheck function for each PO, keeping only the valid ones", async () => {
|
|
26
|
-
const profileObjects = [
|
|
30
|
+
const profileObjects = (0, testHelpers_1.deepPartial)([
|
|
27
31
|
{ n_id: "n1", properties: { fName: "Max", lName: "Mustermann" } },
|
|
28
32
|
{ n_id: "n2", properties: { fName: "Erika", lName: "Mustermann" } },
|
|
29
|
-
];
|
|
30
|
-
const verifications = [
|
|
33
|
+
]);
|
|
34
|
+
const verifications = (0, testHelpers_1.deepPartial)([
|
|
31
35
|
{
|
|
32
36
|
n_id: "nv1",
|
|
33
37
|
properties: {
|
|
@@ -65,6 +69,7 @@ describe("getOwnRealVerifications", () => {
|
|
|
65
69
|
passed: false,
|
|
66
70
|
source_n_id: "n2",
|
|
67
71
|
},
|
|
72
|
+
// legacy: key_id is not specified
|
|
68
73
|
},
|
|
69
74
|
],
|
|
70
75
|
},
|
|
@@ -103,14 +108,56 @@ describe("getOwnRealVerifications", () => {
|
|
|
103
108
|
],
|
|
104
109
|
},
|
|
105
110
|
},
|
|
106
|
-
|
|
111
|
+
{
|
|
112
|
+
n_id: "nv6",
|
|
113
|
+
properties: {
|
|
114
|
+
verifications: [
|
|
115
|
+
{
|
|
116
|
+
signature: "signatureForFirstNameErika",
|
|
117
|
+
data: {
|
|
118
|
+
field: "fName",
|
|
119
|
+
verifier_source_id: "Ministry of pike river mine re-entry",
|
|
120
|
+
verifier_service_id: "Minister for pike river re-entry",
|
|
121
|
+
verifier_id: "v1",
|
|
122
|
+
verification_date: "2020-08-28T23:11:20.592912",
|
|
123
|
+
request_div: "x2",
|
|
124
|
+
passed: false,
|
|
125
|
+
source_n_id: "n2",
|
|
126
|
+
},
|
|
127
|
+
key_id: "thisOneIsInvalid", // signature & data is good, but this key will fail
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
n_id: "nv7",
|
|
134
|
+
properties: {
|
|
135
|
+
verifications: [
|
|
136
|
+
{
|
|
137
|
+
signature: "signatureForFirstNameErika",
|
|
138
|
+
data: {
|
|
139
|
+
field: "fName",
|
|
140
|
+
verifier_source_id: "Ministry of pike river mine re-entry",
|
|
141
|
+
verifier_service_id: "Minister for pike river re-entry",
|
|
142
|
+
verifier_id: "v1",
|
|
143
|
+
verification_date: "2020-08-28T23:11:20.592912",
|
|
144
|
+
request_div: "x2",
|
|
145
|
+
passed: false,
|
|
146
|
+
source_n_id: "n2",
|
|
147
|
+
},
|
|
148
|
+
key_id: "raytio", // everything is valid, this is the right key_id
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
107
154
|
const realVers = await (0, __1.getOwnRealVerifications)({
|
|
108
155
|
profileObjects,
|
|
109
156
|
verifications,
|
|
110
157
|
userId: "geesepolice2002",
|
|
111
158
|
});
|
|
112
|
-
expect(operations_1.checkOwnVerification).toHaveBeenCalledTimes(
|
|
113
|
-
//
|
|
159
|
+
expect(operations_1.checkOwnVerification).toHaveBeenCalledTimes(5);
|
|
160
|
+
// 5 times. Because this new method doesn't need to build a big matrix of possible combinations
|
|
114
161
|
expect(realVers).toStrictEqual([
|
|
115
162
|
{
|
|
116
163
|
fieldName: "fName",
|
|
@@ -147,6 +194,24 @@ describe("getOwnRealVerifications", () => {
|
|
|
147
194
|
xId: "x2",
|
|
148
195
|
},
|
|
149
196
|
// note how lastName is not included since it's invalid
|
|
197
|
+
// nv6 is not included because the key is invalid
|
|
198
|
+
{
|
|
199
|
+
fieldName: "fName",
|
|
200
|
+
value: "Erika",
|
|
201
|
+
belongsToNId: "n2",
|
|
202
|
+
nID: "nv7",
|
|
203
|
+
expired: false,
|
|
204
|
+
metadata: undefined,
|
|
205
|
+
provider: {
|
|
206
|
+
dataSourceNId: "Ministry of pike river mine re-entry",
|
|
207
|
+
date: new Date("2020-08-28T23:11:20.592Z"),
|
|
208
|
+
serviceProviderNId: "Minister for pike river re-entry",
|
|
209
|
+
verifierNId: "v1",
|
|
210
|
+
},
|
|
211
|
+
signature: "signatureForFirstNameErika",
|
|
212
|
+
verified: false,
|
|
213
|
+
xId: "x2",
|
|
214
|
+
},
|
|
150
215
|
]);
|
|
151
216
|
});
|
|
152
217
|
});
|
|
@@ -17,7 +17,8 @@ const getOwnRealVerifications = async ({ verifications, profileObjects, userId,
|
|
|
17
17
|
// because attempting hundreds of webcrypto operations simultaneously will
|
|
18
18
|
// probably upset some heritage web browser.
|
|
19
19
|
for (const ver of verifications) {
|
|
20
|
-
for (const
|
|
20
|
+
for (const verPO of ver.properties.verifications) {
|
|
21
|
+
const { data, signature } = verPO;
|
|
21
22
|
const sourcePO = profileObjects.find(PO => PO.n_id === data.source_n_id);
|
|
22
23
|
if (!sourcePO)
|
|
23
24
|
continue;
|
|
@@ -34,6 +35,7 @@ const getOwnRealVerifications = async ({ verifications, profileObjects, userId,
|
|
|
34
35
|
userId,
|
|
35
36
|
value: (0, maybeRereference_1.maybeRereference)(value),
|
|
36
37
|
signature,
|
|
38
|
+
keyId: verPO.key_id,
|
|
37
39
|
});
|
|
38
40
|
if (!isGenuine)
|
|
39
41
|
continue;
|
|
@@ -7,8 +7,9 @@ const __1 = require("..");
|
|
|
7
7
|
const util_1 = require("../../../../util");
|
|
8
8
|
const checkOwnVerification_1 = require("../checkOwnVerification");
|
|
9
9
|
const sampleBundle_json_1 = __importDefault(require("./sampleBundle.json"));
|
|
10
|
-
|
|
11
|
-
text: async () =>
|
|
10
|
+
global.fetch = jest.fn().mockImplementation(async (url) => ({
|
|
11
|
+
text: async () => url.endsWith("raytio.pem")
|
|
12
|
+
? `-----BEGIN PUBLIC KEY-----
|
|
12
13
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAn9QtCqYa3H3ipFFU0xP3
|
|
13
14
|
n6r7KHS3GMbh0h/xzel57HhCIaXDYjUeUtgUNtzm+uElb/qzGn50xQRzVqO32vKB
|
|
14
15
|
ZAW2kYyZ2+R5ruk9CSxr7K4Vk1FtDMcUCzqxm0eycFD2xbLsN3feRc3BMjfdaQ7P
|
|
@@ -21,10 +22,29 @@ Gut0BoM+DIwDu0uZaUprz7fSgNmYHHEiIFbOMVHiOn8oZAZbJXXbUbFIUYXA8u9+
|
|
|
21
22
|
J1Z+QEpgw+rhGzOf/TSeHfMC9nNbWgYglluAJusWf2XwG/t/VlhtzviHCVGEL7HQ
|
|
22
23
|
jQE5DrM7vaTg6Gu9bjKuoeLIRzbOYK6qAWFoa0CLcN84PLjhDSRw2duatP08hcWg
|
|
23
24
|
jTgOkLWnBFE7NyRU93uPp68CAwEAAQ==
|
|
25
|
+
-----END PUBLIC KEY-----`
|
|
26
|
+
: `-----BEGIN PUBLIC KEY-----
|
|
27
|
+
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgee+uBOgOsbwjMvGN1/H
|
|
28
|
+
qpGXGJLol0Pc2KhI1fh1NBq+UGhk8PqgDd5wHZikbmrtVkvp/maIh+mbIdehY/RC
|
|
29
|
+
ftMylvebCf4Qf+5SWzQsmB1o6nBUbwJzYE2XyvxRiNLhdIeE+GgfdpA5S3l0cDJ5
|
|
30
|
+
B/1TagITmQUjThwTxDYZ6jlGJJ4NSjqlqeQrHhGWRLVQPWU8bYysX3jt3/uiv4tS
|
|
31
|
+
n3TheLGY1TMlbFrVF2Spv1WxuqMZ4bX1mIotK3yEB3TaZSZaOwlUcEZ4xY+J4Vl+
|
|
32
|
+
ZlrOgYbmzFd7UFh9UZbYZUkNSEfddEnFNFlFG3YQVt8UAumPBVJELdjiaRjTj/K4
|
|
33
|
+
62GAFyOcbcw9wcl69fPBnieBo2m2Dqf6U3wcrnvTnkMwjCewWXCH6FdbC4OllBZV
|
|
34
|
+
Nrfn6zf9yX0J8ZEDEcw9ZsNLVkyl2U+Ya/h5CQt43ip/1eNM5LpTbfqBTtAH7iUO
|
|
35
|
+
4L9rxuSJFA2Q9kZfof6kYO9EGgMFB+GM47/Q068+IiTpifvPno4ilnowyS4hbLiy
|
|
36
|
+
Os2yudW79flaz0a7rq5dLdSg9Mm5k7ETBm7WguDcocaiETmuYTJT2PozAGOC3+EP
|
|
37
|
+
w5N7mQff4ecx/880FWYKQmU9Asav1V49DWSnG0ZXQ7U24dG8ANeAgKddDviJGlsh
|
|
38
|
+
SsjKrz8LJqeoNQu30iSGZhUCAwEAAQ==
|
|
24
39
|
-----END PUBLIC KEY-----`,
|
|
25
40
|
}));
|
|
26
41
|
describe("checkOwnVerification", () => {
|
|
27
|
-
it
|
|
42
|
+
it.each `
|
|
43
|
+
keyId | result
|
|
44
|
+
${undefined} | ${true}
|
|
45
|
+
${"whatever/raytio"} | ${true}
|
|
46
|
+
${"whatever/somethingElse"} | ${false}
|
|
47
|
+
`("returns $result for a PO with keyId=$keyId", async ({ keyId, result }) => {
|
|
28
48
|
//
|
|
29
49
|
// This is an important integration test from ca. 2021-12-02.
|
|
30
50
|
// If it is failing, it means our code no longer matches the
|
|
@@ -54,7 +74,13 @@ describe("checkOwnVerification", () => {
|
|
|
54
74
|
};
|
|
55
75
|
const value = "NZ Limited Company";
|
|
56
76
|
const userId = "fd9c4903-65c2-4b75-b454-4fda6b682f3e"; // for 27july21 🦈
|
|
57
|
-
expect(await (0, checkOwnVerification_1.checkOwnVerification)({
|
|
77
|
+
expect(await (0, checkOwnVerification_1.checkOwnVerification)({
|
|
78
|
+
value,
|
|
79
|
+
signature,
|
|
80
|
+
userId,
|
|
81
|
+
verObject,
|
|
82
|
+
keyId,
|
|
83
|
+
})).toBe(result);
|
|
58
84
|
});
|
|
59
85
|
it("errors if you forget to supply the uId", async () => {
|
|
60
86
|
await expect(() => (0, checkOwnVerification_1.checkOwnVerification)({
|
|
@@ -62,8 +88,22 @@ describe("checkOwnVerification", () => {
|
|
|
62
88
|
signature: "",
|
|
63
89
|
userId: "",
|
|
64
90
|
verObject: {},
|
|
91
|
+
keyId: undefined,
|
|
65
92
|
})).rejects.toThrow(new Error("No userId supplied"));
|
|
66
93
|
});
|
|
94
|
+
it.each `
|
|
95
|
+
keyId
|
|
96
|
+
${"../../malicious.pem"}
|
|
97
|
+
${"https://example.com/malicious.pem"}
|
|
98
|
+
`("errors if you supply an invalid keyId", async ({ keyId }) => {
|
|
99
|
+
await expect(() => (0, checkOwnVerification_1.checkOwnVerification)({
|
|
100
|
+
value: "whatever",
|
|
101
|
+
signature: "whatever",
|
|
102
|
+
userId: "whatever",
|
|
103
|
+
verObject: {},
|
|
104
|
+
keyId,
|
|
105
|
+
})).rejects.toThrow(new Error("Invalid key ID"));
|
|
106
|
+
});
|
|
67
107
|
});
|
|
68
108
|
describe("checkSignature", () => {
|
|
69
109
|
it("works", async () => {
|
|
@@ -86,6 +126,6 @@ describe("checkSignature", () => {
|
|
|
86
126
|
});
|
|
87
127
|
describe("checkJsonSignature", () => {
|
|
88
128
|
it("can verify a bundled verification", async () => {
|
|
89
|
-
expect(await (0, __1.checkJsonSignature)(sampleBundle_json_1.default.data, sampleBundle_json_1.default.signature)).toBe(true);
|
|
129
|
+
expect(await (0, __1.checkJsonSignature)(sampleBundle_json_1.default.data, sampleBundle_json_1.default.signature, sampleBundle_json_1.default.key_id)).toBe(true);
|
|
90
130
|
});
|
|
91
131
|
});
|
|
@@ -39,5 +39,6 @@
|
|
|
39
39
|
},
|
|
40
40
|
"valid_until": "2022-03-13T02:26:20.468171"
|
|
41
41
|
},
|
|
42
|
+
"key_id": "any string/raytio",
|
|
42
43
|
"signature": "AiWWrL+S1paYOqJiOtU3qwLTCkkZjwDq3FuHl7oy14IATYOhCeHLf+ca44X1Wc6pYpTQckjKnJZL\nkfgiwNE97aymWIOc+ZZGEb5YhXRNO+inTV4k5zppaDN3n3YAGzn7zMxleh3+opzJqncNaJtpZ0Wv\na9Pu/m4WjyT5ee3Myz6VOOMuVkcaTL4FD8XT7NdCh0ybRevAZ5R9xl0YuWMhvNpf3P6ieTikHXYN\nkKbPTnAhNdBmqV4njSIR66M82Ek0d9VcsX4zhmlhpdCmGRlXLgHEyMCF4iHlCIxSeKtGaOm2QK2R\nOV/lN3VScDNWyD8lPBipcj++5ZGII6BnFFG8LlT3gY/Y/wt8KeH/xgdu0a7Lt6J/BOiGLFfscUmb\nH5K5t48gnQ5BQS+Cf/yhayMV49LlGiK9m1iPlbmuJH1L2/ZM+iLsIrSTGCU0Rpbkw7qvm0dkUNYf\nhvlj/RnUxcy0Lr/84CzLvBhFMmBX+RHlcPrCWpIiibsdaD81kRyvLY2TASLFTeHajfr+UvtP3LVs\n8NGwRQHd6c2/ptxv3ERRUnDtNASatsLe67ZHg9SeF3BDhMHZwU1neYyrBI1TMECasFli5rP5gviq\nC8ZwFQ9lnDDTidWBF8GjRl6ope4wIuNBBkOsIIeyqIJE5BRUH4LhVUnN1be696uCKnWOyOo7fkc=\n"
|
|
43
44
|
}
|
|
@@ -4,12 +4,13 @@ type SingleVerToCheck = {
|
|
|
4
4
|
signature: string;
|
|
5
5
|
userId: UId;
|
|
6
6
|
value: unknown;
|
|
7
|
+
keyId: string | undefined;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* checks that a json object was signed by the provided signature. Unless you're
|
|
10
11
|
* dealing with bundled verifications, you should use `getOwnRealVerifications`
|
|
11
12
|
* or `getSomeoneElsesRealVerifications` instead.
|
|
12
13
|
*/
|
|
13
|
-
export declare const checkJsonSignature: (data: unknown, signature: string) => Promise<boolean>;
|
|
14
|
-
export declare const checkOwnVerification: ({ verObject, signature, userId, value, }: SingleVerToCheck) => Promise<boolean>;
|
|
14
|
+
export declare const checkJsonSignature: (data: unknown, signature: string, keyId: string | undefined) => Promise<boolean>;
|
|
15
|
+
export declare const checkOwnVerification: ({ verObject, signature, userId, value, keyId, }: SingleVerToCheck) => Promise<boolean>;
|
|
15
16
|
export {};
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkOwnVerification = exports.checkJsonSignature = exports.checkSignature = void 0;
|
|
4
4
|
const util_1 = require("../../../util");
|
|
5
|
-
|
|
5
|
+
const cache = {};
|
|
6
6
|
const base64ToArrayBuffer = (str) => Uint8Array.from(atob(str), c => c.charCodeAt(0));
|
|
7
|
-
async function getJwk() {
|
|
7
|
+
async function getJwk(keyUrl) {
|
|
8
8
|
// eslint-disable-next-line fp/no-mutation
|
|
9
|
-
cache || (cache = fetch(
|
|
9
|
+
cache[keyUrl] || (cache[keyUrl] = fetch(keyUrl)
|
|
10
10
|
.then(r => r.text())
|
|
11
11
|
.then(pem => crypto.subtle.importKey("spki", base64ToArrayBuffer(pem.split("-----")[2].trim()), { name: "RSA-PSS", hash: "SHA-512" }, false, ["verify"])));
|
|
12
|
-
return cache;
|
|
12
|
+
return cache[keyUrl];
|
|
13
13
|
}
|
|
14
14
|
/** @internal exported only for tests */
|
|
15
15
|
async function checkSignature(publicCryptoKey, signature, data) {
|
|
@@ -24,16 +24,23 @@ exports.checkSignature = checkSignature;
|
|
|
24
24
|
* dealing with bundled verifications, you should use `getOwnRealVerifications`
|
|
25
25
|
* or `getSomeoneElsesRealVerifications` instead.
|
|
26
26
|
*/
|
|
27
|
-
const checkJsonSignature = async (data, signature) => {
|
|
28
|
-
const
|
|
27
|
+
const checkJsonSignature = async (data, signature, keyId) => {
|
|
28
|
+
const keyFileName = keyId ? keyId.split("/")[1] : "raytio";
|
|
29
|
+
// don't allow any special characters, e.g. to prevent
|
|
30
|
+
// someone using a keyID of "../../someOtherFile"
|
|
31
|
+
if (!keyFileName || /[^\w-]/.test(keyFileName)) {
|
|
32
|
+
throw new Error("Invalid key ID");
|
|
33
|
+
}
|
|
34
|
+
const keyUrl = `https://api-docs.rayt.io/lookups/${keyFileName}.pem`;
|
|
35
|
+
const jwk = await getJwk(keyUrl);
|
|
29
36
|
const stringified = (0, util_1.canonicalJsonify)(data);
|
|
30
37
|
const result = await checkSignature(jwk, signature, stringified);
|
|
31
38
|
return result;
|
|
32
39
|
};
|
|
33
40
|
exports.checkJsonSignature = checkJsonSignature;
|
|
34
|
-
const checkOwnVerification = async ({ verObject, signature, userId, value, }) => {
|
|
41
|
+
const checkOwnVerification = async ({ verObject, signature, userId, value, keyId, }) => {
|
|
35
42
|
if (!userId)
|
|
36
43
|
throw new Error("No userId supplied");
|
|
37
|
-
return (0, exports.checkJsonSignature)(Object.assign(Object.assign({}, verObject), { sub: userId, value }), signature);
|
|
44
|
+
return (0, exports.checkJsonSignature)(Object.assign(Object.assign({}, verObject), { sub: userId, value }), signature, keyId);
|
|
38
45
|
};
|
|
39
46
|
exports.checkOwnVerification = checkOwnVerification;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raytio/core",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "index",
|
|
6
6
|
"types": "index",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@raytio/maxcryptor": "3.1.0",
|
|
20
|
-
"@raytio/types": "7.
|
|
20
|
+
"@raytio/types": "7.2.0",
|
|
21
21
|
"ramda": "0.29.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/ramda": "0.29.
|
|
25
|
-
"jest": "29.
|
|
26
|
-
"ts-jest": "29.1.
|
|
24
|
+
"@types/ramda": "0.29.2",
|
|
25
|
+
"jest": "29.6.1",
|
|
26
|
+
"ts-jest": "29.1.1"
|
|
27
27
|
},
|
|
28
28
|
"jest": {
|
|
29
29
|
"transform": {
|