diff --git a/scripts/ucan/README.md b/scripts/ucan/README.md new file mode 100644 index 000000000..57ee05a84 --- /dev/null +++ b/scripts/ucan/README.md @@ -0,0 +1,22 @@ +## UCAN token generation script + +The script is intended to emit UCAN token which will be valid for 10 days. The output file (token) should be hosted at public route without any risk for the end-users. It will establish secure communication channel between NFT.Storage and Marketplace, and Marketplace and its end-users. + +### Setup + +Before UCAN token generation it is neccessary to set API_KEY environment variable for this directory. (API_KEY could be get at `nft.storage/manage` website per account). +After that, installing dependencies is required by running: + +``` +npm install +``` + +Once the dependencies are installed it is possible to generate UCAN token. The output file (_ucan.json_) will be located in the same directory where scripts resides. + +### UCAN generation + +Run the command below to emit this file: + +``` +node generateNftServiceKeypair.js +``` diff --git a/scripts/ucan/generateNftServiceKeypair.js b/scripts/ucan/generateNftServiceKeypair.js new file mode 100644 index 000000000..3e430d1b8 --- /dev/null +++ b/scripts/ucan/generateNftServiceKeypair.js @@ -0,0 +1,99 @@ +import { writeFile } from 'fs'; +import fetch from 'node-fetch'; +import { KeyPair } from 'ucan-storage/keypair'; +import { build } from 'ucan-storage/ucan-storage'; + +const SERVICE_ENDPOINT = 'https://api.nft.storage'; // default +const API_TOKEN = process.env.API_KEY; + +/** + * Obtaining the service DID + * + */ +async function getServiceDid() { + const didRes = await fetch(new URL('/did', SERVICE_ENDPOINT)); + const { ok, value: serviceDid } = await didRes.json(); + + if (ok) { + return serviceDid; + } else { + throw new Error('Could not get Service DID'); + } +} + +/** + * Obtaining a root UCAN token. + * It will be valid for a duration of two weeks + * + */ +async function getRootToken(token) { + const ucanReq = await fetch(new URL('/ucan/token', SERVICE_ENDPOINT), { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!ucanReq.ok) { + throw new Error('Failed to get root UCAN token'); + } + + const { value: rootUCAN } = await ucanReq.json(); + + return rootUCAN; +} + +/** + * Obtaining UCAN token with specified expiration date (10 days) + * + */ +async function getUCAN(kp, serviceDid, rootUCAN) { + // convert timestamp to seconds + const nowInSeconds = Math.floor(Date.now() / 1000); + const expiration = nowInSeconds + 864000; // 10 days from now + + try { + return await build({ + issuer: kp, + audience: serviceDid, + expiration: expiration, + capabilities: [ + { + with: `storage://${kp.did()}`, + can: 'upload/*', + }, + ], + proofs: [rootUCAN], + }); + } catch (error) { + throw new Error('Could not create UCAN token:', error); + } +} + +(async function main() { + try { + const pair = await KeyPair.create(); + const privateKey = pair.export(); + + const kp = await KeyPair.fromExportedKey(privateKey); + + const serviceDid = await getServiceDid(); + + const rootUCAN = await getRootToken(API_TOKEN); + + const ucan = await getUCAN(kp, serviceDid, rootUCAN); + + const credentials = `{ + "marketplaceDid": "${kp.did()}", + "ucan": "${ucan}" + }\n`; + + writeFile('ucan.json', credentials, (err) => { + if (err) throw new Error(err); + + process.stdout.write('The ucan file has been saved!\n'); + }); + } catch (error) { + console.error(error); + } +})(); diff --git a/scripts/ucan/package-lock.json b/scripts/ucan/package-lock.json new file mode 100644 index 000000000..cc35e3e79 --- /dev/null +++ b/scripts/ucan/package-lock.json @@ -0,0 +1,83 @@ +{ + "name": "ucan", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@noble/ed25519": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.6.1.tgz", + "integrity": "sha512-Gptpue6qPmg7p1E5LBO5GDtXw5WMc2DVtUmu4EQequOcoCvum1dT9sY6s9M8aSJWq9YopCN4jmTOAvqMdw3q7w==" + }, + "base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "requires": { + "mri": "^1.1.0" + } + }, + "ucan-storage": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ucan-storage/-/ucan-storage-1.3.0.tgz", + "integrity": "sha512-C1PvShqWTg6JzcBAuWDeCsaL6AggwsGWqbvKZ8XdN9csAukQVnA5/kerddhdPrpeoCGnJFfSkvBcPklZzdJ+OQ==", + "requires": { + "@noble/ed25519": "^1.5.2", + "base-x": "^4.0.0", + "sade": "^1.8.1" + } + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + } + } +} diff --git a/scripts/ucan/package.json b/scripts/ucan/package.json new file mode 100644 index 000000000..89f276cda --- /dev/null +++ b/scripts/ucan/package.json @@ -0,0 +1,19 @@ +{ + "name": "ucan", + "version": "1.0.0", + "description": "This script generates new ucan token for NFT marketplace each time it runs.", + "main": "generateNftServiceKeypair.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "ucan" + ], + "author": "", + "license": "ISC", + "dependencies": { + "node-fetch": "^3.2.10", + "ucan-storage": "^1.3.0" + }, + "type": "module" +}