Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 1d522173 authored by Yannick HUARD's avatar Yannick HUARD
Browse files

chore: fix CLI - EBSIINT-9950

parent 96fe23f7
Branches
No related tags found
1 merge request!71chore: fix CLI - EBSIINT-9950
---
---
...@@ -52,7 +52,7 @@ Example: ...@@ -52,7 +52,7 @@ Example:
```sh ```sh
EBSI_ENV=pilot EBSI_ENV=pilot
APIS_VERSION=latest TSR_API_VERSION=v3
KID="did:ebsi:z...#..." KID="did:ebsi:z...#..."
PRIVATE_KEY=0x... PRIVATE_KEY=0x...
``` ```
...@@ -60,7 +60,7 @@ PRIVATE_KEY=0x... ...@@ -60,7 +60,7 @@ PRIVATE_KEY=0x...
The CLI supports 4 different environment variables: The CLI supports 4 different environment variables:
- `EBSI_ENV`: the environment to which the schemas should be deployed. Valid values: `test`, `conformance`, `pilot`. - `EBSI_ENV`: the environment to which the schemas should be deployed. Valid values: `test`, `conformance`, `pilot`.
- `APIS_VERSION`: whether the schemas should be deployed to the latest APIs (TSR API v3) or the old ones. Valid values: `latest`, `old`. - `TSR_API_VERSION`: version of TSR API to which the schemas will be published. Valid values: `v2`, `v3`, `v4`.
- `KID`: the deployer's KID. - `KID`: the deployer's KID.
- `PRIVATE_KEY`: the deployer's private key, in hexadecimal, prefixed with `0x`. - `PRIVATE_KEY`: the deployer's private key, in hexadecimal, prefixed with `0x`.
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
import inquirer from "inquirer"; import inquirer from "inquirer";
import { Listr } from "listr2"; import { Listr } from "listr2";
import axios from "axios"; import axios from "axios";
import { hexToBytes, ES256Signer } from "did-jwt";
import { requestSiopJwt } from "../utils/requestSiopJwt.js"; import { requestSiopJwt } from "../utils/requestSiopJwt.js";
import { getAccessToken } from "../utils/getAccessToken.js";
import { publishSchema } from "../utils/ledger.js"; import { publishSchema } from "../utils/ledger.js";
import { getSchemaIds, validateSchema } from "../utils/jsonSchema.js"; import { getSchemaIds, validateSchema } from "../utils/jsonSchema.js";
import CONFIG from "../config.js"; import CONFIG from "../config.js";
...@@ -13,10 +15,7 @@ export default (program) => ...@@ -13,10 +15,7 @@ export default (program) =>
.description("Publish the JSON Schema to the Trusted Schemas Registry") .description("Publish the JSON Schema to the Trusted Schemas Registry")
.argument("[paths...]", "Path(s) to JSON Schema(s)") .argument("[paths...]", "Path(s) to JSON Schema(s)")
.option("--all", "Publish all JSON Schemas") .option("--all", "Publish all JSON Schemas")
.option( .option("-e, --env <env>", "EBSI Environment (test, conformance, pilot)")
"-e, --env <env>",
"EBSI Environment (test, conformance, pilot, preprod)",
)
.action(async (paths, options) => { .action(async (paths, options) => {
// Get IDs of the JSON Schemas the user wants to publish // Get IDs of the JSON Schemas the user wants to publish
const schemaIds = await getSchemaIds(paths, options); const schemaIds = await getSchemaIds(paths, options);
...@@ -35,7 +34,6 @@ export default (program) => ...@@ -35,7 +34,6 @@ export default (program) =>
{ value: "test" }, { value: "test" },
{ value: "conformance" }, { value: "conformance" },
{ value: "pilot" }, { value: "pilot" },
{ value: "preprod" },
], ],
}, },
]); ]);
...@@ -62,33 +60,38 @@ export default (program) => ...@@ -62,33 +60,38 @@ export default (program) =>
{ {
type: "password", type: "password",
name: "privateKey", name: "privateKey",
message: "Enter your hex private key (prefixed with 0x)", message: "Enter your hexadecimal private key (prefixed with 0x)",
}, },
]); ]);
kid = answers.kid; kid = answers.kid;
privateKey = answers.privateKey; privateKey = answers.privateKey;
} }
let apisVersion; // "latest"; "old" let apisVersion; // "v2", "v3", "v4"
if ( if (
process.env.APIS_VERSION && process.env.TSR_API_VERSION &&
["latest", "old"].includes(process.env.APIS_VERSION) (env === "test" ? ["v2", "v3", "v4"] : ["v2", "v3"]).includes(
process.env.TSR_API_VERSION,
)
) { ) {
apisVersion = process.env.APIS_VERSION; apisVersion = process.env.TSR_API_VERSION;
} else { } else {
// Request user KID and private key // Request TSR API version
const answers = await inquirer.prompt([ const answers = await inquirer.prompt([
{ {
type: "list", type: "list",
name: "version", name: "version",
message: message: `On which version of TSR API do you want to publish the schema${schemaIds.length > 1 ? "s" : ""}?`,
"On which version of the APIs do you want to publish the schema(s)?", choices:
choices: [{ value: "latest" }, { value: "old" }], env === "test"
? [{ value: "v2" }, { value: "v3" }, { value: "v4" }]
: [{ value: "v2" }, { value: "v3" }],
}, },
]); ]);
apisVersion = answers.version; apisVersion = answers.version;
} }
console.error("apisVersion", apisVersion);
const tasks = new Listr( const tasks = new Listr(
[ [
{ {
...@@ -121,7 +124,8 @@ export default (program) => ...@@ -121,7 +124,8 @@ export default (program) =>
task.title = "Validation complete"; task.title = "Validation complete";
}, },
}, },
{ apisVersion === "v2"
? {
title: "Requesting SIOP JWT...", title: "Requesting SIOP JWT...",
task: async (ctx, task) => { task: async (ctx, task) => {
// Get SIOP JWT // Get SIOP JWT
...@@ -159,6 +163,50 @@ export default (program) => ...@@ -159,6 +163,50 @@ export default (program) =>
); );
} }
}, },
}
: {
title:
'Requesting access token with "openid tsr_write" scope...',
task: async (ctx, task) => {
// Get "openid tsr_write" access token
try {
const accessToken = await getAccessToken(
CONFIG[env][apisVersion].AUTH,
{
did: kid.split("#")[0],
kid,
alg: "ES256",
signer: ES256Signer(hexToBytes(privateKey)),
},
CONFIG[env][apisVersion].ebsiEnvConfig,
);
ctx.accessToken = accessToken;
task.title = "Access token successfully requested";
} catch (e) {
if (process.env.DEBUG === "true") {
console.error(e);
}
// Handle Problem Details errors
if (
axios.isAxiosError(e) &&
e.response?.data &&
typeof e.response.data === "object" &&
e.response.data.detail
) {
throw new Error(
`Access token request failed: ${e.response.data.detail}`,
);
}
throw new Error(
`Access token request failed: ${
e instanceof Error ? e.message : "unknown error"
}`,
);
}
},
}, },
{ {
title: `Publishing schemas to ${env} environment...`, title: `Publishing schemas to ${env} environment...`,
......
export default { export default {
test: { test: {
latest: { // APIs to be used with TSR API v2 on testnet
AUTH: "https://api-test.ebsi.eu/authorisation/v4", v2: {
LEDGER: "https://api-test.ebsi.eu/ledger/v3", AUTH: "https://api-test.testnode02.ebsi.eu/authorisation/v2",
TSR: "https://api-test.ebsi.eu/trusted-schemas-registry/v3",
TAR: "https://api-test.ebsi.eu/trusted-apps-registry/v4",
},
old: {
AUTH: "https://api-test.ebsi.eu/authorisation/v2",
LEDGER: "https://api-test.ebsi.eu/ledger/v3", LEDGER: "https://api-test.ebsi.eu/ledger/v3",
TSR: "https://api-test.ebsi.eu/trusted-schemas-registry/v2", TSR: "https://api-test.ebsi.eu/trusted-schemas-registry/v2",
TAR: "https://api-test.ebsi.eu/trusted-apps-registry/v3", TAR: "https://api-test.ebsi.eu/trusted-apps-registry/v3",
}, },
// APIs to be used with TSR API v3 on testnet
v3: {
AUTH: "https://api-test.ebsi.eu/authorisation/v4",
LEDGER: "https://api-test.ebsi.eu/ledger/v4",
TSR: "https://api-test.ebsi.eu/trusted-schemas-registry/v3",
ebsiEnvConfig: {
network: "test",
hosts: ["api-test.ebsi.eu"],
services: {
"did-registry": "v5",
"trusted-issuers-registry": "v5",
"trusted-policies-registry": "v3",
"trusted-schemas-registry": "v3",
}, },
conformance: {
latest: {
AUTH: "https://api-conformance.ebsi.eu/authorisation/v4",
LEDGER: "https://api-conformance.ebsi.eu/ledger/v3",
TSR: "https://api-conformance.ebsi.eu/trusted-schemas-registry/v3",
TAR: "https://api-conformance.ebsi.eu/trusted-apps-registry/v4",
}, },
old: { },
// APIs to be used with TSR API v4 on testnet
v4: {
AUTH: "https://api-test.ebsi.eu/authorisation/v5",
LEDGER: "https://api-test.ebsi.eu/ledger/v4",
TSR: "https://api-test.ebsi.eu/trusted-schemas-registry/v4",
ebsiEnvConfig: {
network: "test",
hosts: ["api-test.ebsi.eu"],
services: {
"did-registry": "v6",
"trusted-issuers-registry": "v6",
"trusted-policies-registry": "v4",
"trusted-schemas-registry": "v4",
},
},
},
},
conformance: {
// APIs to be used with TSR API v2 on Conformance
v2: {
AUTH: "https://api-conformance.ebsi.eu/authorisation/v2", AUTH: "https://api-conformance.ebsi.eu/authorisation/v2",
LEDGER: "https://api-conformance.ebsi.eu/ledger/v3", LEDGER: "https://api-conformance.ebsi.eu/ledger/v3",
TSR: "https://api-conformance.ebsi.eu/trusted-schemas-registry/v2", TSR: "https://api-conformance.ebsi.eu/trusted-schemas-registry/v2",
TAR: "https://api-conformance.ebsi.eu/trusted-apps-registry/v3", TAR: "https://api-conformance.ebsi.eu/trusted-apps-registry/v3",
}, },
// APIs to be used with TSR API v3 on Conformance
v3: {
AUTH: "https://api-conformance.ebsi.eu/authorisation/v4",
LEDGER: "https://api-conformance.ebsi.eu/ledger/v3",
TSR: "https://api-conformance.ebsi.eu/trusted-schemas-registry/v3",
ebsiEnvConfig: {
network: "conformance",
hosts: ["api-conformance.ebsi.eu"],
services: {
"did-registry": "v5",
"trusted-issuers-registry": "v5",
"trusted-policies-registry": "v3",
"trusted-schemas-registry": "v3",
},
}, },
pilot: {
latest: {
AUTH: "https://api-pilot.ebsi.eu/authorisation/v4",
LEDGER: "https://api-pilot.ebsi.eu/ledger/v3",
TSR: "https://api-pilot.ebsi.eu/trusted-schemas-registry/v3",
TAR: "https://api-pilot.ebsi.eu/trusted-apps-registry/v4",
}, },
old: { },
pilot: {
// APIs to be used with TSR API v2 on Pilot
v2: {
AUTH: "https://api-pilot.ebsi.eu/authorisation/v2", AUTH: "https://api-pilot.ebsi.eu/authorisation/v2",
LEDGER: "https://api-pilot.ebsi.eu/ledger/v3", LEDGER: "https://api-pilot.ebsi.eu/ledger/v3",
TSR: "https://api-pilot.ebsi.eu/trusted-schemas-registry/v2", TSR: "https://api-pilot.ebsi.eu/trusted-schemas-registry/v2",
TAR: "https://api-pilot.ebsi.eu/trusted-apps-registry/v3", TAR: "https://api-pilot.ebsi.eu/trusted-apps-registry/v3",
}, },
// APIs to be used with TSR API v3 on Pilot
v3: {
AUTH: "https://api-pilot.ebsi.eu/authorisation/v4",
LEDGER: "https://api-pilot.ebsi.eu/ledger/v3",
TSR: "https://api-pilot.ebsi.eu/trusted-schemas-registry/v3",
ebsiEnvConfig: {
network: "pilot",
hosts: ["api-pilot.ebsi.eu"],
services: {
"did-registry": "v5",
"trusted-issuers-registry": "v5",
"trusted-policies-registry": "v3",
"trusted-schemas-registry": "v3",
},
},
},
}, },
}; };
import { randomUUID } from "node:crypto";
import { URLSearchParams } from "node:url";
import axios from "axios";
// Don't know why exactly, but ESLint fails to resolve this import
// eslint-disable-next-line import/no-unresolved
import { createVerifiablePresentationJwt } from "@cef-ebsi/verifiable-presentation";
/**
* Get an actual "tsr_write" access token from Authorisation API.
*/
export async function getAccessToken(
authorisationApiUrl,
subject,
ebsiEnvConfig,
) {
const nonce = randomUUID();
const vpPayload = {
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiablePresentation"],
verifiableCredential: [],
holder: subject.did,
};
const vpJwt = await createVerifiablePresentationJwt(
vpPayload,
subject,
authorisationApiUrl,
{
...ebsiEnvConfig,
skipValidation: true,
nonce,
// Manually add "exp" and "nbf" to the VP JWT because there's no VC to extract from
exp: Math.floor(Date.now() / 1000) + 100,
nbf: Math.floor(Date.now() / 1000) - 100,
},
);
const presentationSubmission = {
id: randomUUID(),
definition_id: "tsr_write_presentation",
descriptor_map: [],
};
try {
const response = await axios.post(
`${authorisationApiUrl}/token`,
new URLSearchParams({
grant_type: "vp_token",
scope: "openid tsr_write",
vp_token: vpJwt,
presentation_submission: JSON.stringify(presentationSubmission),
}).toString(),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
);
// Decode access token
const { access_token: accessToken } = response.data;
return accessToken;
} catch (e) {
if (axios.isAxiosError(e)) {
console.error(e.response?.data);
} else {
console.error(e);
}
throw new Error("Failed to get access token");
}
}
export default getAccessToken;
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"scripts": { "scripts": {
"build": "node ./scripts/build.js", "build": "node ./scripts/build.js",
"format": "prettier . --write", "format": "prettier . --write",
"lint:eslint": "eslint .", "lint:eslint": "eslint . --report-unused-disable-directives",
"lint:prettier": "prettier . --check", "lint:prettier": "prettier . --check",
"lint": "pnpm run lint:eslint && pnpm run lint:prettier", "lint": "pnpm run lint:eslint && pnpm run lint:prettier",
"test": "vitest --run", "test": "vitest --run",
...@@ -26,34 +26,36 @@ ...@@ -26,34 +26,36 @@
"node": ">=18.0.0 || >=20.0.0" "node": ">=18.0.0 || >=20.0.0"
}, },
"devDependencies": { "devDependencies": {
"@apidevtools/json-schema-ref-parser": "^11.1.0", "@apidevtools/json-schema-ref-parser": "^11.7.0",
"@cef-ebsi/siop-auth": "^4.0.0", "@cef-ebsi/siop-auth": "^4.0.2",
"@changesets/cli": "^2.27.1", "@cef-ebsi/verifiable-presentation": "^7.0.0",
"@changesets/cli": "^2.27.7",
"@types/lodash.camelcase": "^4.3.9", "@types/lodash.camelcase": "^4.3.9",
"ajv": "^8.12.0", "ajv": "^8.17.1",
"ajv-formats": "^2.1.1", "ajv-formats": "^3.0.1",
"axios": "^1.6.5", "axios": "^1.7.3",
"canonicalize": "^1.0.8", "canonicalize": "^1.0.8",
"commander": "^9.4.1", "commander": "^9.4.1",
"did-jwt": "^8.0.4",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.2", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vitest": "^0.3.20", "eslint-plugin-vitest": "^0.3.20",
"ethers": "^5.7.2", "ethers": "^5.7.2",
"globby": "^13.1.2", "globby": "^13.1.2",
"husky": "^9.0.11", "husky": "^9.1.4",
"inquirer": "^8.2.0", "inquirer": "^8.2.0",
"is-ci": "^3.0.1", "is-ci": "^3.0.1",
"jose": "^4.11.1", "jose": "^5.6.3",
"json-schema-to-typescript": "^13.1.2", "json-schema-to-typescript": "^15.0.0",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.8",
"listr2": "^4.0.4", "listr2": "^4.0.4",
"lodash.camelcase": "^4.3.0", "lodash.camelcase": "^4.3.0",
"multiformats": "^9.6.4", "multiformats": "^9.6.4",
"prettier": "^3.1.1", "prettier": "^3.3.3",
"ts-morph": "^21.0.1", "ts-morph": "^21.0.1",
"vitest": "^1.2.0" "vitest": "^1.2.0"
}, },
...@@ -91,20 +93,15 @@ ...@@ -91,20 +93,15 @@
}, },
"packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2", "packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2",
"pnpm": { "pnpm": {
"overrides": {
"jose@>=3.0.0 <=4.15.4": ">=4.15.5",
"follow-redirects@<=1.15.5": ">=1.15.6",
"vite@>=5.1.0 <=5.1.6": ">=5.1.7",
"@apidevtools/json-schema-ref-parser@>=11.0.0 <=11.1.0": ">=11.2.0",
"braces@<3.0.3": ">=3.0.3",
"ws@>=7.0.0 <7.5.10": ">=7.5.10"
},
"auditConfig": { "auditConfig": {
"ignoreCves": [ "ignoreCves": [
"CVE-2024-42459", "CVE-2024-42459",
"CVE-2024-42461", "CVE-2024-42461",
"CVE-2024-42460" "CVE-2024-42460"
] ]
},
"overrides": {
"ws@>=7.0.0 <7.5.10": ">=7.5.10"
} }
} }
} }
This diff is collapsed.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"id": "id:1", "id": "id:1",
"type": ["--TYPE-TO-BE-DEFINED-FOR-EHIC--"], "type": ["--TYPE-TO-BE-DEFINED-FOR-EHIC--"],
"issuer": "issuer:0", "issuer": "issuer:0",
"validFrom": "2024-01-01T00:00:00", "validFrom": "2024-01-01T00:00:00Z",
"credentialSubject": { "credentialSubject": {
"v": "1.0.0", "v": "1.0.0",
"ic": "BE", "ic": "BE",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment