From 945c4dea75e237a86088738a29906cfb1b266fc0 Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Fri, 20 Jun 2025 10:27:49 -0700 Subject: [PATCH] chore: add markdown plugin docs: adds and updates some docs comments chore: adds platform client and updates more docs Signed-off-by: Eugene Yakhnenko --- lib/package-lock.json | 206 +++++++++++-------- lib/package.json | 6 +- lib/src/access.ts | 15 ++ lib/src/auth/oidc-refreshtoken-provider.ts | 14 ++ lib/src/index.ts | 1 + lib/src/opentdf.ts | 218 ++++++++++++++------- lib/src/platform.ts | 22 ++- lib/src/seekable.ts | 31 +++ lib/src/utils.ts | 58 +++++- lib/typedoc-theme.css | 42 ++++ 10 files changed, 456 insertions(+), 157 deletions(-) create mode 100644 lib/typedoc-theme.css diff --git a/lib/package-lock.json b/lib/package-lock.json index 241220049..a242b178e 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -58,7 +58,8 @@ "process": "^0.11.10", "sinon": "~19.0.2", "tsconfig-paths": "^4.2.0", - "typedoc": "^0.27.9", + "typedoc": "^0.28.5", + "typedoc-plugin-markdown": "^4.7.0", "typescript": "^5.8.2", "typescript-eslint": "^8.26.0", "webpack": "^5.98.0", @@ -108,13 +109,15 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.26.2", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -245,7 +248,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -253,7 +258,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -269,23 +276,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.2", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.27.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -295,13 +306,15 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -333,12 +346,14 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -785,15 +800,17 @@ "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { - "version": "1.27.2", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", - "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.6.0.tgz", + "integrity": "sha512-KaeJvPNofTEZR9EzVNp/GQzbQqkGfjiu6k3CXKvhVTX+8OoAKSX/k7qxLKOX3B0yh2XqVAc93rsOu48CGt2Qug==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^1.27.2", - "@shikijs/types": "^1.27.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/engine-oniguruma": "^3.6.0", + "@shikijs/langs": "^3.6.0", + "@shikijs/themes": "^3.6.0", + "@shikijs/types": "^3.6.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@humanwhocodes/config-array": { @@ -1333,24 +1350,44 @@ ] }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz", + "integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.7.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz", + "integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.7.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz", + "integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.7.0" } }, "node_modules/@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz", + "integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, @@ -1979,9 +2016,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3022,7 +3059,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3136,9 +3175,9 @@ } }, "node_modules/c8/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5259,9 +5298,9 @@ "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5341,17 +5380,6 @@ "dev": true, "license": "MIT" }, - "node_modules/has": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5729,11 +5757,16 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7253,9 +7286,9 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8399,9 +8432,9 @@ } }, "node_modules/read-installed-packages/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8625,17 +8658,22 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.1", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10087,32 +10125,46 @@ } }, "node_modules/typedoc": { - "version": "0.27.9", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz", - "integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==", + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.5.tgz", + "integrity": "sha512-5PzUddaA9FbaarUzIsEc4wNXCiO4Ot3bJNeMF2qKpYlTmM9TTaSHQ7162w756ERCkXER/+o2purRG6YOAv6EMA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@gerrit0/mini-shiki": "^1.24.0", + "@gerrit0/mini-shiki": "^3.2.2", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "yaml": "^2.6.1" + "yaml": "^2.7.1" }, "bin": { "typedoc": "bin/typedoc" }, "engines": { - "node": ">= 18" + "node": ">= 18", + "pnpm": ">= 10" }, "peerDependencies": { "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" } }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.0.tgz", + "integrity": "sha512-PitbnAps2vpcqK2gargKoiFXLWFttvwUbyns/E6zGIFG5Gz8ZQJGttHnYR9csOlcSjB/uyjd8tnoayrtsXG17w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.28.x" + } + }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10671,16 +10723,16 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/lib/package.json b/lib/package.json index 7eb0d6641..f00f81c2e 100644 --- a/lib/package.json +++ b/lib/package.json @@ -69,7 +69,8 @@ "build:watch": "tsc --watch", "clean": "rm -rf {build,coverage,dist,tests/mocha/dist}", "coverage:merge": "for x in mocha wtr; do cp coverage/$x/coverage-final.json coverage/$x.json; done; nyc report --reporter text --reporter lcov -t coverage --lines 75 --statements 75 --branches 70 --functions 65 --check-coverage >coverage/coverage.txt", - "doc": "typedoc --out dist/docs src/index.ts", + "doc": "typedoc --out dist/docs src/index.ts --customCss ./typedoc-theme.css", + "doc:md": "typedoc --plugin typedoc-plugin-markdown --out dist/docs-md src/index.ts", "format": "prettier --write \"{src,tdf3,tests}/**/*.ts\"", "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", @@ -133,7 +134,8 @@ "process": "^0.11.10", "sinon": "~19.0.2", "tsconfig-paths": "^4.2.0", - "typedoc": "^0.27.9", + "typedoc": "^0.28.5", + "typedoc-plugin-markdown": "^4.7.0", "typescript": "^5.8.2", "typescript-eslint": "^8.26.0", "webpack": "^5.98.0", diff --git a/lib/src/access.ts b/lib/src/access.ts index 6be9f5185..2523efaa8 100644 --- a/lib/src/access.ts +++ b/lib/src/access.ts @@ -136,6 +136,12 @@ export async function noteInvalidPublicKey(url: URL, r: Promise): Pro } } +/** + * Fetches the key access servers for a given platform URL. + * @param platformUrl The platform URL to fetch key access servers for. + * @param authProvider The authentication provider to use for the request. + * @returns A promise that resolves to an OriginAllowList. + */ export async function fetchKeyAccessServers( platformUrl: string, authProvider: AuthProvider @@ -190,6 +196,15 @@ const origin = (u: string): string => { } }; +/** + * Manages a list of origins that are allowed to access the Key Access Server (KAS). + * @origins A list of origins that are allowed to access the KAS. + * @allowAll If true, all origins are allowed to access the KAS. + * If false, only the origins in the list are allowed to access the KAS. + * @description This class is used to manage a list of origins that are allowed to access the KAS. + * It validates the URLs and provides a method to check if a given URL is allowed. + * It is used to ensure that only authorized origins can access the KAS. + */ export class OriginAllowList { origins: string[]; allowAll: boolean; diff --git a/lib/src/auth/oidc-refreshtoken-provider.ts b/lib/src/auth/oidc-refreshtoken-provider.ts index 9c5da4cc4..91fa946f8 100644 --- a/lib/src/auth/oidc-refreshtoken-provider.ts +++ b/lib/src/auth/oidc-refreshtoken-provider.ts @@ -2,6 +2,20 @@ import { ConfigurationError } from '../errors.js'; import { type AuthProvider, type HttpRequest } from './auth.js'; import { AccessToken, type RefreshTokenCredentials } from './oidc.js'; +/** + * An AuthProvider that uses an OIDC refresh token to obtain an access token. + * It exchanges the refresh token for an access token and uses that to augment HTTP requests with credentials. + * @example + * ```ts + * import { OIDCRefreshTokenProvider } from '@opentdf/sdk'; + * await AuthProviders.refreshAuthProvider({ + clientId: 'my-client-id', + exchange: 'refresh', + refreshToken: 'refresh-token-from-oidc-provider', + oidcOrigin: 'https://example.oidc.provider.com', + }); + ``` + */ export class OIDCRefreshTokenProvider implements AuthProvider { oidcAuth: AccessToken; refreshToken?: string; diff --git a/lib/src/index.ts b/lib/src/index.ts index 13750bbaa..923bc8e7e 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -2,6 +2,7 @@ export { type AuthProvider, type HttpMethod, HttpRequest, withHeaders } from './ export * as AuthProviders from './auth/providers.js'; export { attributeFQNsAsValues } from './policy/api.js'; export { version, clientType, tdfSpecVersion } from './version.js'; +export { PlatformClient, type PlatformClientOptions, type PlatformServices } from './platform.js'; export * from './opentdf.js'; export * from './seekable.js'; export * from '../tdf3/src/models/index.js'; diff --git a/lib/src/opentdf.ts b/lib/src/opentdf.ts index c84efe405..425c7fa57 100644 --- a/lib/src/opentdf.ts +++ b/lib/src/opentdf.ts @@ -50,162 +50,175 @@ export { isPublicKeyAlgorithm, }; +/** A map of key identifiers to cryptographic keys. */ export type Keys = { [keyID: string]: CryptoKey | CryptoKeyPair; }; -// Options when creating a new TDF object -// that are shared between all container types. +/** Options for creating a new TDF object, shared between all container types. */ export type CreateOptions = { - // If the policy service should be used to control creation options + /** If the policy service should be used to control creation options. */ autoconfigure?: boolean; - // List of attributes that will be assigned to the object's policy + /** List of attributes that will be assigned to the object's policy. */ attributes?: string[]; - // If set and positive, this represents the maxiumum number of bytes to read from a stream to encrypt. - // This is helpful for enforcing size limits and preventing DoS attacks. + /** + * If set and positive, this represents the maxiumum number of bytes to read from a stream to encrypt. + * This is helpful for enforcing size limits and preventing DoS attacks. + */ byteLimit?: number; - // The KAS to use for creation, if none is specified by the attribute service. + /** The KAS to use for creation, if none is specified by the attribute service. */ defaultKASEndpoint?: string; - // Private (or shared) keys for signing assertions and bindings + /** Private (or shared) keys for signing assertions and bindings. */ signers?: Keys; - // Source of plaintext data + /** Source of plaintext data. */ source: Source; }; +/** Options for creating a NanoTDF. */ export type CreateNanoTDFOptions = CreateOptions & { + /** The type of binding to use for the NanoTDF. */ bindingType?: 'ecdsa' | 'gmac'; - // When creating a new collection, use ECDSA binding with this key id from the signers, - // instead of the DEK. + /** When creating a new collection, use ECDSA binding with this key id from the signers, instead of the DEK. */ ecdsaBindingKeyID?: string; - // When creating a new collection, - // use the key in the `signers` list with this id - // to generate a signature for each element. - // When absent, the nanotdf is unsigned. + /** + * When creating a new collection, use the key in the `signers` list with this id + * to generate a signature for each element. When absent, the nanotdf is unsigned. + */ signingKeyID?: string; }; +/** Options for creating a NanoTDF collection. */ export type CreateNanoTDFCollectionOptions = CreateNanoTDFOptions & { + /** The platform URL. */ platformUrl: string; - // The maximum number of key iterations to use for a single DEK. + /** The maximum number of key iterations to use for a single DEK. */ maxKeyIterations?: number; }; -// Metadata for a TDF object. +/** Metadata for a TDF object. */ export type Metadata = object; -// MIME type of the decrypted content. +/** MIME type of the decrypted content. */ export type MimeType = `${string}/${string}`; -// Template for a Key Access Object (KAO) to be filled in during encrypt. +/** Template for a Key Access Object (KAO) to be filled in during encrypt. */ export type SplitStep = { - // Which KAS to use to rewrap this segment of the key + /** Which KAS to use to rewrap this segment of the key. */ kas: string; - // An identifier for a key segment. - // Leave empty to share the key. + /** + * An identifier for a key segment. + * Leave empty to share the key. + */ sid?: string; }; -/// Options specific to the ZTDF container format. +/** Options specific to the ZTDF container format. */ export type CreateZTDFOptions = CreateOptions & { - // Configuration for bound metadata. + /** Configuration for bound metadata. */ assertionConfigs?: AssertionConfig[]; - // Unbound metadata (deprecated) + /** Unbound metadata (deprecated). */ metadata?: Metadata; - // MIME type of the decrypted content. Used for display. + /** MIME type of the decrypted content. Used for display. */ mimeType?: MimeType; - // How to split or share the data encryption key across multiple KASes. + /** How to split or share the data encryption key across multiple KASes. */ splitPlan?: SplitStep[]; - // The segment size for the content; smaller is slower, but allows faster random access. - // The current default is 1 MiB (2^20 bytes). + /** + * The segment size for the content; smaller is slower, but allows faster random access. + * The current default is 1 MiB (2^20 bytes). + */ windowSize?: number; - // Preferred algorithm to use for Key Access Objects. + /** Preferred algorithm to use for Key Access Objects. */ wrappingKeyAlgorithm?: KasPublicKeyAlgorithm; - // TDF spec version to target + /** TDF spec version to target. */ tdfSpecVersion?: '4.2.2' | '4.3.0'; }; -// Settings for decrypting any variety of TDF file. +/** Settings for decrypting any variety of TDF file. */ export type ReadOptions = { - // ciphertext + /** The ciphertext source. */ source: Source; - // Platform URL + /** The platform URL. */ platformUrl?: string; - // list of KASes that may be contacted for a rewrap + /** List of KASes that may be contacted for a rewrap. */ allowedKASEndpoints?: string[]; - // Optionally disable checking the allowlist + /** Optionally disable checking the allowlist. */ ignoreAllowlist?: boolean; - // Public (or shared) keys for verifying assertions + /** Public (or shared) keys for verifying assertions. */ assertionVerificationKeys?: AssertionVerificationKeys; - // Optionally disable assertion verification + /** Optionally disable assertion verification. */ noVerify?: boolean; - // If set, prevents more than this number of concurrent requests to the KAS. + /** If set, prevents more than this number of concurrent requests to the KAS. */ concurrencyLimit?: number; - // Type of key to use for wrapping responses. + /** Type of key to use for wrapping responses. */ wrappingKeyAlgorithm?: KasPublicKeyAlgorithm; }; -// Defaults and shared settings that are relevant to creating TDF objects. +/** Defaults and shared settings that are relevant to creating TDF objects. */ export type OpenTDFOptions = { - // Policy service endpoint + /** Policy service endpoint. */ policyEndpoint?: string; - // Platform URL + /** Platform URL. */ platformUrl?: string; - // Auth provider for connections to the policy service and KASes. + /** Auth provider for connections to the policy service and KASes. */ authProvider: AuthProvider; - // Default settings for 'encrypt' type requests. + /** Default settings for 'encrypt' type requests. */ defaultCreateOptions?: Omit; - // Default settings for 'decrypt' type requests. + /** Default settings for 'decrypt' type requests. */ defaultReadOptions?: Omit; - // If we want to *not* send a DPoP token + /** If we want to *not* send a DPoP token. */ disableDPoP?: boolean; - // Optional keys for DPoP requests to a server. - // These often must be registered via a DPoP flow with the IdP - // which is out of the scope of this library. + /** + * Optional keys for DPoP requests to a server. + * These often must be registered via a DPoP flow with the IdP + * which is out of the scope of this library. + */ dpopKeys?: Promise; - // Configuration options for the collection header cache. + /** Configuration options for the collection header cache. */ rewrapCacheOptions?: RewrapCacheOptions; }; +/** A decorated readable stream. */ export type DecoratedStream = ReadableStream & { - // If the source is a TDF3/ZTDF, and includes metadata, and it has been read. + /** If the source is a TDF3/ZTDF, and includes metadata, and it has been read. */ metadata?: Promise; + /** The TDF manifest. */ manifest?: Promise; - // If the source is a NanoTDF, this will be set. + /** If the source is a NanoTDF, this will be set. */ header?: Header; }; -// Configuration options for the collection header cache. +/** Configuration options for the collection header cache. */ export type RewrapCacheOptions = { - // If we should disable (bypass) the cache. + /** If we should disable (bypass) the cache. */ bypass?: boolean; - // Evict keys after this many milliseconds. + /** Evict keys after this many milliseconds. */ maxAge?: number; - // Check for expired keys once every this many milliseconds. + /** Check for expired keys once every this many milliseconds. */ pollInterval?: number; }; @@ -215,10 +228,11 @@ const defaultRewrapCacheOptions: Required = { pollInterval: 500, }; -// Cache for headers of nanotdf collections. -// This allows the SDK to quickly open multiple entries of the same collection. -// It has a demon that removes all keys that have not been accessed in the last 5 minutes. -// To cancel the demon, and clear the cache, call `close()`. +/** + * Cache for headers of nanotdf collections, to quickly open multiple entries of the same collection. + * It has a demon that removes all keys that have not been accessed in the last 5 minutes. + * To cancel the demon, and clear the cache, call `close()`. + * */ export class RewrapCache { private cache?: Map; private closer?: ReturnType; @@ -254,6 +268,7 @@ export class RewrapCache { return undefined; } + /** Set a key in the cache. */ set(key: Uint8Array, value: CryptoKey) { if (!this.cache) { return; @@ -261,6 +276,7 @@ export class RewrapCache { this.cache.set(key, { lastAccessTime: Date.now(), value }); } + /** Close the cache and release any resources. */ close() { if (this.closer !== undefined) { clearInterval(this.closer); @@ -294,19 +310,56 @@ export type TDFReader = { attributes: () => Promise; }; -// SDK for dealing with OpenTDF data and policy services. +/** + * The main OpenTDF class that provides methods for creating and reading TDF files. + * It supports both NanoTDF and ZTDF formats. + * It can be used to create new TDF files and read existing ones. + * This class is the entry point for using the OpenTDF SDK. + * It requires an authentication provider to be passed in the constructor. + * It also requires a platform URL to be set, which is used to fetch key access servers and policies. + * @example + * ``` + * import { type Chunker, OpenTDF } from '@opentdf/sdk'; + * + * const oidcCredentials: RefreshTokenCredentials = { + * clientId: keycloakClientId, + * exchange: 'refresh', + * refreshToken: refreshToken, + * oidcOrigin: keycloakUrl, + * }; + * const authProvider = await AuthProviders.refreshAuthProvider(oidcCredentials); + * + * const client = new OpenTDF({ + * authProvider, + * platformUrl: 'https://platform.example.com', + * }); + * + * const cipherText = await client.createZTDF({ + * source: { type: 'stream', location: source }, + * autoconfigure: false, + * }); + * + * const clearText = await client.read({ type: 'stream', location: cipherText }); + * ``` + */ export class OpenTDF { - // Configuration service and more is at this URL/connectRPC endpoint + /** The platform URL */ readonly platformUrl: string; + /** The policy service endpoint */ readonly policyEndpoint: string; + /** The auth provider for the OpenTDF instance. */ readonly authProvider: AuthProvider; + /** If DPoP is enabled for this instance. */ readonly dpopEnabled: boolean; + /** Default options for creating TDF objects. */ defaultCreateOptions: Omit; + /** Default options for reading TDF objects. */ defaultReadOptions: Omit; + /** The DPoP keys for this instance, if any. */ readonly dpopKeys: Promise; - - // Header cache for reading nanotdf collections + /** Cache for rewrapped keys */ private readonly rewrapCache: RewrapCache; + /** The TDF3 client for encrypting and decrypting ZTDF files. */ readonly tdf3Client: TDF3Client; constructor({ @@ -352,6 +405,7 @@ export class OpenTDF { ); } + /** Creates a new NanoTDF stream. */ async createNanoTDF(opts: CreateNanoTDFOptions): Promise { opts = { ...this.defaultCreateOptions, @@ -370,7 +424,6 @@ export class OpenTDF { /** * Creates a new collection object, which can be used to encrypt a series of data with the same policy. - * @returns */ async createNanoTDFCollection( opts: CreateNanoTDFCollectionOptions @@ -379,6 +432,7 @@ export class OpenTDF { return new Collection(this.authProvider, opts); } + /** Creates a new ZTDF stream. */ async createZTDF(opts: CreateZTDFOptions): Promise { opts = { ...this.defaultCreateOptions, ...opts }; const oldStream = await this.tdf3Client.encrypt({ @@ -403,26 +457,25 @@ export class OpenTDF { return stream; } - /** - * Opens a TDF file for inspection and decryption. - * @param opts the file to open, and any appropriate configuration options - * @returns - */ + /** Opens a TDF file for inspection and decryption. */ open(opts: ReadOptions): TDFReader { opts = { ...this.defaultReadOptions, ...opts }; return new UnknownTypeReader(this, opts, this.rewrapCache); } + /** Decrypts a TDF file. */ async read(opts: ReadOptions): Promise { const reader = this.open(opts); return reader.decrypt(); } + /** Closes the OpenTDF instance and releases any resources. */ close() { this.rewrapCache.close(); } } +/** A TDF reader that can automatically detect the TDF type. */ class UnknownTypeReader { delegate: Promise; state: 'init' | 'resolving' | 'loaded' | 'decrypting' | 'closing' | 'done' | 'error' = 'init'; @@ -434,6 +487,7 @@ class UnknownTypeReader { this.delegate = this.resolveType(); } + /** Resolves the TDF type based on the file prefix. */ async resolveType(): Promise { if (this.state === 'done') { throw new ConfigurationError('reader is closed'); @@ -455,21 +509,25 @@ class UnknownTypeReader { throw new InvalidFileError(`unsupported format; prefix not recognized ${prefix}`); } + /** Decrypts the TDF file */ async decrypt(): Promise { const actual = await this.delegate; return actual.decrypt(); } + /** Returns the attributes of the TDF file */ async attributes(): Promise { const actual = await this.delegate; return actual.attributes(); } + /** Returns the manifest of the TDF file */ async manifest(): Promise { const actual = await this.delegate; return actual.manifest(); } + /** Closes the TDF reader */ async close() { if (this.state === 'done') { return; @@ -487,6 +545,7 @@ class UnknownTypeReader { } } +/** A TDF reader for NanoTDF files. */ class NanoTDFReader { container: Promise; constructor( @@ -514,6 +573,7 @@ class NanoTDFReader { }); } + /** Decrypts the NanoTDF file and returns a decorated stream. */ async decrypt(): Promise { const nanotdf = await this.container; const cachedDEK = this.rewrapCache.get(nanotdf.header.ephemeralPublicKey); @@ -556,10 +616,12 @@ class NanoTDFReader { async close() {} + /** Returns blank manifest. NanoTDF has no manifest. */ async manifest(): Promise { return {} as Manifest; } + /** Returns the attributes of the NanoTDF file. */ async attributes(): Promise { const nanotdf = await this.container; if (!nanotdf.header.policy?.content) { @@ -574,6 +636,7 @@ class NanoTDFReader { } } +/** A reader for TDF files. */ class ZTDFReader { overview: Promise; constructor( @@ -584,6 +647,10 @@ class ZTDFReader { this.overview = loadTDFStream(source); } + /** + * Decrypts the TDF file and returns a decorated stream. + * The stream will have a manifest and metadata attached if available. + */ async decrypt(): Promise { const { assertionVerificationKeys, @@ -641,11 +708,13 @@ class ZTDFReader { // TODO figure out how to close a chunker, if we want to. } + /** Returns the manifest of the TDF file. */ async manifest(): Promise { const overview = await this.overview; return overview.manifest; } + /** Returns the attributes of the TDF file. */ async attributes(): Promise { const manifest = await this.manifest(); const policyJSON = base64.decode(manifest.encryptionInformation.policy); @@ -666,13 +735,18 @@ async function streamify(ab: Promise): Promise Promise>; + /** Closes the collection and releases any resources. */ close: () => Promise; }; class Collection { + /** The NanoTDF client used for encrypting data in this collection. */ client?: NanoTDFDatasetClient; + /** Options for encrypting data in this collection. */ encryptOptions?: NanoEncryptOptions; constructor(authProvider: AuthProvider, opts: CreateNanoTDFCollectionOptions) { @@ -705,6 +779,7 @@ class Collection { }); } + /** Encrypts a source into a NanoTDF stream. */ async encrypt(source: Source): Promise { if (!this.client) { throw new ConfigurationError('Collection is closed'); @@ -722,6 +797,7 @@ class Collection { return stream; } + /** Releases client resources. */ async close() { delete this.client; } diff --git a/lib/src/platform.ts b/lib/src/platform.ts index b90129668..ca8786b35 100644 --- a/lib/src/platform.ts +++ b/lib/src/platform.ts @@ -33,11 +33,11 @@ export interface PlatformServices { } export interface PlatformClientOptions { - // Optional authentication provider for generating auth interceptor. + /** Optional authentication provider for generating auth interceptor. */ authProvider?: AuthProvider; - // Array of custom interceptors to apply to rpc requests. + /** Array of custom interceptors to apply to rpc requests. */ interceptors?: Interceptor[]; - // Base URL of the platform API. + /** Base URL of the platform API. */ platformUrl: string; } @@ -50,8 +50,22 @@ export interface PlatformClientOptions { * * This client supports authentication via an `AuthProvider` or custom interceptors, which can * be used to add authentication headers or other custom logic to outgoing requests. + * @example + * ``` + * import { AuthProviders, OpenTDF } from '@opentdf/sdk'; + * import { PlatformClient } from '@opentdf/sdk/platform'; * + * const authProvider: AuthProvider = await AuthProviders.refreshAuthProvider({...}); + * const platform = new PlatformClient({ + * authProvider, + * platformUrl: 'https://platform.example.com', + * }); + * + * const wellKnownResponse = await platform.v1.wellknown.getWellKnownConfiguration({}); + * console.log('Well-known configuration:', wellKnownResponse.configuration); + * ``` */ + export class PlatformClient { readonly v1: PlatformServices; @@ -96,8 +110,6 @@ export class PlatformClient { * that returns an object containing authentication headers. These headers are then * added to the request before it is sent to the server. * - * @param authProvider - An instance of `AuthProvider` used to generate authentication credentials. - * @returns An `Interceptor` function that modifies requests to include authentication headers. */ function createAuthInterceptor(authProvider: AuthProvider): Interceptor { const authInterceptor: Interceptor = (next) => async (req) => { diff --git a/lib/src/seekable.ts b/lib/src/seekable.ts index 061dcfbec..8d5ea5f19 100644 --- a/lib/src/seekable.ts +++ b/lib/src/seekable.ts @@ -33,12 +33,30 @@ export const fromBrowserFile = (fileRef: Blob): Chunker => { }; }; +/** + * Creates a seekable object from a buffer. + * @param source A Uint8Array to read from. + * If byteStart and byteEnd are not provided, reads the entire array. + * If byteStart is provided, reads from that index to the end of the array. + * If byteEnd is provided, reads from byteStart to byteEnd (exclusive). + * If both byteStart and byteEnd are provided, reads from byteStart to byteEnd (exclusive). + * @returns A promise that resolves to a Uint8Array containing the requested data. + */ export const fromBuffer = (source: Uint8Array): Chunker => { return (byteStart?: number, byteEnd?: number) => { return Promise.resolve(source.slice(byteStart, byteEnd)); }; }; +/** + * Creates a seekable object from a string. + * @param source A string to read from. + * If byteStart and byteEnd are not provided, reads the entire string. + * If byteStart is provided, reads from that index to the end of the string. + * If byteEnd is provided, reads from byteStart to byteEnd (exclusive). + * If both byteStart and byteEnd are provided, reads from byteStart to byteEnd (exclusive). + * @returns A promise that resolves to a Uint8Array containing the requested data. + */ export const fromString = (source: string): Chunker => { return fromBuffer(new TextEncoder().encode(source)); }; @@ -110,6 +128,12 @@ export const fromUrl = async (location: string): Promise => { }; }; +/** + * Creates a seekable object from a source. + * @param source A Source object containing the type and location of the data. + * @returns A promise that resolves to a Chunker function. + * @throws ConfigurationError if the source type is not supported or the location is invalid. + */ export const fromSource = async ({ type, location }: Source): Promise => { switch (type) { case 'buffer': @@ -139,6 +163,13 @@ export const fromSource = async ({ type, location }: Source): Promise = } }; +/** + * Converts a Source object to a ReadableStream. + * @param source A Source object containing the type and location of the data. + * Converts the source to a ReadableStream of Uint8Array. + * This is useful for streaming data from various sources like files, remote URLs, or chunkers. + * @returns A ReadableStream of Uint8Array. + */ export async function sourceToStream(source: Source): Promise> { switch (source.type) { case 'stream': diff --git a/lib/src/utils.ts b/lib/src/utils.ts index 1a05efa6f..30ecb9a45 100644 --- a/lib/src/utils.ts +++ b/lib/src/utils.ts @@ -35,6 +35,12 @@ export function validateSecureUrl(url: string): boolean { return true; } +/** + * Pads a URL with a trailing slash if it does not already have one. + * This is useful for ensuring that URLs are in a consistent format. + * @param u The URL to pad. + * @returns The padded URL. + */ export function padSlashToUrl(u: string): string { if (u.endsWith('/')) { return u; @@ -42,10 +48,21 @@ export function padSlashToUrl(u: string): string { return `${u}/`; } +/** + * Checks if the current environment is a browser. + * This is useful for determining if certain APIs or features are available. + * @returns true if running in a browser, false otherwise. + */ export function isBrowser() { return typeof window !== 'undefined'; // eslint-disable-line } +/** + * Removes trailing characters from a string. + * @param str The string to trim. + * @param suffix The suffix to remove (default is a single space). + * @returns The trimmed string. + */ export const rstrip = (str: string, suffix = ' '): string => { while (str && suffix && str.endsWith(suffix)) { str = str.slice(0, -suffix.length); @@ -92,6 +109,15 @@ export const estimateSkewFromHeaders = (headers: AnyHeaders, dateNowBefore?: num return Math.round((deltaBefore + deltaAfter) / 2); }; +/** + * Adds new lines to a string every 64 characters. + * @param str A string to add new lines to. + * This function takes a string and adds new lines every 64 characters. + * If the string is empty or undefined, it returns the original string. + * This is useful for formatting long strings, such as public keys or certificates, + * to ensure they are properly formatted for PEM encoding. + * @returns The formatted string with new lines added. + */ export function addNewLines(str: string): string { if (!str) { return str; @@ -105,6 +131,11 @@ export function addNewLines(str: string): string { return finalString; } +/** + * Creates a PEM-encoded string from a public key. + * @param publicKey The public key to convert. + * @returns A promise that resolves to a PEM-encoded string. + */ export async function cryptoPublicToPem(publicKey: CryptoKey): Promise { if (publicKey.type !== 'public') { throw new ConfigurationError('incorrect key type'); @@ -116,6 +147,11 @@ export async function cryptoPublicToPem(publicKey: CryptoKey): Promise { return `-----BEGIN PUBLIC KEY-----\r\n${pem}-----END PUBLIC KEY-----`; } +/** + * Converts a PEM-encoded public key to a CryptoKey. + * @param pem The PEM-encoded public key. + * @returns A promise that resolves to a CryptoKey. + */ export async function pemToCryptoPublicKey(pem: string): Promise { if (/-----BEGIN PUBLIC KEY-----/.test(pem)) { return pemPublicToCrypto(pem); @@ -128,6 +164,15 @@ export async function pemToCryptoPublicKey(pem: string): Promise { throw new TypeError(`unsupported pem type [${pem}]`); } +/** + * Extracts the PEM-encoded public key from a key string. + * @param keyString A string containing a public key or certificate. + * This function extracts the PEM-encoded public key from a given key string. + * If the key string contains a certificate, it imports the certificate and exports + * the public key in PEM format. If the key string is already in PEM format, it returns + * the key string as is. + * @returns A promise that resolves to a PEM-encoded public key. + */ export async function extractPemFromKeyString(keyString: string): Promise { let pem: string = keyString; @@ -143,6 +188,12 @@ export async function extractPemFromKeyString(keyString: string): Promise