@fntio/infra-client 0.0.0-stage → 0.5.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 +193 -0
- package/LICENSE +93 -0
- package/README.md +875 -2
- package/assets/acl-reconciler/index.js +1604 -0
- package/assets/api/index.js +9857 -0
- package/assets/api/package.json +9 -0
- package/assets/api/vendor_modules/.package-lock.json +857 -0
- package/assets/api/vendor_modules/accepts/HISTORY.md +243 -0
- package/assets/api/vendor_modules/accepts/LICENSE +23 -0
- package/assets/api/vendor_modules/accepts/README.md +140 -0
- package/assets/api/vendor_modules/accepts/index.js +238 -0
- package/assets/api/vendor_modules/accepts/package.json +47 -0
- package/assets/api/vendor_modules/array-flatten/LICENSE +21 -0
- package/assets/api/vendor_modules/array-flatten/README.md +43 -0
- package/assets/api/vendor_modules/array-flatten/array-flatten.js +64 -0
- package/assets/api/vendor_modules/array-flatten/package.json +39 -0
- package/assets/api/vendor_modules/aws-jwt-verify/LICENSE +175 -0
- package/assets/api/vendor_modules/aws-jwt-verify/README.md +1033 -0
- package/assets/api/vendor_modules/aws-jwt-verify/asn1.d.ts +20 -0
- package/assets/api/vendor_modules/aws-jwt-verify/assert.d.ts +41 -0
- package/assets/api/vendor_modules/aws-jwt-verify/cognito-verifier.d.ts +183 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/asn1.js +301 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/assert.js +108 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/cognito-verifier.js +158 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/error.js +131 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/https-common.js +29 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/https-node.js +89 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/https.js +52 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/index.js +9 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/jwk.js +221 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/jwt-model.js +10 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/jwt-rsa.js +397 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/jwt.js +161 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/node-web-compat-node.js +48 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/node-web-compat-web.js +78 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/node-web-compat.js +11 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/package.json +1 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/safe-json-parse.js +34 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/cjs/typing-util.js +6 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/asn1.js +296 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/assert.js +101 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/cognito-verifier.js +153 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/error.js +101 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/https-common.js +25 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/https-node.js +85 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/https.js +48 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/index.js +4 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/jwk.js +208 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/jwt-model.js +7 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/jwt-rsa.js +389 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/jwt.js +156 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/node-web-compat-node.js +45 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/node-web-compat-web.js +75 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/node-web-compat.js +10 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/package.json +1 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/safe-json-parse.js +29 -0
- package/assets/api/vendor_modules/aws-jwt-verify/dist/esm/typing-util.js +5 -0
- package/assets/api/vendor_modules/aws-jwt-verify/error.d.ts +108 -0
- package/assets/api/vendor_modules/aws-jwt-verify/https-common.d.ts +9 -0
- package/assets/api/vendor_modules/aws-jwt-verify/https-node.d.ts +20 -0
- package/assets/api/vendor_modules/aws-jwt-verify/https.d.ts +35 -0
- package/assets/api/vendor_modules/aws-jwt-verify/index.d.ts +2 -0
- package/assets/api/vendor_modules/aws-jwt-verify/jwk.d.ts +76 -0
- package/assets/api/vendor_modules/aws-jwt-verify/jwt-model.d.ts +78 -0
- package/assets/api/vendor_modules/aws-jwt-verify/jwt-rsa.d.ts +348 -0
- package/assets/api/vendor_modules/aws-jwt-verify/jwt.d.ts +51 -0
- package/assets/api/vendor_modules/aws-jwt-verify/package.json +136 -0
- package/assets/api/vendor_modules/aws-jwt-verify/safe-json-parse.d.ts +18 -0
- package/assets/api/vendor_modules/aws-jwt-verify/typing-util.d.ts +38 -0
- package/assets/api/vendor_modules/body-parser/HISTORY.md +703 -0
- package/assets/api/vendor_modules/body-parser/LICENSE +23 -0
- package/assets/api/vendor_modules/body-parser/README.md +476 -0
- package/assets/api/vendor_modules/body-parser/index.js +156 -0
- package/assets/api/vendor_modules/body-parser/lib/read.js +205 -0
- package/assets/api/vendor_modules/body-parser/lib/types/json.js +247 -0
- package/assets/api/vendor_modules/body-parser/lib/types/raw.js +105 -0
- package/assets/api/vendor_modules/body-parser/lib/types/text.js +125 -0
- package/assets/api/vendor_modules/body-parser/lib/types/urlencoded.js +303 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/.editorconfig +46 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/.github/SECURITY.md +11 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/.github/THREAT_MODEL.md +80 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/.nycrc +13 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/CHANGELOG.md +872 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/LICENSE.md +29 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/README.md +804 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/dist/qs.js +141 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/eslint.config.mjs +57 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/lib/formats.js +23 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/lib/index.js +11 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/lib/parse.js +503 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/lib/stringify.js +378 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/lib/utils.js +512 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/package.json +95 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/test/empty-keys-cases.js +267 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/test/parse.js +2296 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/test/stringify.js +1995 -0
- package/assets/api/vendor_modules/body-parser/node_modules/qs/test/utils.js +822 -0
- package/assets/api/vendor_modules/body-parser/package.json +55 -0
- package/assets/api/vendor_modules/bytes/History.md +97 -0
- package/assets/api/vendor_modules/bytes/LICENSE +23 -0
- package/assets/api/vendor_modules/bytes/Readme.md +152 -0
- package/assets/api/vendor_modules/bytes/index.js +170 -0
- package/assets/api/vendor_modules/bytes/package.json +42 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/.eslintrc +17 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/.nycrc +9 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/CHANGELOG.md +30 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/LICENSE +21 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/README.md +62 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/actualApply.d.ts +1 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/actualApply.js +10 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/applyBind.d.ts +19 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/applyBind.js +10 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/functionApply.d.ts +1 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/functionApply.js +4 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/functionCall.d.ts +1 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/functionCall.js +4 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/index.d.ts +64 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/index.js +15 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/package.json +85 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/reflectApply.d.ts +3 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/reflectApply.js +4 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/test/index.js +63 -0
- package/assets/api/vendor_modules/call-bind-apply-helpers/tsconfig.json +9 -0
- package/assets/api/vendor_modules/call-bound/.eslintrc +13 -0
- package/assets/api/vendor_modules/call-bound/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/call-bound/.nycrc +9 -0
- package/assets/api/vendor_modules/call-bound/CHANGELOG.md +42 -0
- package/assets/api/vendor_modules/call-bound/LICENSE +21 -0
- package/assets/api/vendor_modules/call-bound/README.md +53 -0
- package/assets/api/vendor_modules/call-bound/index.d.ts +94 -0
- package/assets/api/vendor_modules/call-bound/index.js +19 -0
- package/assets/api/vendor_modules/call-bound/package.json +99 -0
- package/assets/api/vendor_modules/call-bound/test/index.js +61 -0
- package/assets/api/vendor_modules/call-bound/tsconfig.json +10 -0
- package/assets/api/vendor_modules/content-disposition/HISTORY.md +60 -0
- package/assets/api/vendor_modules/content-disposition/LICENSE +22 -0
- package/assets/api/vendor_modules/content-disposition/README.md +142 -0
- package/assets/api/vendor_modules/content-disposition/index.js +458 -0
- package/assets/api/vendor_modules/content-disposition/package.json +44 -0
- package/assets/api/vendor_modules/content-type/HISTORY.md +29 -0
- package/assets/api/vendor_modules/content-type/LICENSE +22 -0
- package/assets/api/vendor_modules/content-type/README.md +94 -0
- package/assets/api/vendor_modules/content-type/index.js +225 -0
- package/assets/api/vendor_modules/content-type/package.json +42 -0
- package/assets/api/vendor_modules/cookie/LICENSE +24 -0
- package/assets/api/vendor_modules/cookie/README.md +317 -0
- package/assets/api/vendor_modules/cookie/SECURITY.md +25 -0
- package/assets/api/vendor_modules/cookie/index.js +335 -0
- package/assets/api/vendor_modules/cookie/package.json +44 -0
- package/assets/api/vendor_modules/cookie-signature/History.md +42 -0
- package/assets/api/vendor_modules/cookie-signature/Readme.md +42 -0
- package/assets/api/vendor_modules/cookie-signature/index.js +51 -0
- package/assets/api/vendor_modules/cookie-signature/package.json +18 -0
- package/assets/api/vendor_modules/debug/.coveralls.yml +1 -0
- package/assets/api/vendor_modules/debug/.eslintrc +11 -0
- package/assets/api/vendor_modules/debug/.travis.yml +14 -0
- package/assets/api/vendor_modules/debug/CHANGELOG.md +362 -0
- package/assets/api/vendor_modules/debug/LICENSE +19 -0
- package/assets/api/vendor_modules/debug/Makefile +50 -0
- package/assets/api/vendor_modules/debug/README.md +312 -0
- package/assets/api/vendor_modules/debug/component.json +19 -0
- package/assets/api/vendor_modules/debug/karma.conf.js +70 -0
- package/assets/api/vendor_modules/debug/node.js +1 -0
- package/assets/api/vendor_modules/debug/package.json +49 -0
- package/assets/api/vendor_modules/debug/src/browser.js +185 -0
- package/assets/api/vendor_modules/debug/src/debug.js +202 -0
- package/assets/api/vendor_modules/debug/src/index.js +10 -0
- package/assets/api/vendor_modules/debug/src/inspector-log.js +15 -0
- package/assets/api/vendor_modules/debug/src/node.js +248 -0
- package/assets/api/vendor_modules/depd/History.md +103 -0
- package/assets/api/vendor_modules/depd/LICENSE +22 -0
- package/assets/api/vendor_modules/depd/Readme.md +280 -0
- package/assets/api/vendor_modules/depd/index.js +538 -0
- package/assets/api/vendor_modules/depd/lib/browser/index.js +77 -0
- package/assets/api/vendor_modules/depd/package.json +45 -0
- package/assets/api/vendor_modules/destroy/LICENSE +23 -0
- package/assets/api/vendor_modules/destroy/README.md +63 -0
- package/assets/api/vendor_modules/destroy/index.js +209 -0
- package/assets/api/vendor_modules/destroy/package.json +48 -0
- package/assets/api/vendor_modules/dunder-proto/.eslintrc +5 -0
- package/assets/api/vendor_modules/dunder-proto/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/dunder-proto/.nycrc +13 -0
- package/assets/api/vendor_modules/dunder-proto/CHANGELOG.md +24 -0
- package/assets/api/vendor_modules/dunder-proto/LICENSE +21 -0
- package/assets/api/vendor_modules/dunder-proto/README.md +54 -0
- package/assets/api/vendor_modules/dunder-proto/get.d.ts +5 -0
- package/assets/api/vendor_modules/dunder-proto/get.js +30 -0
- package/assets/api/vendor_modules/dunder-proto/package.json +76 -0
- package/assets/api/vendor_modules/dunder-proto/set.d.ts +5 -0
- package/assets/api/vendor_modules/dunder-proto/set.js +35 -0
- package/assets/api/vendor_modules/dunder-proto/test/get.js +34 -0
- package/assets/api/vendor_modules/dunder-proto/test/index.js +4 -0
- package/assets/api/vendor_modules/dunder-proto/test/set.js +50 -0
- package/assets/api/vendor_modules/dunder-proto/tsconfig.json +9 -0
- package/assets/api/vendor_modules/ee-first/LICENSE +22 -0
- package/assets/api/vendor_modules/ee-first/README.md +80 -0
- package/assets/api/vendor_modules/ee-first/index.js +95 -0
- package/assets/api/vendor_modules/ee-first/package.json +29 -0
- package/assets/api/vendor_modules/encodeurl/LICENSE +22 -0
- package/assets/api/vendor_modules/encodeurl/README.md +109 -0
- package/assets/api/vendor_modules/encodeurl/index.js +60 -0
- package/assets/api/vendor_modules/encodeurl/package.json +40 -0
- package/assets/api/vendor_modules/es-define-property/.eslintrc +13 -0
- package/assets/api/vendor_modules/es-define-property/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/es-define-property/.nycrc +9 -0
- package/assets/api/vendor_modules/es-define-property/CHANGELOG.md +29 -0
- package/assets/api/vendor_modules/es-define-property/LICENSE +21 -0
- package/assets/api/vendor_modules/es-define-property/README.md +49 -0
- package/assets/api/vendor_modules/es-define-property/index.d.ts +3 -0
- package/assets/api/vendor_modules/es-define-property/index.js +14 -0
- package/assets/api/vendor_modules/es-define-property/package.json +81 -0
- package/assets/api/vendor_modules/es-define-property/test/index.js +56 -0
- package/assets/api/vendor_modules/es-define-property/tsconfig.json +10 -0
- package/assets/api/vendor_modules/es-errors/.eslintrc +5 -0
- package/assets/api/vendor_modules/es-errors/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/es-errors/CHANGELOG.md +40 -0
- package/assets/api/vendor_modules/es-errors/LICENSE +21 -0
- package/assets/api/vendor_modules/es-errors/README.md +55 -0
- package/assets/api/vendor_modules/es-errors/eval.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/eval.js +4 -0
- package/assets/api/vendor_modules/es-errors/index.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/index.js +4 -0
- package/assets/api/vendor_modules/es-errors/package.json +80 -0
- package/assets/api/vendor_modules/es-errors/range.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/range.js +4 -0
- package/assets/api/vendor_modules/es-errors/ref.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/ref.js +4 -0
- package/assets/api/vendor_modules/es-errors/syntax.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/syntax.js +4 -0
- package/assets/api/vendor_modules/es-errors/test/index.js +19 -0
- package/assets/api/vendor_modules/es-errors/tsconfig.json +49 -0
- package/assets/api/vendor_modules/es-errors/type.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/type.js +4 -0
- package/assets/api/vendor_modules/es-errors/uri.d.ts +3 -0
- package/assets/api/vendor_modules/es-errors/uri.js +4 -0
- package/assets/api/vendor_modules/es-object-atoms/.eslintrc +16 -0
- package/assets/api/vendor_modules/es-object-atoms/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/es-object-atoms/CHANGELOG.md +44 -0
- package/assets/api/vendor_modules/es-object-atoms/LICENSE +21 -0
- package/assets/api/vendor_modules/es-object-atoms/README.md +63 -0
- package/assets/api/vendor_modules/es-object-atoms/RequireObjectCoercible.d.ts +3 -0
- package/assets/api/vendor_modules/es-object-atoms/RequireObjectCoercible.js +11 -0
- package/assets/api/vendor_modules/es-object-atoms/ToObject.d.ts +7 -0
- package/assets/api/vendor_modules/es-object-atoms/ToObject.js +10 -0
- package/assets/api/vendor_modules/es-object-atoms/index.d.ts +3 -0
- package/assets/api/vendor_modules/es-object-atoms/index.js +4 -0
- package/assets/api/vendor_modules/es-object-atoms/isObject.d.ts +3 -0
- package/assets/api/vendor_modules/es-object-atoms/isObject.js +6 -0
- package/assets/api/vendor_modules/es-object-atoms/package.json +79 -0
- package/assets/api/vendor_modules/es-object-atoms/test/index.js +38 -0
- package/assets/api/vendor_modules/es-object-atoms/tsconfig.json +7 -0
- package/assets/api/vendor_modules/escape-html/LICENSE +24 -0
- package/assets/api/vendor_modules/escape-html/Readme.md +43 -0
- package/assets/api/vendor_modules/escape-html/index.js +78 -0
- package/assets/api/vendor_modules/escape-html/package.json +24 -0
- package/assets/api/vendor_modules/etag/HISTORY.md +83 -0
- package/assets/api/vendor_modules/etag/LICENSE +22 -0
- package/assets/api/vendor_modules/etag/README.md +159 -0
- package/assets/api/vendor_modules/etag/index.js +131 -0
- package/assets/api/vendor_modules/etag/package.json +47 -0
- package/assets/api/vendor_modules/express/History.md +3676 -0
- package/assets/api/vendor_modules/express/LICENSE +24 -0
- package/assets/api/vendor_modules/express/Readme.md +260 -0
- package/assets/api/vendor_modules/express/index.js +11 -0
- package/assets/api/vendor_modules/express/lib/application.js +661 -0
- package/assets/api/vendor_modules/express/lib/express.js +116 -0
- package/assets/api/vendor_modules/express/lib/middleware/init.js +43 -0
- package/assets/api/vendor_modules/express/lib/middleware/query.js +47 -0
- package/assets/api/vendor_modules/express/lib/request.js +525 -0
- package/assets/api/vendor_modules/express/lib/response.js +1179 -0
- package/assets/api/vendor_modules/express/lib/router/index.js +673 -0
- package/assets/api/vendor_modules/express/lib/router/layer.js +181 -0
- package/assets/api/vendor_modules/express/lib/router/route.js +230 -0
- package/assets/api/vendor_modules/express/lib/utils.js +304 -0
- package/assets/api/vendor_modules/express/lib/view.js +182 -0
- package/assets/api/vendor_modules/express/package.json +102 -0
- package/assets/api/vendor_modules/finalhandler/HISTORY.md +216 -0
- package/assets/api/vendor_modules/finalhandler/LICENSE +22 -0
- package/assets/api/vendor_modules/finalhandler/README.md +147 -0
- package/assets/api/vendor_modules/finalhandler/SECURITY.md +25 -0
- package/assets/api/vendor_modules/finalhandler/index.js +341 -0
- package/assets/api/vendor_modules/finalhandler/package.json +47 -0
- package/assets/api/vendor_modules/forwarded/HISTORY.md +21 -0
- package/assets/api/vendor_modules/forwarded/LICENSE +22 -0
- package/assets/api/vendor_modules/forwarded/README.md +57 -0
- package/assets/api/vendor_modules/forwarded/index.js +90 -0
- package/assets/api/vendor_modules/forwarded/package.json +45 -0
- package/assets/api/vendor_modules/fresh/HISTORY.md +70 -0
- package/assets/api/vendor_modules/fresh/LICENSE +23 -0
- package/assets/api/vendor_modules/fresh/README.md +119 -0
- package/assets/api/vendor_modules/fresh/index.js +137 -0
- package/assets/api/vendor_modules/fresh/package.json +46 -0
- package/assets/api/vendor_modules/function-bind/.eslintrc +21 -0
- package/assets/api/vendor_modules/function-bind/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/function-bind/.github/SECURITY.md +3 -0
- package/assets/api/vendor_modules/function-bind/.nycrc +13 -0
- package/assets/api/vendor_modules/function-bind/CHANGELOG.md +136 -0
- package/assets/api/vendor_modules/function-bind/LICENSE +20 -0
- package/assets/api/vendor_modules/function-bind/README.md +46 -0
- package/assets/api/vendor_modules/function-bind/implementation.js +84 -0
- package/assets/api/vendor_modules/function-bind/index.js +5 -0
- package/assets/api/vendor_modules/function-bind/package.json +87 -0
- package/assets/api/vendor_modules/function-bind/test/.eslintrc +9 -0
- package/assets/api/vendor_modules/function-bind/test/index.js +252 -0
- package/assets/api/vendor_modules/get-intrinsic/.eslintrc +42 -0
- package/assets/api/vendor_modules/get-intrinsic/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/get-intrinsic/.nycrc +9 -0
- package/assets/api/vendor_modules/get-intrinsic/CHANGELOG.md +186 -0
- package/assets/api/vendor_modules/get-intrinsic/LICENSE +21 -0
- package/assets/api/vendor_modules/get-intrinsic/README.md +71 -0
- package/assets/api/vendor_modules/get-intrinsic/index.js +378 -0
- package/assets/api/vendor_modules/get-intrinsic/package.json +97 -0
- package/assets/api/vendor_modules/get-intrinsic/test/GetIntrinsic.js +274 -0
- package/assets/api/vendor_modules/get-proto/.eslintrc +10 -0
- package/assets/api/vendor_modules/get-proto/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/get-proto/.nycrc +9 -0
- package/assets/api/vendor_modules/get-proto/CHANGELOG.md +21 -0
- package/assets/api/vendor_modules/get-proto/LICENSE +21 -0
- package/assets/api/vendor_modules/get-proto/Object.getPrototypeOf.d.ts +5 -0
- package/assets/api/vendor_modules/get-proto/Object.getPrototypeOf.js +6 -0
- package/assets/api/vendor_modules/get-proto/README.md +50 -0
- package/assets/api/vendor_modules/get-proto/Reflect.getPrototypeOf.d.ts +3 -0
- package/assets/api/vendor_modules/get-proto/Reflect.getPrototypeOf.js +4 -0
- package/assets/api/vendor_modules/get-proto/index.d.ts +5 -0
- package/assets/api/vendor_modules/get-proto/index.js +27 -0
- package/assets/api/vendor_modules/get-proto/package.json +81 -0
- package/assets/api/vendor_modules/get-proto/test/index.js +68 -0
- package/assets/api/vendor_modules/get-proto/tsconfig.json +9 -0
- package/assets/api/vendor_modules/gopd/.eslintrc +16 -0
- package/assets/api/vendor_modules/gopd/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/gopd/CHANGELOG.md +45 -0
- package/assets/api/vendor_modules/gopd/LICENSE +21 -0
- package/assets/api/vendor_modules/gopd/README.md +40 -0
- package/assets/api/vendor_modules/gopd/gOPD.d.ts +1 -0
- package/assets/api/vendor_modules/gopd/gOPD.js +4 -0
- package/assets/api/vendor_modules/gopd/index.d.ts +5 -0
- package/assets/api/vendor_modules/gopd/index.js +15 -0
- package/assets/api/vendor_modules/gopd/package.json +77 -0
- package/assets/api/vendor_modules/gopd/test/index.js +36 -0
- package/assets/api/vendor_modules/gopd/tsconfig.json +9 -0
- package/assets/api/vendor_modules/has-symbols/.eslintrc +11 -0
- package/assets/api/vendor_modules/has-symbols/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/has-symbols/.nycrc +9 -0
- package/assets/api/vendor_modules/has-symbols/CHANGELOG.md +91 -0
- package/assets/api/vendor_modules/has-symbols/LICENSE +21 -0
- package/assets/api/vendor_modules/has-symbols/README.md +46 -0
- package/assets/api/vendor_modules/has-symbols/index.d.ts +3 -0
- package/assets/api/vendor_modules/has-symbols/index.js +14 -0
- package/assets/api/vendor_modules/has-symbols/package.json +111 -0
- package/assets/api/vendor_modules/has-symbols/shams.d.ts +3 -0
- package/assets/api/vendor_modules/has-symbols/shams.js +45 -0
- package/assets/api/vendor_modules/has-symbols/test/index.js +22 -0
- package/assets/api/vendor_modules/has-symbols/test/shams/core-js.js +29 -0
- package/assets/api/vendor_modules/has-symbols/test/shams/get-own-property-symbols.js +29 -0
- package/assets/api/vendor_modules/has-symbols/test/tests.js +58 -0
- package/assets/api/vendor_modules/has-symbols/tsconfig.json +10 -0
- package/assets/api/vendor_modules/hasown/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/hasown/.nycrc +13 -0
- package/assets/api/vendor_modules/hasown/CHANGELOG.md +58 -0
- package/assets/api/vendor_modules/hasown/LICENSE +21 -0
- package/assets/api/vendor_modules/hasown/README.md +40 -0
- package/assets/api/vendor_modules/hasown/eslint.config.mjs +6 -0
- package/assets/api/vendor_modules/hasown/index.d.ts +3 -0
- package/assets/api/vendor_modules/hasown/index.js +8 -0
- package/assets/api/vendor_modules/hasown/package.json +91 -0
- package/assets/api/vendor_modules/hasown/tsconfig.json +6 -0
- package/assets/api/vendor_modules/http-errors/HISTORY.md +186 -0
- package/assets/api/vendor_modules/http-errors/LICENSE +23 -0
- package/assets/api/vendor_modules/http-errors/README.md +169 -0
- package/assets/api/vendor_modules/http-errors/index.js +290 -0
- package/assets/api/vendor_modules/http-errors/package.json +54 -0
- package/assets/api/vendor_modules/iconv-lite/Changelog.md +162 -0
- package/assets/api/vendor_modules/iconv-lite/LICENSE +21 -0
- package/assets/api/vendor_modules/iconv-lite/README.md +156 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/dbcs-codec.js +555 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/dbcs-data.js +176 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/index.js +22 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/internal.js +188 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/sbcs-codec.js +72 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/sbcs-data-generated.js +451 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/sbcs-data.js +174 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/big5-added.json +122 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/cp936.json +264 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/cp949.json +273 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/cp950.json +177 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/eucjp.json +182 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/gb18030-ranges.json +1 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/gbk-added.json +55 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/tables/shiftjis.json +125 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/utf16.js +177 -0
- package/assets/api/vendor_modules/iconv-lite/encodings/utf7.js +290 -0
- package/assets/api/vendor_modules/iconv-lite/lib/bom-handling.js +52 -0
- package/assets/api/vendor_modules/iconv-lite/lib/extend-node.js +217 -0
- package/assets/api/vendor_modules/iconv-lite/lib/index.d.ts +24 -0
- package/assets/api/vendor_modules/iconv-lite/lib/index.js +153 -0
- package/assets/api/vendor_modules/iconv-lite/lib/streams.js +121 -0
- package/assets/api/vendor_modules/iconv-lite/package.json +46 -0
- package/assets/api/vendor_modules/inherits/LICENSE +16 -0
- package/assets/api/vendor_modules/inherits/README.md +42 -0
- package/assets/api/vendor_modules/inherits/inherits.js +9 -0
- package/assets/api/vendor_modules/inherits/inherits_browser.js +27 -0
- package/assets/api/vendor_modules/inherits/package.json +29 -0
- package/assets/api/vendor_modules/ipaddr.js/LICENSE +19 -0
- package/assets/api/vendor_modules/ipaddr.js/README.md +233 -0
- package/assets/api/vendor_modules/ipaddr.js/ipaddr.min.js +1 -0
- package/assets/api/vendor_modules/ipaddr.js/lib/ipaddr.js +673 -0
- package/assets/api/vendor_modules/ipaddr.js/lib/ipaddr.js.d.ts +68 -0
- package/assets/api/vendor_modules/ipaddr.js/package.json +35 -0
- package/assets/api/vendor_modules/math-intrinsics/.eslintrc +16 -0
- package/assets/api/vendor_modules/math-intrinsics/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/math-intrinsics/CHANGELOG.md +24 -0
- package/assets/api/vendor_modules/math-intrinsics/LICENSE +21 -0
- package/assets/api/vendor_modules/math-intrinsics/README.md +50 -0
- package/assets/api/vendor_modules/math-intrinsics/abs.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/abs.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/constants/maxArrayLength.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/constants/maxArrayLength.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/constants/maxSafeInteger.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/constants/maxSafeInteger.js +5 -0
- package/assets/api/vendor_modules/math-intrinsics/constants/maxValue.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/constants/maxValue.js +5 -0
- package/assets/api/vendor_modules/math-intrinsics/floor.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/floor.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/isFinite.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/isFinite.js +12 -0
- package/assets/api/vendor_modules/math-intrinsics/isInteger.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/isInteger.js +16 -0
- package/assets/api/vendor_modules/math-intrinsics/isNaN.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/isNaN.js +6 -0
- package/assets/api/vendor_modules/math-intrinsics/isNegativeZero.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/isNegativeZero.js +6 -0
- package/assets/api/vendor_modules/math-intrinsics/max.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/max.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/min.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/min.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/mod.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/mod.js +9 -0
- package/assets/api/vendor_modules/math-intrinsics/package.json +86 -0
- package/assets/api/vendor_modules/math-intrinsics/pow.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/pow.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/round.d.ts +1 -0
- package/assets/api/vendor_modules/math-intrinsics/round.js +4 -0
- package/assets/api/vendor_modules/math-intrinsics/sign.d.ts +3 -0
- package/assets/api/vendor_modules/math-intrinsics/sign.js +11 -0
- package/assets/api/vendor_modules/math-intrinsics/test/index.js +192 -0
- package/assets/api/vendor_modules/math-intrinsics/tsconfig.json +3 -0
- package/assets/api/vendor_modules/media-typer/HISTORY.md +22 -0
- package/assets/api/vendor_modules/media-typer/LICENSE +22 -0
- package/assets/api/vendor_modules/media-typer/README.md +81 -0
- package/assets/api/vendor_modules/media-typer/index.js +270 -0
- package/assets/api/vendor_modules/media-typer/package.json +26 -0
- package/assets/api/vendor_modules/merge-descriptors/HISTORY.md +21 -0
- package/assets/api/vendor_modules/merge-descriptors/LICENSE +23 -0
- package/assets/api/vendor_modules/merge-descriptors/README.md +49 -0
- package/assets/api/vendor_modules/merge-descriptors/index.js +60 -0
- package/assets/api/vendor_modules/merge-descriptors/package.json +39 -0
- package/assets/api/vendor_modules/methods/HISTORY.md +29 -0
- package/assets/api/vendor_modules/methods/LICENSE +24 -0
- package/assets/api/vendor_modules/methods/README.md +51 -0
- package/assets/api/vendor_modules/methods/index.js +69 -0
- package/assets/api/vendor_modules/methods/package.json +36 -0
- package/assets/api/vendor_modules/mime/CHANGELOG.md +164 -0
- package/assets/api/vendor_modules/mime/LICENSE +21 -0
- package/assets/api/vendor_modules/mime/README.md +90 -0
- package/assets/api/vendor_modules/mime/cli.js +8 -0
- package/assets/api/vendor_modules/mime/mime.js +108 -0
- package/assets/api/vendor_modules/mime/package.json +44 -0
- package/assets/api/vendor_modules/mime/src/build.js +53 -0
- package/assets/api/vendor_modules/mime/src/test.js +60 -0
- package/assets/api/vendor_modules/mime/types.json +1 -0
- package/assets/api/vendor_modules/mime-db/HISTORY.md +507 -0
- package/assets/api/vendor_modules/mime-db/LICENSE +23 -0
- package/assets/api/vendor_modules/mime-db/README.md +100 -0
- package/assets/api/vendor_modules/mime-db/db.json +8519 -0
- package/assets/api/vendor_modules/mime-db/index.js +12 -0
- package/assets/api/vendor_modules/mime-db/package.json +60 -0
- package/assets/api/vendor_modules/mime-types/HISTORY.md +397 -0
- package/assets/api/vendor_modules/mime-types/LICENSE +23 -0
- package/assets/api/vendor_modules/mime-types/README.md +113 -0
- package/assets/api/vendor_modules/mime-types/index.js +188 -0
- package/assets/api/vendor_modules/mime-types/package.json +44 -0
- package/assets/api/vendor_modules/ms/index.js +152 -0
- package/assets/api/vendor_modules/ms/license.md +21 -0
- package/assets/api/vendor_modules/ms/package.json +37 -0
- package/assets/api/vendor_modules/ms/readme.md +51 -0
- package/assets/api/vendor_modules/negotiator/HISTORY.md +108 -0
- package/assets/api/vendor_modules/negotiator/LICENSE +24 -0
- package/assets/api/vendor_modules/negotiator/README.md +203 -0
- package/assets/api/vendor_modules/negotiator/index.js +82 -0
- package/assets/api/vendor_modules/negotiator/lib/charset.js +169 -0
- package/assets/api/vendor_modules/negotiator/lib/encoding.js +184 -0
- package/assets/api/vendor_modules/negotiator/lib/language.js +179 -0
- package/assets/api/vendor_modules/negotiator/lib/mediaType.js +294 -0
- package/assets/api/vendor_modules/negotiator/package.json +42 -0
- package/assets/api/vendor_modules/object-inspect/.eslintrc +53 -0
- package/assets/api/vendor_modules/object-inspect/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/object-inspect/.nycrc +13 -0
- package/assets/api/vendor_modules/object-inspect/CHANGELOG.md +424 -0
- package/assets/api/vendor_modules/object-inspect/LICENSE +21 -0
- package/assets/api/vendor_modules/object-inspect/example/all.js +23 -0
- package/assets/api/vendor_modules/object-inspect/example/circular.js +6 -0
- package/assets/api/vendor_modules/object-inspect/example/fn.js +5 -0
- package/assets/api/vendor_modules/object-inspect/example/inspect.js +10 -0
- package/assets/api/vendor_modules/object-inspect/index.js +544 -0
- package/assets/api/vendor_modules/object-inspect/package-support.json +20 -0
- package/assets/api/vendor_modules/object-inspect/package.json +105 -0
- package/assets/api/vendor_modules/object-inspect/readme.markdown +84 -0
- package/assets/api/vendor_modules/object-inspect/test/bigint.js +58 -0
- package/assets/api/vendor_modules/object-inspect/test/browser/dom.js +15 -0
- package/assets/api/vendor_modules/object-inspect/test/circular.js +16 -0
- package/assets/api/vendor_modules/object-inspect/test/deep.js +12 -0
- package/assets/api/vendor_modules/object-inspect/test/element.js +53 -0
- package/assets/api/vendor_modules/object-inspect/test/err.js +48 -0
- package/assets/api/vendor_modules/object-inspect/test/fakes.js +29 -0
- package/assets/api/vendor_modules/object-inspect/test/fn.js +76 -0
- package/assets/api/vendor_modules/object-inspect/test/global.js +17 -0
- package/assets/api/vendor_modules/object-inspect/test/has.js +15 -0
- package/assets/api/vendor_modules/object-inspect/test/holes.js +15 -0
- package/assets/api/vendor_modules/object-inspect/test/indent-option.js +271 -0
- package/assets/api/vendor_modules/object-inspect/test/inspect.js +139 -0
- package/assets/api/vendor_modules/object-inspect/test/lowbyte.js +12 -0
- package/assets/api/vendor_modules/object-inspect/test/number.js +58 -0
- package/assets/api/vendor_modules/object-inspect/test/quoteStyle.js +26 -0
- package/assets/api/vendor_modules/object-inspect/test/toStringTag.js +40 -0
- package/assets/api/vendor_modules/object-inspect/test/undef.js +12 -0
- package/assets/api/vendor_modules/object-inspect/test/values.js +261 -0
- package/assets/api/vendor_modules/object-inspect/test-core-js.js +26 -0
- package/assets/api/vendor_modules/object-inspect/util.inspect.js +1 -0
- package/assets/api/vendor_modules/on-finished/HISTORY.md +98 -0
- package/assets/api/vendor_modules/on-finished/LICENSE +23 -0
- package/assets/api/vendor_modules/on-finished/README.md +162 -0
- package/assets/api/vendor_modules/on-finished/index.js +234 -0
- package/assets/api/vendor_modules/on-finished/package.json +39 -0
- package/assets/api/vendor_modules/parseurl/HISTORY.md +58 -0
- package/assets/api/vendor_modules/parseurl/LICENSE +24 -0
- package/assets/api/vendor_modules/parseurl/README.md +133 -0
- package/assets/api/vendor_modules/parseurl/index.js +158 -0
- package/assets/api/vendor_modules/parseurl/package.json +40 -0
- package/assets/api/vendor_modules/path-to-regexp/LICENSE +21 -0
- package/assets/api/vendor_modules/path-to-regexp/Readme.md +35 -0
- package/assets/api/vendor_modules/path-to-regexp/index.js +158 -0
- package/assets/api/vendor_modules/path-to-regexp/package.json +30 -0
- package/assets/api/vendor_modules/proxy-addr/HISTORY.md +167 -0
- package/assets/api/vendor_modules/proxy-addr/LICENSE +22 -0
- package/assets/api/vendor_modules/proxy-addr/README.md +139 -0
- package/assets/api/vendor_modules/proxy-addr/index.js +359 -0
- package/assets/api/vendor_modules/proxy-addr/package.json +51 -0
- package/assets/api/vendor_modules/qs/.editorconfig +46 -0
- package/assets/api/vendor_modules/qs/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/qs/.github/SECURITY.md +11 -0
- package/assets/api/vendor_modules/qs/.github/THREAT_MODEL.md +78 -0
- package/assets/api/vendor_modules/qs/.nycrc +13 -0
- package/assets/api/vendor_modules/qs/CHANGELOG.md +833 -0
- package/assets/api/vendor_modules/qs/LICENSE.md +29 -0
- package/assets/api/vendor_modules/qs/README.md +758 -0
- package/assets/api/vendor_modules/qs/dist/qs.js +141 -0
- package/assets/api/vendor_modules/qs/eslint.config.mjs +57 -0
- package/assets/api/vendor_modules/qs/lib/formats.js +23 -0
- package/assets/api/vendor_modules/qs/lib/index.js +11 -0
- package/assets/api/vendor_modules/qs/lib/parse.js +414 -0
- package/assets/api/vendor_modules/qs/lib/stringify.js +363 -0
- package/assets/api/vendor_modules/qs/lib/utils.js +381 -0
- package/assets/api/vendor_modules/qs/package.json +95 -0
- package/assets/api/vendor_modules/qs/test/empty-keys-cases.js +267 -0
- package/assets/api/vendor_modules/qs/test/parse.js +1933 -0
- package/assets/api/vendor_modules/qs/test/stringify.js +1448 -0
- package/assets/api/vendor_modules/qs/test/utils.js +626 -0
- package/assets/api/vendor_modules/range-parser/HISTORY.md +56 -0
- package/assets/api/vendor_modules/range-parser/LICENSE +23 -0
- package/assets/api/vendor_modules/range-parser/README.md +84 -0
- package/assets/api/vendor_modules/range-parser/index.js +162 -0
- package/assets/api/vendor_modules/range-parser/package.json +44 -0
- package/assets/api/vendor_modules/raw-body/LICENSE +22 -0
- package/assets/api/vendor_modules/raw-body/README.md +223 -0
- package/assets/api/vendor_modules/raw-body/index.d.ts +87 -0
- package/assets/api/vendor_modules/raw-body/index.js +336 -0
- package/assets/api/vendor_modules/raw-body/package.json +47 -0
- package/assets/api/vendor_modules/safe-buffer/LICENSE +21 -0
- package/assets/api/vendor_modules/safe-buffer/README.md +584 -0
- package/assets/api/vendor_modules/safe-buffer/index.d.ts +187 -0
- package/assets/api/vendor_modules/safe-buffer/index.js +65 -0
- package/assets/api/vendor_modules/safe-buffer/package.json +51 -0
- package/assets/api/vendor_modules/safer-buffer/LICENSE +21 -0
- package/assets/api/vendor_modules/safer-buffer/Porting-Buffer.md +268 -0
- package/assets/api/vendor_modules/safer-buffer/Readme.md +156 -0
- package/assets/api/vendor_modules/safer-buffer/dangerous.js +58 -0
- package/assets/api/vendor_modules/safer-buffer/package.json +34 -0
- package/assets/api/vendor_modules/safer-buffer/safer.js +77 -0
- package/assets/api/vendor_modules/safer-buffer/tests.js +406 -0
- package/assets/api/vendor_modules/send/HISTORY.md +538 -0
- package/assets/api/vendor_modules/send/LICENSE +23 -0
- package/assets/api/vendor_modules/send/README.md +327 -0
- package/assets/api/vendor_modules/send/SECURITY.md +24 -0
- package/assets/api/vendor_modules/send/index.js +1142 -0
- package/assets/api/vendor_modules/send/node_modules/ms/index.js +162 -0
- package/assets/api/vendor_modules/send/node_modules/ms/license.md +21 -0
- package/assets/api/vendor_modules/send/node_modules/ms/package.json +38 -0
- package/assets/api/vendor_modules/send/node_modules/ms/readme.md +59 -0
- package/assets/api/vendor_modules/send/package.json +62 -0
- package/assets/api/vendor_modules/serve-static/HISTORY.md +493 -0
- package/assets/api/vendor_modules/serve-static/LICENSE +25 -0
- package/assets/api/vendor_modules/serve-static/README.md +257 -0
- package/assets/api/vendor_modules/serve-static/index.js +209 -0
- package/assets/api/vendor_modules/serve-static/package.json +42 -0
- package/assets/api/vendor_modules/serverless-http/LICENSE.txt +33 -0
- package/assets/api/vendor_modules/serverless-http/README.md +117 -0
- package/assets/api/vendor_modules/serverless-http/lib/finish.js +42 -0
- package/assets/api/vendor_modules/serverless-http/lib/framework/get-framework.js +79 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/aws/clean-up-event.js +98 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/aws/create-request.js +110 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/aws/format-response.js +45 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/aws/index.js +15 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/aws/is-binary.js +42 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/aws/sanitize-headers.js +21 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/clean-up-request.js +41 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/create-request.js +45 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/format-response.js +18 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/index.js +13 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/is-binary.js +42 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/sanitize-headers.js +23 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/azure/set-cookie.json +1 -0
- package/assets/api/vendor_modules/serverless-http/lib/provider/get-provider.js +17 -0
- package/assets/api/vendor_modules/serverless-http/lib/request.js +38 -0
- package/assets/api/vendor_modules/serverless-http/lib/response.js +135 -0
- package/assets/api/vendor_modules/serverless-http/package.json +93 -0
- package/assets/api/vendor_modules/serverless-http/serverless-http.d.ts +41 -0
- package/assets/api/vendor_modules/serverless-http/serverless-http.js +23 -0
- package/assets/api/vendor_modules/setprototypeof/LICENSE +13 -0
- package/assets/api/vendor_modules/setprototypeof/README.md +31 -0
- package/assets/api/vendor_modules/setprototypeof/index.d.ts +2 -0
- package/assets/api/vendor_modules/setprototypeof/index.js +17 -0
- package/assets/api/vendor_modules/setprototypeof/package.json +38 -0
- package/assets/api/vendor_modules/setprototypeof/test/index.js +24 -0
- package/assets/api/vendor_modules/side-channel/.editorconfig +9 -0
- package/assets/api/vendor_modules/side-channel/.eslintrc +12 -0
- package/assets/api/vendor_modules/side-channel/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/side-channel/.nycrc +13 -0
- package/assets/api/vendor_modules/side-channel/CHANGELOG.md +120 -0
- package/assets/api/vendor_modules/side-channel/LICENSE +21 -0
- package/assets/api/vendor_modules/side-channel/README.md +61 -0
- package/assets/api/vendor_modules/side-channel/index.d.ts +14 -0
- package/assets/api/vendor_modules/side-channel/index.js +46 -0
- package/assets/api/vendor_modules/side-channel/package.json +85 -0
- package/assets/api/vendor_modules/side-channel/test/index.js +120 -0
- package/assets/api/vendor_modules/side-channel/tsconfig.json +9 -0
- package/assets/api/vendor_modules/side-channel-list/.editorconfig +9 -0
- package/assets/api/vendor_modules/side-channel-list/.eslintrc +11 -0
- package/assets/api/vendor_modules/side-channel-list/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/side-channel-list/.nycrc +13 -0
- package/assets/api/vendor_modules/side-channel-list/CHANGELOG.md +36 -0
- package/assets/api/vendor_modules/side-channel-list/LICENSE +21 -0
- package/assets/api/vendor_modules/side-channel-list/README.md +62 -0
- package/assets/api/vendor_modules/side-channel-list/index.d.ts +13 -0
- package/assets/api/vendor_modules/side-channel-list/index.js +111 -0
- package/assets/api/vendor_modules/side-channel-list/list.d.ts +14 -0
- package/assets/api/vendor_modules/side-channel-list/package.json +77 -0
- package/assets/api/vendor_modules/side-channel-list/test/index.js +154 -0
- package/assets/api/vendor_modules/side-channel-list/tsconfig.json +9 -0
- package/assets/api/vendor_modules/side-channel-map/.editorconfig +9 -0
- package/assets/api/vendor_modules/side-channel-map/.eslintrc +11 -0
- package/assets/api/vendor_modules/side-channel-map/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/side-channel-map/.nycrc +13 -0
- package/assets/api/vendor_modules/side-channel-map/CHANGELOG.md +22 -0
- package/assets/api/vendor_modules/side-channel-map/LICENSE +21 -0
- package/assets/api/vendor_modules/side-channel-map/README.md +62 -0
- package/assets/api/vendor_modules/side-channel-map/index.d.ts +15 -0
- package/assets/api/vendor_modules/side-channel-map/index.js +68 -0
- package/assets/api/vendor_modules/side-channel-map/package.json +80 -0
- package/assets/api/vendor_modules/side-channel-map/test/index.js +114 -0
- package/assets/api/vendor_modules/side-channel-map/tsconfig.json +9 -0
- package/assets/api/vendor_modules/side-channel-weakmap/.editorconfig +9 -0
- package/assets/api/vendor_modules/side-channel-weakmap/.eslintrc +12 -0
- package/assets/api/vendor_modules/side-channel-weakmap/.github/FUNDING.yml +12 -0
- package/assets/api/vendor_modules/side-channel-weakmap/.nycrc +13 -0
- package/assets/api/vendor_modules/side-channel-weakmap/CHANGELOG.md +28 -0
- package/assets/api/vendor_modules/side-channel-weakmap/LICENSE +21 -0
- package/assets/api/vendor_modules/side-channel-weakmap/README.md +62 -0
- package/assets/api/vendor_modules/side-channel-weakmap/index.d.ts +15 -0
- package/assets/api/vendor_modules/side-channel-weakmap/index.js +84 -0
- package/assets/api/vendor_modules/side-channel-weakmap/package.json +87 -0
- package/assets/api/vendor_modules/side-channel-weakmap/test/index.js +114 -0
- package/assets/api/vendor_modules/side-channel-weakmap/tsconfig.json +9 -0
- package/assets/api/vendor_modules/statuses/HISTORY.md +87 -0
- package/assets/api/vendor_modules/statuses/LICENSE +23 -0
- package/assets/api/vendor_modules/statuses/README.md +139 -0
- package/assets/api/vendor_modules/statuses/codes.json +65 -0
- package/assets/api/vendor_modules/statuses/index.js +146 -0
- package/assets/api/vendor_modules/statuses/package.json +49 -0
- package/assets/api/vendor_modules/toidentifier/HISTORY.md +9 -0
- package/assets/api/vendor_modules/toidentifier/LICENSE +21 -0
- package/assets/api/vendor_modules/toidentifier/README.md +61 -0
- package/assets/api/vendor_modules/toidentifier/index.js +32 -0
- package/assets/api/vendor_modules/toidentifier/package.json +38 -0
- package/assets/api/vendor_modules/type-is/HISTORY.md +259 -0
- package/assets/api/vendor_modules/type-is/LICENSE +23 -0
- package/assets/api/vendor_modules/type-is/README.md +170 -0
- package/assets/api/vendor_modules/type-is/index.js +266 -0
- package/assets/api/vendor_modules/type-is/package.json +45 -0
- package/assets/api/vendor_modules/unpipe/HISTORY.md +4 -0
- package/assets/api/vendor_modules/unpipe/LICENSE +22 -0
- package/assets/api/vendor_modules/unpipe/README.md +43 -0
- package/assets/api/vendor_modules/unpipe/index.js +69 -0
- package/assets/api/vendor_modules/unpipe/package.json +27 -0
- package/assets/api/vendor_modules/utils-merge/LICENSE +20 -0
- package/assets/api/vendor_modules/utils-merge/README.md +34 -0
- package/assets/api/vendor_modules/utils-merge/index.js +23 -0
- package/assets/api/vendor_modules/utils-merge/package.json +40 -0
- package/assets/api/vendor_modules/vary/HISTORY.md +39 -0
- package/assets/api/vendor_modules/vary/LICENSE +22 -0
- package/assets/api/vendor_modules/vary/README.md +101 -0
- package/assets/api/vendor_modules/vary/index.js +149 -0
- package/assets/api/vendor_modules/vary/package.json +43 -0
- package/assets/federation-presignup/index.js +108 -0
- package/assets/firstadmin/index.js +361 -0
- package/assets/headscale-apikey/index.js +140 -0
- package/assets/invoke-relay/index.js +96 -0
- package/assets/issuer-canary/index.js +71 -0
- package/assets/issuer-publish/index.js +87 -0
- package/assets/keyminter/index.js +209 -0
- package/assets/plugin-authorizer/index.js +1740 -0
- package/assets/plugin-catalog/index.js +881 -0
- package/assets/reapproval/index.js +3761 -0
- package/assets/sg-manager/index.js +176 -0
- package/assets/tailnet-resolver/index.js +55 -0
- package/assets/web/chunk-2MOGDYK4.js +1 -0
- package/assets/web/chunk-2SO4BVIT.js +1 -0
- package/assets/web/chunk-2V6RSAAC.js +1 -0
- package/assets/web/chunk-32GNBTAL.js +1 -0
- package/assets/web/chunk-33SYY2JB.js +1 -0
- package/assets/web/chunk-3VWYT7AX.js +1 -0
- package/assets/web/chunk-4FG4DAWU.js +4 -0
- package/assets/web/chunk-4N2IUS6T.js +1 -0
- package/assets/web/chunk-4SK6XQ6C.js +1 -0
- package/assets/web/chunk-65WE5MXA.js +8 -0
- package/assets/web/chunk-6D7SSSJF.js +1 -0
- package/assets/web/chunk-6KFR7DYY.js +1 -0
- package/assets/web/chunk-A4WHI3ZG.js +8 -0
- package/assets/web/chunk-BNFKRZTP.js +1 -0
- package/assets/web/chunk-CG56PH3M.js +1 -0
- package/assets/web/chunk-CGO25AIM.js +1 -0
- package/assets/web/chunk-CP4XZPOA.js +1 -0
- package/assets/web/chunk-CV7EODPO.js +1 -0
- package/assets/web/chunk-D7TR2ORJ.js +1 -0
- package/assets/web/chunk-DCOFBAKJ.js +1 -0
- package/assets/web/chunk-DOLRLYW7.js +1 -0
- package/assets/web/chunk-EEJ3X466.js +1 -0
- package/assets/web/chunk-EQ4PF25J.js +1 -0
- package/assets/web/chunk-EVP5UVDQ.js +1 -0
- package/assets/web/chunk-EW2VMNHE.js +1 -0
- package/assets/web/chunk-FIUVIKAN.js +1 -0
- package/assets/web/chunk-GHEU25TG.js +1 -0
- package/assets/web/chunk-GK2T2SMO.js +1 -0
- package/assets/web/chunk-GKI3R2JB.js +1 -0
- package/assets/web/chunk-GPKKIOHD.js +2 -0
- package/assets/web/chunk-H4AVFUDM.js +1 -0
- package/assets/web/chunk-H5H727H6.js +1 -0
- package/assets/web/chunk-HGVFH6YM.js +1 -0
- package/assets/web/chunk-HHPIANBK.js +1 -0
- package/assets/web/chunk-HK56COCB.js +1 -0
- package/assets/web/chunk-HZUB7LFV.js +1 -0
- package/assets/web/chunk-I7NTNTBA.js +1 -0
- package/assets/web/chunk-IKK5DQ3Z.js +1 -0
- package/assets/web/chunk-IMH56UBX.js +1 -0
- package/assets/web/chunk-J3SSPCFV.js +1 -0
- package/assets/web/chunk-J4C7C5KO.js +1 -0
- package/assets/web/chunk-J57JB4MU.js +1 -0
- package/assets/web/chunk-JMBXRK3I.js +4 -0
- package/assets/web/chunk-KCCEQIXX.js +1 -0
- package/assets/web/chunk-KEBESIXR.js +1 -0
- package/assets/web/chunk-KPNHT4MK.js +1 -0
- package/assets/web/chunk-LMLXTVZG.js +1 -0
- package/assets/web/chunk-M6OAYNRE.js +1 -0
- package/assets/web/chunk-M7MGX5I4.js +1 -0
- package/assets/web/chunk-MU2VNTNY.js +1 -0
- package/assets/web/chunk-NABAVDRL.js +1 -0
- package/assets/web/chunk-NO5D6WDF.js +1 -0
- package/assets/web/chunk-O4QSFUXB.js +1 -0
- package/assets/web/chunk-OATVFQXO.js +1 -0
- package/assets/web/chunk-P5WRM37W.js +5 -0
- package/assets/web/chunk-PCP6D2SM.js +1 -0
- package/assets/web/chunk-PMMBJK7Q.js +1 -0
- package/assets/web/chunk-PSMER3HX.js +1 -0
- package/assets/web/chunk-QPBWZLCT.js +1 -0
- package/assets/web/chunk-QVABVDO5.js +1 -0
- package/assets/web/chunk-RI77LE5V.js +1 -0
- package/assets/web/chunk-RKGUEXZ2.js +1 -0
- package/assets/web/chunk-RMFH2YTZ.js +1 -0
- package/assets/web/chunk-RROPCBUH.js +1 -0
- package/assets/web/chunk-SJMLX5AT.js +1 -0
- package/assets/web/chunk-TKVALOE4.js +2 -0
- package/assets/web/chunk-TM43WUKM.js +1 -0
- package/assets/web/chunk-VKWV3ANT.js +1 -0
- package/assets/web/chunk-VTMYG6YW.js +1 -0
- package/assets/web/chunk-VWW4SA5R.js +1 -0
- package/assets/web/chunk-WEDAFMID.js +1 -0
- package/assets/web/chunk-WFIHWVQO.js +1 -0
- package/assets/web/chunk-XJM3HDJQ.js +1 -0
- package/assets/web/chunk-YVJV2RUY.js +1 -0
- package/assets/web/chunk-Z3KONI7L.js +1 -0
- package/assets/web/chunk-ZDCURXPS.js +1 -0
- package/assets/web/favicon.ico +0 -0
- package/assets/web/index.html +63 -0
- package/assets/web/main-S6E3BC5O.js +2 -0
- package/assets/web/polyfills-7RSNIJ6J.js +2 -0
- package/assets/web/styles-R6ZUWC6K.css +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +66 -0
- package/dist/lambdacode/api/services/brandingShapes.d.ts +5 -0
- package/dist/lambdacode/api/services/brandingShapes.js +15 -0
- package/dist/lambdacode/issuer/objects.d.ts +8 -0
- package/dist/lambdacode/issuer/objects.js +11 -0
- package/dist/lambdacode/network/naming.d.ts +52 -0
- package/dist/lambdacode/network/naming.js +67 -0
- package/dist/lib/access-portal-hub.d.ts +53 -0
- package/dist/lib/access-portal-hub.js +87 -0
- package/dist/lib/access-portal.d.ts +32 -0
- package/dist/lib/access-portal.js +174 -0
- package/dist/lib/audit-log.d.ts +18 -0
- package/dist/lib/audit-log.js +25 -0
- package/dist/lib/connector-stack.d.ts +28 -0
- package/dist/lib/connector-stack.js +81 -0
- package/dist/lib/constructs/app-function.d.ts +14 -0
- package/dist/lib/constructs/app-function.js +84 -0
- package/dist/lib/constructs/app-table.d.ts +12 -0
- package/dist/lib/constructs/app-table.js +58 -0
- package/dist/lib/constructs/mirrored-image.d.ts +12 -0
- package/dist/lib/constructs/mirrored-image.js +80 -0
- package/dist/lib/constructs/monitoring.d.ts +21 -0
- package/dist/lib/constructs/monitoring.js +133 -0
- package/dist/lib/constructs/oidc-issuer.d.ts +44 -0
- package/dist/lib/constructs/oidc-issuer.js +234 -0
- package/dist/lib/constructs/scratch-tmpfs.d.ts +5 -0
- package/dist/lib/constructs/scratch-tmpfs.js +48 -0
- package/dist/lib/constructs/sg-manager.d.ts +27 -0
- package/dist/lib/constructs/sg-manager.js +88 -0
- package/dist/lib/constructs/tailnet-connector.d.ts +29 -0
- package/dist/lib/constructs/tailnet-connector.js +177 -0
- package/dist/lib/constructs/tailnet-resolver.d.ts +30 -0
- package/dist/lib/constructs/tailnet-resolver.js +71 -0
- package/dist/lib/constructs/target-account-trust.d.ts +32 -0
- package/dist/lib/constructs/target-account-trust.js +81 -0
- package/dist/lib/constructs/vending-api.d.ts +103 -0
- package/dist/lib/constructs/vending-api.js +203 -0
- package/dist/lib/constructs/web-acl.d.ts +61 -0
- package/dist/lib/constructs/web-acl.js +213 -0
- package/dist/lib/constructs/web-identity.d.ts +49 -0
- package/dist/lib/constructs/web-identity.js +116 -0
- package/dist/lib/creds-stack.d.ts +151 -0
- package/dist/lib/creds-stack.js +592 -0
- package/dist/lib/data-stack.d.ts +53 -0
- package/dist/lib/data-stack.js +282 -0
- package/dist/lib/dimensions-validation.d.ts +20 -0
- package/dist/lib/dimensions-validation.js +51 -0
- package/dist/lib/federation.d.ts +113 -0
- package/dist/lib/federation.js +254 -0
- package/dist/lib/managed-login-branding.json +450 -0
- package/dist/lib/network-stack.d.ts +58 -0
- package/dist/lib/network-stack.js +776 -0
- package/dist/lib/network-validation.d.ts +54 -0
- package/dist/lib/network-validation.js +220 -0
- package/dist/lib/pkg-paths.d.ts +3 -0
- package/dist/lib/pkg-paths.js +51 -0
- package/dist/lib/plugins/plugin-api.d.ts +40 -0
- package/dist/lib/plugins/plugin-api.js +125 -0
- package/dist/lib/plugins/plugin-catalog-activation.d.ts +31 -0
- package/dist/lib/plugins/plugin-catalog-activation.js +109 -0
- package/dist/lib/plugins/registration.d.ts +41 -0
- package/dist/lib/plugins/registration.js +121 -0
- package/dist/lib/plugins/runtime.d.ts +10 -0
- package/dist/lib/plugins/runtime.js +17 -0
- package/dist/lib/project-config.d.ts +164 -0
- package/dist/lib/project-config.js +2 -0
- package/dist/lib/target-account-stack.d.ts +15 -0
- package/dist/lib/target-account-stack.js +59 -0
- package/dist/lib/target-account-template.d.ts +13 -0
- package/dist/lib/target-account-template.js +64 -0
- package/dist/lib/web-cert-stack.d.ts +30 -0
- package/dist/lib/web-cert-stack.js +90 -0
- package/dist/lib/web-stack.d.ts +103 -0
- package/dist/lib/web-stack.js +540 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/README.md +303 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/backend/context.d.ts +31 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/backend/context.js +58 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/backend/index.d.ts +9 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/backend/index.js +26 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/client.d.ts +59 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/client.js +290 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/errors.d.ts +14 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/errors.js +32 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/http.d.ts +72 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/http.js +170 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/index.d.ts +11 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/index.js +28 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/theme-apply.d.ts +22 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/browser/theme-apply.js +22 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/access.d.ts +14 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/access.js +41 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/authorizer.d.ts +17 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/authorizer.js +44 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/canonical.d.ts +2 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/canonical.js +21 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/catalog.d.ts +39 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/catalog.js +18 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/discovery.d.ts +78 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/discovery.js +48 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/fields.d.ts +7 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/fields.js +2 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/manifest.d.ts +59 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/manifest.js +19 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/messages.d.ts +111 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/messages.js +109 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/paths.d.ts +20 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/paths.js +49 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/theme.d.ts +21 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/theme.js +60 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/transport.d.ts +14 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/transport.js +10 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/validate.d.ts +27 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/contracts/validate.js +321 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/devHost.d.ts +61 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/devHost.js +99 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/fakeDocument.d.ts +12 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/fakeDocument.js +22 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/fakeWindows.d.ts +22 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/fakeWindows.js +46 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/index.d.ts +4 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/index.js +20 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/samples.d.ts +37 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/fixtures/samples.js +228 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/host/host.d.ts +47 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/host/host.js +121 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/host/index.d.ts +5 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/host/index.js +22 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/index.d.ts +10 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/dist/index.js +28 -0
- package/node_modules/@fntio/infra-client-plugin-sdk/package.json +99 -0
- package/openapi.yaml +4284 -0
- package/package.json +124 -4
|
@@ -0,0 +1,3761 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
|
|
23
|
+
// lambdacode/api/services/httpErrors.ts
|
|
24
|
+
var ConflictError, NotFoundError, ValidationError;
|
|
25
|
+
var init_httpErrors = __esm({
|
|
26
|
+
"lambdacode/api/services/httpErrors.ts"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
ConflictError = class extends Error {
|
|
29
|
+
};
|
|
30
|
+
NotFoundError = class extends Error {
|
|
31
|
+
};
|
|
32
|
+
ValidationError = class extends Error {
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ../infra-client-plugin-sdk/src/contracts/manifest.ts
|
|
38
|
+
var PLUGIN_PERMISSIONS, PLUGIN_MANIFEST_SCHEMA_VERSION, PLUGIN_HOST_API_VERSION, PLUGIN_ID_PATTERN, AWS_ACCOUNT_PATTERN;
|
|
39
|
+
var init_manifest = __esm({
|
|
40
|
+
"../infra-client-plugin-sdk/src/contracts/manifest.ts"() {
|
|
41
|
+
"use strict";
|
|
42
|
+
PLUGIN_PERMISSIONS = ["user", "admin"];
|
|
43
|
+
PLUGIN_MANIFEST_SCHEMA_VERSION = 1;
|
|
44
|
+
PLUGIN_HOST_API_VERSION = 1;
|
|
45
|
+
PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
46
|
+
AWS_ACCOUNT_PATTERN = /^\d{12}$/;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ../infra-client-plugin-sdk/src/contracts/validate.ts
|
|
51
|
+
var init_validate = __esm({
|
|
52
|
+
"../infra-client-plugin-sdk/src/contracts/validate.ts"() {
|
|
53
|
+
"use strict";
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ../infra-client-plugin-sdk/src/contracts/canonical.ts
|
|
58
|
+
var init_canonical = __esm({
|
|
59
|
+
"../infra-client-plugin-sdk/src/contracts/canonical.ts"() {
|
|
60
|
+
"use strict";
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ../infra-client-plugin-sdk/src/contracts/paths.ts
|
|
65
|
+
var init_paths = __esm({
|
|
66
|
+
"../infra-client-plugin-sdk/src/contracts/paths.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ../infra-client-plugin-sdk/src/contracts/access.ts
|
|
72
|
+
function pluginTargetKey(target) {
|
|
73
|
+
return `${PLUGIN_TARGET_PREFIX}${target.pluginId}#${target.accountNumber}#${target.permission}`;
|
|
74
|
+
}
|
|
75
|
+
function parsePluginTargetKey(key) {
|
|
76
|
+
if (!key.startsWith(PLUGIN_TARGET_PREFIX)) return null;
|
|
77
|
+
const parts = key.slice(PLUGIN_TARGET_PREFIX.length).split("#");
|
|
78
|
+
if (parts.length !== 3) return null;
|
|
79
|
+
const [pluginId, accountNumber, permission] = parts;
|
|
80
|
+
if (!PLUGIN_ID_PATTERN.test(pluginId) || !AWS_ACCOUNT_PATTERN.test(accountNumber)) return null;
|
|
81
|
+
if (!PLUGIN_PERMISSIONS.includes(permission)) return null;
|
|
82
|
+
return { pluginId, accountNumber, permission };
|
|
83
|
+
}
|
|
84
|
+
function pluginPermissionsImpliedBy(granted, declared) {
|
|
85
|
+
if (!declared.includes(granted)) return [];
|
|
86
|
+
if (granted === "admin" && declared.includes("user")) return ["admin", "user"];
|
|
87
|
+
return [granted];
|
|
88
|
+
}
|
|
89
|
+
var PLUGIN_TARGET_PREFIX;
|
|
90
|
+
var init_access = __esm({
|
|
91
|
+
"../infra-client-plugin-sdk/src/contracts/access.ts"() {
|
|
92
|
+
"use strict";
|
|
93
|
+
init_manifest();
|
|
94
|
+
PLUGIN_TARGET_PREFIX = "plugin#";
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ../infra-client-plugin-sdk/src/contracts/catalog.ts
|
|
99
|
+
function pluginCatalogStatus(record) {
|
|
100
|
+
if (!record.installed) return "removed";
|
|
101
|
+
if (record.schemaVersion !== PLUGIN_MANIFEST_SCHEMA_VERSION || record.manifest.hostApiVersion !== PLUGIN_HOST_API_VERSION) {
|
|
102
|
+
return "unsupported";
|
|
103
|
+
}
|
|
104
|
+
return record.manifest.enabled ? "active" : "disabled";
|
|
105
|
+
}
|
|
106
|
+
var init_catalog = __esm({
|
|
107
|
+
"../infra-client-plugin-sdk/src/contracts/catalog.ts"() {
|
|
108
|
+
"use strict";
|
|
109
|
+
init_manifest();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ../infra-client-plugin-sdk/src/contracts/discovery.ts
|
|
114
|
+
var init_discovery = __esm({
|
|
115
|
+
"../infra-client-plugin-sdk/src/contracts/discovery.ts"() {
|
|
116
|
+
"use strict";
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ../infra-client-plugin-sdk/src/contracts/authorizer.ts
|
|
121
|
+
var init_authorizer = __esm({
|
|
122
|
+
"../infra-client-plugin-sdk/src/contracts/authorizer.ts"() {
|
|
123
|
+
"use strict";
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ../infra-client-plugin-sdk/src/backend/context.ts
|
|
128
|
+
var init_context = __esm({
|
|
129
|
+
"../infra-client-plugin-sdk/src/backend/context.ts"() {
|
|
130
|
+
"use strict";
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ../infra-client-plugin-sdk/src/backend/index.ts
|
|
135
|
+
var init_backend = __esm({
|
|
136
|
+
"../infra-client-plugin-sdk/src/backend/index.ts"() {
|
|
137
|
+
"use strict";
|
|
138
|
+
init_manifest();
|
|
139
|
+
init_validate();
|
|
140
|
+
init_canonical();
|
|
141
|
+
init_paths();
|
|
142
|
+
init_access();
|
|
143
|
+
init_catalog();
|
|
144
|
+
init_discovery();
|
|
145
|
+
init_authorizer();
|
|
146
|
+
init_context();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// lambdacode/api/services/access.ts
|
|
151
|
+
function parsePrincipal(p) {
|
|
152
|
+
if (p.startsWith("user#")) return { kind: "user", userId: p.slice(5) };
|
|
153
|
+
if (p.startsWith("group#")) return { kind: "group", groupId: p.slice(6) };
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function parseTarget(t) {
|
|
157
|
+
if (t.startsWith("role#")) {
|
|
158
|
+
const rest = t.slice(5);
|
|
159
|
+
const hash = rest.indexOf("#");
|
|
160
|
+
if (hash < 0) return null;
|
|
161
|
+
return { kind: "role", accountNumber: rest.slice(0, hash), roleName: rest.slice(hash + 1) };
|
|
162
|
+
}
|
|
163
|
+
if (t.startsWith("resource#")) return { kind: "resource", resourceId: t.slice(9) };
|
|
164
|
+
if (t.startsWith("share#")) return { kind: "share", shareId: t.slice(6) };
|
|
165
|
+
if (t.startsWith("plugin#")) {
|
|
166
|
+
const p = parsePluginTargetKey(t);
|
|
167
|
+
return p ? { kind: "plugin", ...p } : null;
|
|
168
|
+
}
|
|
169
|
+
if (t.startsWith("member#")) return { kind: "member", groupId: t.slice(7) };
|
|
170
|
+
if (t.startsWith("pref#role#")) {
|
|
171
|
+
const rest = t.slice(10);
|
|
172
|
+
const hash = rest.indexOf("#");
|
|
173
|
+
if (hash < 0) return null;
|
|
174
|
+
return { kind: "pref", accountNumber: rest.slice(0, hash), roleName: rest.slice(hash + 1) };
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function isExpired(row, now = Date.now()) {
|
|
179
|
+
return Boolean(row.expiresAt) && !(Date.parse(row.expiresAt) > now);
|
|
180
|
+
}
|
|
181
|
+
async function getRow(principal, target, opts = {}) {
|
|
182
|
+
const res = await ddb.send(
|
|
183
|
+
new import_lib_dynamodb.GetCommand({ TableName: ACCESS_TABLE_NAME, Key: { principal, target }, ...consistentRead(opts) })
|
|
184
|
+
);
|
|
185
|
+
return res.Item;
|
|
186
|
+
}
|
|
187
|
+
async function queryPrincipal(principal, targetPrefix, opts = {}) {
|
|
188
|
+
const rows = [];
|
|
189
|
+
let ExclusiveStartKey;
|
|
190
|
+
do {
|
|
191
|
+
const page = await ddb.send(
|
|
192
|
+
new import_lib_dynamodb.QueryCommand({
|
|
193
|
+
TableName: ACCESS_TABLE_NAME,
|
|
194
|
+
KeyConditionExpression: targetPrefix ? "principal = :p AND begins_with(#t, :pre)" : "principal = :p",
|
|
195
|
+
ExpressionAttributeValues: {
|
|
196
|
+
":p": principal,
|
|
197
|
+
...targetPrefix ? { ":pre": targetPrefix } : {}
|
|
198
|
+
},
|
|
199
|
+
...targetPrefix ? { ExpressionAttributeNames: { "#t": "target" } } : {},
|
|
200
|
+
...consistentRead(opts),
|
|
201
|
+
ExclusiveStartKey
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
rows.push(...page?.Items ?? []);
|
|
205
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
206
|
+
} while (ExclusiveStartKey);
|
|
207
|
+
return rows;
|
|
208
|
+
}
|
|
209
|
+
async function queryTarget(target) {
|
|
210
|
+
const rows = [];
|
|
211
|
+
let ExclusiveStartKey;
|
|
212
|
+
do {
|
|
213
|
+
const page = await ddb.send(
|
|
214
|
+
new import_lib_dynamodb.QueryCommand({
|
|
215
|
+
TableName: ACCESS_TABLE_NAME,
|
|
216
|
+
IndexName: "byTarget",
|
|
217
|
+
KeyConditionExpression: "#t = :t",
|
|
218
|
+
ExpressionAttributeNames: { "#t": "target" },
|
|
219
|
+
ExpressionAttributeValues: { ":t": target },
|
|
220
|
+
ExclusiveStartKey
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
rows.push(...page?.Items ?? []);
|
|
224
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
225
|
+
} while (ExclusiveStartKey);
|
|
226
|
+
return rows;
|
|
227
|
+
}
|
|
228
|
+
async function scanAll(opts = {}) {
|
|
229
|
+
const rows = [];
|
|
230
|
+
const prefixes = opts.targetPrefixes ?? [];
|
|
231
|
+
const filter = prefixes.length ? {
|
|
232
|
+
FilterExpression: prefixes.map((_, i) => `begins_with(#t, :p${i})`).join(" OR "),
|
|
233
|
+
ExpressionAttributeNames: { "#t": "target" },
|
|
234
|
+
ExpressionAttributeValues: Object.fromEntries(prefixes.map((p, i) => [`:p${i}`, p]))
|
|
235
|
+
} : {};
|
|
236
|
+
let ExclusiveStartKey;
|
|
237
|
+
do {
|
|
238
|
+
const page = await ddb.send(
|
|
239
|
+
new import_lib_dynamodb.ScanCommand({ TableName: ACCESS_TABLE_NAME, ExclusiveStartKey, ...filter })
|
|
240
|
+
);
|
|
241
|
+
rows.push(...page?.Items ?? []);
|
|
242
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
243
|
+
} while (ExclusiveStartKey);
|
|
244
|
+
return rows;
|
|
245
|
+
}
|
|
246
|
+
async function putRow(row) {
|
|
247
|
+
await ddb.send(new import_lib_dynamodb.PutCommand({ TableName: ACCESS_TABLE_NAME, Item: row }));
|
|
248
|
+
}
|
|
249
|
+
async function deleteRow(principal, target) {
|
|
250
|
+
try {
|
|
251
|
+
await ddb.send(
|
|
252
|
+
new import_lib_dynamodb.DeleteCommand({
|
|
253
|
+
TableName: ACCESS_TABLE_NAME,
|
|
254
|
+
Key: { principal, target },
|
|
255
|
+
ConditionExpression: "attribute_exists(principal)"
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
return true;
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (err.name === CONDITION_FAILED) return false;
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function updateExisting(principal, target, set, remove = []) {
|
|
265
|
+
const names = {};
|
|
266
|
+
const values = {};
|
|
267
|
+
const sets = Object.entries(set).map(([k, v], i) => {
|
|
268
|
+
names[`#s${i}`] = k;
|
|
269
|
+
values[`:s${i}`] = v;
|
|
270
|
+
return `#s${i} = :s${i}`;
|
|
271
|
+
});
|
|
272
|
+
const removes = remove.map((k, i) => {
|
|
273
|
+
names[`#r${i}`] = k;
|
|
274
|
+
return `#r${i}`;
|
|
275
|
+
});
|
|
276
|
+
const expr = [
|
|
277
|
+
sets.length ? `SET ${sets.join(", ")}` : "",
|
|
278
|
+
removes.length ? `REMOVE ${removes.join(", ")}` : ""
|
|
279
|
+
].filter(Boolean).join(" ");
|
|
280
|
+
if (!expr) return true;
|
|
281
|
+
try {
|
|
282
|
+
await ddb.send(
|
|
283
|
+
new import_lib_dynamodb.UpdateCommand({
|
|
284
|
+
TableName: ACCESS_TABLE_NAME,
|
|
285
|
+
Key: { principal, target },
|
|
286
|
+
UpdateExpression: expr,
|
|
287
|
+
ConditionExpression: "attribute_exists(principal)",
|
|
288
|
+
ExpressionAttributeNames: names,
|
|
289
|
+
...Object.keys(values).length ? { ExpressionAttributeValues: values } : {}
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
return true;
|
|
293
|
+
} catch (err) {
|
|
294
|
+
if (err.name === CONDITION_FAILED) return false;
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async function upsertPref(userId, target, set, remove = []) {
|
|
299
|
+
const names = {};
|
|
300
|
+
const values = {};
|
|
301
|
+
const sets = Object.entries(set).map(([k, v], i) => {
|
|
302
|
+
names[`#s${i}`] = k;
|
|
303
|
+
values[`:s${i}`] = v;
|
|
304
|
+
return `#s${i} = :s${i}`;
|
|
305
|
+
});
|
|
306
|
+
const removes = remove.map((k, i) => {
|
|
307
|
+
names[`#r${i}`] = k;
|
|
308
|
+
return `#r${i}`;
|
|
309
|
+
});
|
|
310
|
+
const expr = [
|
|
311
|
+
sets.length ? `SET ${sets.join(", ")}` : "",
|
|
312
|
+
removes.length ? `REMOVE ${removes.join(", ")}` : ""
|
|
313
|
+
].filter(Boolean).join(" ");
|
|
314
|
+
if (!expr) return;
|
|
315
|
+
await ddb.send(
|
|
316
|
+
new import_lib_dynamodb.UpdateCommand({
|
|
317
|
+
TableName: ACCESS_TABLE_NAME,
|
|
318
|
+
Key: { principal: userPrincipal(userId), target },
|
|
319
|
+
UpdateExpression: expr,
|
|
320
|
+
ExpressionAttributeNames: names,
|
|
321
|
+
...Object.keys(values).length ? { ExpressionAttributeValues: values } : {}
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
async function membersOf(groupId) {
|
|
326
|
+
return (await queryTarget(memberTarget(groupId))).map((r) => parsePrincipal(r.principal)).filter((p) => p?.kind === "user").map((p) => p.userId);
|
|
327
|
+
}
|
|
328
|
+
function entryOf(target, direct, groups, now) {
|
|
329
|
+
const d = direct ? { grantedBy: direct.grantedBy, grantedAt: direct.grantedAt, expiresAt: direct.expiresAt } : void 0;
|
|
330
|
+
const directActive = Boolean(direct && !isExpired(direct, now));
|
|
331
|
+
const active = directActive || groups.length > 0;
|
|
332
|
+
return {
|
|
333
|
+
target,
|
|
334
|
+
direct: d,
|
|
335
|
+
groups,
|
|
336
|
+
active,
|
|
337
|
+
expiresAt: groups.length === 0 && directActive ? direct?.expiresAt : void 0
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function effectiveAccess(userId) {
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
const own = await queryPrincipal(userPrincipal(userId));
|
|
343
|
+
const directByTarget = /* @__PURE__ */ new Map();
|
|
344
|
+
const groupIds = [];
|
|
345
|
+
for (const r of own) {
|
|
346
|
+
if (isGrantTarget(r.target)) directByTarget.set(r.target, r);
|
|
347
|
+
else if (r.target.startsWith("member#")) groupIds.push(r.target.slice("member#".length));
|
|
348
|
+
}
|
|
349
|
+
const groupsByTarget = /* @__PURE__ */ new Map();
|
|
350
|
+
for (const g of groupIds) {
|
|
351
|
+
for (const r of await queryPrincipal(groupPrincipal(g))) {
|
|
352
|
+
if (!isGrantTarget(r.target) || isExpired(r, now)) continue;
|
|
353
|
+
const list = groupsByTarget.get(r.target) ?? [];
|
|
354
|
+
list.push(g);
|
|
355
|
+
groupsByTarget.set(r.target, list);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const out = /* @__PURE__ */ new Map();
|
|
359
|
+
const targets = /* @__PURE__ */ new Set([...directByTarget.keys(), ...groupsByTarget.keys()]);
|
|
360
|
+
for (const t of targets) {
|
|
361
|
+
out.set(t, entryOf(t, directByTarget.get(t), groupsByTarget.get(t) ?? [], now));
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
async function batchGetAll(table, keys, opts = {}) {
|
|
366
|
+
const out = [];
|
|
367
|
+
for (let i = 0; i < keys.length; i += 100) {
|
|
368
|
+
let chunk = keys.slice(i, i + 100);
|
|
369
|
+
for (let attempt = 0; chunk.length; attempt++) {
|
|
370
|
+
if (attempt >= BATCH_GET_ATTEMPTS) {
|
|
371
|
+
throw new Error(`${table} BatchGet left ${chunk.length} keys unprocessed after ${attempt} attempts`);
|
|
372
|
+
}
|
|
373
|
+
if (attempt) await sleep(20 * attempt);
|
|
374
|
+
const res = await ddb.send(
|
|
375
|
+
new import_lib_dynamodb.BatchGetCommand({ RequestItems: { [table]: { Keys: chunk, ...consistentRead(opts) } } })
|
|
376
|
+
);
|
|
377
|
+
out.push(...res?.Responses?.[table] ?? []);
|
|
378
|
+
chunk = res?.UnprocessedKeys?.[table]?.Keys ?? [];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
async function effectiveForMany(userId, targets, opts = {}) {
|
|
384
|
+
const out = /* @__PURE__ */ new Map();
|
|
385
|
+
const wanted = [...new Set(targets)];
|
|
386
|
+
if (!wanted.length) return out;
|
|
387
|
+
const [ownWanted, memberships] = await Promise.all([
|
|
388
|
+
batchGetAll(ACCESS_TABLE_NAME, wanted.map((target) => ({ principal: userPrincipal(userId), target })), opts),
|
|
389
|
+
queryPrincipal(userPrincipal(userId), "member#", opts)
|
|
390
|
+
]);
|
|
391
|
+
const direct = new Map(ownWanted.map((r) => [r.target, r]));
|
|
392
|
+
const groupIds = memberships.map((r) => r.target.slice("member#".length));
|
|
393
|
+
const holding = /* @__PURE__ */ new Map();
|
|
394
|
+
const keys = groupIds.flatMap((g) => wanted.map((target) => ({ principal: groupPrincipal(g), target })));
|
|
395
|
+
for (const it of await batchGetAll(ACCESS_TABLE_NAME, keys, opts)) {
|
|
396
|
+
const row = it;
|
|
397
|
+
const p = parsePrincipal(row.principal);
|
|
398
|
+
if (p?.kind !== "group" || isExpired(row)) continue;
|
|
399
|
+
const list = holding.get(row.target) ?? [];
|
|
400
|
+
list.push(p.groupId);
|
|
401
|
+
holding.set(row.target, list);
|
|
402
|
+
}
|
|
403
|
+
const now = Date.now();
|
|
404
|
+
for (const t of wanted) {
|
|
405
|
+
const d = direct.get(t);
|
|
406
|
+
const g = holding.get(t) ?? [];
|
|
407
|
+
if (!d && !g.length) continue;
|
|
408
|
+
out.set(t, entryOf(t, d, g, now));
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
async function holdersOf(target) {
|
|
413
|
+
const rows = await queryTarget(target);
|
|
414
|
+
const byUser = /* @__PURE__ */ new Map();
|
|
415
|
+
const now = Date.now();
|
|
416
|
+
const ensure = (userId) => {
|
|
417
|
+
let h = byUser.get(userId);
|
|
418
|
+
if (!h) {
|
|
419
|
+
h = { userId, groups: [], active: false };
|
|
420
|
+
byUser.set(userId, h);
|
|
421
|
+
}
|
|
422
|
+
return h;
|
|
423
|
+
};
|
|
424
|
+
for (const r of rows) {
|
|
425
|
+
const p = parsePrincipal(r.principal);
|
|
426
|
+
if (p?.kind === "user") {
|
|
427
|
+
const h = ensure(p.userId);
|
|
428
|
+
h.direct = { grantedBy: r.grantedBy, grantedAt: r.grantedAt, expiresAt: r.expiresAt };
|
|
429
|
+
if (!isExpired(r, now)) h.active = true;
|
|
430
|
+
} else if (p?.kind === "group" && !isExpired(r, now)) {
|
|
431
|
+
for (const userId of await membersOf(p.groupId)) {
|
|
432
|
+
const h = ensure(userId);
|
|
433
|
+
h.groups.push(p.groupId);
|
|
434
|
+
h.active = true;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return [...byUser.values()];
|
|
439
|
+
}
|
|
440
|
+
function buildSnapshot(rows, now = Date.now()) {
|
|
441
|
+
const groupTargets = /* @__PURE__ */ new Map();
|
|
442
|
+
const groupMembers = /* @__PURE__ */ new Map();
|
|
443
|
+
const directRows = /* @__PURE__ */ new Map();
|
|
444
|
+
const userRows = [];
|
|
445
|
+
const presence = /* @__PURE__ */ new Map();
|
|
446
|
+
for (const r of rows) {
|
|
447
|
+
const p = parsePrincipal(r.principal);
|
|
448
|
+
if (!p) continue;
|
|
449
|
+
if (p.kind === "user" && r.target === PRESENCE_TARGET) {
|
|
450
|
+
if (typeof r.lastSeenAt === "string") presence.set(p.userId, r.lastSeenAt);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (p.kind === "group") {
|
|
454
|
+
if (!isGrantTarget(r.target) || isExpired(r, now)) continue;
|
|
455
|
+
const list = groupTargets.get(p.groupId) ?? [];
|
|
456
|
+
list.push(r.target);
|
|
457
|
+
groupTargets.set(p.groupId, list);
|
|
458
|
+
} else if (r.target.startsWith("member#")) {
|
|
459
|
+
const g = r.target.slice("member#".length);
|
|
460
|
+
const list = groupMembers.get(g) ?? [];
|
|
461
|
+
list.push(p.userId);
|
|
462
|
+
groupMembers.set(g, list);
|
|
463
|
+
} else if (isGrantTarget(r.target)) {
|
|
464
|
+
const m = directRows.get(p.userId) ?? /* @__PURE__ */ new Map();
|
|
465
|
+
m.set(r.target, r);
|
|
466
|
+
directRows.set(p.userId, m);
|
|
467
|
+
userRows.push(r);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const users = /* @__PURE__ */ new Map();
|
|
471
|
+
const groupsByUserTarget = /* @__PURE__ */ new Map();
|
|
472
|
+
for (const [g, members] of groupMembers) {
|
|
473
|
+
for (const u of members) {
|
|
474
|
+
const m = groupsByUserTarget.get(u) ?? /* @__PURE__ */ new Map();
|
|
475
|
+
for (const t of groupTargets.get(g) ?? []) {
|
|
476
|
+
const list = m.get(t) ?? [];
|
|
477
|
+
list.push(g);
|
|
478
|
+
m.set(t, list);
|
|
479
|
+
}
|
|
480
|
+
groupsByUserTarget.set(u, m);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const userIds = /* @__PURE__ */ new Set([...directRows.keys(), ...groupsByUserTarget.keys()]);
|
|
484
|
+
for (const u of userIds) {
|
|
485
|
+
const direct = directRows.get(u) ?? /* @__PURE__ */ new Map();
|
|
486
|
+
const viaGroups = groupsByUserTarget.get(u) ?? /* @__PURE__ */ new Map();
|
|
487
|
+
const targets = /* @__PURE__ */ new Set([...direct.keys(), ...viaGroups.keys()]);
|
|
488
|
+
const m = /* @__PURE__ */ new Map();
|
|
489
|
+
for (const t of targets) m.set(t, entryOf(t, direct.get(t), viaGroups.get(t) ?? [], now));
|
|
490
|
+
users.set(u, m);
|
|
491
|
+
}
|
|
492
|
+
return { users, groupTargets, groupMembers, userRows, presence };
|
|
493
|
+
}
|
|
494
|
+
async function snapshot() {
|
|
495
|
+
return buildSnapshot(await scanAll());
|
|
496
|
+
}
|
|
497
|
+
async function deleteShareRows(shareId, rows) {
|
|
498
|
+
const target = shareTarget(shareId);
|
|
499
|
+
const all = rows ?? await queryTarget(target);
|
|
500
|
+
const grants = all.filter((r) => r.target === target && r.principal !== SHARE_DEF_PRINCIPAL);
|
|
501
|
+
for (let i = 0; i < grants.length; i += DELETE_BATCH) {
|
|
502
|
+
await Promise.all(grants.slice(i, i + DELETE_BATCH).map((r) => deleteRow(r.principal, r.target)));
|
|
503
|
+
}
|
|
504
|
+
await deleteRow(SHARE_DEF_PRINCIPAL, target);
|
|
505
|
+
}
|
|
506
|
+
var import_client_dynamodb, import_lib_dynamodb, ddb, ACCESS_TABLE_NAME, CONDITION_FAILED, userPrincipal, groupPrincipal, roleTarget, resourceTarget, pluginTarget, shareTarget, SHARE_DEF_PRINCIPAL, memberTarget, groupTarget, PRESENCE_TARGET, ADMIN_REVIEW_TARGET, principalOf, isGrantTarget, consistentRead, BATCH_GET_ATTEMPTS, sleep, isShareDefRow, DELETE_BATCH;
|
|
507
|
+
var init_access2 = __esm({
|
|
508
|
+
"lambdacode/api/services/access.ts"() {
|
|
509
|
+
"use strict";
|
|
510
|
+
import_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
511
|
+
init_backend();
|
|
512
|
+
import_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
513
|
+
ddb = import_lib_dynamodb.DynamoDBDocumentClient.from(new import_client_dynamodb.DynamoDBClient({}));
|
|
514
|
+
ACCESS_TABLE_NAME = process.env.ACCESS_TABLE_NAME;
|
|
515
|
+
CONDITION_FAILED = "ConditionalCheckFailedException";
|
|
516
|
+
userPrincipal = (sub) => `user#${sub}`;
|
|
517
|
+
groupPrincipal = (groupId) => `group#${groupId}`;
|
|
518
|
+
roleTarget = (accountNumber, roleName) => `role#${accountNumber}#${roleName}`;
|
|
519
|
+
resourceTarget = (resourceId) => `resource#${resourceId}`;
|
|
520
|
+
pluginTarget = (pluginId, accountNumber, permission) => pluginTargetKey({ pluginId, accountNumber, permission });
|
|
521
|
+
shareTarget = (shareId) => `share#${shareId}`;
|
|
522
|
+
SHARE_DEF_PRINCIPAL = "sharedef";
|
|
523
|
+
memberTarget = (groupId) => `member#${groupId}`;
|
|
524
|
+
groupTarget = groupPrincipal;
|
|
525
|
+
PRESENCE_TARGET = "pref#presence";
|
|
526
|
+
ADMIN_REVIEW_TARGET = "pref#adminReview";
|
|
527
|
+
principalOf = (p) => p.kind === "user" ? userPrincipal(p.userId) : groupPrincipal(p.groupId);
|
|
528
|
+
isGrantTarget = (t) => t.startsWith("role#") || t.startsWith("resource#") || t.startsWith("share#") || t.startsWith("plugin#");
|
|
529
|
+
consistentRead = (opts) => opts.consistent ? { ConsistentRead: true } : {};
|
|
530
|
+
BATCH_GET_ATTEMPTS = 5;
|
|
531
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
532
|
+
isShareDefRow = (r) => r.principal === SHARE_DEF_PRINCIPAL && typeof r.shareId === "string" && typeof r.ownerSub === "string" && typeof r.port === "number" && typeof r.siteId === "string" && typeof r.expiresAt === "string";
|
|
533
|
+
DELETE_BATCH = 25;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// lambdacode/api/services/pluginCatalog.ts
|
|
538
|
+
function isPluginCatalogRecord(item) {
|
|
539
|
+
if (!item || typeof item !== "object") return false;
|
|
540
|
+
const r = item;
|
|
541
|
+
const m = r.manifest;
|
|
542
|
+
return typeof r.pluginId === "string" && typeof r.schemaVersion === "number" && typeof r.installed === "boolean" && typeof r.revision === "string" && !!m && typeof m === "object" && m.id === r.pluginId && typeof m.enabled === "boolean" && typeof m.hostApiVersion === "number" && Array.isArray(m.accounts) && Array.isArray(m.permissions) && Array.isArray(r.routes);
|
|
543
|
+
}
|
|
544
|
+
function dynamoPluginCatalog(tableName2) {
|
|
545
|
+
const accept = (item) => {
|
|
546
|
+
if (isPluginCatalogRecord(item)) return item;
|
|
547
|
+
log("WARN: malformed plugin catalog record skipped", {
|
|
548
|
+
pluginId: item?.pluginId
|
|
549
|
+
});
|
|
550
|
+
return void 0;
|
|
551
|
+
};
|
|
552
|
+
return {
|
|
553
|
+
async list(opts = {}) {
|
|
554
|
+
const out = [];
|
|
555
|
+
let ExclusiveStartKey;
|
|
556
|
+
do {
|
|
557
|
+
const page = await ddb2.send(
|
|
558
|
+
new import_lib_dynamodb2.ScanCommand({ TableName: tableName2, ExclusiveStartKey, ...opts.consistent ? { ConsistentRead: true } : {} })
|
|
559
|
+
);
|
|
560
|
+
for (const item of page?.Items ?? []) {
|
|
561
|
+
const r = accept(item);
|
|
562
|
+
if (r) out.push(r);
|
|
563
|
+
}
|
|
564
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
565
|
+
} while (ExclusiveStartKey);
|
|
566
|
+
return out;
|
|
567
|
+
},
|
|
568
|
+
async get(pluginId, opts = {}) {
|
|
569
|
+
const res = await ddb2.send(
|
|
570
|
+
new import_lib_dynamodb2.GetCommand({
|
|
571
|
+
TableName: tableName2,
|
|
572
|
+
Key: { pluginId },
|
|
573
|
+
...opts.consistent ? { ConsistentRead: true } : {}
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
return res.Item ? accept(res.Item) : void 0;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function pluginCatalog() {
|
|
581
|
+
const table = process.env.PLUGIN_CATALOG_TABLE_NAME;
|
|
582
|
+
return table ? dynamoPluginCatalog(table) : emptyPluginCatalog;
|
|
583
|
+
}
|
|
584
|
+
var import_client_dynamodb2, import_lib_dynamodb2, ddb2, emptyPluginCatalog, log;
|
|
585
|
+
var init_pluginCatalog = __esm({
|
|
586
|
+
"lambdacode/api/services/pluginCatalog.ts"() {
|
|
587
|
+
"use strict";
|
|
588
|
+
import_client_dynamodb2 = require("@aws-sdk/client-dynamodb");
|
|
589
|
+
import_lib_dynamodb2 = require("@aws-sdk/lib-dynamodb");
|
|
590
|
+
ddb2 = import_lib_dynamodb2.DynamoDBDocumentClient.from(new import_client_dynamodb2.DynamoDBClient({}));
|
|
591
|
+
emptyPluginCatalog = {
|
|
592
|
+
async list() {
|
|
593
|
+
return [];
|
|
594
|
+
},
|
|
595
|
+
async get() {
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
log = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// lambdacode/api/services/pluginAccess.ts
|
|
604
|
+
function pluginAvailability(record, accountNumber, permission, registeredAccounts2) {
|
|
605
|
+
if (!record) return { available: false, reason: "plugin-unknown" };
|
|
606
|
+
const status = pluginCatalogStatus(record);
|
|
607
|
+
if (status !== "active") return { available: false, reason: `plugin-${status}` };
|
|
608
|
+
if (!record.manifest.permissions.some((p) => p.id === permission)) {
|
|
609
|
+
return { available: false, reason: "permission-undeclared" };
|
|
610
|
+
}
|
|
611
|
+
if (!record.manifest.accounts.some((a) => a.accountNumber === accountNumber)) {
|
|
612
|
+
return { available: false, reason: "account-unbound" };
|
|
613
|
+
}
|
|
614
|
+
if (!registeredAccounts2.has(accountNumber)) return { available: false, reason: "account-unregistered" };
|
|
615
|
+
return { available: true };
|
|
616
|
+
}
|
|
617
|
+
async function loadPluginContext(opts = {}) {
|
|
618
|
+
const records = await (opts.catalog ?? pluginCatalog()).list(opts);
|
|
619
|
+
const catalog2 = new Map(records.map((r) => [r.pluginId, r]));
|
|
620
|
+
const bound = /* @__PURE__ */ new Set();
|
|
621
|
+
for (const r of records) for (const a of r.manifest.accounts) bound.add(a.accountNumber);
|
|
622
|
+
return withAccounts(catalog2, await registeredAccounts([...bound], opts));
|
|
623
|
+
}
|
|
624
|
+
function withAccounts(catalog2, accounts) {
|
|
625
|
+
const accountNames = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const [n, meta] of accounts) if (meta.name) accountNames.set(n, meta.name);
|
|
627
|
+
return { catalog: catalog2, registeredAccounts: new Set(accounts.keys()), accountNames };
|
|
628
|
+
}
|
|
629
|
+
function deriveCapabilities(pluginId, accountNumber, held, ctx) {
|
|
630
|
+
const record = ctx.catalog.get(pluginId);
|
|
631
|
+
const declared = record?.manifest.permissions.map((p) => p.id) ?? [];
|
|
632
|
+
const out = [];
|
|
633
|
+
for (const permission of PLUGIN_PERMISSIONS) {
|
|
634
|
+
const sources = [...held.values()].filter(
|
|
635
|
+
(s) => s.permission === permission || pluginPermissionsImpliedBy(s.permission, declared).includes(permission)
|
|
636
|
+
);
|
|
637
|
+
if (!sources.length) continue;
|
|
638
|
+
const a = pluginAvailability(record, accountNumber, permission, ctx.registeredAccounts);
|
|
639
|
+
out.push({ pluginId, accountNumber, permission, sources, ...a.available ? { available: true } : a });
|
|
640
|
+
}
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
643
|
+
function resolvePluginCapabilities(entries, ctx) {
|
|
644
|
+
const byBinding = /* @__PURE__ */ new Map();
|
|
645
|
+
for (const e of entries) {
|
|
646
|
+
if (!e.active) continue;
|
|
647
|
+
const p = parseTarget(e.target);
|
|
648
|
+
if (p?.kind !== "plugin") continue;
|
|
649
|
+
const key = `${p.pluginId}#${p.accountNumber}`;
|
|
650
|
+
const b = byBinding.get(key) ?? { pluginId: p.pluginId, accountNumber: p.accountNumber, held: /* @__PURE__ */ new Map() };
|
|
651
|
+
b.held.set(p.permission, sourceOf(p.permission, e.direct, e.groups));
|
|
652
|
+
byBinding.set(key, b);
|
|
653
|
+
}
|
|
654
|
+
const out = [];
|
|
655
|
+
for (const b of byBinding.values()) out.push(...deriveCapabilities(b.pluginId, b.accountNumber, b.held, ctx));
|
|
656
|
+
return out.sort(byBindingThenPermission);
|
|
657
|
+
}
|
|
658
|
+
function availableCapabilityKeys(capabilities) {
|
|
659
|
+
return new Set(capabilities.filter((c) => c.available).map((c) => pluginTarget(c.pluginId, c.accountNumber, c.permission)));
|
|
660
|
+
}
|
|
661
|
+
async function registeredAccounts(numbers, opts = {}) {
|
|
662
|
+
const out = /* @__PURE__ */ new Map();
|
|
663
|
+
const ids = [...new Set(numbers)];
|
|
664
|
+
if (!ids.length) return out;
|
|
665
|
+
const table = process.env.ACCOUNTS_TABLE_NAME;
|
|
666
|
+
if (!table) throw new Error("ACCOUNTS_TABLE_NAME is not set (plugin catalog readers need the Accounts table)");
|
|
667
|
+
for (const item of await batchGetAll(table, ids.map((accountNumber) => ({ accountNumber })), opts)) {
|
|
668
|
+
out.set(item.accountNumber, { name: item.name || void 0 });
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
var sourceOf, byBindingThenPermission;
|
|
673
|
+
var init_pluginAccess = __esm({
|
|
674
|
+
"lambdacode/api/services/pluginAccess.ts"() {
|
|
675
|
+
"use strict";
|
|
676
|
+
init_backend();
|
|
677
|
+
init_access2();
|
|
678
|
+
init_pluginCatalog();
|
|
679
|
+
sourceOf = (permission, direct, groups) => ({
|
|
680
|
+
permission,
|
|
681
|
+
...direct && !isExpired(direct) ? { direct } : {},
|
|
682
|
+
groups
|
|
683
|
+
});
|
|
684
|
+
byBindingThenPermission = (a, b) => a.pluginId.localeCompare(b.pluginId) || a.accountNumber.localeCompare(b.accountNumber) || PLUGIN_PERMISSIONS.indexOf(a.permission) - PLUGIN_PERMISSIONS.indexOf(b.permission);
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// lambdacode/api/services/revocationsService.ts
|
|
689
|
+
function noticeItem(userId, input, revokedAt) {
|
|
690
|
+
return {
|
|
691
|
+
userId,
|
|
692
|
+
// Timestamp-first sort key: a plain descending Query returns
|
|
693
|
+
// newest-first without a GSI.
|
|
694
|
+
noticeId: `${revokedAt}#${(0, import_crypto.randomUUID)().slice(0, 8)}`,
|
|
695
|
+
change: input.change ?? "REVOKED",
|
|
696
|
+
kind: input.kind,
|
|
697
|
+
...input.accountNumber ? { accountNumber: input.accountNumber } : {},
|
|
698
|
+
...input.roleName ? { roleName: input.roleName } : {},
|
|
699
|
+
...input.resourceId ? { resourceId: input.resourceId } : {},
|
|
700
|
+
...input.resourceName ? { resourceName: input.resourceName } : {},
|
|
701
|
+
...input.pluginId ? { pluginId: input.pluginId } : {},
|
|
702
|
+
...input.permission ? { permission: input.permission } : {},
|
|
703
|
+
...input.pluginName ? { pluginName: input.pluginName } : {},
|
|
704
|
+
...input.count ? { count: input.count } : {},
|
|
705
|
+
...input.groupId ? { groupId: input.groupId } : {},
|
|
706
|
+
...input.groupName ? { groupName: input.groupName } : {},
|
|
707
|
+
revokedAt,
|
|
708
|
+
via: input.via,
|
|
709
|
+
seen: false,
|
|
710
|
+
ttl: Math.floor(Date.now() / 1e3) + NOTICE_RETENTION_DAYS * 24 * 60 * 60
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
async function recordRevocations(notices) {
|
|
714
|
+
if (!REVOCATIONS_TABLE_NAME) return;
|
|
715
|
+
const items = notices.filter((n) => n.userId).map((n) => noticeItem(n.userId, n.input, (/* @__PURE__ */ new Date()).toISOString()));
|
|
716
|
+
for (let i = 0; i < items.length; i += 25) {
|
|
717
|
+
let requests = items.slice(i, i + 25).map((Item) => ({ PutRequest: { Item } }));
|
|
718
|
+
try {
|
|
719
|
+
for (let attempt = 0; attempt < 3 && requests.length; attempt++) {
|
|
720
|
+
const res = await ddb3.send(new import_lib_dynamodb3.BatchWriteCommand({ RequestItems: { [REVOCATIONS_TABLE_NAME]: requests } }));
|
|
721
|
+
requests = res.UnprocessedItems?.[REVOCATIONS_TABLE_NAME] ?? [];
|
|
722
|
+
}
|
|
723
|
+
if (requests.length) log2("access notices left unprocessed", { count: requests.length });
|
|
724
|
+
} catch (err) {
|
|
725
|
+
log2("access notice batch write failed", { count: requests.length, error: err.message });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (items.length) log2("recorded access notices", { count: items.length });
|
|
729
|
+
}
|
|
730
|
+
var import_crypto, import_client_dynamodb3, import_lib_dynamodb3, ddb3, REVOCATIONS_TABLE_NAME, log2, NOTICE_RETENTION_DAYS;
|
|
731
|
+
var init_revocationsService = __esm({
|
|
732
|
+
"lambdacode/api/services/revocationsService.ts"() {
|
|
733
|
+
"use strict";
|
|
734
|
+
import_crypto = require("crypto");
|
|
735
|
+
import_client_dynamodb3 = require("@aws-sdk/client-dynamodb");
|
|
736
|
+
import_lib_dynamodb3 = require("@aws-sdk/lib-dynamodb");
|
|
737
|
+
ddb3 = import_lib_dynamodb3.DynamoDBDocumentClient.from(new import_client_dynamodb3.DynamoDBClient({}));
|
|
738
|
+
REVOCATIONS_TABLE_NAME = process.env.REVOCATIONS_TABLE_NAME;
|
|
739
|
+
log2 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
740
|
+
NOTICE_RETENTION_DAYS = 30;
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// lambdacode/api/services/actorContext.ts
|
|
745
|
+
function runAsActor(actor, fn) {
|
|
746
|
+
return store.run(actor, fn);
|
|
747
|
+
}
|
|
748
|
+
function currentActor() {
|
|
749
|
+
return store.getStore();
|
|
750
|
+
}
|
|
751
|
+
var import_async_hooks, store, systemActor;
|
|
752
|
+
var init_actorContext = __esm({
|
|
753
|
+
"lambdacode/api/services/actorContext.ts"() {
|
|
754
|
+
"use strict";
|
|
755
|
+
import_async_hooks = require("async_hooks");
|
|
756
|
+
store = new import_async_hooks.AsyncLocalStorage();
|
|
757
|
+
systemActor = (job, name) => ({
|
|
758
|
+
kind: "system",
|
|
759
|
+
id: `system:${job}`,
|
|
760
|
+
name: name ?? job
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
// lambdacode/api/services/auditService.ts
|
|
766
|
+
function retentionDays() {
|
|
767
|
+
const n = Number(process.env.AUDIT_RETENTION_DAYS);
|
|
768
|
+
return Number.isInteger(n) && n > 0 ? n : DEFAULT_RETENTION_DAYS;
|
|
769
|
+
}
|
|
770
|
+
function tableOr() {
|
|
771
|
+
const table = tableName();
|
|
772
|
+
if (table) return table;
|
|
773
|
+
if (!warnedNoTable) {
|
|
774
|
+
warnedNoTable = true;
|
|
775
|
+
log3("WARN: AUDIT_TABLE_NAME unset \u2014 activity is not recorded");
|
|
776
|
+
}
|
|
777
|
+
return void 0;
|
|
778
|
+
}
|
|
779
|
+
function compact(value) {
|
|
780
|
+
if (Array.isArray(value)) return value.map(compact);
|
|
781
|
+
if (value && typeof value === "object" && !(value instanceof Set)) {
|
|
782
|
+
const out = {};
|
|
783
|
+
for (const [k, v] of Object.entries(value)) {
|
|
784
|
+
if (v !== void 0) out[k] = compact(v);
|
|
785
|
+
}
|
|
786
|
+
return out;
|
|
787
|
+
}
|
|
788
|
+
return value;
|
|
789
|
+
}
|
|
790
|
+
function toRow(input, now) {
|
|
791
|
+
const bound = input.actor ?? currentActor();
|
|
792
|
+
if (!bound) log3("ERROR: activity row without a bound actor (missing runAsActor?)", { action: input.action, target: input.target });
|
|
793
|
+
const actor = bound ?? UNKNOWN_ACTOR;
|
|
794
|
+
const details = input.details ? { ...input.details } : void 0;
|
|
795
|
+
if (details?.note !== void 0) details.note = String(details.note).slice(0, NOTE_MAX);
|
|
796
|
+
if (details?.affected && details.affected.length > AFFECTED_CAP) {
|
|
797
|
+
details.affectedCount = details.affected.length;
|
|
798
|
+
details.affected = details.affected.slice(0, AFFECTED_CAP);
|
|
799
|
+
}
|
|
800
|
+
if (details?.unchanged && details.unchanged.length > AFFECTED_CAP) {
|
|
801
|
+
details.unchangedCount = details.unchanged.length;
|
|
802
|
+
details.unchanged = details.unchanged.slice(0, AFFECTED_CAP);
|
|
803
|
+
}
|
|
804
|
+
const accountNumber = input.accountNumber ?? accountOf(input.target);
|
|
805
|
+
return compact({
|
|
806
|
+
...input,
|
|
807
|
+
eventId: (0, import_crypto2.randomUUID)(),
|
|
808
|
+
outcome: input.outcome ?? "OK",
|
|
809
|
+
reason: input.reason === void 0 ? void 0 : String(input.reason).slice(0, REASON_MAX),
|
|
810
|
+
actor: { kind: actor.kind, id: actor.id, name: actor.name, claims: actor.claims && Object.keys(actor.claims).length ? actor.claims : void 0 },
|
|
811
|
+
accountNumber,
|
|
812
|
+
details,
|
|
813
|
+
at: now.toISOString(),
|
|
814
|
+
day: now.toISOString().slice(0, 10),
|
|
815
|
+
userId: input.subject?.id,
|
|
816
|
+
actorId: SESSION_ACTIONS.has(input.action) && input.subject?.kind === "user" ? input.subject.id : actor.id,
|
|
817
|
+
expiresAt: Math.floor(now.getTime() / 1e3) + retentionDays() * 24 * 60 * 60
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
function accountOf(target) {
|
|
821
|
+
if (!target) return void 0;
|
|
822
|
+
const p = parseTarget(target);
|
|
823
|
+
if (p?.kind === "role" || p?.kind === "plugin") return p.accountNumber;
|
|
824
|
+
if (target.startsWith("account#")) return target.slice("account#".length);
|
|
825
|
+
return void 0;
|
|
826
|
+
}
|
|
827
|
+
async function recordEvent(input) {
|
|
828
|
+
const table = tableOr();
|
|
829
|
+
if (!table) return;
|
|
830
|
+
const row = toRow(input, /* @__PURE__ */ new Date());
|
|
831
|
+
try {
|
|
832
|
+
await ddb4.send(new import_lib_dynamodb4.PutCommand({ TableName: table, Item: row }));
|
|
833
|
+
log3("activity recorded", { action: row.action, outcome: row.outcome, actor: row.actorId, subject: row.userId, target: row.target, eventId: row.eventId });
|
|
834
|
+
} catch (err) {
|
|
835
|
+
log3("ERROR: activity write failed (mutation unaffected)", { action: row.action, target: row.target, error: err.message });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async function recordEvents(inputs) {
|
|
839
|
+
if (!inputs.length) return;
|
|
840
|
+
const table = tableOr();
|
|
841
|
+
if (!table) return;
|
|
842
|
+
const start = Date.now();
|
|
843
|
+
const rows = inputs.map((input, i) => toRow(input, new Date(start + i)));
|
|
844
|
+
for (let i = 0; i < rows.length; i += 25) {
|
|
845
|
+
let requests = rows.slice(i, i + 25).map((Item) => ({ PutRequest: { Item } }));
|
|
846
|
+
try {
|
|
847
|
+
for (let attempt = 0; attempt < 3 && requests.length; attempt++) {
|
|
848
|
+
const res = await ddb4.send(new import_lib_dynamodb4.BatchWriteCommand({ RequestItems: { [table]: requests } }));
|
|
849
|
+
requests = res.UnprocessedItems?.[table] ?? [];
|
|
850
|
+
}
|
|
851
|
+
if (requests.length) log3("ERROR: activity batch left unprocessed rows", { count: requests.length });
|
|
852
|
+
} catch (err) {
|
|
853
|
+
log3("ERROR: activity batch write failed (mutation unaffected)", { count: requests.length, error: err.message });
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
log3("activity batch recorded", { count: rows.length, actions: [...new Set(rows.map((r) => r.action))] });
|
|
857
|
+
}
|
|
858
|
+
var import_crypto2, import_client_dynamodb4, import_lib_dynamodb4, ddb4, tableName, log3, DEFAULT_RETENTION_DAYS, REASON_MAX, NOTE_MAX, AFFECTED_CAP, DAY_MS, newBatchId, UNKNOWN_ACTOR, warnedNoTable, SESSION_ACTIONS;
|
|
859
|
+
var init_auditService = __esm({
|
|
860
|
+
"lambdacode/api/services/auditService.ts"() {
|
|
861
|
+
"use strict";
|
|
862
|
+
import_crypto2 = require("crypto");
|
|
863
|
+
import_client_dynamodb4 = require("@aws-sdk/client-dynamodb");
|
|
864
|
+
import_lib_dynamodb4 = require("@aws-sdk/lib-dynamodb");
|
|
865
|
+
init_actorContext();
|
|
866
|
+
init_access2();
|
|
867
|
+
ddb4 = import_lib_dynamodb4.DynamoDBDocumentClient.from(new import_client_dynamodb4.DynamoDBClient({}));
|
|
868
|
+
tableName = () => process.env.AUDIT_TABLE_NAME ?? "";
|
|
869
|
+
log3 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
870
|
+
DEFAULT_RETENTION_DAYS = 400;
|
|
871
|
+
REASON_MAX = 300;
|
|
872
|
+
NOTE_MAX = 500;
|
|
873
|
+
AFFECTED_CAP = 50;
|
|
874
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
875
|
+
newBatchId = () => (0, import_crypto2.randomUUID)();
|
|
876
|
+
UNKNOWN_ACTOR = { kind: "system", id: "system:unknown" };
|
|
877
|
+
warnedNoTable = false;
|
|
878
|
+
SESSION_ACTIONS = /* @__PURE__ */ new Set(["vpn.connect", "vpn.disconnect"]);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// lambdacode/api/services/accessNotices.ts
|
|
883
|
+
async function activeTargets(userId, plugins = loadPluginContext) {
|
|
884
|
+
const entries = await effectiveAccess(userId);
|
|
885
|
+
const out = /* @__PURE__ */ new Set();
|
|
886
|
+
const pluginEntries = [];
|
|
887
|
+
for (const [t, e] of entries) {
|
|
888
|
+
if (!e.active) continue;
|
|
889
|
+
if (parseTarget(t)?.kind === "plugin") pluginEntries.push(e);
|
|
890
|
+
else out.add(t);
|
|
891
|
+
}
|
|
892
|
+
if (pluginEntries.length) {
|
|
893
|
+
for (const k of availableCapabilityKeys(resolvePluginCapabilities(pluginEntries, await plugins()))) out.add(k);
|
|
894
|
+
}
|
|
895
|
+
return out;
|
|
896
|
+
}
|
|
897
|
+
async function mapLimit(items, fn) {
|
|
898
|
+
const out = new Array(items.length);
|
|
899
|
+
let next = 0;
|
|
900
|
+
const worker = async () => {
|
|
901
|
+
for (let i = next++; i < items.length; i = next++) out[i] = await fn(items[i]);
|
|
902
|
+
};
|
|
903
|
+
await Promise.all(Array.from({ length: Math.min(DIFF_CONCURRENCY, items.length) }, worker));
|
|
904
|
+
return out;
|
|
905
|
+
}
|
|
906
|
+
function affectedOf(changes, only = {}) {
|
|
907
|
+
const out = [];
|
|
908
|
+
for (const c of changes) {
|
|
909
|
+
for (const [change, targets] of [["gained", c.gained], ["lost", c.lost]]) {
|
|
910
|
+
if (only.change && only.change !== change) continue;
|
|
911
|
+
for (const t of targets) {
|
|
912
|
+
if (only.target && only.target !== t) continue;
|
|
913
|
+
out.push({ userId: c.userId, change, target: t });
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return out;
|
|
918
|
+
}
|
|
919
|
+
function unchangedOf(changes, only = {}) {
|
|
920
|
+
const moved = (c) => {
|
|
921
|
+
const lists = only.change === "gained" ? [c.gained] : only.change === "lost" ? [c.lost] : [c.gained, c.lost];
|
|
922
|
+
return lists.some((l) => only.target ? l.includes(only.target) : l.length > 0);
|
|
923
|
+
};
|
|
924
|
+
return changes.filter((c) => !moved(c)).map((c) => ({ userId: c.userId }));
|
|
925
|
+
}
|
|
926
|
+
async function activeAmong(userId, targets) {
|
|
927
|
+
const out = /* @__PURE__ */ new Set();
|
|
928
|
+
for (const [t, e] of await effectiveForMany(userId, targets)) if (e.active) out.add(t);
|
|
929
|
+
return out;
|
|
930
|
+
}
|
|
931
|
+
async function withAccessDiff(userIds, via, mutate, resourceNames, opts = {}) {
|
|
932
|
+
const users = [...new Set(userIds)].filter(Boolean);
|
|
933
|
+
const scoped = opts.targets?.length ? [...new Set(opts.targets)] : void 0;
|
|
934
|
+
const narrow = scoped !== void 0 && scoped.length <= NARROW_MAX_TARGETS && !scoped.some((t) => parseTarget(t)?.kind === "plugin");
|
|
935
|
+
const read = (u, plugins) => narrow ? activeAmong(u, scoped) : activeTargets(u, plugins);
|
|
936
|
+
const pluginsBefore = memo(() => loadPluginContext());
|
|
937
|
+
const before = /* @__PURE__ */ new Map();
|
|
938
|
+
for (const [i, set] of (await mapLimit(users, (u) => read(u, pluginsBefore))).entries()) before.set(users[i], set);
|
|
939
|
+
const result = await mutate();
|
|
940
|
+
const pluginsAfter = opts.catalogChanges ? memo(() => loadPluginContext({ consistent: true })) : pluginsBefore;
|
|
941
|
+
const batchId = opts.batchId ?? newBatchId();
|
|
942
|
+
const changes = [];
|
|
943
|
+
const derived = [];
|
|
944
|
+
const notices = [];
|
|
945
|
+
const afterSets = await mapLimit(users, (u) => read(u, pluginsAfter));
|
|
946
|
+
const shareLabel = memoBy(
|
|
947
|
+
async (shareId, t) => resourceNames?.get(shareId) ?? (await getRow(SHARE_DEF_PRINCIPAL, t))?.label
|
|
948
|
+
);
|
|
949
|
+
const pluginName = memoBy(async (pluginId) => {
|
|
950
|
+
const record = (await pluginsAfter()).catalog.get(pluginId) ?? (await pluginsBefore()).catalog.get(pluginId);
|
|
951
|
+
return record?.manifest.displayName;
|
|
952
|
+
});
|
|
953
|
+
for (const [i, u] of users.entries()) {
|
|
954
|
+
const after = afterSets[i];
|
|
955
|
+
const change = { userId: u, gained: [], lost: [], after };
|
|
956
|
+
for (const t of after) if (!before.get(u).has(t)) change.gained.push(t);
|
|
957
|
+
for (const t of before.get(u)) if (!after.has(t)) change.lost.push(t);
|
|
958
|
+
const notice = async (t, kind) => {
|
|
959
|
+
const parsed = parseTarget(t);
|
|
960
|
+
let input;
|
|
961
|
+
if (parsed?.kind === "role") {
|
|
962
|
+
input = { change: kind, kind: "ROLE", accountNumber: parsed.accountNumber, roleName: parsed.roleName, via };
|
|
963
|
+
} else if (parsed?.kind === "resource") {
|
|
964
|
+
input = { change: kind, kind: "RESOURCE", resourceId: parsed.resourceId, resourceName: resourceNames?.get(parsed.resourceId), via };
|
|
965
|
+
} else if (parsed?.kind === "share") {
|
|
966
|
+
input = { change: kind, kind: "SHARE", resourceId: parsed.shareId, resourceName: await shareLabel(parsed.shareId, t), via };
|
|
967
|
+
} else if (parsed?.kind === "plugin") {
|
|
968
|
+
input = {
|
|
969
|
+
change: kind,
|
|
970
|
+
kind: "PLUGIN",
|
|
971
|
+
pluginId: parsed.pluginId,
|
|
972
|
+
accountNumber: parsed.accountNumber,
|
|
973
|
+
permission: parsed.permission,
|
|
974
|
+
pluginName: await pluginName(parsed.pluginId),
|
|
975
|
+
via
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
if (input) notices.push({ userId: u, input });
|
|
979
|
+
};
|
|
980
|
+
const noticeAll = async (targets, kind) => {
|
|
981
|
+
if (bundled(via, targets)) {
|
|
982
|
+
notices.push({
|
|
983
|
+
userId: u,
|
|
984
|
+
input: {
|
|
985
|
+
change: kind,
|
|
986
|
+
kind: "BUNDLE",
|
|
987
|
+
count: targets.length,
|
|
988
|
+
...opts.groupId ? { groupId: opts.groupId } : {},
|
|
989
|
+
...opts.groupName ? { groupName: opts.groupName } : {},
|
|
990
|
+
via
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
for (const t of targets) await notice(t, kind);
|
|
996
|
+
};
|
|
997
|
+
await noticeAll(change.lost, "REVOKED");
|
|
998
|
+
if (opts.gainNotices !== false) await noticeAll(change.gained, "GRANTED");
|
|
999
|
+
changes.push(change);
|
|
1000
|
+
if (opts.derived !== false) {
|
|
1001
|
+
for (const [list, action] of [
|
|
1002
|
+
[change.gained, "access.gain"],
|
|
1003
|
+
[change.lost, "access.lose"]
|
|
1004
|
+
]) {
|
|
1005
|
+
const groupDetails = {
|
|
1006
|
+
...opts.groupId ? { groupId: opts.groupId } : {},
|
|
1007
|
+
...opts.groupName ? { groupName: opts.groupName } : {}
|
|
1008
|
+
};
|
|
1009
|
+
if (bundled(via, list)) {
|
|
1010
|
+
derived.push({
|
|
1011
|
+
category: "access",
|
|
1012
|
+
action,
|
|
1013
|
+
subject: { kind: "user", id: u },
|
|
1014
|
+
details: { via, batchId, count: list.length, ...groupDetails },
|
|
1015
|
+
derived: true
|
|
1016
|
+
});
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
for (const t of list) {
|
|
1020
|
+
derived.push({
|
|
1021
|
+
category: "access",
|
|
1022
|
+
action,
|
|
1023
|
+
subject: { kind: "user", id: u },
|
|
1024
|
+
target: t,
|
|
1025
|
+
targetName: targetLabel(t, resourceNames),
|
|
1026
|
+
details: { via, batchId, ...groupDetails },
|
|
1027
|
+
derived: true
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
await recordRevocations(notices);
|
|
1034
|
+
await recordEvents(derived);
|
|
1035
|
+
opts.onDiff?.(changes);
|
|
1036
|
+
if (notices.length || derived.length) log4("access diff", { via, users: users.length, notices: notices.length, derived: derived.length });
|
|
1037
|
+
return result;
|
|
1038
|
+
}
|
|
1039
|
+
function memoBy(load) {
|
|
1040
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1041
|
+
return (...args) => {
|
|
1042
|
+
const k = args[0];
|
|
1043
|
+
let p = cache.get(k);
|
|
1044
|
+
if (!p) {
|
|
1045
|
+
p = load(...args);
|
|
1046
|
+
cache.set(k, p);
|
|
1047
|
+
}
|
|
1048
|
+
return p;
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function targetLabel(target, names) {
|
|
1052
|
+
const p = parseTarget(target);
|
|
1053
|
+
if (p?.kind === "role") return p.roleName;
|
|
1054
|
+
if (p?.kind === "resource") return names?.get(p.resourceId);
|
|
1055
|
+
if (p?.kind === "share") return names?.get(p.shareId);
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
var log4, memo, DIFF_CONCURRENCY, NARROW_MAX_TARGETS, NOTICE_BUNDLE_THRESHOLD, BUNDLED_VIAS, bundled;
|
|
1059
|
+
var init_accessNotices = __esm({
|
|
1060
|
+
"lambdacode/api/services/accessNotices.ts"() {
|
|
1061
|
+
"use strict";
|
|
1062
|
+
init_access2();
|
|
1063
|
+
init_pluginAccess();
|
|
1064
|
+
init_revocationsService();
|
|
1065
|
+
init_auditService();
|
|
1066
|
+
log4 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
1067
|
+
memo = (load) => {
|
|
1068
|
+
let p;
|
|
1069
|
+
return () => p ??= load();
|
|
1070
|
+
};
|
|
1071
|
+
DIFF_CONCURRENCY = 8;
|
|
1072
|
+
NARROW_MAX_TARGETS = 100;
|
|
1073
|
+
NOTICE_BUNDLE_THRESHOLD = 5;
|
|
1074
|
+
BUNDLED_VIAS = /* @__PURE__ */ new Set(["GROUP", "CASCADE"]);
|
|
1075
|
+
bundled = (via, targets) => BUNDLED_VIAS.has(via) && targets.length > NOTICE_BUNDLE_THRESHOLD;
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
// lambdacode/api/services/grantsService.ts
|
|
1080
|
+
async function revokeDirectRow(userId, target, via, row, resourceNames) {
|
|
1081
|
+
let stillHeld = false;
|
|
1082
|
+
const removed = await withAccessDiff([userId], via, () => deleteRow(userPrincipal(userId), target), resourceNames, {
|
|
1083
|
+
derived: false,
|
|
1084
|
+
// No loss means something else still covers the target (a group; for plugins an admin row) — or the row
|
|
1085
|
+
// had already expired; the diff's after-set tells them apart so the flag never claims access that isn't there.
|
|
1086
|
+
onDiff: (changes) => {
|
|
1087
|
+
stillHeld = changes.some((c) => c.userId === userId && c.after.has(target));
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
if (!removed) return false;
|
|
1091
|
+
if (via !== "REAPPROVAL") await clearPendingReviews(userId, target);
|
|
1092
|
+
const groups = stillHeld ? (await effectiveAccess(userId)).get(target)?.groups ?? [] : [];
|
|
1093
|
+
const stillHeldVia = stillHeld ? groups.length ? groups : true : void 0;
|
|
1094
|
+
await recordEvent({
|
|
1095
|
+
category: "access",
|
|
1096
|
+
...row,
|
|
1097
|
+
subject: { kind: "user", id: userId },
|
|
1098
|
+
target,
|
|
1099
|
+
details: { via, ...stillHeldVia ? { stillHeldVia } : {} }
|
|
1100
|
+
});
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
async function clearPendingReviews(userId, target, targets) {
|
|
1104
|
+
const { deleteRequestsForUser: deleteRequestsForUser2 } = (init_requestsService(), __toCommonJS(requestsService_exports));
|
|
1105
|
+
await deleteRequestsForUser2(userId, { onlyPending: true, onlyReviews: true, target, targets });
|
|
1106
|
+
}
|
|
1107
|
+
async function assertRoleExists(accountNumber, roleName) {
|
|
1108
|
+
const role = await ddb5.send(
|
|
1109
|
+
new import_lib_dynamodb5.GetCommand({ TableName: ROLES_TABLE_NAME, Key: { accountNumber, roleName } })
|
|
1110
|
+
);
|
|
1111
|
+
if (!role.Item) {
|
|
1112
|
+
throw new NotFoundError(`Role ${roleName} not found in account ${accountNumber}`);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async function assignRole(accountNumber, roleName, userId, grantedBy, expiresAt, opts) {
|
|
1116
|
+
await assertRoleExists(accountNumber, roleName);
|
|
1117
|
+
const target = roleTarget(accountNumber, roleName);
|
|
1118
|
+
if (expiresAt) {
|
|
1119
|
+
const existing = await getRow(userPrincipal(userId), target);
|
|
1120
|
+
if (existing && !existing.expiresAt) {
|
|
1121
|
+
log5("kept long-term grant (no downgrade)", { userId, accountNumber, roleName, requestedExpiresAt: expiresAt });
|
|
1122
|
+
return { userId, accountNumber, roleName };
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
await withAccessDiff(
|
|
1126
|
+
[userId],
|
|
1127
|
+
"ADMIN",
|
|
1128
|
+
() => putRow({
|
|
1129
|
+
principal: userPrincipal(userId),
|
|
1130
|
+
target,
|
|
1131
|
+
grantedBy,
|
|
1132
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1133
|
+
...expiresAt ? { expiresAt } : {}
|
|
1134
|
+
}),
|
|
1135
|
+
void 0,
|
|
1136
|
+
{ derived: false, gainNotices: !opts?.fromRequestId }
|
|
1137
|
+
);
|
|
1138
|
+
await recordEvent({
|
|
1139
|
+
category: "access",
|
|
1140
|
+
action: "role.grant",
|
|
1141
|
+
subject: { kind: "user", id: userId },
|
|
1142
|
+
target,
|
|
1143
|
+
targetName: roleName,
|
|
1144
|
+
details: { expiresAt, requestId: opts?.fromRequestId }
|
|
1145
|
+
});
|
|
1146
|
+
log5("assigned role", { userId, accountNumber, roleName, grantedBy, expiresAt });
|
|
1147
|
+
return { userId, accountNumber, roleName, expiresAt };
|
|
1148
|
+
}
|
|
1149
|
+
async function renewRole(accountNumber, roleName, userId, grantedBy, opts) {
|
|
1150
|
+
const target = roleTarget(accountNumber, roleName);
|
|
1151
|
+
const renewed = await updateExisting(userPrincipal(userId), target, {
|
|
1152
|
+
grantedBy,
|
|
1153
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1154
|
+
});
|
|
1155
|
+
if (!renewed) {
|
|
1156
|
+
throw new ConflictError(`${userId} no longer holds ${roleName} in ${accountNumber} \u2014 there is nothing to renew`);
|
|
1157
|
+
}
|
|
1158
|
+
await recordEvent({
|
|
1159
|
+
category: "access",
|
|
1160
|
+
action: "role.grant",
|
|
1161
|
+
subject: { kind: "user", id: userId },
|
|
1162
|
+
target,
|
|
1163
|
+
targetName: roleName,
|
|
1164
|
+
details: { requestId: opts?.fromRequestId, renewed: true }
|
|
1165
|
+
});
|
|
1166
|
+
log5("renewed role", { userId, accountNumber, roleName, grantedBy });
|
|
1167
|
+
}
|
|
1168
|
+
async function revokeRole(accountNumber, roleName, userId, via = "ADMIN") {
|
|
1169
|
+
const target = roleTarget(accountNumber, roleName);
|
|
1170
|
+
const removed = await revokeDirectRow(userId, target, via, { action: "role.revoke", targetName: roleName });
|
|
1171
|
+
if (!removed) {
|
|
1172
|
+
throw new NotFoundError(`No grant for user ${userId} on ${roleName} in ${accountNumber}`);
|
|
1173
|
+
}
|
|
1174
|
+
log5("revoked role", { userId, accountNumber, roleName });
|
|
1175
|
+
}
|
|
1176
|
+
async function listUserGrants(userId) {
|
|
1177
|
+
const out = [];
|
|
1178
|
+
for (const [t, e] of await effectiveAccess(userId)) {
|
|
1179
|
+
const p = parseTarget(t);
|
|
1180
|
+
if (p?.kind !== "role") continue;
|
|
1181
|
+
out.push({
|
|
1182
|
+
accountNumber: p.accountNumber,
|
|
1183
|
+
roleName: p.roleName,
|
|
1184
|
+
grantedBy: e.direct?.grantedBy,
|
|
1185
|
+
grantedAt: e.direct?.grantedAt,
|
|
1186
|
+
// Effective expiry: a temporary direct grant with a group behind it is
|
|
1187
|
+
// long-term in practice, so no expiry is reported.
|
|
1188
|
+
expiresAt: e.groups.length ? void 0 : e.direct?.expiresAt,
|
|
1189
|
+
viaGroup: e.direct ? void 0 : e.groups[0],
|
|
1190
|
+
viaGroups: e.groups
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
return out;
|
|
1194
|
+
}
|
|
1195
|
+
async function revokeAllForUser(userId) {
|
|
1196
|
+
const rows = await queryPrincipal(userPrincipal(userId));
|
|
1197
|
+
for (const r of rows) await deleteRow(r.principal, r.target);
|
|
1198
|
+
if (rows.length) log5("removed all access rows for deleted user", { userId, removed: rows.length });
|
|
1199
|
+
return rows.length;
|
|
1200
|
+
}
|
|
1201
|
+
var import_client_dynamodb5, import_lib_dynamodb5, ddb5, ROLES_TABLE_NAME, log5, EXPIRING_WINDOW_MS;
|
|
1202
|
+
var init_grantsService = __esm({
|
|
1203
|
+
"lambdacode/api/services/grantsService.ts"() {
|
|
1204
|
+
"use strict";
|
|
1205
|
+
import_client_dynamodb5 = require("@aws-sdk/client-dynamodb");
|
|
1206
|
+
import_lib_dynamodb5 = require("@aws-sdk/lib-dynamodb");
|
|
1207
|
+
init_httpErrors();
|
|
1208
|
+
init_access2();
|
|
1209
|
+
init_accessNotices();
|
|
1210
|
+
init_auditService();
|
|
1211
|
+
ddb5 = import_lib_dynamodb5.DynamoDBDocumentClient.from(new import_client_dynamodb5.DynamoDBClient({}));
|
|
1212
|
+
ROLES_TABLE_NAME = process.env.ROLES_TABLE_NAME;
|
|
1213
|
+
log5 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
1214
|
+
EXPIRING_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
// lambdacode/api/services/accountOrder.ts
|
|
1219
|
+
function compareAccounts(a, b) {
|
|
1220
|
+
const ao = order(a.sortOrder);
|
|
1221
|
+
const bo = order(b.sortOrder);
|
|
1222
|
+
if (ao !== bo) return ao - bo;
|
|
1223
|
+
const an = text(a.name);
|
|
1224
|
+
const bn = text(b.name);
|
|
1225
|
+
if (!!an !== !!bn) return an ? -1 : 1;
|
|
1226
|
+
return an.localeCompare(bn) || String(a.accountNumber).localeCompare(String(b.accountNumber));
|
|
1227
|
+
}
|
|
1228
|
+
function compareRoles(a, b) {
|
|
1229
|
+
const ao = order(a.sortOrder);
|
|
1230
|
+
const bo = order(b.sortOrder);
|
|
1231
|
+
if (ao !== bo) return ao - bo;
|
|
1232
|
+
const al = text(a.displayName) || String(a.roleName);
|
|
1233
|
+
const bl = text(b.displayName) || String(b.roleName);
|
|
1234
|
+
return al.localeCompare(bl) || String(a.roleName).localeCompare(String(b.roleName));
|
|
1235
|
+
}
|
|
1236
|
+
var text, order;
|
|
1237
|
+
var init_accountOrder = __esm({
|
|
1238
|
+
"lambdacode/api/services/accountOrder.ts"() {
|
|
1239
|
+
"use strict";
|
|
1240
|
+
text = (v) => typeof v === "string" ? v : "";
|
|
1241
|
+
order = (v) => typeof v === "number" && Number.isFinite(v) ? v : Number.MAX_SAFE_INTEGER;
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
// lambdacode/api/services/dimensions.ts
|
|
1246
|
+
function cleanDims(input) {
|
|
1247
|
+
if (!input || typeof input !== "object") return void 0;
|
|
1248
|
+
const o = input;
|
|
1249
|
+
const pick = (k) => typeof o[k] === "string" && o[k].trim() ? o[k].trim() : void 0;
|
|
1250
|
+
const tenant = pick("tenant");
|
|
1251
|
+
const stage = pick("stage");
|
|
1252
|
+
const project = pick("project");
|
|
1253
|
+
if (!tenant || !stage) return void 0;
|
|
1254
|
+
return { tenant, stage, ...project ? { project } : {} };
|
|
1255
|
+
}
|
|
1256
|
+
var init_dimensions = __esm({
|
|
1257
|
+
"lambdacode/api/services/dimensions.ts"() {
|
|
1258
|
+
"use strict";
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
// lambdacode/api/services/catalogIndex.ts
|
|
1263
|
+
async function readVersion() {
|
|
1264
|
+
const res = await ddb6.send(
|
|
1265
|
+
new import_lib_dynamodb6.GetCommand({ TableName: RESOURCES_TABLE_NAME, Key: { resourceId: CATALOG_VERSION_KEY }, ConsistentRead: true })
|
|
1266
|
+
);
|
|
1267
|
+
const v = res?.Item?.version;
|
|
1268
|
+
return typeof v === "number" ? v : void 0;
|
|
1269
|
+
}
|
|
1270
|
+
async function scanAll2(TableName) {
|
|
1271
|
+
const items = [];
|
|
1272
|
+
let ExclusiveStartKey;
|
|
1273
|
+
do {
|
|
1274
|
+
const page = await ddb6.send(new import_lib_dynamodb6.ScanCommand({ TableName, ExclusiveStartKey, ConsistentRead: true }));
|
|
1275
|
+
items.push(...page?.Items ?? []);
|
|
1276
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
1277
|
+
} while (ExclusiveStartKey);
|
|
1278
|
+
return items;
|
|
1279
|
+
}
|
|
1280
|
+
async function build(version) {
|
|
1281
|
+
const started = Date.now();
|
|
1282
|
+
const [accountItems, roleItems, groupItems] = await Promise.all([
|
|
1283
|
+
scanAll2(ACCOUNTS_TABLE_NAME),
|
|
1284
|
+
scanAll2(ROLES_TABLE_NAME2),
|
|
1285
|
+
scanAll2(GROUPS_TABLE_NAME)
|
|
1286
|
+
]);
|
|
1287
|
+
const byNumber = /* @__PURE__ */ new Map();
|
|
1288
|
+
for (const a of accountItems) {
|
|
1289
|
+
const accountNumber = String(a.accountNumber);
|
|
1290
|
+
byNumber.set(accountNumber, {
|
|
1291
|
+
accountNumber,
|
|
1292
|
+
name: str(a.name),
|
|
1293
|
+
environment: str(a.environment),
|
|
1294
|
+
status: str(a.status),
|
|
1295
|
+
profileName: str(a.profileName),
|
|
1296
|
+
sortOrder: num(a.sortOrder),
|
|
1297
|
+
main: a.notMain !== true,
|
|
1298
|
+
registered: true,
|
|
1299
|
+
dims: cleanDims(a.dims),
|
|
1300
|
+
roles: []
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
const roleByKey = /* @__PURE__ */ new Map();
|
|
1304
|
+
for (const r of roleItems) {
|
|
1305
|
+
const accountNumber = String(r.accountNumber);
|
|
1306
|
+
const roleName = String(r.roleName);
|
|
1307
|
+
const role = {
|
|
1308
|
+
accountNumber,
|
|
1309
|
+
roleName,
|
|
1310
|
+
roleArn: str(r.roleArn) ?? `arn:aws:iam::${accountNumber}:role/${roleName}`,
|
|
1311
|
+
displayName: str(r.displayName),
|
|
1312
|
+
description: str(r.description),
|
|
1313
|
+
icon: str(r.icon),
|
|
1314
|
+
color: str(r.color),
|
|
1315
|
+
policyTier: str(r.policyTier),
|
|
1316
|
+
profileName: str(r.profileName),
|
|
1317
|
+
sessionDurationSeconds: num(r.sessionDurationSeconds),
|
|
1318
|
+
sortOrder: num(r.sortOrder)
|
|
1319
|
+
};
|
|
1320
|
+
let account = byNumber.get(accountNumber);
|
|
1321
|
+
if (!account) {
|
|
1322
|
+
account = { accountNumber, main: true, registered: false, roles: [] };
|
|
1323
|
+
byNumber.set(accountNumber, account);
|
|
1324
|
+
}
|
|
1325
|
+
account.roles.push(role);
|
|
1326
|
+
roleByKey.set(roleKey(accountNumber, roleName), role);
|
|
1327
|
+
}
|
|
1328
|
+
const accounts = [...byNumber.values()].sort(compareAccounts);
|
|
1329
|
+
for (const a of accounts) a.roles.sort(compareRoles);
|
|
1330
|
+
const groups = groupItems.filter((g) => typeof g.groupId === "string" && typeof g.name === "string").map((g) => ({ groupId: g.groupId, name: g.name, description: str(g.description) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1331
|
+
log6("catalog index built", {
|
|
1332
|
+
version,
|
|
1333
|
+
accounts: accounts.length,
|
|
1334
|
+
roles: roleByKey.size,
|
|
1335
|
+
groups: groups.length,
|
|
1336
|
+
ms: Date.now() - started
|
|
1337
|
+
});
|
|
1338
|
+
return {
|
|
1339
|
+
version,
|
|
1340
|
+
builtAt: Date.now(),
|
|
1341
|
+
accounts,
|
|
1342
|
+
byNumber,
|
|
1343
|
+
roleByKey,
|
|
1344
|
+
groups,
|
|
1345
|
+
groupName: new Map(groups.map((g) => [g.groupId, g.name]))
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
async function ensureVersionRow() {
|
|
1349
|
+
try {
|
|
1350
|
+
await ddb6.send(
|
|
1351
|
+
new import_lib_dynamodb6.PutCommand({
|
|
1352
|
+
TableName: RESOURCES_TABLE_NAME,
|
|
1353
|
+
Item: { resourceId: CATALOG_VERSION_KEY, version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1354
|
+
ConditionExpression: "attribute_not_exists(resourceId)"
|
|
1355
|
+
})
|
|
1356
|
+
);
|
|
1357
|
+
return 1;
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
if (err.name !== "ConditionalCheckFailedException") throw err;
|
|
1360
|
+
return await readVersion() ?? 1;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async function catalog() {
|
|
1364
|
+
let version = await readVersion();
|
|
1365
|
+
if (cached && version !== void 0 && cached.version === version) return cached;
|
|
1366
|
+
if (!building) {
|
|
1367
|
+
if (version === void 0) version = await ensureVersionRow();
|
|
1368
|
+
building = build(version).then((c) => {
|
|
1369
|
+
cached = c;
|
|
1370
|
+
return c;
|
|
1371
|
+
}).finally(() => {
|
|
1372
|
+
building = void 0;
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
return building;
|
|
1376
|
+
}
|
|
1377
|
+
async function bumpCatalogVersion() {
|
|
1378
|
+
cached = void 0;
|
|
1379
|
+
try {
|
|
1380
|
+
await ddb6.send(
|
|
1381
|
+
new import_lib_dynamodb6.UpdateCommand({
|
|
1382
|
+
TableName: RESOURCES_TABLE_NAME,
|
|
1383
|
+
Key: { resourceId: CATALOG_VERSION_KEY },
|
|
1384
|
+
UpdateExpression: "ADD #v :one SET updatedAt = :now",
|
|
1385
|
+
ExpressionAttributeNames: { "#v": "version" },
|
|
1386
|
+
ExpressionAttributeValues: { ":one": 1, ":now": (/* @__PURE__ */ new Date()).toISOString() }
|
|
1387
|
+
})
|
|
1388
|
+
);
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
log6("ERROR: catalog version bump failed \u2014 other containers list stale until the next write", {
|
|
1391
|
+
error: err.message
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
var import_client_dynamodb6, import_lib_dynamodb6, ddb6, ACCOUNTS_TABLE_NAME, ROLES_TABLE_NAME2, GROUPS_TABLE_NAME, RESOURCES_TABLE_NAME, CATALOG_VERSION_KEY, log6, roleKey, cached, building, str, num;
|
|
1396
|
+
var init_catalogIndex = __esm({
|
|
1397
|
+
"lambdacode/api/services/catalogIndex.ts"() {
|
|
1398
|
+
"use strict";
|
|
1399
|
+
import_client_dynamodb6 = require("@aws-sdk/client-dynamodb");
|
|
1400
|
+
import_lib_dynamodb6 = require("@aws-sdk/lib-dynamodb");
|
|
1401
|
+
init_accountOrder();
|
|
1402
|
+
init_dimensions();
|
|
1403
|
+
ddb6 = import_lib_dynamodb6.DynamoDBDocumentClient.from(new import_client_dynamodb6.DynamoDBClient({}));
|
|
1404
|
+
ACCOUNTS_TABLE_NAME = process.env.ACCOUNTS_TABLE_NAME;
|
|
1405
|
+
ROLES_TABLE_NAME2 = process.env.ROLES_TABLE_NAME;
|
|
1406
|
+
GROUPS_TABLE_NAME = process.env.GROUPS_TABLE_NAME;
|
|
1407
|
+
RESOURCES_TABLE_NAME = process.env.RESOURCES_TABLE_NAME;
|
|
1408
|
+
CATALOG_VERSION_KEY = "__catalog-version__";
|
|
1409
|
+
log6 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
1410
|
+
roleKey = (accountNumber, roleName) => `${accountNumber}#${roleName}`;
|
|
1411
|
+
str = (v) => typeof v === "string" && v ? v : void 0;
|
|
1412
|
+
num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
// lambdacode/api/services/groupsService.ts
|
|
1417
|
+
async function bundlesAndMembers() {
|
|
1418
|
+
const targets = /* @__PURE__ */ new Map();
|
|
1419
|
+
const members = /* @__PURE__ */ new Map();
|
|
1420
|
+
for (const r of await scanAll()) {
|
|
1421
|
+
const p = parsePrincipal(r.principal);
|
|
1422
|
+
if (p?.kind === "group") {
|
|
1423
|
+
const list = targets.get(p.groupId) ?? [];
|
|
1424
|
+
list.push(r.target);
|
|
1425
|
+
targets.set(p.groupId, list);
|
|
1426
|
+
} else if (p?.kind === "user" && r.target.startsWith("member#")) {
|
|
1427
|
+
const g = r.target.slice("member#".length);
|
|
1428
|
+
const list = members.get(g) ?? [];
|
|
1429
|
+
list.push(p.userId);
|
|
1430
|
+
members.set(g, list);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return { targets, members };
|
|
1434
|
+
}
|
|
1435
|
+
function toGroup(row, targets, members) {
|
|
1436
|
+
const roles = [];
|
|
1437
|
+
const resources = [];
|
|
1438
|
+
const plugins = [];
|
|
1439
|
+
for (const t of targets) {
|
|
1440
|
+
const p = parseTarget(t);
|
|
1441
|
+
if (p?.kind === "role") roles.push({ accountNumber: p.accountNumber, roleName: p.roleName });
|
|
1442
|
+
else if (p?.kind === "resource") resources.push(p.resourceId);
|
|
1443
|
+
else if (p?.kind === "plugin") plugins.push({ pluginId: p.pluginId, accountNumber: p.accountNumber, permission: p.permission });
|
|
1444
|
+
}
|
|
1445
|
+
return {
|
|
1446
|
+
groupId: row.groupId,
|
|
1447
|
+
name: row.name,
|
|
1448
|
+
description: row.description,
|
|
1449
|
+
roles,
|
|
1450
|
+
resources,
|
|
1451
|
+
plugins,
|
|
1452
|
+
members: [...members].sort(),
|
|
1453
|
+
createdAt: row.createdAt,
|
|
1454
|
+
createdBy: row.createdBy,
|
|
1455
|
+
...row.reviewedAt ? { reviewedAt: row.reviewedAt } : {}
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
async function markGroupReviewed(groupId, adminUserId) {
|
|
1459
|
+
try {
|
|
1460
|
+
await ddb7.send(
|
|
1461
|
+
new import_lib_dynamodb7.UpdateCommand({
|
|
1462
|
+
TableName: GROUPS_TABLE_NAME2,
|
|
1463
|
+
Key: { groupId },
|
|
1464
|
+
UpdateExpression: "SET reviewedAt = :at, reviewedBy = :by",
|
|
1465
|
+
ExpressionAttributeValues: { ":at": (/* @__PURE__ */ new Date()).toISOString(), ":by": adminUserId },
|
|
1466
|
+
ConditionExpression: "attribute_exists(groupId)"
|
|
1467
|
+
})
|
|
1468
|
+
);
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
if (err.name === "ConditionalCheckFailedException") throw new NotFoundError(`Group ${groupId} not found`);
|
|
1471
|
+
throw err;
|
|
1472
|
+
}
|
|
1473
|
+
log7("group reviewed", { groupId, by: adminUserId });
|
|
1474
|
+
}
|
|
1475
|
+
async function listGroupRows() {
|
|
1476
|
+
return scanAllGroups();
|
|
1477
|
+
}
|
|
1478
|
+
async function listGroups() {
|
|
1479
|
+
const [rows, { targets, members }] = await Promise.all([scanAllGroups(), bundlesAndMembers()]);
|
|
1480
|
+
return rows.map((r) => toGroup(r, targets.get(r.groupId) ?? [], members.get(r.groupId) ?? [])).sort((a, b) => a.name.localeCompare(b.name));
|
|
1481
|
+
}
|
|
1482
|
+
async function getGroup(groupId) {
|
|
1483
|
+
const row = await getGroupRow(groupId);
|
|
1484
|
+
const [targets, memberRows] = await Promise.all([
|
|
1485
|
+
queryPrincipal(groupPrincipal(groupId)),
|
|
1486
|
+
queryTarget(memberTarget(groupId))
|
|
1487
|
+
]);
|
|
1488
|
+
return toGroup(
|
|
1489
|
+
row,
|
|
1490
|
+
targets.map((t) => t.target),
|
|
1491
|
+
memberRows.map((r) => parsePrincipal(r.principal)).filter((p) => p?.kind === "user").map((p) => p.userId)
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
async function deleteGroup(groupId) {
|
|
1495
|
+
const group = await getGroup(groupId);
|
|
1496
|
+
const batchId = newBatchId();
|
|
1497
|
+
let changes = [];
|
|
1498
|
+
await withAccessDiff(
|
|
1499
|
+
group.members,
|
|
1500
|
+
"GROUP",
|
|
1501
|
+
async () => {
|
|
1502
|
+
for (const r of await queryPrincipal(groupPrincipal(groupId))) await deleteRow(r.principal, r.target);
|
|
1503
|
+
for (const u of group.members) await deleteRow(userPrincipal(u), memberTarget(groupId));
|
|
1504
|
+
},
|
|
1505
|
+
void 0,
|
|
1506
|
+
{ batchId, groupId, groupName: group.name, onDiff: (c) => changes = c }
|
|
1507
|
+
);
|
|
1508
|
+
await ddb7.send(new import_lib_dynamodb7.DeleteCommand({ TableName: GROUPS_TABLE_NAME2, Key: { groupId } }));
|
|
1509
|
+
await bumpCatalogVersion();
|
|
1510
|
+
const { deleteRequestsForUser: deleteRequestsForUser2 } = (init_requestsService(), __toCommonJS(requestsService_exports));
|
|
1511
|
+
await deleteRequestsForUser2(groupId, { onlyPending: true });
|
|
1512
|
+
await recordEvent({
|
|
1513
|
+
category: "catalog",
|
|
1514
|
+
action: "group.delete",
|
|
1515
|
+
subject: { kind: "group", id: groupId, name: group.name },
|
|
1516
|
+
target: groupTarget(groupId),
|
|
1517
|
+
targetName: group.name,
|
|
1518
|
+
details: { members: group.members.length, batchId, affected: affectedOf(changes, { change: "lost" }), unchanged: unchangedOf(changes, { change: "lost" }) }
|
|
1519
|
+
});
|
|
1520
|
+
log7("deleted group", { groupId, name: group.name, members: group.members.length });
|
|
1521
|
+
}
|
|
1522
|
+
async function groupHoldingTarget(groupId, target) {
|
|
1523
|
+
const group = await getGroup(groupId);
|
|
1524
|
+
if (!targetsOf(group).has(target)) {
|
|
1525
|
+
throw new NotFoundError(`Group ${groupId} does not hold ${target}`);
|
|
1526
|
+
}
|
|
1527
|
+
return group;
|
|
1528
|
+
}
|
|
1529
|
+
async function revokeGroupTarget(groupId, target) {
|
|
1530
|
+
const group = await groupHoldingTarget(groupId, target);
|
|
1531
|
+
if (targetsOf(group).size === 1) {
|
|
1532
|
+
await deleteGroup(groupId);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const batchId = newBatchId();
|
|
1536
|
+
let changes = [];
|
|
1537
|
+
await withAccessDiff(
|
|
1538
|
+
group.members,
|
|
1539
|
+
"GROUP",
|
|
1540
|
+
async () => {
|
|
1541
|
+
await deleteRow(groupPrincipal(groupId), target);
|
|
1542
|
+
},
|
|
1543
|
+
void 0,
|
|
1544
|
+
{ batchId, groupId, groupName: group.name, onDiff: (c) => changes = c }
|
|
1545
|
+
);
|
|
1546
|
+
await clearPendingGroupReviews(groupId);
|
|
1547
|
+
await recordEvent(
|
|
1548
|
+
bundleEvent("group.bundle.remove", { kind: "group", id: groupId, name: group.name }, target, {
|
|
1549
|
+
via: "REAPPROVAL",
|
|
1550
|
+
batchId,
|
|
1551
|
+
affected: affectedOf(changes, { target, change: "lost" }),
|
|
1552
|
+
unchanged: unchangedOf(changes, { target, change: "lost" })
|
|
1553
|
+
})
|
|
1554
|
+
);
|
|
1555
|
+
log7("revoked group target", { groupId, name: group.name, target });
|
|
1556
|
+
}
|
|
1557
|
+
async function restampGroupTarget(groupId, target, adminUserId) {
|
|
1558
|
+
const group = await groupHoldingTarget(groupId, target);
|
|
1559
|
+
await putRow({
|
|
1560
|
+
principal: groupPrincipal(groupId),
|
|
1561
|
+
target,
|
|
1562
|
+
grantedBy: adminUserId,
|
|
1563
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1564
|
+
});
|
|
1565
|
+
log7("restamped group target", { groupId, name: group.name, target });
|
|
1566
|
+
}
|
|
1567
|
+
async function clearPendingGroupReviews(groupId) {
|
|
1568
|
+
const { deleteRequestsForUser: deleteRequestsForUser2 } = (init_requestsService(), __toCommonJS(requestsService_exports));
|
|
1569
|
+
await deleteRequestsForUser2(groupId, { onlyPending: true, exceptKinds: ["GROUP"] });
|
|
1570
|
+
}
|
|
1571
|
+
async function removeUserFromAllGroups(userId) {
|
|
1572
|
+
const rows = await queryPrincipal(userPrincipal(userId), "member#");
|
|
1573
|
+
for (const r of rows) await deleteRow(r.principal, r.target);
|
|
1574
|
+
if (rows.length) log7("removed deleted user from groups", { userId, touched: rows.length });
|
|
1575
|
+
return rows.length;
|
|
1576
|
+
}
|
|
1577
|
+
async function getGroupRow(groupId) {
|
|
1578
|
+
const res = await ddb7.send(new import_lib_dynamodb7.GetCommand({ TableName: GROUPS_TABLE_NAME2, Key: { groupId } }));
|
|
1579
|
+
if (!res.Item) throw new NotFoundError(`Group ${groupId} not found`);
|
|
1580
|
+
return res.Item;
|
|
1581
|
+
}
|
|
1582
|
+
async function scanAllGroups() {
|
|
1583
|
+
const rows = [];
|
|
1584
|
+
let ExclusiveStartKey;
|
|
1585
|
+
do {
|
|
1586
|
+
const page = await ddb7.send(new import_lib_dynamodb7.ScanCommand({ TableName: GROUPS_TABLE_NAME2, ExclusiveStartKey }));
|
|
1587
|
+
rows.push(...page?.Items ?? []);
|
|
1588
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
1589
|
+
} while (ExclusiveStartKey);
|
|
1590
|
+
return rows;
|
|
1591
|
+
}
|
|
1592
|
+
var import_client_dynamodb7, import_lib_dynamodb7, ddb7, GROUPS_TABLE_NAME2, ROLES_TABLE_NAME3, RESOURCES_TABLE_NAME2, log7, toKey, bundleEvent, targetsOf;
|
|
1593
|
+
var init_groupsService = __esm({
|
|
1594
|
+
"lambdacode/api/services/groupsService.ts"() {
|
|
1595
|
+
"use strict";
|
|
1596
|
+
import_client_dynamodb7 = require("@aws-sdk/client-dynamodb");
|
|
1597
|
+
import_lib_dynamodb7 = require("@aws-sdk/lib-dynamodb");
|
|
1598
|
+
init_httpErrors();
|
|
1599
|
+
init_access2();
|
|
1600
|
+
init_accessNotices();
|
|
1601
|
+
init_auditService();
|
|
1602
|
+
init_catalogIndex();
|
|
1603
|
+
init_resourcesService();
|
|
1604
|
+
ddb7 = import_lib_dynamodb7.DynamoDBDocumentClient.from(new import_client_dynamodb7.DynamoDBClient({}));
|
|
1605
|
+
GROUPS_TABLE_NAME2 = process.env.GROUPS_TABLE_NAME;
|
|
1606
|
+
ROLES_TABLE_NAME3 = process.env.ROLES_TABLE_NAME;
|
|
1607
|
+
RESOURCES_TABLE_NAME2 = process.env.RESOURCES_TABLE_NAME;
|
|
1608
|
+
log7 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
1609
|
+
toKey = (r) => roleTarget(r.accountNumber, r.roleName);
|
|
1610
|
+
bundleEvent = (action, subject, target, details) => {
|
|
1611
|
+
const p = parseTarget(target);
|
|
1612
|
+
return { category: "access", action, subject, target, targetName: p?.kind === "role" ? p.roleName : void 0, details };
|
|
1613
|
+
};
|
|
1614
|
+
targetsOf = (group) => /* @__PURE__ */ new Set([
|
|
1615
|
+
...group.roles.map(toKey),
|
|
1616
|
+
...group.resources.map(resourceTarget),
|
|
1617
|
+
...group.plugins.map((p) => pluginTarget(p.pluginId, p.accountNumber, p.permission))
|
|
1618
|
+
]);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
// lambdacode/api/services/resourcesService.ts
|
|
1623
|
+
function toResource(it) {
|
|
1624
|
+
return {
|
|
1625
|
+
resourceId: it.resourceId,
|
|
1626
|
+
accountNumber: it.accountNumber,
|
|
1627
|
+
name: it.name,
|
|
1628
|
+
type: it.type,
|
|
1629
|
+
cidr: it.cidr,
|
|
1630
|
+
ports: it.ports,
|
|
1631
|
+
dnsName: it.dnsName,
|
|
1632
|
+
sshUser: it.sshUser,
|
|
1633
|
+
securityGroupId: it.securityGroupId,
|
|
1634
|
+
description: it.description,
|
|
1635
|
+
createdAt: it.createdAt,
|
|
1636
|
+
createdBy: it.createdBy
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
async function listResources() {
|
|
1640
|
+
const items = [];
|
|
1641
|
+
let ExclusiveStartKey;
|
|
1642
|
+
do {
|
|
1643
|
+
const page = await ddb8.send(
|
|
1644
|
+
new import_lib_dynamodb8.ScanCommand({ TableName: RESOURCES_TABLE_NAME3, ExclusiveStartKey })
|
|
1645
|
+
);
|
|
1646
|
+
items.push(...page.Items ?? []);
|
|
1647
|
+
ExclusiveStartKey = page.LastEvaluatedKey;
|
|
1648
|
+
} while (ExclusiveStartKey);
|
|
1649
|
+
return items.filter((it) => !isSentinelRow(it.resourceId)).map(toResource).sort((a, b) => a.name.localeCompare(b.name));
|
|
1650
|
+
}
|
|
1651
|
+
async function getResource(resourceId) {
|
|
1652
|
+
if (isSentinelRow(resourceId)) {
|
|
1653
|
+
throw new NotFoundError(`Resource ${resourceId} not found`);
|
|
1654
|
+
}
|
|
1655
|
+
const res = await ddb8.send(
|
|
1656
|
+
new import_lib_dynamodb8.GetCommand({ TableName: RESOURCES_TABLE_NAME3, Key: { resourceId } })
|
|
1657
|
+
);
|
|
1658
|
+
if (!res.Item) throw new NotFoundError(`Resource ${resourceId} not found`);
|
|
1659
|
+
return toResource(res.Item);
|
|
1660
|
+
}
|
|
1661
|
+
var import_client_dynamodb8, import_lib_dynamodb8, ddb8, RESOURCES_TABLE_NAME3, ACCOUNTS_TABLE_NAME2, isSentinelRow;
|
|
1662
|
+
var init_resourcesService = __esm({
|
|
1663
|
+
"lambdacode/api/services/resourcesService.ts"() {
|
|
1664
|
+
"use strict";
|
|
1665
|
+
import_client_dynamodb8 = require("@aws-sdk/client-dynamodb");
|
|
1666
|
+
import_lib_dynamodb8 = require("@aws-sdk/lib-dynamodb");
|
|
1667
|
+
init_httpErrors();
|
|
1668
|
+
init_resourceGrantsService();
|
|
1669
|
+
init_groupsService();
|
|
1670
|
+
init_access2();
|
|
1671
|
+
init_auditService();
|
|
1672
|
+
init_accessNotices();
|
|
1673
|
+
ddb8 = import_lib_dynamodb8.DynamoDBDocumentClient.from(new import_client_dynamodb8.DynamoDBClient({}));
|
|
1674
|
+
RESOURCES_TABLE_NAME3 = process.env.RESOURCES_TABLE_NAME;
|
|
1675
|
+
ACCOUNTS_TABLE_NAME2 = process.env.ACCOUNTS_TABLE_NAME;
|
|
1676
|
+
isSentinelRow = (resourceId) => String(resourceId).startsWith("__");
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
// lambdacode/api/services/resourceGrantsService.ts
|
|
1681
|
+
async function catalogRow(resourceId) {
|
|
1682
|
+
if (isSentinelRow(resourceId)) return void 0;
|
|
1683
|
+
const res = await ddb9.send(new import_lib_dynamodb9.GetCommand({ TableName: RESOURCES_TABLE_NAME4, Key: { resourceId } }));
|
|
1684
|
+
return res.Item;
|
|
1685
|
+
}
|
|
1686
|
+
async function assignResource(resourceId, userId, grantedBy, expiresAt, opts) {
|
|
1687
|
+
const resource = await catalogRow(resourceId);
|
|
1688
|
+
if (!resource) throw new NotFoundError(`Resource ${resourceId} not found`);
|
|
1689
|
+
const target = resourceTarget(resourceId);
|
|
1690
|
+
if (expiresAt) {
|
|
1691
|
+
const existing = await getRow(userPrincipal(userId), target);
|
|
1692
|
+
if (existing && !existing.expiresAt) {
|
|
1693
|
+
log8("kept long-term resource grant (no downgrade)", { userId, resourceId, requestedExpiresAt: expiresAt });
|
|
1694
|
+
return { resourceId, grantedBy: existing.grantedBy, grantedAt: existing.grantedAt };
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
await withAccessDiff(
|
|
1698
|
+
[userId],
|
|
1699
|
+
"ADMIN",
|
|
1700
|
+
() => putRow({
|
|
1701
|
+
principal: userPrincipal(userId),
|
|
1702
|
+
target,
|
|
1703
|
+
grantedBy,
|
|
1704
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1705
|
+
...expiresAt ? { expiresAt } : {}
|
|
1706
|
+
}),
|
|
1707
|
+
nameMap(resourceId, resource.name),
|
|
1708
|
+
{ derived: false, gainNotices: !opts?.fromRequestId }
|
|
1709
|
+
);
|
|
1710
|
+
await recordEvent({
|
|
1711
|
+
category: "access",
|
|
1712
|
+
action: "resource.grant",
|
|
1713
|
+
subject: { kind: "user", id: userId },
|
|
1714
|
+
target,
|
|
1715
|
+
targetName: resource.name,
|
|
1716
|
+
accountNumber: resource.accountNumber,
|
|
1717
|
+
details: { expiresAt, requestId: opts?.fromRequestId }
|
|
1718
|
+
});
|
|
1719
|
+
log8("assigned resource", { userId, resourceId, grantedBy, expiresAt });
|
|
1720
|
+
return { resourceId, grantedBy, expiresAt };
|
|
1721
|
+
}
|
|
1722
|
+
async function renewResourceGrant(resourceId, userId, grantedBy, opts) {
|
|
1723
|
+
const resource = await catalogRow(resourceId);
|
|
1724
|
+
if (!resource) throw new NotFoundError(`Resource ${resourceId} not found`);
|
|
1725
|
+
const target = resourceTarget(resourceId);
|
|
1726
|
+
const renewed = await updateExisting(userPrincipal(userId), target, {
|
|
1727
|
+
grantedBy,
|
|
1728
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1729
|
+
});
|
|
1730
|
+
if (!renewed) {
|
|
1731
|
+
throw new ConflictError(`${userId} no longer has access to ${resource.name} \u2014 there is nothing to renew`);
|
|
1732
|
+
}
|
|
1733
|
+
await recordEvent({
|
|
1734
|
+
category: "access",
|
|
1735
|
+
action: "resource.grant",
|
|
1736
|
+
subject: { kind: "user", id: userId },
|
|
1737
|
+
target,
|
|
1738
|
+
targetName: resource.name,
|
|
1739
|
+
accountNumber: resource.accountNumber,
|
|
1740
|
+
details: { requestId: opts?.fromRequestId, renewed: true }
|
|
1741
|
+
});
|
|
1742
|
+
log8("renewed resource grant", { userId, resourceId, grantedBy });
|
|
1743
|
+
}
|
|
1744
|
+
async function revokeResourceGrant(resourceId, userId, via = "ADMIN") {
|
|
1745
|
+
const resource = await catalogRow(resourceId);
|
|
1746
|
+
const removed = await revokeDirectRow(
|
|
1747
|
+
userId,
|
|
1748
|
+
resourceTarget(resourceId),
|
|
1749
|
+
via,
|
|
1750
|
+
{ action: "resource.revoke", targetName: resource?.name, accountNumber: resource?.accountNumber },
|
|
1751
|
+
nameMap(resourceId, resource?.name)
|
|
1752
|
+
);
|
|
1753
|
+
if (!removed) throw new NotFoundError(`No grant for user ${userId} on resource ${resourceId}`);
|
|
1754
|
+
log8("revoked resource grant", { userId, resourceId });
|
|
1755
|
+
}
|
|
1756
|
+
async function listUserResourceGrants(userId) {
|
|
1757
|
+
const out = [];
|
|
1758
|
+
for (const [t, e] of await effectiveAccess(userId)) {
|
|
1759
|
+
const p = parseTarget(t);
|
|
1760
|
+
if (p?.kind !== "resource") continue;
|
|
1761
|
+
out.push({
|
|
1762
|
+
resourceId: p.resourceId,
|
|
1763
|
+
grantedBy: e.direct?.grantedBy,
|
|
1764
|
+
grantedAt: e.direct?.grantedAt,
|
|
1765
|
+
expiresAt: e.groups.length ? void 0 : e.direct?.expiresAt,
|
|
1766
|
+
// effective
|
|
1767
|
+
viaGroup: e.direct ? void 0 : e.groups[0],
|
|
1768
|
+
viaGroups: e.groups
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
return out;
|
|
1772
|
+
}
|
|
1773
|
+
async function revokeAllResourcesForUser(userId) {
|
|
1774
|
+
const rows = await queryPrincipal(userPrincipal(userId), "resource#");
|
|
1775
|
+
for (const r of rows) await deleteRow(r.principal, r.target);
|
|
1776
|
+
if (rows.length) log8("revoked all resource grants for user", { userId, count: rows.length });
|
|
1777
|
+
return rows.length;
|
|
1778
|
+
}
|
|
1779
|
+
var import_client_dynamodb9, import_lib_dynamodb9, ddb9, RESOURCES_TABLE_NAME4, log8, nameMap;
|
|
1780
|
+
var init_resourceGrantsService = __esm({
|
|
1781
|
+
"lambdacode/api/services/resourceGrantsService.ts"() {
|
|
1782
|
+
"use strict";
|
|
1783
|
+
import_client_dynamodb9 = require("@aws-sdk/client-dynamodb");
|
|
1784
|
+
import_lib_dynamodb9 = require("@aws-sdk/lib-dynamodb");
|
|
1785
|
+
init_httpErrors();
|
|
1786
|
+
init_access2();
|
|
1787
|
+
init_accessNotices();
|
|
1788
|
+
init_auditService();
|
|
1789
|
+
init_grantsService();
|
|
1790
|
+
init_resourcesService();
|
|
1791
|
+
ddb9 = import_lib_dynamodb9.DynamoDBDocumentClient.from(new import_client_dynamodb9.DynamoDBClient({}));
|
|
1792
|
+
RESOURCES_TABLE_NAME4 = process.env.RESOURCES_TABLE_NAME;
|
|
1793
|
+
log8 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
1794
|
+
nameMap = (resourceId, name) => name ? /* @__PURE__ */ new Map([[resourceId, name]]) : void 0;
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
// lambdacode/api/services/pluginGrantsService.ts
|
|
1799
|
+
function validatePluginGrantKey(key) {
|
|
1800
|
+
if (!PLUGIN_ID_PATTERN.test(key.pluginId)) throw new ValidationError("pluginId is not a valid plugin id");
|
|
1801
|
+
if (!AWS_ACCOUNT_PATTERN.test(key.accountNumber)) throw new ValidationError("accountNumber must be 12 digits");
|
|
1802
|
+
if (!PLUGIN_PERMISSIONS.includes(key.permission)) throw new ValidationError(`permission must be "user" or "admin"`);
|
|
1803
|
+
}
|
|
1804
|
+
function toAssignment(row, ctx) {
|
|
1805
|
+
const t = parseTarget(row.target);
|
|
1806
|
+
const p = parsePrincipal(row.principal);
|
|
1807
|
+
if (t?.kind !== "plugin" || !p) return null;
|
|
1808
|
+
const a = pluginAvailability(ctx.catalog.get(t.pluginId), t.accountNumber, t.permission, ctx.registeredAccounts);
|
|
1809
|
+
return {
|
|
1810
|
+
pluginId: t.pluginId,
|
|
1811
|
+
accountNumber: t.accountNumber,
|
|
1812
|
+
permission: t.permission,
|
|
1813
|
+
principal: p,
|
|
1814
|
+
grantedBy: row.grantedBy,
|
|
1815
|
+
grantedAt: row.grantedAt,
|
|
1816
|
+
expiresAt: row.expiresAt,
|
|
1817
|
+
expired: isExpired(row),
|
|
1818
|
+
...a.available ? { available: true } : a
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
async function assertPluginAssignable(key, ctx) {
|
|
1822
|
+
validatePluginGrantKey(key);
|
|
1823
|
+
const context = ctx ?? await loadPluginContext();
|
|
1824
|
+
const record = context.catalog.get(key.pluginId);
|
|
1825
|
+
if (!record) throw new NotFoundError(`Plugin ${key.pluginId} is not installed`);
|
|
1826
|
+
const a = pluginAvailability(record, key.accountNumber, key.permission, context.registeredAccounts);
|
|
1827
|
+
if (a.available) return context;
|
|
1828
|
+
if (a.reason === "permission-undeclared") {
|
|
1829
|
+
throw new ValidationError(`Plugin ${key.pluginId} does not declare the "${key.permission}" permission`);
|
|
1830
|
+
}
|
|
1831
|
+
if (a.reason === "account-unbound") {
|
|
1832
|
+
throw new NotFoundError(`Plugin ${key.pluginId} is not bound to account ${key.accountNumber}`);
|
|
1833
|
+
}
|
|
1834
|
+
throw new ConflictError(`Plugin ${key.pluginId} is not available for account ${key.accountNumber} (${a.reason})`);
|
|
1835
|
+
}
|
|
1836
|
+
async function assignPlugin(input, ctx) {
|
|
1837
|
+
if (input.expiresAt !== void 0) {
|
|
1838
|
+
if (input.principal.kind === "group") throw new ValidationError("group assignments are standing \u2014 no expiry");
|
|
1839
|
+
if (!(Date.parse(input.expiresAt) > Date.now())) throw new ValidationError("expiresAt must be in the future");
|
|
1840
|
+
}
|
|
1841
|
+
const context = await assertPluginAssignable(input, ctx);
|
|
1842
|
+
const principal = principalOf(input.principal);
|
|
1843
|
+
const target = pluginTarget(input.pluginId, input.accountNumber, input.permission);
|
|
1844
|
+
if (input.expiresAt) {
|
|
1845
|
+
const existing = await getRow(principal, target);
|
|
1846
|
+
if (existing && !existing.expiresAt) {
|
|
1847
|
+
log9("kept long-term plugin grant (no downgrade)", { principal, target, requestedExpiresAt: input.expiresAt });
|
|
1848
|
+
return toAssignment(existing, context);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
const row = {
|
|
1852
|
+
principal,
|
|
1853
|
+
target,
|
|
1854
|
+
grantedBy: input.grantedBy,
|
|
1855
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1856
|
+
...input.expiresAt ? { expiresAt: input.expiresAt } : {}
|
|
1857
|
+
};
|
|
1858
|
+
const targetName = context.catalog.get(input.pluginId)?.manifest.displayName;
|
|
1859
|
+
if (input.principal.kind === "user") {
|
|
1860
|
+
await withAccessDiff([input.principal.userId], "ADMIN", () => putRow(row), void 0, {
|
|
1861
|
+
derived: false,
|
|
1862
|
+
gainNotices: !input.fromRequestId
|
|
1863
|
+
});
|
|
1864
|
+
await recordEvent({
|
|
1865
|
+
category: "access",
|
|
1866
|
+
action: "plugin.grant",
|
|
1867
|
+
subject: { kind: "user", id: input.principal.userId },
|
|
1868
|
+
target,
|
|
1869
|
+
targetName,
|
|
1870
|
+
details: { expiresAt: input.expiresAt, requestId: input.fromRequestId }
|
|
1871
|
+
});
|
|
1872
|
+
} else {
|
|
1873
|
+
const { groupId } = input.principal;
|
|
1874
|
+
const batchId = newBatchId();
|
|
1875
|
+
await withAccessDiff(await membersOf(groupId), "ADMIN", () => putRow(row), void 0, { batchId, groupId });
|
|
1876
|
+
await recordEvent({
|
|
1877
|
+
category: "access",
|
|
1878
|
+
action: "plugin.grant",
|
|
1879
|
+
subject: { kind: "group", id: groupId },
|
|
1880
|
+
target,
|
|
1881
|
+
targetName,
|
|
1882
|
+
details: { batchId }
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
log9("assigned plugin permission", { principal, target, grantedBy: input.grantedBy, expiresAt: input.expiresAt });
|
|
1886
|
+
return toAssignment(row, context);
|
|
1887
|
+
}
|
|
1888
|
+
async function revokePlugin(input) {
|
|
1889
|
+
validatePluginGrantKey(input);
|
|
1890
|
+
const principal = principalOf(input.principal);
|
|
1891
|
+
const target = pluginTarget(input.pluginId, input.accountNumber, input.permission);
|
|
1892
|
+
const via = input.via ?? "ADMIN";
|
|
1893
|
+
const notFound = () => new NotFoundError(`No ${input.permission} assignment for ${describe(input.principal)} on plugin ${input.pluginId} in account ${input.accountNumber}`);
|
|
1894
|
+
const targetName = await pluginCatalog().get(input.pluginId).then((r) => r?.manifest.displayName, () => void 0);
|
|
1895
|
+
if (input.principal.kind === "user") {
|
|
1896
|
+
if (!await revokeDirectRow(input.principal.userId, target, via, { action: "plugin.revoke", targetName })) throw notFound();
|
|
1897
|
+
} else {
|
|
1898
|
+
const rows = await queryPrincipal(principal);
|
|
1899
|
+
const others = rows.filter((r) => r.target !== target && BUNDLE_KINDS.has(parseTarget(r.target)?.kind ?? ""));
|
|
1900
|
+
if (rows.some((r) => r.target === target) && !others.length) {
|
|
1901
|
+
throw new ConflictError("This is the group's last permission \u2014 delete the group instead");
|
|
1902
|
+
}
|
|
1903
|
+
const { groupId } = input.principal;
|
|
1904
|
+
const batchId = newBatchId();
|
|
1905
|
+
const affected = (await holdersOf(target)).map((h) => h.userId);
|
|
1906
|
+
const removed = await withAccessDiff(affected, via, () => deleteRow(principal, target), void 0, { batchId, groupId });
|
|
1907
|
+
if (!removed) throw notFound();
|
|
1908
|
+
await recordEvent({
|
|
1909
|
+
category: "access",
|
|
1910
|
+
action: "plugin.revoke",
|
|
1911
|
+
subject: { kind: "group", id: groupId },
|
|
1912
|
+
target,
|
|
1913
|
+
targetName,
|
|
1914
|
+
details: { via, batchId }
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
log9("revoked plugin permission", { principal, target, via });
|
|
1918
|
+
}
|
|
1919
|
+
function standingPluginGrants(rows, ctx) {
|
|
1920
|
+
const out = [];
|
|
1921
|
+
for (const r of rows) {
|
|
1922
|
+
if (r.expiresAt) continue;
|
|
1923
|
+
const p = parsePrincipal(r.principal);
|
|
1924
|
+
const t = parseTarget(r.target);
|
|
1925
|
+
if (p?.kind !== "user" || t?.kind !== "plugin") continue;
|
|
1926
|
+
const available = pluginAvailability(ctx.catalog.get(t.pluginId), t.accountNumber, t.permission, ctx.registeredAccounts).available;
|
|
1927
|
+
out.push({ userId: p.userId, target: r.target, pluginId: t.pluginId, accountNumber: t.accountNumber, permission: t.permission, grantedAt: r.grantedAt, available });
|
|
1928
|
+
}
|
|
1929
|
+
return out;
|
|
1930
|
+
}
|
|
1931
|
+
var log9, describe, BUNDLE_KINDS;
|
|
1932
|
+
var init_pluginGrantsService = __esm({
|
|
1933
|
+
"lambdacode/api/services/pluginGrantsService.ts"() {
|
|
1934
|
+
"use strict";
|
|
1935
|
+
init_backend();
|
|
1936
|
+
init_httpErrors();
|
|
1937
|
+
init_access2();
|
|
1938
|
+
init_accessNotices();
|
|
1939
|
+
init_auditService();
|
|
1940
|
+
init_grantsService();
|
|
1941
|
+
init_pluginCatalog();
|
|
1942
|
+
init_pluginAccess();
|
|
1943
|
+
log9 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
1944
|
+
describe = (p) => p.kind === "user" ? `user ${p.userId}` : `group ${p.groupId}`;
|
|
1945
|
+
BUNDLE_KINDS = /* @__PURE__ */ new Set(["role", "resource", "plugin"]);
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
// lambdacode/api/services/serviceUserRegistry.ts
|
|
1950
|
+
async function getServiceUserName(clientId) {
|
|
1951
|
+
const out = await ddb10.send(
|
|
1952
|
+
new import_lib_dynamodb10.GetCommand({ TableName: SERVICE_USERS_TABLE_NAME, Key: { clientId }, ConsistentRead: true })
|
|
1953
|
+
);
|
|
1954
|
+
return out.Item?.name || void 0;
|
|
1955
|
+
}
|
|
1956
|
+
var import_client_dynamodb10, import_lib_dynamodb10, ddb10, SERVICE_USERS_TABLE_NAME;
|
|
1957
|
+
var init_serviceUserRegistry = __esm({
|
|
1958
|
+
"lambdacode/api/services/serviceUserRegistry.ts"() {
|
|
1959
|
+
"use strict";
|
|
1960
|
+
import_client_dynamodb10 = require("@aws-sdk/client-dynamodb");
|
|
1961
|
+
import_lib_dynamodb10 = require("@aws-sdk/lib-dynamodb");
|
|
1962
|
+
ddb10 = import_lib_dynamodb10.DynamoDBDocumentClient.from(new import_client_dynamodb10.DynamoDBClient({}));
|
|
1963
|
+
SERVICE_USERS_TABLE_NAME = process.env.SERVICE_USERS_TABLE_NAME;
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
|
|
1967
|
+
// lambdacode/api/services/serviceUsersService.ts
|
|
1968
|
+
async function scanRegistry() {
|
|
1969
|
+
const rows = [];
|
|
1970
|
+
let ExclusiveStartKey;
|
|
1971
|
+
do {
|
|
1972
|
+
const page = await ddb11.send(
|
|
1973
|
+
new import_lib_dynamodb11.ScanCommand({ TableName: SERVICE_USERS_TABLE_NAME2, ExclusiveStartKey })
|
|
1974
|
+
);
|
|
1975
|
+
rows.push(...page?.Items ?? []);
|
|
1976
|
+
ExclusiveStartKey = page?.LastEvaluatedKey;
|
|
1977
|
+
} while (ExclusiveStartKey);
|
|
1978
|
+
return rows;
|
|
1979
|
+
}
|
|
1980
|
+
async function getRow2(clientId) {
|
|
1981
|
+
const res = await ddb11.send(
|
|
1982
|
+
new import_lib_dynamodb11.GetCommand({ TableName: SERVICE_USERS_TABLE_NAME2, Key: { clientId } })
|
|
1983
|
+
);
|
|
1984
|
+
return res.Item;
|
|
1985
|
+
}
|
|
1986
|
+
async function getCurrentRow(clientId) {
|
|
1987
|
+
const row = await getRow2(clientId);
|
|
1988
|
+
if (!row || row.retiredInto) {
|
|
1989
|
+
throw new NotFoundError(`Service user ${clientId} not found`);
|
|
1990
|
+
}
|
|
1991
|
+
return row;
|
|
1992
|
+
}
|
|
1993
|
+
async function deleteAppClient(clientId) {
|
|
1994
|
+
try {
|
|
1995
|
+
await cognito.send(
|
|
1996
|
+
new import_client_cognito_identity_provider.DeleteUserPoolClientCommand({ UserPoolId: USER_POOL_ID, ClientId: clientId })
|
|
1997
|
+
);
|
|
1998
|
+
} catch (err) {
|
|
1999
|
+
if (err.name !== "ResourceNotFoundException") throw err;
|
|
2000
|
+
log10("app client already gone", { clientId });
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
async function markReviewed(clientId) {
|
|
2004
|
+
try {
|
|
2005
|
+
await ddb11.send(
|
|
2006
|
+
new import_lib_dynamodb11.UpdateCommand({
|
|
2007
|
+
TableName: SERVICE_USERS_TABLE_NAME2,
|
|
2008
|
+
Key: { clientId },
|
|
2009
|
+
UpdateExpression: "SET reviewedAt = :t",
|
|
2010
|
+
ConditionExpression: "attribute_exists(clientId)",
|
|
2011
|
+
ExpressionAttributeValues: { ":t": (/* @__PURE__ */ new Date()).toISOString() }
|
|
2012
|
+
})
|
|
2013
|
+
);
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
if (err.name === "ConditionalCheckFailedException") {
|
|
2016
|
+
throw new NotFoundError(`Service user ${clientId} not found`);
|
|
2017
|
+
}
|
|
2018
|
+
throw err;
|
|
2019
|
+
}
|
|
2020
|
+
log10("marked service user reviewed", { clientId });
|
|
2021
|
+
}
|
|
2022
|
+
async function deleteServiceUser(clientId) {
|
|
2023
|
+
const row = await getCurrentRow(clientId);
|
|
2024
|
+
const { deleteRequestsForUser: deleteRequestsForUser2 } = (init_requestsService(), __toCommonJS(requestsService_exports));
|
|
2025
|
+
for (const id of [row.previousClientId, clientId].filter(Boolean)) {
|
|
2026
|
+
await deleteAppClient(id);
|
|
2027
|
+
await revokeAllForUser(id);
|
|
2028
|
+
await deleteRequestsForUser2(id, { onlyPending: true });
|
|
2029
|
+
await ddb11.send(
|
|
2030
|
+
new import_lib_dynamodb11.DeleteCommand({ TableName: SERVICE_USERS_TABLE_NAME2, Key: { clientId: id } })
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
await recordServiceUserEvent("serviceuser.delete", clientId, row.name);
|
|
2034
|
+
log10("deleted service user", { clientId, name: row.name });
|
|
2035
|
+
}
|
|
2036
|
+
async function reviewableServiceUsers() {
|
|
2037
|
+
return (await scanRegistry()).filter((r) => !r.retiredInto).map((r) => ({ clientId: r.clientId, since: r.reviewedAt ?? r.createdAt }));
|
|
2038
|
+
}
|
|
2039
|
+
var import_client_dynamodb11, import_lib_dynamodb11, import_client_cognito_identity_provider, ddb11, cognito, SERVICE_USERS_TABLE_NAME2, USER_POOL_ID, log10, recordServiceUserEvent;
|
|
2040
|
+
var init_serviceUsersService = __esm({
|
|
2041
|
+
"lambdacode/api/services/serviceUsersService.ts"() {
|
|
2042
|
+
"use strict";
|
|
2043
|
+
import_client_dynamodb11 = require("@aws-sdk/client-dynamodb");
|
|
2044
|
+
import_lib_dynamodb11 = require("@aws-sdk/lib-dynamodb");
|
|
2045
|
+
import_client_cognito_identity_provider = require("@aws-sdk/client-cognito-identity-provider");
|
|
2046
|
+
init_httpErrors();
|
|
2047
|
+
init_access2();
|
|
2048
|
+
init_grantsService();
|
|
2049
|
+
init_auditService();
|
|
2050
|
+
init_serviceUserRegistry();
|
|
2051
|
+
init_serviceUserRegistry();
|
|
2052
|
+
ddb11 = import_lib_dynamodb11.DynamoDBDocumentClient.from(new import_client_dynamodb11.DynamoDBClient({}));
|
|
2053
|
+
cognito = new import_client_cognito_identity_provider.CognitoIdentityProviderClient({});
|
|
2054
|
+
SERVICE_USERS_TABLE_NAME2 = process.env.SERVICE_USERS_TABLE_NAME;
|
|
2055
|
+
USER_POOL_ID = process.env.USER_POOL_ID;
|
|
2056
|
+
log10 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
2057
|
+
recordServiceUserEvent = (action, clientId, name, details) => recordEvent({
|
|
2058
|
+
category: "users",
|
|
2059
|
+
action,
|
|
2060
|
+
subject: { kind: "serviceUser", id: clientId, name },
|
|
2061
|
+
target: `serviceuser#${clientId}`,
|
|
2062
|
+
targetName: name,
|
|
2063
|
+
details
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
// lambdacode/api/services/claims.ts
|
|
2069
|
+
function customClaims(source) {
|
|
2070
|
+
const out = {};
|
|
2071
|
+
for (const [k, v] of Object.entries(source)) {
|
|
2072
|
+
if (k.startsWith(CUSTOM_PREFIX) && typeof v === "string" && v !== "") {
|
|
2073
|
+
out[k.slice(CUSTOM_PREFIX.length)] = v;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return out;
|
|
2077
|
+
}
|
|
2078
|
+
var CUSTOM_PREFIX;
|
|
2079
|
+
var init_claims = __esm({
|
|
2080
|
+
"lambdacode/api/services/claims.ts"() {
|
|
2081
|
+
"use strict";
|
|
2082
|
+
CUSTOM_PREFIX = "custom:";
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
// lambdacode/api/services/directory.ts
|
|
2087
|
+
async function listDirectoryUsers() {
|
|
2088
|
+
const users = [];
|
|
2089
|
+
let PaginationToken;
|
|
2090
|
+
do {
|
|
2091
|
+
const page = await cognito2.send(new import_client_cognito_identity_provider2.ListUsersCommand({ UserPoolId: USER_POOL_ID2, PaginationToken }));
|
|
2092
|
+
for (const u of page?.Users ?? []) {
|
|
2093
|
+
const attrs = u.Attributes ?? [];
|
|
2094
|
+
const sub = attrs.find((a) => a.Name === "sub")?.Value;
|
|
2095
|
+
if (!sub) continue;
|
|
2096
|
+
if (u.UserStatus === "EXTERNAL_PROVIDER") continue;
|
|
2097
|
+
const claims = customClaims(Object.fromEntries(attrs.map((a) => [a.Name ?? "", a.Value])));
|
|
2098
|
+
users.push({
|
|
2099
|
+
userId: sub,
|
|
2100
|
+
email: attrs.find((a) => a.Name === "email")?.Value ?? "",
|
|
2101
|
+
pendingInvite: u.UserStatus === "FORCE_CHANGE_PASSWORD",
|
|
2102
|
+
...Object.keys(claims).length ? { claims } : {}
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
PaginationToken = page.PaginationToken;
|
|
2106
|
+
} while (PaginationToken);
|
|
2107
|
+
return users;
|
|
2108
|
+
}
|
|
2109
|
+
var import_client_cognito_identity_provider2, cognito2, USER_POOL_ID2;
|
|
2110
|
+
var init_directory = __esm({
|
|
2111
|
+
"lambdacode/api/services/directory.ts"() {
|
|
2112
|
+
"use strict";
|
|
2113
|
+
import_client_cognito_identity_provider2 = require("@aws-sdk/client-cognito-identity-provider");
|
|
2114
|
+
init_claims();
|
|
2115
|
+
cognito2 = new import_client_cognito_identity_provider2.CognitoIdentityProviderClient({});
|
|
2116
|
+
USER_POOL_ID2 = process.env.USER_POOL_ID;
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
// lambdacode/api/services/preferencesService.ts
|
|
2121
|
+
var import_client_dynamodb12, import_lib_dynamodb12, ddb12, PRESENCE_THROTTLE_MS;
|
|
2122
|
+
var init_preferencesService = __esm({
|
|
2123
|
+
"lambdacode/api/services/preferencesService.ts"() {
|
|
2124
|
+
"use strict";
|
|
2125
|
+
import_client_dynamodb12 = require("@aws-sdk/client-dynamodb");
|
|
2126
|
+
import_lib_dynamodb12 = require("@aws-sdk/lib-dynamodb");
|
|
2127
|
+
init_access2();
|
|
2128
|
+
init_httpErrors();
|
|
2129
|
+
ddb12 = import_lib_dynamodb12.DynamoDBDocumentClient.from(new import_client_dynamodb12.DynamoDBClient({}));
|
|
2130
|
+
PRESENCE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
// lambdacode/api/services/sharesService.ts
|
|
2135
|
+
function parseConfig() {
|
|
2136
|
+
const off = { enabled: false, maxDurationHours: 8, allowGroupTargets: true };
|
|
2137
|
+
const raw = process.env.DEV_SHARES;
|
|
2138
|
+
if (!raw) return off;
|
|
2139
|
+
try {
|
|
2140
|
+
const parsed = JSON.parse(raw);
|
|
2141
|
+
const max = Number(parsed.maxDurationHours);
|
|
2142
|
+
return {
|
|
2143
|
+
enabled: parsed.enabled === true,
|
|
2144
|
+
maxDurationHours: Number.isInteger(max) && max >= 1 && max <= 72 ? max : 8,
|
|
2145
|
+
allowGroupTargets: parsed.allowGroupTargets !== false
|
|
2146
|
+
};
|
|
2147
|
+
} catch {
|
|
2148
|
+
return off;
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
async function listShareDefs(now = Date.now()) {
|
|
2152
|
+
const live = [];
|
|
2153
|
+
for (const row of await queryPrincipal(SHARE_DEF_PRINCIPAL, "share#")) {
|
|
2154
|
+
if (!isShareDefRow(row)) continue;
|
|
2155
|
+
if (!isExpired(row, now)) {
|
|
2156
|
+
live.push(row);
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
await deleteShareRows(row.shareId).catch(
|
|
2160
|
+
(err) => log11("WARN: expired share cleanup failed", { shareId: row.shareId, error: err.message })
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
return live;
|
|
2164
|
+
}
|
|
2165
|
+
async function holdersFromRows(rows) {
|
|
2166
|
+
const users = /* @__PURE__ */ new Set();
|
|
2167
|
+
for (const r of rows) {
|
|
2168
|
+
if (isExpired(r)) continue;
|
|
2169
|
+
const p = parsePrincipal(r.principal);
|
|
2170
|
+
if (p?.kind === "user") users.add(p.userId);
|
|
2171
|
+
else if (p?.kind === "group") for (const u of await membersOf(p.groupId)) users.add(u);
|
|
2172
|
+
}
|
|
2173
|
+
return [...users];
|
|
2174
|
+
}
|
|
2175
|
+
async function endShare(def, via) {
|
|
2176
|
+
const rows = await queryTarget(def.target);
|
|
2177
|
+
const holders = await holdersFromRows(rows);
|
|
2178
|
+
const batchId = newBatchId();
|
|
2179
|
+
await withAccessDiff(holders, via, () => deleteShareRows(def.shareId, rows), /* @__PURE__ */ new Map([[def.shareId, def.label]]), { batchId });
|
|
2180
|
+
await recordEvent({
|
|
2181
|
+
category: "access",
|
|
2182
|
+
action: "share.stop",
|
|
2183
|
+
subject: { kind: "user", id: def.ownerSub, name: def.ownerName },
|
|
2184
|
+
target: def.target,
|
|
2185
|
+
targetName: def.label,
|
|
2186
|
+
details: { via, batchId }
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
async function removeSharesOwnedBy(ownerSub) {
|
|
2190
|
+
const mine = (await listShareDefs()).filter((d) => d.ownerSub === ownerSub);
|
|
2191
|
+
for (const def of mine) await endShare(def, "ADMIN");
|
|
2192
|
+
if (mine.length) log11("shares removed with owner", { ownerSub, count: mine.length });
|
|
2193
|
+
return mine.length;
|
|
2194
|
+
}
|
|
2195
|
+
var log11, CONFIG, TTL_GRACE_S;
|
|
2196
|
+
var init_sharesService = __esm({
|
|
2197
|
+
"lambdacode/api/services/sharesService.ts"() {
|
|
2198
|
+
"use strict";
|
|
2199
|
+
init_access2();
|
|
2200
|
+
init_accessNotices();
|
|
2201
|
+
init_auditService();
|
|
2202
|
+
init_directory();
|
|
2203
|
+
init_preferencesService();
|
|
2204
|
+
init_groupsService();
|
|
2205
|
+
init_httpErrors();
|
|
2206
|
+
log11 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
2207
|
+
CONFIG = parseConfig();
|
|
2208
|
+
TTL_GRACE_S = 24 * 60 * 60;
|
|
2209
|
+
}
|
|
2210
|
+
});
|
|
2211
|
+
|
|
2212
|
+
// lambdacode/api/services/tempPassword.ts
|
|
2213
|
+
function tempPassword(len = 24) {
|
|
2214
|
+
const all = TEMP_PW_SETS.join("");
|
|
2215
|
+
const chars = TEMP_PW_SETS.map((s) => s[(0, import_crypto3.randomInt)(s.length)]);
|
|
2216
|
+
while (chars.length < len) chars.push(all[(0, import_crypto3.randomInt)(all.length)]);
|
|
2217
|
+
for (let i = chars.length - 1; i > 0; i--) {
|
|
2218
|
+
const j = (0, import_crypto3.randomInt)(i + 1);
|
|
2219
|
+
[chars[i], chars[j]] = [chars[j], chars[i]];
|
|
2220
|
+
}
|
|
2221
|
+
return chars.join("");
|
|
2222
|
+
}
|
|
2223
|
+
var import_crypto3, TEMP_PW_SETS;
|
|
2224
|
+
var init_tempPassword = __esm({
|
|
2225
|
+
"lambdacode/api/services/tempPassword.ts"() {
|
|
2226
|
+
"use strict";
|
|
2227
|
+
import_crypto3 = require("crypto");
|
|
2228
|
+
TEMP_PW_SETS = [
|
|
2229
|
+
"ABCDEFGHJKMNPQRSTUVWXYZ",
|
|
2230
|
+
"abcdefghjkmnpqrstuvwxyz",
|
|
2231
|
+
"23456789",
|
|
2232
|
+
"!$*._~-"
|
|
2233
|
+
];
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
|
|
2237
|
+
// lambdacode/api/services/usersService.ts
|
|
2238
|
+
var usersService_exports = {};
|
|
2239
|
+
__export(usersService_exports, {
|
|
2240
|
+
ADMIN_GROUP: () => ADMIN_GROUP,
|
|
2241
|
+
LIST_CAP: () => LIST_CAP,
|
|
2242
|
+
assertUserExists: () => assertUserExists,
|
|
2243
|
+
capLists: () => capLists,
|
|
2244
|
+
createUser: () => createUser,
|
|
2245
|
+
createUsers: () => createUsers,
|
|
2246
|
+
deleteUser: () => deleteUser,
|
|
2247
|
+
deleteUsers: () => deleteUsers,
|
|
2248
|
+
getUser: () => getUser,
|
|
2249
|
+
listAdminSubs: () => listAdminSubs,
|
|
2250
|
+
listUsers: () => listUsers,
|
|
2251
|
+
mintSignupLink: () => mintSignupLink,
|
|
2252
|
+
normalizeEmail: () => normalizeEmail,
|
|
2253
|
+
resendInvite: () => resendInvite,
|
|
2254
|
+
resetUserMfa: () => resetUserMfa,
|
|
2255
|
+
signupLink: () => signupLink,
|
|
2256
|
+
updateUser: () => updateUser
|
|
2257
|
+
});
|
|
2258
|
+
async function listUsers() {
|
|
2259
|
+
const [directory, adminSubs, graph, resourceMeta, groups] = await Promise.all([
|
|
2260
|
+
listDirectory(),
|
|
2261
|
+
listAdminSubs(),
|
|
2262
|
+
snapshot(),
|
|
2263
|
+
scanResourceMeta(),
|
|
2264
|
+
listGroups()
|
|
2265
|
+
]);
|
|
2266
|
+
const groupName = new Map(groups.map((g) => [g.groupId, g.name]));
|
|
2267
|
+
const users = directory.map((u) => {
|
|
2268
|
+
const { roles, resources } = accessLists(graph.users.get(u.userId)?.values() ?? [], groupName, resourceMeta);
|
|
2269
|
+
return {
|
|
2270
|
+
userId: u.userId,
|
|
2271
|
+
email: u.email,
|
|
2272
|
+
admin: adminSubs.has(u.userId),
|
|
2273
|
+
// A federated sign-in never runs the set-password step, so a user who only ever signs in
|
|
2274
|
+
// that way stays FORCE_CHANGE_PASSWORD in Cognito; being seen by the API proves they got in.
|
|
2275
|
+
pendingInvite: u.pendingInvite && !graph.presence.has(u.userId),
|
|
2276
|
+
lastSeenAt: graph.presence.get(u.userId),
|
|
2277
|
+
...u.claims ? { claims: u.claims } : {},
|
|
2278
|
+
roles,
|
|
2279
|
+
roleCount: roles.length,
|
|
2280
|
+
resources,
|
|
2281
|
+
resourceCount: resources.length
|
|
2282
|
+
};
|
|
2283
|
+
});
|
|
2284
|
+
log12("listed users", { count: users.length });
|
|
2285
|
+
return users;
|
|
2286
|
+
}
|
|
2287
|
+
function capLists(u) {
|
|
2288
|
+
const roleNames = [...new Set(u.roles.map((r) => r.roleName))].sort();
|
|
2289
|
+
const groupNames = [.../* @__PURE__ */ new Set([...u.roles.flatMap((r) => r.groups), ...u.resources.flatMap((r) => r.groups)])].sort();
|
|
2290
|
+
return { ...u, roles: u.roles.slice(0, LIST_CAP), resources: u.resources.slice(0, LIST_CAP), roleNames, groupNames };
|
|
2291
|
+
}
|
|
2292
|
+
function accessLists(entries, groupName, resourceMeta) {
|
|
2293
|
+
const roles = [];
|
|
2294
|
+
const resources = [];
|
|
2295
|
+
for (const e of entries) {
|
|
2296
|
+
if (!e.active) continue;
|
|
2297
|
+
const p = parseTarget(e.target);
|
|
2298
|
+
const teams = e.groups.map((g) => groupName.get(g) ?? g).sort();
|
|
2299
|
+
const direct = Boolean(e.direct && !isExpired(e.direct));
|
|
2300
|
+
if (p?.kind === "role") {
|
|
2301
|
+
roles.push({ accountNumber: p.accountNumber, roleName: p.roleName, direct, groups: teams });
|
|
2302
|
+
} else if (p?.kind === "resource") {
|
|
2303
|
+
const meta = resourceMeta.get(p.resourceId);
|
|
2304
|
+
resources.push({
|
|
2305
|
+
resourceId: p.resourceId,
|
|
2306
|
+
name: meta?.name ?? "(deleted resource)",
|
|
2307
|
+
accountNumber: meta?.accountNumber,
|
|
2308
|
+
direct,
|
|
2309
|
+
groups: teams
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
roles.sort((a, b) => a.accountNumber.localeCompare(b.accountNumber) || a.roleName.localeCompare(b.roleName));
|
|
2314
|
+
resources.sort((a, b) => a.name.localeCompare(b.name));
|
|
2315
|
+
return { roles, resources };
|
|
2316
|
+
}
|
|
2317
|
+
async function assertUserExists(userId) {
|
|
2318
|
+
if (await getServiceUserName(userId)) return;
|
|
2319
|
+
try {
|
|
2320
|
+
await cognito3.send(new import_client_cognito_identity_provider3.AdminGetUserCommand({ UserPoolId: USER_POOL_ID3, Username: userId }));
|
|
2321
|
+
} catch (err) {
|
|
2322
|
+
if (err.name === "UserNotFoundException") throw new NotFoundError(`User ${userId} not found`);
|
|
2323
|
+
throw err;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
async function getUser(userId) {
|
|
2327
|
+
let found;
|
|
2328
|
+
try {
|
|
2329
|
+
found = await cognito3.send(new import_client_cognito_identity_provider3.AdminGetUserCommand({ UserPoolId: USER_POOL_ID3, Username: userId }));
|
|
2330
|
+
} catch (err) {
|
|
2331
|
+
if (err.name === "UserNotFoundException") throw new NotFoundError(`User ${userId} not found`);
|
|
2332
|
+
throw err;
|
|
2333
|
+
}
|
|
2334
|
+
const attrs = Object.fromEntries((found.UserAttributes ?? []).map((a) => [a.Name ?? "", a.Value]));
|
|
2335
|
+
const [adminSubs, entries, resourceMeta, index, presence] = await Promise.all([
|
|
2336
|
+
listAdminSubs(),
|
|
2337
|
+
effectiveAccess(userId),
|
|
2338
|
+
scanResourceMeta(),
|
|
2339
|
+
catalog(),
|
|
2340
|
+
getRow(userPrincipal(userId), PRESENCE_TARGET)
|
|
2341
|
+
]);
|
|
2342
|
+
const { roles, resources } = accessLists(entries.values(), index.groupName, resourceMeta);
|
|
2343
|
+
const claims = customClaims(attrs);
|
|
2344
|
+
const lastSeenAt = typeof presence?.lastSeenAt === "string" ? presence.lastSeenAt : void 0;
|
|
2345
|
+
return {
|
|
2346
|
+
userId,
|
|
2347
|
+
email: attrs.email ?? "",
|
|
2348
|
+
admin: adminSubs.has(userId),
|
|
2349
|
+
pendingInvite: found.UserStatus === "FORCE_CHANGE_PASSWORD" && !lastSeenAt,
|
|
2350
|
+
lastSeenAt,
|
|
2351
|
+
...Object.keys(claims).length ? { claims } : {},
|
|
2352
|
+
roles,
|
|
2353
|
+
roleCount: roles.length,
|
|
2354
|
+
resources,
|
|
2355
|
+
resourceCount: resources.length
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
async function listAdminSubs() {
|
|
2359
|
+
const subs = /* @__PURE__ */ new Set();
|
|
2360
|
+
let NextToken;
|
|
2361
|
+
do {
|
|
2362
|
+
const page = await cognito3.send(
|
|
2363
|
+
new import_client_cognito_identity_provider3.ListUsersInGroupCommand({
|
|
2364
|
+
UserPoolId: USER_POOL_ID3,
|
|
2365
|
+
GroupName: ADMIN_GROUP,
|
|
2366
|
+
NextToken
|
|
2367
|
+
})
|
|
2368
|
+
);
|
|
2369
|
+
for (const u of page?.Users ?? []) {
|
|
2370
|
+
const sub = (u.Attributes ?? []).find((a) => a.Name === "sub")?.Value;
|
|
2371
|
+
if (sub) subs.add(sub);
|
|
2372
|
+
}
|
|
2373
|
+
NextToken = page?.NextToken;
|
|
2374
|
+
} while (NextToken);
|
|
2375
|
+
return subs;
|
|
2376
|
+
}
|
|
2377
|
+
async function scanResourceMeta() {
|
|
2378
|
+
const meta = /* @__PURE__ */ new Map();
|
|
2379
|
+
let ExclusiveStartKey;
|
|
2380
|
+
do {
|
|
2381
|
+
const page = await ddb13.send(
|
|
2382
|
+
new import_lib_dynamodb13.ScanCommand({
|
|
2383
|
+
TableName: RESOURCES_TABLE_NAME5,
|
|
2384
|
+
ProjectionExpression: "resourceId, #n, accountNumber",
|
|
2385
|
+
ExpressionAttributeNames: { "#n": "name" },
|
|
2386
|
+
// reserved word
|
|
2387
|
+
ExclusiveStartKey
|
|
2388
|
+
})
|
|
2389
|
+
);
|
|
2390
|
+
for (const it of page.Items ?? []) {
|
|
2391
|
+
if (isSentinelRow(it.resourceId)) continue;
|
|
2392
|
+
meta.set(it.resourceId, {
|
|
2393
|
+
name: it.name,
|
|
2394
|
+
accountNumber: it.accountNumber
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
ExclusiveStartKey = page.LastEvaluatedKey;
|
|
2398
|
+
} while (ExclusiveStartKey);
|
|
2399
|
+
return meta;
|
|
2400
|
+
}
|
|
2401
|
+
async function createUser(rawEmail, admin, sendEmail = true) {
|
|
2402
|
+
const user = await invite(rawEmail, admin, sendEmail);
|
|
2403
|
+
await recordEvent(inviteRow(user, sendEmail ? "email" : "link"));
|
|
2404
|
+
return sendEmail ? withoutLink(user) : user;
|
|
2405
|
+
}
|
|
2406
|
+
async function invite(rawEmail, admin, sendEmail) {
|
|
2407
|
+
const email = normalizeEmail(rawEmail);
|
|
2408
|
+
const temp = tempPassword();
|
|
2409
|
+
try {
|
|
2410
|
+
const res = await cognito3.send(
|
|
2411
|
+
new import_client_cognito_identity_provider3.AdminCreateUserCommand({
|
|
2412
|
+
UserPoolId: USER_POOL_ID3,
|
|
2413
|
+
Username: email,
|
|
2414
|
+
UserAttributes: [
|
|
2415
|
+
{ Name: "email", Value: email },
|
|
2416
|
+
{ Name: "email_verified", Value: "true" }
|
|
2417
|
+
],
|
|
2418
|
+
TemporaryPassword: temp,
|
|
2419
|
+
DesiredDeliveryMediums: ["EMAIL"],
|
|
2420
|
+
...sendEmail ? {} : { MessageAction: "SUPPRESS" }
|
|
2421
|
+
})
|
|
2422
|
+
);
|
|
2423
|
+
const userId = res.User?.Attributes?.find((a) => a.Name === SUB_ATTR)?.Value ?? "";
|
|
2424
|
+
const username = res.User?.Username ?? email;
|
|
2425
|
+
if (admin) {
|
|
2426
|
+
await cognito3.send(
|
|
2427
|
+
new import_client_cognito_identity_provider3.AdminAddUserToGroupCommand({
|
|
2428
|
+
UserPoolId: USER_POOL_ID3,
|
|
2429
|
+
Username: userId || email,
|
|
2430
|
+
GroupName: ADMIN_GROUP
|
|
2431
|
+
})
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
await linkFederatedProviders(username, email);
|
|
2435
|
+
log12("created user", { userId, email, admin, sendEmail });
|
|
2436
|
+
return { userId, email, admin, pendingInvite: true, roles: [], roleCount: 0, resources: [], resourceCount: 0, signupLink: signupLink(username, temp) };
|
|
2437
|
+
} catch (err) {
|
|
2438
|
+
throw translateCognitoError(err);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
async function federatedProviders() {
|
|
2442
|
+
if (providersCache && Date.now() - providersCache.at < PROVIDERS_TTL_MS) return providersCache.value;
|
|
2443
|
+
const value = (async () => {
|
|
2444
|
+
const names = [];
|
|
2445
|
+
let NextToken;
|
|
2446
|
+
do {
|
|
2447
|
+
const page = await cognito3.send(
|
|
2448
|
+
new import_client_cognito_identity_provider3.ListIdentityProvidersCommand({ UserPoolId: USER_POOL_ID3, MaxResults: 60, NextToken })
|
|
2449
|
+
);
|
|
2450
|
+
for (const p of page.Providers ?? []) if (p.ProviderName && p.ProviderType === "OIDC") names.push(p.ProviderName);
|
|
2451
|
+
NextToken = page.NextToken;
|
|
2452
|
+
} while (NextToken);
|
|
2453
|
+
return names;
|
|
2454
|
+
})();
|
|
2455
|
+
providersCache = { at: Date.now(), value };
|
|
2456
|
+
value.catch(() => providersCache = void 0);
|
|
2457
|
+
return value;
|
|
2458
|
+
}
|
|
2459
|
+
async function linkFederatedProviders(username, email) {
|
|
2460
|
+
let providers;
|
|
2461
|
+
try {
|
|
2462
|
+
providers = await federatedProviders();
|
|
2463
|
+
} catch (err) {
|
|
2464
|
+
log12("WARN: could not list federated providers (trigger links at first sign-in)", {
|
|
2465
|
+
error: err.message
|
|
2466
|
+
});
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
await Promise.all(
|
|
2470
|
+
providers.map(
|
|
2471
|
+
(provider) => cognito3.send(
|
|
2472
|
+
new import_client_cognito_identity_provider3.AdminLinkProviderForUserCommand({
|
|
2473
|
+
UserPoolId: USER_POOL_ID3,
|
|
2474
|
+
DestinationUser: { ProviderName: "Cognito", ProviderAttributeValue: username },
|
|
2475
|
+
SourceUser: { ProviderName: provider, ProviderAttributeName: LINK_ATTRIBUTE, ProviderAttributeValue: email }
|
|
2476
|
+
})
|
|
2477
|
+
).catch(
|
|
2478
|
+
(err) => log12("WARN: federated link failed (trigger links at first sign-in)", {
|
|
2479
|
+
provider,
|
|
2480
|
+
email,
|
|
2481
|
+
error: err.message
|
|
2482
|
+
})
|
|
2483
|
+
)
|
|
2484
|
+
)
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
async function unlinkFederatedProviders(email) {
|
|
2488
|
+
let providers;
|
|
2489
|
+
try {
|
|
2490
|
+
providers = await federatedProviders();
|
|
2491
|
+
} catch (err) {
|
|
2492
|
+
log12("WARN: could not list federated providers (old email link kept)", { error: err.message });
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
await Promise.all(
|
|
2496
|
+
providers.map(
|
|
2497
|
+
(provider) => cognito3.send(
|
|
2498
|
+
new import_client_cognito_identity_provider3.AdminDisableProviderForUserCommand({
|
|
2499
|
+
UserPoolId: USER_POOL_ID3,
|
|
2500
|
+
User: { ProviderName: provider, ProviderAttributeName: LINK_ATTRIBUTE, ProviderAttributeValue: email }
|
|
2501
|
+
})
|
|
2502
|
+
).catch(
|
|
2503
|
+
(err) => log12("WARN: could not drop old federated link", { provider, email, error: err.message })
|
|
2504
|
+
)
|
|
2505
|
+
)
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
async function createUsers(emails, admin, sendEmail = true) {
|
|
2509
|
+
const results = [];
|
|
2510
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2511
|
+
const batchId = newBatchId();
|
|
2512
|
+
const rows = [];
|
|
2513
|
+
for (const raw of emails) {
|
|
2514
|
+
const email = typeof raw === "string" ? raw.trim() : "";
|
|
2515
|
+
const key = email.toLowerCase();
|
|
2516
|
+
if (seen.has(key)) continue;
|
|
2517
|
+
seen.add(key);
|
|
2518
|
+
if (!BULK_EMAIL_RE.test(email)) {
|
|
2519
|
+
const message = "Not a valid email address";
|
|
2520
|
+
results.push({ email: email || String(raw), status: "FAILED", message });
|
|
2521
|
+
rows.push(failedInviteRow(email || String(raw), admin, message, batchId));
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
try {
|
|
2525
|
+
const user = await invite(email, admin, sendEmail);
|
|
2526
|
+
results.push({ email, status: "CREATED", userId: user.userId, ...sendEmail ? {} : { signupLink: user.signupLink } });
|
|
2527
|
+
rows.push(inviteRow(user, sendEmail ? "email" : "link", batchId));
|
|
2528
|
+
} catch (err) {
|
|
2529
|
+
const message = err.message || "Could not create the user";
|
|
2530
|
+
results.push({ email, status: "FAILED", message });
|
|
2531
|
+
rows.push(failedInviteRow(email, admin, message, batchId));
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
await recordEvents(rows);
|
|
2535
|
+
const created = results.filter((r) => r.status === "CREATED").length;
|
|
2536
|
+
log12("bulk created users", { requested: emails.length, created, admin, sendEmail });
|
|
2537
|
+
return results;
|
|
2538
|
+
}
|
|
2539
|
+
async function identityBySub(userId) {
|
|
2540
|
+
try {
|
|
2541
|
+
const user = await cognito3.send(new import_client_cognito_identity_provider3.AdminGetUserCommand({ UserPoolId: USER_POOL_ID3, Username: userId }));
|
|
2542
|
+
const email = user.UserAttributes?.find((a) => a.Name === "email")?.Value;
|
|
2543
|
+
if (!email) return void 0;
|
|
2544
|
+
return { email, username: user.Username ?? email, status: user.UserStatus };
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
if (err.name === "UserNotFoundException") return void 0;
|
|
2547
|
+
throw err;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
async function mintSignupLink(userId) {
|
|
2551
|
+
const who = await identityBySub(userId);
|
|
2552
|
+
if (!who) throw new NotFoundError("User not found");
|
|
2553
|
+
if (who.status && who.status !== "FORCE_CHANGE_PASSWORD") {
|
|
2554
|
+
throw new ConflictError("This user has already set a password");
|
|
2555
|
+
}
|
|
2556
|
+
const temp = tempPassword();
|
|
2557
|
+
try {
|
|
2558
|
+
await cognito3.send(
|
|
2559
|
+
new import_client_cognito_identity_provider3.AdminSetUserPasswordCommand({ UserPoolId: USER_POOL_ID3, Username: who.username, Password: temp, Permanent: false })
|
|
2560
|
+
);
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
throw translateCognitoError(err);
|
|
2563
|
+
}
|
|
2564
|
+
await recordEvent({
|
|
2565
|
+
category: "users",
|
|
2566
|
+
action: "user.reinvite",
|
|
2567
|
+
subject: { kind: "user", id: userId, name: who.email },
|
|
2568
|
+
target: userTarget(userId),
|
|
2569
|
+
details: { delivery: "link" }
|
|
2570
|
+
});
|
|
2571
|
+
log12("minted sign-up link", { userId });
|
|
2572
|
+
return signupLink(who.username, temp);
|
|
2573
|
+
}
|
|
2574
|
+
async function resendInvite(userId) {
|
|
2575
|
+
const email = await emailBySub(userId);
|
|
2576
|
+
if (!email) {
|
|
2577
|
+
throw new NotFoundError("User not found");
|
|
2578
|
+
}
|
|
2579
|
+
try {
|
|
2580
|
+
await cognito3.send(
|
|
2581
|
+
new import_client_cognito_identity_provider3.AdminCreateUserCommand({
|
|
2582
|
+
UserPoolId: USER_POOL_ID3,
|
|
2583
|
+
Username: email,
|
|
2584
|
+
MessageAction: "RESEND",
|
|
2585
|
+
// Without this, RESEND regenerates with Cognito's own alphabet,
|
|
2586
|
+
// which isn't URL-fragment-safe (see tempPassword).
|
|
2587
|
+
TemporaryPassword: tempPassword(),
|
|
2588
|
+
DesiredDeliveryMediums: ["EMAIL"]
|
|
2589
|
+
})
|
|
2590
|
+
);
|
|
2591
|
+
} catch (err) {
|
|
2592
|
+
throw translateCognitoError(err);
|
|
2593
|
+
}
|
|
2594
|
+
await recordEvent({
|
|
2595
|
+
category: "users",
|
|
2596
|
+
action: "user.reinvite",
|
|
2597
|
+
subject: { kind: "user", id: userId, name: email },
|
|
2598
|
+
target: userTarget(userId),
|
|
2599
|
+
details: { delivery: "email" }
|
|
2600
|
+
});
|
|
2601
|
+
log12("resent invite", { userId, email });
|
|
2602
|
+
}
|
|
2603
|
+
async function resetUserMfa(userId) {
|
|
2604
|
+
const email = await emailBySub(userId);
|
|
2605
|
+
if (!email) {
|
|
2606
|
+
throw new NotFoundError("User not found");
|
|
2607
|
+
}
|
|
2608
|
+
await cognito3.send(
|
|
2609
|
+
new import_client_cognito_identity_provider3.AdminSetUserMFAPreferenceCommand({
|
|
2610
|
+
UserPoolId: USER_POOL_ID3,
|
|
2611
|
+
Username: email,
|
|
2612
|
+
SoftwareTokenMfaSettings: { Enabled: false, PreferredMfa: false }
|
|
2613
|
+
})
|
|
2614
|
+
);
|
|
2615
|
+
await recordEvent({
|
|
2616
|
+
category: "users",
|
|
2617
|
+
action: "user.mfa.reset",
|
|
2618
|
+
subject: { kind: "user", id: userId, name: email },
|
|
2619
|
+
target: userTarget(userId)
|
|
2620
|
+
});
|
|
2621
|
+
log12("reset MFA", { userId, email });
|
|
2622
|
+
}
|
|
2623
|
+
async function signOutEverywhere(userId) {
|
|
2624
|
+
try {
|
|
2625
|
+
await cognito3.send(new import_client_cognito_identity_provider3.AdminUserGlobalSignOutCommand({ UserPoolId: USER_POOL_ID3, Username: userId }));
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
if (err.name !== "UserNotFoundException") throw err;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
async function assertNotLastAdmin(userId, admins) {
|
|
2631
|
+
admins ??= await listAdminSubs();
|
|
2632
|
+
if (admins.has(userId) && admins.size <= 1) {
|
|
2633
|
+
throw new ConflictError(
|
|
2634
|
+
"That would remove the last admin \u2014 make another user an admin first"
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
async function updateUser(userId, changes) {
|
|
2639
|
+
const admins = changes.admin === void 0 ? void 0 : await listAdminSubs();
|
|
2640
|
+
if (changes.admin === false) await assertNotLastAdmin(userId, admins);
|
|
2641
|
+
const rows = [];
|
|
2642
|
+
try {
|
|
2643
|
+
if (changes.email !== void 0) {
|
|
2644
|
+
const email = normalizeEmail(changes.email);
|
|
2645
|
+
const previous = await emailBySub(userId).catch(() => void 0);
|
|
2646
|
+
await cognito3.send(
|
|
2647
|
+
new import_client_cognito_identity_provider3.AdminUpdateUserAttributesCommand({
|
|
2648
|
+
UserPoolId: USER_POOL_ID3,
|
|
2649
|
+
Username: userId,
|
|
2650
|
+
UserAttributes: [
|
|
2651
|
+
{ Name: "email", Value: email },
|
|
2652
|
+
{ Name: "email_verified", Value: "true" }
|
|
2653
|
+
]
|
|
2654
|
+
})
|
|
2655
|
+
);
|
|
2656
|
+
if (previous && previous !== email) await unlinkFederatedProviders(previous);
|
|
2657
|
+
await linkFederatedProviders(userId, email);
|
|
2658
|
+
if (previous !== email) {
|
|
2659
|
+
rows.push({
|
|
2660
|
+
category: "users",
|
|
2661
|
+
action: "user.email.change",
|
|
2662
|
+
subject: { kind: "user", id: userId, name: email },
|
|
2663
|
+
target: userTarget(userId),
|
|
2664
|
+
details: { changed: { email: { from: previous, to: email } } }
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (changes.admin !== void 0) {
|
|
2669
|
+
const cmd = changes.admin ? new import_client_cognito_identity_provider3.AdminAddUserToGroupCommand({
|
|
2670
|
+
UserPoolId: USER_POOL_ID3,
|
|
2671
|
+
Username: userId,
|
|
2672
|
+
GroupName: ADMIN_GROUP
|
|
2673
|
+
}) : new import_client_cognito_identity_provider3.AdminRemoveUserFromGroupCommand({
|
|
2674
|
+
UserPoolId: USER_POOL_ID3,
|
|
2675
|
+
Username: userId,
|
|
2676
|
+
GroupName: ADMIN_GROUP
|
|
2677
|
+
});
|
|
2678
|
+
await cognito3.send(cmd);
|
|
2679
|
+
if (!changes.admin && admins.has(userId)) {
|
|
2680
|
+
await signOutEverywhere(userId).catch(
|
|
2681
|
+
(err) => log12("WARN: could not end the demoted admin's sessions", { userId, error: err.message })
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
if (changes.admin !== admins.has(userId)) {
|
|
2685
|
+
rows.push({
|
|
2686
|
+
category: "users",
|
|
2687
|
+
action: changes.admin ? "user.admin.grant" : "user.admin.revoke",
|
|
2688
|
+
subject: { kind: "user", id: userId },
|
|
2689
|
+
target: userTarget(userId)
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
} catch (err) {
|
|
2694
|
+
throw translateCognitoError(err);
|
|
2695
|
+
}
|
|
2696
|
+
await recordEvents(rows);
|
|
2697
|
+
log12("updated user", { userId, ...changes });
|
|
2698
|
+
}
|
|
2699
|
+
async function deleteUser(userId) {
|
|
2700
|
+
await assertNotLastAdmin(userId);
|
|
2701
|
+
await removeSharesOwnedBy(userId);
|
|
2702
|
+
const email = await emailBySub(userId).catch(() => void 0);
|
|
2703
|
+
let alreadyGone = false;
|
|
2704
|
+
try {
|
|
2705
|
+
await signOutEverywhere(userId);
|
|
2706
|
+
await cognito3.send(
|
|
2707
|
+
new import_client_cognito_identity_provider3.AdminDeleteUserCommand({
|
|
2708
|
+
UserPoolId: USER_POOL_ID3,
|
|
2709
|
+
Username: userId
|
|
2710
|
+
})
|
|
2711
|
+
);
|
|
2712
|
+
} catch (err) {
|
|
2713
|
+
const translated = translateCognitoError(err);
|
|
2714
|
+
if (!(translated instanceof NotFoundError)) throw translated;
|
|
2715
|
+
alreadyGone = true;
|
|
2716
|
+
}
|
|
2717
|
+
await revokeAllForUser(userId);
|
|
2718
|
+
await revokeAllResourcesForUser(userId);
|
|
2719
|
+
await deleteRequestsForUser(userId);
|
|
2720
|
+
await removeUserFromAllGroups(userId);
|
|
2721
|
+
await recordEvent({
|
|
2722
|
+
category: "users",
|
|
2723
|
+
action: "user.delete",
|
|
2724
|
+
subject: { kind: "user", id: userId, name: email },
|
|
2725
|
+
target: userTarget(userId),
|
|
2726
|
+
targetName: email
|
|
2727
|
+
});
|
|
2728
|
+
if (alreadyGone) {
|
|
2729
|
+
log12("re-swept deleted user", { userId });
|
|
2730
|
+
throw new NotFoundError(`User ${userId} not found`);
|
|
2731
|
+
}
|
|
2732
|
+
log12("deleted user", { userId });
|
|
2733
|
+
}
|
|
2734
|
+
async function deleteUsers(userIds, actingUserId) {
|
|
2735
|
+
const results = [];
|
|
2736
|
+
for (const userId of [...new Set(userIds)].filter(Boolean)) {
|
|
2737
|
+
if (userId === actingUserId) {
|
|
2738
|
+
results.push({
|
|
2739
|
+
userId,
|
|
2740
|
+
status: "FAILED",
|
|
2741
|
+
message: "You can't delete your own account"
|
|
2742
|
+
});
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
try {
|
|
2746
|
+
await deleteUser(userId);
|
|
2747
|
+
results.push({ userId, status: "DELETED" });
|
|
2748
|
+
} catch (err) {
|
|
2749
|
+
results.push({
|
|
2750
|
+
userId,
|
|
2751
|
+
status: "FAILED",
|
|
2752
|
+
message: err.message || "Could not delete the user"
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
const deleted = results.filter((r) => r.status === "DELETED").length;
|
|
2757
|
+
log12("bulk deleted users", { requested: userIds.length, deleted });
|
|
2758
|
+
return results;
|
|
2759
|
+
}
|
|
2760
|
+
function emailLimitMessage(err) {
|
|
2761
|
+
if (err.name !== "LimitExceededException" || !/email/i.test(err.message)) return null;
|
|
2762
|
+
return process.env.EMAIL_CONFIGURED === "1" ? "The email service has reached its daily sending limit, so this invite was not sent and the user was not created. Check the SES sending quota in the AWS console (a sandboxed SES also delivers only to verified addresses) and try again tomorrow." : "Cognito's built-in email sender has reached its daily limit \u2014 about 50 messages per AWS account, shared by invites, sign-in codes and password resets \u2014 so this invite was not sent and the user was not created. Try again tomorrow, or set the `email` block in the deployment config so mail goes through SES from your own domain (see the Email deliverability runbook).";
|
|
2763
|
+
}
|
|
2764
|
+
function translateCognitoError(err) {
|
|
2765
|
+
const name = err.name;
|
|
2766
|
+
switch (name) {
|
|
2767
|
+
case "LimitExceededException": {
|
|
2768
|
+
const friendly = emailLimitMessage(err);
|
|
2769
|
+
return friendly ? new ValidationError(friendly) : err;
|
|
2770
|
+
}
|
|
2771
|
+
case "UsernameExistsException":
|
|
2772
|
+
return new ConflictError("A user with that email already exists");
|
|
2773
|
+
case "UserNotFoundException":
|
|
2774
|
+
return new NotFoundError("User not found");
|
|
2775
|
+
case "AliasExistsException":
|
|
2776
|
+
return new ConflictError("That email is already in use by another user");
|
|
2777
|
+
case "UnsupportedUserStateException":
|
|
2778
|
+
return new ConflictError("User has already completed account setup");
|
|
2779
|
+
case "InvalidParameterException":
|
|
2780
|
+
case "InvalidPasswordException":
|
|
2781
|
+
case "CodeDeliveryFailureException":
|
|
2782
|
+
return new ValidationError(err.message);
|
|
2783
|
+
default:
|
|
2784
|
+
return err;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
var import_client_dynamodb13, import_lib_dynamodb13, import_client_cognito_identity_provider3, ddb13, cognito3, RESOURCES_TABLE_NAME5, USER_POOL_ID3, APP_URL, ADMIN_GROUP, log12, listDirectory, LIST_CAP, SUB_ATTR, normalizeEmail, userTarget, inviteRow, signupLink, failedInviteRow, withoutLink, PROVIDERS_TTL_MS, providersCache, LINK_ATTRIBUTE, BULK_EMAIL_RE, emailBySub;
|
|
2788
|
+
var init_usersService = __esm({
|
|
2789
|
+
"lambdacode/api/services/usersService.ts"() {
|
|
2790
|
+
"use strict";
|
|
2791
|
+
import_client_dynamodb13 = require("@aws-sdk/client-dynamodb");
|
|
2792
|
+
import_lib_dynamodb13 = require("@aws-sdk/lib-dynamodb");
|
|
2793
|
+
import_client_cognito_identity_provider3 = require("@aws-sdk/client-cognito-identity-provider");
|
|
2794
|
+
init_grantsService();
|
|
2795
|
+
init_directory();
|
|
2796
|
+
init_sharesService();
|
|
2797
|
+
init_access2();
|
|
2798
|
+
init_catalogIndex();
|
|
2799
|
+
init_claims();
|
|
2800
|
+
init_resourceGrantsService();
|
|
2801
|
+
init_groupsService();
|
|
2802
|
+
init_requestsService();
|
|
2803
|
+
init_serviceUserRegistry();
|
|
2804
|
+
init_auditService();
|
|
2805
|
+
init_httpErrors();
|
|
2806
|
+
init_tempPassword();
|
|
2807
|
+
init_resourcesService();
|
|
2808
|
+
ddb13 = import_lib_dynamodb13.DynamoDBDocumentClient.from(new import_client_dynamodb13.DynamoDBClient({}));
|
|
2809
|
+
cognito3 = new import_client_cognito_identity_provider3.CognitoIdentityProviderClient({});
|
|
2810
|
+
RESOURCES_TABLE_NAME5 = process.env.RESOURCES_TABLE_NAME;
|
|
2811
|
+
USER_POOL_ID3 = process.env.USER_POOL_ID;
|
|
2812
|
+
APP_URL = process.env.APP_URL ?? "";
|
|
2813
|
+
ADMIN_GROUP = "admins";
|
|
2814
|
+
log12 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
2815
|
+
listDirectory = listDirectoryUsers;
|
|
2816
|
+
LIST_CAP = 50;
|
|
2817
|
+
SUB_ATTR = "sub";
|
|
2818
|
+
normalizeEmail = (email) => email.trim();
|
|
2819
|
+
userTarget = (userId) => `user#${userId}`;
|
|
2820
|
+
inviteRow = (user, delivery, batchId) => ({
|
|
2821
|
+
category: "users",
|
|
2822
|
+
action: "user.invite",
|
|
2823
|
+
subject: { kind: "user", id: user.userId, name: user.email },
|
|
2824
|
+
target: userTarget(user.userId),
|
|
2825
|
+
details: { admin: user.admin, batchId, delivery }
|
|
2826
|
+
});
|
|
2827
|
+
signupLink = (username, temp) => `${APP_URL}/login#inv=${username}:${temp}`;
|
|
2828
|
+
failedInviteRow = (email, admin, reason, batchId) => ({
|
|
2829
|
+
category: "users",
|
|
2830
|
+
action: "user.invite",
|
|
2831
|
+
outcome: "FAILED",
|
|
2832
|
+
reason,
|
|
2833
|
+
subject: { kind: "user", id: email, name: email },
|
|
2834
|
+
details: { admin, batchId }
|
|
2835
|
+
});
|
|
2836
|
+
withoutLink = ({ signupLink: _link, ...user }) => user;
|
|
2837
|
+
PROVIDERS_TTL_MS = 5 * 60 * 1e3;
|
|
2838
|
+
LINK_ATTRIBUTE = "email";
|
|
2839
|
+
BULK_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2840
|
+
emailBySub = async (userId) => (await identityBySub(userId))?.email;
|
|
2841
|
+
}
|
|
2842
|
+
});
|
|
2843
|
+
|
|
2844
|
+
// lambdacode/api/services/requestsService.ts
|
|
2845
|
+
var requestsService_exports = {};
|
|
2846
|
+
__export(requestsService_exports, {
|
|
2847
|
+
ADMIN_REVIEW_REASON: () => ADMIN_REVIEW_REASON,
|
|
2848
|
+
DURATION_PRESET_HOURS: () => DURATION_PRESET_HOURS,
|
|
2849
|
+
GROUP_REVIEW_REASON: () => GROUP_REVIEW_REASON,
|
|
2850
|
+
REAPPROVAL_GRACE_DAYS: () => REAPPROVAL_GRACE_DAYS,
|
|
2851
|
+
REAPPROVAL_INTERVAL_MONTHS: () => REAPPROVAL_INTERVAL_MONTHS,
|
|
2852
|
+
REAPPROVAL_REASON: () => REAPPROVAL_REASON,
|
|
2853
|
+
SERVICE_USER_REVIEW_REASON: () => SERVICE_USER_REVIEW_REASON,
|
|
2854
|
+
approveRequest: () => approveRequest,
|
|
2855
|
+
closePendingRequestsFor: () => closePendingRequestsFor,
|
|
2856
|
+
createPluginReapprovalRequest: () => createPluginReapprovalRequest,
|
|
2857
|
+
createReapprovalRequest: () => createReapprovalRequest,
|
|
2858
|
+
createRequest: () => createRequest,
|
|
2859
|
+
createResourceReapprovalRequest: () => createResourceReapprovalRequest,
|
|
2860
|
+
createResourceRequest: () => createResourceRequest,
|
|
2861
|
+
createReviewRequest: () => createReviewRequest,
|
|
2862
|
+
deleteRequestsForUser: () => deleteRequestsForUser,
|
|
2863
|
+
denyRequest: () => denyRequest,
|
|
2864
|
+
groupTargetOf: () => groupTargetOf,
|
|
2865
|
+
isDurationPreset: () => isDurationPreset,
|
|
2866
|
+
listMyRequests: () => listMyRequests,
|
|
2867
|
+
listRequestableResources: () => listRequestableResources,
|
|
2868
|
+
listRequestableRoles: () => listRequestableRoles,
|
|
2869
|
+
listRequests: () => listRequests,
|
|
2870
|
+
markRequestsSeen: () => markRequestsSeen,
|
|
2871
|
+
targetOfRequest: () => targetOfRequest
|
|
2872
|
+
});
|
|
2873
|
+
function isDurationPreset(hours) {
|
|
2874
|
+
return DURATION_PRESET_HOURS.includes(hours);
|
|
2875
|
+
}
|
|
2876
|
+
async function listRequestableRoles(userId) {
|
|
2877
|
+
const [index, held, pending] = await Promise.all([
|
|
2878
|
+
catalog(),
|
|
2879
|
+
listHeldAccountRoles(userId),
|
|
2880
|
+
listPendingAccountRoles(userId)
|
|
2881
|
+
]);
|
|
2882
|
+
const byAccount = /* @__PURE__ */ new Map();
|
|
2883
|
+
for (const role of index.roleByKey.values()) {
|
|
2884
|
+
const key = `${role.accountNumber}#${role.roleName}`;
|
|
2885
|
+
const grant = held.get(key);
|
|
2886
|
+
if (grant && !isExpired(grant) || pending.has(key)) continue;
|
|
2887
|
+
const account = index.byNumber.get(role.accountNumber);
|
|
2888
|
+
const entry = byAccount.get(role.accountNumber) ?? {
|
|
2889
|
+
accountNumber: role.accountNumber,
|
|
2890
|
+
name: account?.name,
|
|
2891
|
+
...account?.dims ? { dims: account.dims } : {},
|
|
2892
|
+
roles: []
|
|
2893
|
+
};
|
|
2894
|
+
entry.roles.push({
|
|
2895
|
+
roleName: role.roleName,
|
|
2896
|
+
displayName: role.displayName,
|
|
2897
|
+
description: role.description
|
|
2898
|
+
});
|
|
2899
|
+
byAccount.set(role.accountNumber, entry);
|
|
2900
|
+
}
|
|
2901
|
+
const accounts = [...byAccount.values()].sort(
|
|
2902
|
+
(a, b) => (a.name ?? a.accountNumber).localeCompare(b.name ?? b.accountNumber)
|
|
2903
|
+
);
|
|
2904
|
+
for (const a of accounts) {
|
|
2905
|
+
a.roles.sort((x, y) => x.roleName.localeCompare(y.roleName));
|
|
2906
|
+
}
|
|
2907
|
+
log13("listed requestable roles", { userId, accountCount: accounts.length });
|
|
2908
|
+
return accounts;
|
|
2909
|
+
}
|
|
2910
|
+
async function createRequest(userId, accountNumber, roleName, reason, requestedDurationHours) {
|
|
2911
|
+
const role = await ddb14.send(
|
|
2912
|
+
new import_lib_dynamodb14.GetCommand({
|
|
2913
|
+
TableName: ROLES_TABLE_NAME4,
|
|
2914
|
+
Key: { accountNumber, roleName }
|
|
2915
|
+
})
|
|
2916
|
+
);
|
|
2917
|
+
if (!role.Item) {
|
|
2918
|
+
throw new NotFoundError(
|
|
2919
|
+
`Role ${roleName} not found in account ${accountNumber}`
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
if (requestedDurationHours !== void 0 && !isDurationPreset(requestedDurationHours)) {
|
|
2923
|
+
throw new ValidationError(
|
|
2924
|
+
`duration must be one of ${DURATION_PRESET_HOURS.join(", ")} hours`
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
const key = `${accountNumber}#${roleName}`;
|
|
2928
|
+
const [held, pending] = await Promise.all([
|
|
2929
|
+
listHeldAccountRoles(userId),
|
|
2930
|
+
listPendingAccountRoles(userId)
|
|
2931
|
+
]);
|
|
2932
|
+
const heldGrant = held.get(key);
|
|
2933
|
+
if (heldGrant && !heldGrant.expiresAt) {
|
|
2934
|
+
throw new ConflictError(`You already have ${roleName} in ${accountNumber}`);
|
|
2935
|
+
}
|
|
2936
|
+
if (pending.has(key)) {
|
|
2937
|
+
throw new ConflictError(
|
|
2938
|
+
`You already have a pending request for ${roleName} in ${accountNumber}`
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
const request = {
|
|
2942
|
+
requestId: (0, import_crypto4.randomUUID)(),
|
|
2943
|
+
userId,
|
|
2944
|
+
accountNumber,
|
|
2945
|
+
roleName,
|
|
2946
|
+
reason: reason?.trim() || void 0,
|
|
2947
|
+
...requestedDurationHours !== void 0 ? { requestedDurationHours } : {},
|
|
2948
|
+
status: "PENDING",
|
|
2949
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2950
|
+
};
|
|
2951
|
+
await ddb14.send(
|
|
2952
|
+
new import_lib_dynamodb14.PutCommand({ TableName: REQUESTS_TABLE_NAME, Item: request })
|
|
2953
|
+
);
|
|
2954
|
+
await recordEvent({
|
|
2955
|
+
category: "request",
|
|
2956
|
+
action: "request.create",
|
|
2957
|
+
...auditShape(request),
|
|
2958
|
+
details: { requestId: request.requestId, requestedDurationHours, note: request.reason }
|
|
2959
|
+
});
|
|
2960
|
+
log13("created request", {
|
|
2961
|
+
requestId: request.requestId,
|
|
2962
|
+
userId,
|
|
2963
|
+
key,
|
|
2964
|
+
requestedDurationHours
|
|
2965
|
+
});
|
|
2966
|
+
return request;
|
|
2967
|
+
}
|
|
2968
|
+
async function listRequestableResources(userId) {
|
|
2969
|
+
const [resources, index, held, pending] = await Promise.all([
|
|
2970
|
+
listResources(),
|
|
2971
|
+
catalog(),
|
|
2972
|
+
listUserResourceGrants(userId),
|
|
2973
|
+
listPendingResourceIds(userId)
|
|
2974
|
+
]);
|
|
2975
|
+
const accountNames = new Map([...index.byNumber.values()].filter((a) => a.name).map((a) => [a.accountNumber, a.name]));
|
|
2976
|
+
const heldById = new Map(held.map((g) => [g.resourceId, g]));
|
|
2977
|
+
const out = [];
|
|
2978
|
+
for (const r of resources) {
|
|
2979
|
+
const grant = heldById.get(r.resourceId);
|
|
2980
|
+
if (grant && !isExpired(grant) || pending.has(r.resourceId)) continue;
|
|
2981
|
+
out.push({
|
|
2982
|
+
resourceId: r.resourceId,
|
|
2983
|
+
name: r.name,
|
|
2984
|
+
type: r.type,
|
|
2985
|
+
description: r.description,
|
|
2986
|
+
accountNumber: r.accountNumber,
|
|
2987
|
+
accountName: accountNames.get(r.accountNumber)
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
log13("listed requestable resources", { userId, count: out.length });
|
|
2991
|
+
return out;
|
|
2992
|
+
}
|
|
2993
|
+
async function createResourceRequest(userId, resourceId, reason, requestedDurationHours) {
|
|
2994
|
+
const resource = await getResource(resourceId);
|
|
2995
|
+
if (requestedDurationHours !== void 0 && !isDurationPreset(requestedDurationHours)) {
|
|
2996
|
+
throw new ValidationError(
|
|
2997
|
+
`duration must be one of ${DURATION_PRESET_HOURS.join(", ")} hours`
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
const [held, pending] = await Promise.all([
|
|
3001
|
+
listUserResourceGrants(userId),
|
|
3002
|
+
listPendingResourceIds(userId)
|
|
3003
|
+
]);
|
|
3004
|
+
const heldGrant = held.find((g) => g.resourceId === resourceId);
|
|
3005
|
+
if (heldGrant && !heldGrant.expiresAt) {
|
|
3006
|
+
throw new ConflictError(`You already have access to ${resource.name}`);
|
|
3007
|
+
}
|
|
3008
|
+
if (pending.has(resourceId)) {
|
|
3009
|
+
throw new ConflictError(
|
|
3010
|
+
`You already have a pending request for ${resource.name}`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
const request = {
|
|
3014
|
+
requestId: (0, import_crypto4.randomUUID)(),
|
|
3015
|
+
userId,
|
|
3016
|
+
kind: "RESOURCE",
|
|
3017
|
+
resourceId,
|
|
3018
|
+
reason: reason?.trim() || void 0,
|
|
3019
|
+
...requestedDurationHours !== void 0 ? { requestedDurationHours } : {},
|
|
3020
|
+
status: "PENDING",
|
|
3021
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3022
|
+
};
|
|
3023
|
+
await ddb14.send(
|
|
3024
|
+
new import_lib_dynamodb14.PutCommand({ TableName: REQUESTS_TABLE_NAME, Item: request })
|
|
3025
|
+
);
|
|
3026
|
+
await recordEvent({
|
|
3027
|
+
category: "request",
|
|
3028
|
+
action: "request.create",
|
|
3029
|
+
...auditShape(request),
|
|
3030
|
+
targetName: resource.name,
|
|
3031
|
+
accountNumber: resource.accountNumber,
|
|
3032
|
+
details: { requestId: request.requestId, requestedDurationHours, note: request.reason }
|
|
3033
|
+
});
|
|
3034
|
+
log13("created resource request", {
|
|
3035
|
+
requestId: request.requestId,
|
|
3036
|
+
userId,
|
|
3037
|
+
resourceId,
|
|
3038
|
+
requestedDurationHours
|
|
3039
|
+
});
|
|
3040
|
+
return request;
|
|
3041
|
+
}
|
|
3042
|
+
async function createResourceReapprovalRequest(userId, resourceId) {
|
|
3043
|
+
let resource;
|
|
3044
|
+
try {
|
|
3045
|
+
resource = await getResource(resourceId);
|
|
3046
|
+
} catch (err) {
|
|
3047
|
+
if (err instanceof NotFoundError) return null;
|
|
3048
|
+
throw err;
|
|
3049
|
+
}
|
|
3050
|
+
const pending = await listPendingResourceIds(userId);
|
|
3051
|
+
if (pending.has(resourceId)) return null;
|
|
3052
|
+
const request = {
|
|
3053
|
+
requestId: (0, import_crypto4.randomUUID)(),
|
|
3054
|
+
userId,
|
|
3055
|
+
kind: "RESOURCE",
|
|
3056
|
+
resourceId,
|
|
3057
|
+
type: "REAPPROVAL",
|
|
3058
|
+
reason: REAPPROVAL_REASON,
|
|
3059
|
+
status: "PENDING",
|
|
3060
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3061
|
+
};
|
|
3062
|
+
await ddb14.send(
|
|
3063
|
+
new import_lib_dynamodb14.PutCommand({ TableName: REQUESTS_TABLE_NAME, Item: request })
|
|
3064
|
+
);
|
|
3065
|
+
await recordReviewRaised(request, { targetName: resource.name, accountNumber: resource.accountNumber });
|
|
3066
|
+
log13("created resource reapproval request", {
|
|
3067
|
+
requestId: request.requestId,
|
|
3068
|
+
userId,
|
|
3069
|
+
resourceId
|
|
3070
|
+
});
|
|
3071
|
+
return request;
|
|
3072
|
+
}
|
|
3073
|
+
async function createPluginReapprovalRequest(userId, pluginId, accountNumber, permission) {
|
|
3074
|
+
const pending = await listPendingRows(userId);
|
|
3075
|
+
const dup = pending.some(
|
|
3076
|
+
(it) => it.kind === "PLUGIN" && it.pluginId === pluginId && it.accountNumber === accountNumber && it.permission === permission
|
|
3077
|
+
);
|
|
3078
|
+
if (dup) return null;
|
|
3079
|
+
const request = {
|
|
3080
|
+
requestId: (0, import_crypto4.randomUUID)(),
|
|
3081
|
+
userId,
|
|
3082
|
+
kind: "PLUGIN",
|
|
3083
|
+
pluginId,
|
|
3084
|
+
accountNumber,
|
|
3085
|
+
permission,
|
|
3086
|
+
type: "REAPPROVAL",
|
|
3087
|
+
reason: REAPPROVAL_REASON,
|
|
3088
|
+
status: "PENDING",
|
|
3089
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3090
|
+
};
|
|
3091
|
+
await ddb14.send(new import_lib_dynamodb14.PutCommand({ TableName: REQUESTS_TABLE_NAME, Item: request }));
|
|
3092
|
+
await recordReviewRaised(request);
|
|
3093
|
+
log13("created plugin reapproval request", { requestId: request.requestId, userId, pluginId, accountNumber, permission });
|
|
3094
|
+
return request;
|
|
3095
|
+
}
|
|
3096
|
+
async function createReapprovalRequest(userId, accountNumber, roleName) {
|
|
3097
|
+
const key = `${accountNumber}#${roleName}`;
|
|
3098
|
+
const pending = await listPendingAccountRoles(userId);
|
|
3099
|
+
if (pending.has(key)) return null;
|
|
3100
|
+
const request = {
|
|
3101
|
+
requestId: (0, import_crypto4.randomUUID)(),
|
|
3102
|
+
userId,
|
|
3103
|
+
accountNumber,
|
|
3104
|
+
roleName,
|
|
3105
|
+
type: "REAPPROVAL",
|
|
3106
|
+
reason: REAPPROVAL_REASON,
|
|
3107
|
+
status: "PENDING",
|
|
3108
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3109
|
+
};
|
|
3110
|
+
await ddb14.send(
|
|
3111
|
+
new import_lib_dynamodb14.PutCommand({ TableName: REQUESTS_TABLE_NAME, Item: request })
|
|
3112
|
+
);
|
|
3113
|
+
await recordReviewRaised(request);
|
|
3114
|
+
log13("created reapproval request", { requestId: request.requestId, userId, key });
|
|
3115
|
+
return request;
|
|
3116
|
+
}
|
|
3117
|
+
async function createReviewRequest(subjectId, kind) {
|
|
3118
|
+
const pending = await listPendingRows(subjectId);
|
|
3119
|
+
if (pending.some((it) => it.kind === kind)) return null;
|
|
3120
|
+
const request = {
|
|
3121
|
+
requestId: (0, import_crypto4.randomUUID)(),
|
|
3122
|
+
userId: subjectId,
|
|
3123
|
+
kind,
|
|
3124
|
+
// A GROUP review is the whole bundle at once (one question per group, however many roles it holds).
|
|
3125
|
+
...kind === "GROUP" ? { groupId: subjectId } : {},
|
|
3126
|
+
type: "REAPPROVAL",
|
|
3127
|
+
reason: kind === "ADMIN" ? ADMIN_REVIEW_REASON : kind === "SERVICE_USER" ? SERVICE_USER_REVIEW_REASON : GROUP_REVIEW_REASON,
|
|
3128
|
+
status: "PENDING",
|
|
3129
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3130
|
+
};
|
|
3131
|
+
await ddb14.send(
|
|
3132
|
+
new import_lib_dynamodb14.PutCommand({ TableName: REQUESTS_TABLE_NAME, Item: request })
|
|
3133
|
+
);
|
|
3134
|
+
await recordReviewRaised(request);
|
|
3135
|
+
log13("created review request", {
|
|
3136
|
+
requestId: request.requestId,
|
|
3137
|
+
subjectId,
|
|
3138
|
+
kind
|
|
3139
|
+
});
|
|
3140
|
+
return request;
|
|
3141
|
+
}
|
|
3142
|
+
async function listMyRequests(userId) {
|
|
3143
|
+
const items = await queryAll({
|
|
3144
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3145
|
+
IndexName: "byUser",
|
|
3146
|
+
KeyConditionExpression: "userId = :u",
|
|
3147
|
+
ExpressionAttributeValues: { ":u": userId },
|
|
3148
|
+
ScanIndexForward: false
|
|
3149
|
+
// newest first
|
|
3150
|
+
});
|
|
3151
|
+
log13("listed my requests", { userId, count: items.length });
|
|
3152
|
+
return items;
|
|
3153
|
+
}
|
|
3154
|
+
async function markRequestsSeen(userId) {
|
|
3155
|
+
const mine = await listMyRequests(userId);
|
|
3156
|
+
const unseen = mine.filter((r) => r.status !== "PENDING" && !r.seen);
|
|
3157
|
+
for (const r of unseen) {
|
|
3158
|
+
try {
|
|
3159
|
+
await ddb14.send(
|
|
3160
|
+
new import_lib_dynamodb14.UpdateCommand({
|
|
3161
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3162
|
+
Key: { requestId: r.requestId },
|
|
3163
|
+
UpdateExpression: "SET seen = :true",
|
|
3164
|
+
// The condition means a mismatched id can never touch someone else's row.
|
|
3165
|
+
ConditionExpression: "userId = :u",
|
|
3166
|
+
ExpressionAttributeValues: { ":true": true, ":u": userId }
|
|
3167
|
+
})
|
|
3168
|
+
);
|
|
3169
|
+
} catch (err) {
|
|
3170
|
+
if (err.name !== CONDITION_FAILED2) throw err;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
if (unseen.length) log13("marked requests seen", { userId, count: unseen.length });
|
|
3174
|
+
return unseen.length;
|
|
3175
|
+
}
|
|
3176
|
+
async function listRequests(status) {
|
|
3177
|
+
if (!status) {
|
|
3178
|
+
const all = await scanAll3({ TableName: REQUESTS_TABLE_NAME });
|
|
3179
|
+
return all.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
3180
|
+
}
|
|
3181
|
+
const items = await queryAll({
|
|
3182
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3183
|
+
IndexName: "byStatus",
|
|
3184
|
+
KeyConditionExpression: "#s = :s",
|
|
3185
|
+
ExpressionAttributeNames: STATUS_ALIAS,
|
|
3186
|
+
ExpressionAttributeValues: { ":s": status },
|
|
3187
|
+
ScanIndexForward: true
|
|
3188
|
+
// oldest first — longest wait at the top
|
|
3189
|
+
});
|
|
3190
|
+
log13("listed requests", { status, count: items.length });
|
|
3191
|
+
return items;
|
|
3192
|
+
}
|
|
3193
|
+
async function approveRequest(requestId, adminUserId, note, durationHours) {
|
|
3194
|
+
if (durationHours !== void 0 && !isDurationPreset(durationHours)) {
|
|
3195
|
+
throw new ValidationError(
|
|
3196
|
+
`duration must be one of ${DURATION_PRESET_HOURS.join(", ")} hours`
|
|
3197
|
+
);
|
|
3198
|
+
}
|
|
3199
|
+
if (durationHours !== void 0) {
|
|
3200
|
+
const current = await ddb14.send(
|
|
3201
|
+
new import_lib_dynamodb14.GetCommand({ TableName: REQUESTS_TABLE_NAME, Key: { requestId } })
|
|
3202
|
+
);
|
|
3203
|
+
if (current.Item?.type === "REAPPROVAL") {
|
|
3204
|
+
throw new ValidationError("A reapproval renews the existing grant as it is \u2014 a duration does not apply");
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
const claimed = await decide(
|
|
3208
|
+
requestId,
|
|
3209
|
+
"APPROVED",
|
|
3210
|
+
adminUserId,
|
|
3211
|
+
note,
|
|
3212
|
+
durationHours
|
|
3213
|
+
);
|
|
3214
|
+
const expiresAt = durationHours !== void 0 && claimed.decidedAt ? new Date(
|
|
3215
|
+
Date.parse(claimed.decidedAt) + durationHours * 60 * 60 * 1e3
|
|
3216
|
+
).toISOString() : void 0;
|
|
3217
|
+
try {
|
|
3218
|
+
if (claimed.kind === "GROUP") {
|
|
3219
|
+
await markGroupReviewed(claimed.userId, adminUserId);
|
|
3220
|
+
} else if (claimed.kind === "ADMIN") {
|
|
3221
|
+
await upsertPref(claimed.userId, ADMIN_REVIEW_TARGET, {
|
|
3222
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3223
|
+
});
|
|
3224
|
+
} else if (claimed.kind === "SERVICE_USER") {
|
|
3225
|
+
await markReviewed(claimed.userId);
|
|
3226
|
+
} else if (claimed.groupId) {
|
|
3227
|
+
if (claimed.kind === "PLUGIN") await assertPluginAssignable(pluginKeyOf(claimed));
|
|
3228
|
+
await restampGroupTarget(claimed.groupId, groupTargetOf(claimed), adminUserId);
|
|
3229
|
+
} else if (claimed.kind === "PLUGIN") {
|
|
3230
|
+
if (claimed.type === "REAPPROVAL") await assertStillHeld(claimed);
|
|
3231
|
+
await assignPlugin({ ...pluginKeyOf(claimed), principal: { kind: "user", userId: claimed.userId }, grantedBy: adminUserId, fromRequestId: requestId });
|
|
3232
|
+
} else if (claimed.type === "REAPPROVAL" && claimed.kind === "RESOURCE") {
|
|
3233
|
+
await renewResourceGrant(claimed.resourceId, claimed.userId, adminUserId, { fromRequestId: requestId });
|
|
3234
|
+
} else if (claimed.type === "REAPPROVAL") {
|
|
3235
|
+
await renewRole(claimed.accountNumber, claimed.roleName, claimed.userId, adminUserId, { fromRequestId: requestId });
|
|
3236
|
+
} else if (claimed.kind === "RESOURCE") {
|
|
3237
|
+
await assignResource(
|
|
3238
|
+
claimed.resourceId,
|
|
3239
|
+
claimed.userId,
|
|
3240
|
+
adminUserId,
|
|
3241
|
+
expiresAt,
|
|
3242
|
+
{ fromRequestId: requestId }
|
|
3243
|
+
);
|
|
3244
|
+
} else {
|
|
3245
|
+
await assignRole(
|
|
3246
|
+
claimed.accountNumber,
|
|
3247
|
+
claimed.roleName,
|
|
3248
|
+
claimed.userId,
|
|
3249
|
+
adminUserId,
|
|
3250
|
+
expiresAt,
|
|
3251
|
+
{ fromRequestId: requestId }
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
} catch (err) {
|
|
3255
|
+
await releaseClaim(requestId);
|
|
3256
|
+
log13("ERROR: approve failed, request returned to pending", {
|
|
3257
|
+
requestId,
|
|
3258
|
+
error: err.message
|
|
3259
|
+
});
|
|
3260
|
+
throw err;
|
|
3261
|
+
}
|
|
3262
|
+
await recordEvent({
|
|
3263
|
+
category: "request",
|
|
3264
|
+
action: "request.approve",
|
|
3265
|
+
...auditShape(claimed),
|
|
3266
|
+
details: { requestId, durationHours, note, expiresAt, ...reviewFlag(claimed) }
|
|
3267
|
+
});
|
|
3268
|
+
log13("approved request", {
|
|
3269
|
+
requestId,
|
|
3270
|
+
userId: claimed.userId,
|
|
3271
|
+
accountNumber: claimed.accountNumber,
|
|
3272
|
+
roleName: claimed.roleName,
|
|
3273
|
+
adminUserId
|
|
3274
|
+
});
|
|
3275
|
+
return claimed;
|
|
3276
|
+
}
|
|
3277
|
+
async function denyRequest(requestId, adminUserId, note) {
|
|
3278
|
+
const decided = await decide(requestId, "DENIED", adminUserId, note);
|
|
3279
|
+
if (decided.type === "REAPPROVAL") {
|
|
3280
|
+
if (decided.kind === "ADMIN" || decided.kind === "SERVICE_USER" || decided.kind === "GROUP") {
|
|
3281
|
+
try {
|
|
3282
|
+
if (decided.kind === "ADMIN") {
|
|
3283
|
+
await lazyUsers().updateUser(decided.userId, { admin: false });
|
|
3284
|
+
} else if (decided.kind === "GROUP") {
|
|
3285
|
+
await deleteGroup(decided.userId);
|
|
3286
|
+
} else {
|
|
3287
|
+
await deleteServiceUser(decided.userId);
|
|
3288
|
+
}
|
|
3289
|
+
} catch (err) {
|
|
3290
|
+
if (!(err instanceof NotFoundError)) {
|
|
3291
|
+
await releaseClaim(requestId);
|
|
3292
|
+
throw err;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
} else {
|
|
3296
|
+
try {
|
|
3297
|
+
if (decided.groupId) {
|
|
3298
|
+
await revokeGroupTarget(decided.groupId, groupTargetOf(decided));
|
|
3299
|
+
} else if (decided.kind === "PLUGIN") {
|
|
3300
|
+
await revokePlugin({ ...pluginKeyOf(decided), principal: { kind: "user", userId: decided.userId }, via: "REAPPROVAL" });
|
|
3301
|
+
} else if (decided.kind === "RESOURCE") {
|
|
3302
|
+
await revokeResourceGrant(
|
|
3303
|
+
decided.resourceId,
|
|
3304
|
+
decided.userId,
|
|
3305
|
+
"REAPPROVAL"
|
|
3306
|
+
);
|
|
3307
|
+
} else {
|
|
3308
|
+
await revokeRole(
|
|
3309
|
+
decided.accountNumber,
|
|
3310
|
+
decided.roleName,
|
|
3311
|
+
decided.userId,
|
|
3312
|
+
"REAPPROVAL"
|
|
3313
|
+
);
|
|
3314
|
+
}
|
|
3315
|
+
} catch (err) {
|
|
3316
|
+
if (!(err instanceof NotFoundError) && !(err instanceof ConflictError)) {
|
|
3317
|
+
await releaseClaim(requestId);
|
|
3318
|
+
throw err;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
await recordEvent({
|
|
3324
|
+
category: "request",
|
|
3325
|
+
action: "request.deny",
|
|
3326
|
+
reason: note || void 0,
|
|
3327
|
+
...auditShape(decided),
|
|
3328
|
+
details: { requestId, note: note || void 0, ...reviewFlag(decided) }
|
|
3329
|
+
});
|
|
3330
|
+
log13("denied request", {
|
|
3331
|
+
requestId,
|
|
3332
|
+
userId: decided.userId,
|
|
3333
|
+
adminUserId,
|
|
3334
|
+
type: decided.type ?? "ACCESS"
|
|
3335
|
+
});
|
|
3336
|
+
return decided;
|
|
3337
|
+
}
|
|
3338
|
+
function groupTargetOf(req) {
|
|
3339
|
+
if (req.kind === "RESOURCE") return resourceTarget(req.resourceId);
|
|
3340
|
+
if (req.kind === "PLUGIN") return pluginTarget(req.pluginId, req.accountNumber, req.permission);
|
|
3341
|
+
return roleTarget(req.accountNumber, req.roleName);
|
|
3342
|
+
}
|
|
3343
|
+
function auditShape(req) {
|
|
3344
|
+
const subject = req.groupId ? { kind: "group", id: req.groupId } : { kind: req.kind === "SERVICE_USER" ? "serviceUser" : "user", id: req.userId };
|
|
3345
|
+
const hasTarget = req.kind === "RESOURCE" ? Boolean(req.resourceId) : req.kind === "PLUGIN" ? Boolean(req.pluginId && req.accountNumber && req.permission) : req.kind !== "ADMIN" && req.kind !== "SERVICE_USER" && req.kind !== "GROUP" && Boolean(req.accountNumber && req.roleName);
|
|
3346
|
+
return hasTarget ? { subject, target: groupTargetOf(req), targetName: req.roleName } : { subject };
|
|
3347
|
+
}
|
|
3348
|
+
async function recordReviewRaised(request, snapshot2 = {}) {
|
|
3349
|
+
await recordEvent({
|
|
3350
|
+
category: "request",
|
|
3351
|
+
action: "review.raise",
|
|
3352
|
+
...auditShape(request),
|
|
3353
|
+
...snapshot2,
|
|
3354
|
+
details: { requestId: request.requestId }
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
async function decide(requestId, status, adminUserId, note, grantedDurationHours) {
|
|
3358
|
+
const current = await ddb14.send(
|
|
3359
|
+
new import_lib_dynamodb14.GetCommand({ TableName: REQUESTS_TABLE_NAME, Key: { requestId } })
|
|
3360
|
+
);
|
|
3361
|
+
const pending = current.Item;
|
|
3362
|
+
if (pending?.kind === "ADMIN" && pending.userId === adminUserId) {
|
|
3363
|
+
const admins = await lazyUsers().listAdminSubs();
|
|
3364
|
+
if (admins.size > 1) {
|
|
3365
|
+
throw new ConflictError(
|
|
3366
|
+
"This is your own admin review \u2014 another admin must decide it."
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
try {
|
|
3371
|
+
const res = await ddb14.send(
|
|
3372
|
+
new import_lib_dynamodb14.UpdateCommand({
|
|
3373
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3374
|
+
Key: { requestId },
|
|
3375
|
+
UpdateExpression: "SET #s = :next, decidedAt = :at, decidedBy = :by, seen = :false" + (note ? ", decisionNote = :note" : "") + (grantedDurationHours !== void 0 ? ", grantedDurationHours = :dur" : ""),
|
|
3376
|
+
ConditionExpression: "attribute_exists(requestId) AND #s = :pending",
|
|
3377
|
+
ExpressionAttributeNames: STATUS_ALIAS,
|
|
3378
|
+
ExpressionAttributeValues: {
|
|
3379
|
+
":next": status,
|
|
3380
|
+
":pending": "PENDING",
|
|
3381
|
+
":at": (/* @__PURE__ */ new Date()).toISOString(),
|
|
3382
|
+
":by": adminUserId,
|
|
3383
|
+
":false": false,
|
|
3384
|
+
...note ? { ":note": note } : {},
|
|
3385
|
+
...grantedDurationHours !== void 0 ? { ":dur": grantedDurationHours } : {}
|
|
3386
|
+
},
|
|
3387
|
+
ReturnValues: "ALL_NEW"
|
|
3388
|
+
})
|
|
3389
|
+
);
|
|
3390
|
+
return res.Attributes;
|
|
3391
|
+
} catch (err) {
|
|
3392
|
+
if (err.name === CONDITION_FAILED2) {
|
|
3393
|
+
const current2 = await ddb14.send(
|
|
3394
|
+
new import_lib_dynamodb14.GetCommand({
|
|
3395
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3396
|
+
Key: { requestId }
|
|
3397
|
+
})
|
|
3398
|
+
);
|
|
3399
|
+
if (!current2.Item) {
|
|
3400
|
+
throw new NotFoundError(`Request ${requestId} not found`);
|
|
3401
|
+
}
|
|
3402
|
+
throw new ConflictError(
|
|
3403
|
+
`Request was already ${String(current2.Item.status).toLowerCase()}`
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
throw err;
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
async function releaseClaim(requestId) {
|
|
3410
|
+
try {
|
|
3411
|
+
await ddb14.send(
|
|
3412
|
+
new import_lib_dynamodb14.UpdateCommand({
|
|
3413
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3414
|
+
Key: { requestId },
|
|
3415
|
+
UpdateExpression: "SET #s = :pending REMOVE decidedAt, decidedBy, seen, decisionNote, grantedDurationHours",
|
|
3416
|
+
ConditionExpression: "attribute_exists(requestId)",
|
|
3417
|
+
ExpressionAttributeNames: STATUS_ALIAS,
|
|
3418
|
+
ExpressionAttributeValues: { ":pending": "PENDING" }
|
|
3419
|
+
})
|
|
3420
|
+
);
|
|
3421
|
+
} catch (err) {
|
|
3422
|
+
if (err.name !== CONDITION_FAILED2) {
|
|
3423
|
+
log13("ERROR: could not roll back request claim", {
|
|
3424
|
+
requestId,
|
|
3425
|
+
error: err.message
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
async function deleteRequestsForUser(userId, opts = {}) {
|
|
3431
|
+
const targets = opts.targets ? new Set(opts.targets) : void 0;
|
|
3432
|
+
const except = new Set(opts.exceptKinds ?? []);
|
|
3433
|
+
const rows = await queryAll({
|
|
3434
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3435
|
+
IndexName: "byUser",
|
|
3436
|
+
KeyConditionExpression: "userId = :u",
|
|
3437
|
+
ExpressionAttributeValues: { ":u": userId },
|
|
3438
|
+
ProjectionExpression: "requestId, #s, #t, #k, accountNumber, roleName, resourceId, pluginId, #p",
|
|
3439
|
+
ExpressionAttributeNames: { ...STATUS_ALIAS, "#t": "type", "#k": "kind", "#p": "permission" }
|
|
3440
|
+
});
|
|
3441
|
+
const mine = rows.filter(
|
|
3442
|
+
(r) => (!opts.onlyPending || r.status === "PENDING") && (!opts.onlyReviews || r.type === "REAPPROVAL") && !(r.kind && except.has(r.kind)) && (!opts.target || targetOfRequest(r) === opts.target) && (!targets || targets.has(targetOfRequest(r) ?? ""))
|
|
3443
|
+
);
|
|
3444
|
+
for (const r of mine) {
|
|
3445
|
+
await ddb14.send(
|
|
3446
|
+
new import_lib_dynamodb14.DeleteCommand({
|
|
3447
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3448
|
+
Key: { requestId: r.requestId }
|
|
3449
|
+
})
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
if (mine.length) {
|
|
3453
|
+
log13("deleted requests", { userId, removed: mine.length, ...opts });
|
|
3454
|
+
}
|
|
3455
|
+
return mine.length;
|
|
3456
|
+
}
|
|
3457
|
+
async function closePendingRequestsFor(targets, note) {
|
|
3458
|
+
const wanted = new Set(Array.isArray(targets) ? targets : [targets]);
|
|
3459
|
+
if (!wanted.size) return 0;
|
|
3460
|
+
const pending = (await listRequests("PENDING")).filter((r) => {
|
|
3461
|
+
const t = targetOfRequest(r);
|
|
3462
|
+
return t !== void 0 && wanted.has(t);
|
|
3463
|
+
});
|
|
3464
|
+
const by = currentActor()?.id ?? "system:catalog";
|
|
3465
|
+
let closed = 0;
|
|
3466
|
+
for (const r of pending) {
|
|
3467
|
+
const reason = typeof note === "function" ? note(targetOfRequest(r)) : note;
|
|
3468
|
+
try {
|
|
3469
|
+
const decided = await decide(r.requestId, "DENIED", by, reason);
|
|
3470
|
+
await recordEvent({
|
|
3471
|
+
category: "request",
|
|
3472
|
+
action: "request.deny",
|
|
3473
|
+
reason,
|
|
3474
|
+
...auditShape(decided),
|
|
3475
|
+
details: { requestId: r.requestId, note: reason, ...reviewFlag(decided), catalogCascade: true }
|
|
3476
|
+
});
|
|
3477
|
+
closed++;
|
|
3478
|
+
} catch (err) {
|
|
3479
|
+
if (!(err instanceof ConflictError) && !(err instanceof NotFoundError)) throw err;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
if (closed) log13("closed pending requests for removed targets", { targets: [...wanted], closed });
|
|
3483
|
+
return closed;
|
|
3484
|
+
}
|
|
3485
|
+
function targetOfRequest(r) {
|
|
3486
|
+
if (r.kind === "ADMIN" || r.kind === "SERVICE_USER" || r.kind === "GROUP") return void 0;
|
|
3487
|
+
if (r.kind === "RESOURCE") return r.resourceId ? resourceTarget(r.resourceId) : void 0;
|
|
3488
|
+
if (r.kind === "PLUGIN") {
|
|
3489
|
+
return r.pluginId && r.accountNumber && r.permission ? pluginTarget(r.pluginId, r.accountNumber, r.permission) : void 0;
|
|
3490
|
+
}
|
|
3491
|
+
return r.accountNumber && r.roleName ? roleTarget(r.accountNumber, r.roleName) : void 0;
|
|
3492
|
+
}
|
|
3493
|
+
async function assertStillHeld(req) {
|
|
3494
|
+
const target = targetOfRequest(req);
|
|
3495
|
+
if (!target || !await getRow(userPrincipal(req.userId), target)) {
|
|
3496
|
+
throw new ConflictError("This review's access was already revoked \u2014 there is nothing to renew");
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
async function listHeldAccountRoles(userId) {
|
|
3500
|
+
const grants = await listUserGrants(userId);
|
|
3501
|
+
return new Map(
|
|
3502
|
+
grants.map((g) => [`${g.accountNumber}#${g.roleName}`, { expiresAt: g.expiresAt }])
|
|
3503
|
+
);
|
|
3504
|
+
}
|
|
3505
|
+
async function listPendingAccountRoles(userId) {
|
|
3506
|
+
const items = await listPendingRows(userId);
|
|
3507
|
+
return new Set(
|
|
3508
|
+
items.filter((it) => it.kind !== "RESOURCE" && it.kind !== "PLUGIN" && it.accountNumber).map((it) => `${it.accountNumber}#${it.roleName}`)
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
async function listPendingResourceIds(userId) {
|
|
3512
|
+
const items = await listPendingRows(userId);
|
|
3513
|
+
return new Set(
|
|
3514
|
+
items.filter((it) => it.kind === "RESOURCE").map((it) => it.resourceId)
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
async function listPendingRows(userId) {
|
|
3518
|
+
return queryAll({
|
|
3519
|
+
TableName: REQUESTS_TABLE_NAME,
|
|
3520
|
+
IndexName: "byUser",
|
|
3521
|
+
KeyConditionExpression: "userId = :u",
|
|
3522
|
+
FilterExpression: "#s = :pending",
|
|
3523
|
+
ExpressionAttributeNames: STATUS_ALIAS,
|
|
3524
|
+
ExpressionAttributeValues: { ":u": userId, ":pending": "PENDING" }
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
async function queryAll(input) {
|
|
3528
|
+
const all = [];
|
|
3529
|
+
let ExclusiveStartKey;
|
|
3530
|
+
do {
|
|
3531
|
+
const page = await ddb14.send(
|
|
3532
|
+
new import_lib_dynamodb14.QueryCommand({ ...input, ExclusiveStartKey })
|
|
3533
|
+
);
|
|
3534
|
+
all.push(...page.Items ?? []);
|
|
3535
|
+
ExclusiveStartKey = page.LastEvaluatedKey;
|
|
3536
|
+
} while (ExclusiveStartKey);
|
|
3537
|
+
return all;
|
|
3538
|
+
}
|
|
3539
|
+
async function scanAll3(input) {
|
|
3540
|
+
const all = [];
|
|
3541
|
+
let ExclusiveStartKey;
|
|
3542
|
+
do {
|
|
3543
|
+
const page = await ddb14.send(
|
|
3544
|
+
new import_lib_dynamodb14.ScanCommand({ ...input, ExclusiveStartKey })
|
|
3545
|
+
);
|
|
3546
|
+
all.push(...page.Items ?? []);
|
|
3547
|
+
ExclusiveStartKey = page.LastEvaluatedKey;
|
|
3548
|
+
} while (ExclusiveStartKey);
|
|
3549
|
+
return all;
|
|
3550
|
+
}
|
|
3551
|
+
var import_crypto4, import_client_dynamodb14, import_lib_dynamodb14, lazyUsers, ddb14, REQUESTS_TABLE_NAME, ROLES_TABLE_NAME4, log13, CONDITION_FAILED2, STATUS_ALIAS, REAPPROVAL_INTERVAL_MONTHS, REAPPROVAL_GRACE_DAYS, REAPPROVAL_REASON, DURATION_PRESET_HOURS, ADMIN_REVIEW_REASON, SERVICE_USER_REVIEW_REASON, GROUP_REVIEW_REASON, pluginKeyOf, reviewFlag;
|
|
3552
|
+
var init_requestsService = __esm({
|
|
3553
|
+
"lambdacode/api/services/requestsService.ts"() {
|
|
3554
|
+
"use strict";
|
|
3555
|
+
import_crypto4 = require("crypto");
|
|
3556
|
+
import_client_dynamodb14 = require("@aws-sdk/client-dynamodb");
|
|
3557
|
+
import_lib_dynamodb14 = require("@aws-sdk/lib-dynamodb");
|
|
3558
|
+
init_grantsService();
|
|
3559
|
+
init_resourceGrantsService();
|
|
3560
|
+
init_resourcesService();
|
|
3561
|
+
init_access2();
|
|
3562
|
+
init_pluginGrantsService();
|
|
3563
|
+
init_groupsService();
|
|
3564
|
+
init_serviceUsersService();
|
|
3565
|
+
init_auditService();
|
|
3566
|
+
init_actorContext();
|
|
3567
|
+
init_httpErrors();
|
|
3568
|
+
init_catalogIndex();
|
|
3569
|
+
lazyUsers = () => (init_usersService(), __toCommonJS(usersService_exports));
|
|
3570
|
+
ddb14 = import_lib_dynamodb14.DynamoDBDocumentClient.from(new import_client_dynamodb14.DynamoDBClient({}));
|
|
3571
|
+
REQUESTS_TABLE_NAME = process.env.REQUESTS_TABLE_NAME;
|
|
3572
|
+
ROLES_TABLE_NAME4 = process.env.ROLES_TABLE_NAME;
|
|
3573
|
+
log13 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
3574
|
+
CONDITION_FAILED2 = "ConditionalCheckFailedException";
|
|
3575
|
+
STATUS_ALIAS = { "#s": "status" };
|
|
3576
|
+
REAPPROVAL_INTERVAL_MONTHS = 12;
|
|
3577
|
+
REAPPROVAL_GRACE_DAYS = 14;
|
|
3578
|
+
REAPPROVAL_REASON = "12-month reapproval \u2014 standing access is due for periodic review.";
|
|
3579
|
+
DURATION_PRESET_HOURS = [1, 12, 24, 72, 168, 720];
|
|
3580
|
+
ADMIN_REVIEW_REASON = "12-month review \u2014 should this user remain an admin?";
|
|
3581
|
+
SERVICE_USER_REVIEW_REASON = "12-month review \u2014 is this automation still needed?";
|
|
3582
|
+
GROUP_REVIEW_REASON = "12-month review \u2014 is this group's standing access still needed?";
|
|
3583
|
+
pluginKeyOf = (req) => ({ pluginId: req.pluginId, accountNumber: req.accountNumber, permission: req.permission });
|
|
3584
|
+
reviewFlag = (req) => req.type === "REAPPROVAL" ? { review: true } : {};
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
3587
|
+
|
|
3588
|
+
// lambdacode/reapproval/handler.ts
|
|
3589
|
+
var handler_exports = {};
|
|
3590
|
+
__export(handler_exports, {
|
|
3591
|
+
handler: () => handler
|
|
3592
|
+
});
|
|
3593
|
+
module.exports = __toCommonJS(handler_exports);
|
|
3594
|
+
|
|
3595
|
+
// lambdacode/api/services/reapprovalService.ts
|
|
3596
|
+
init_requestsService();
|
|
3597
|
+
init_access2();
|
|
3598
|
+
init_serviceUsersService();
|
|
3599
|
+
init_groupsService();
|
|
3600
|
+
init_pluginAccess();
|
|
3601
|
+
init_pluginGrantsService();
|
|
3602
|
+
init_usersService();
|
|
3603
|
+
init_auditService();
|
|
3604
|
+
init_actorContext();
|
|
3605
|
+
init_httpErrors();
|
|
3606
|
+
var SYSTEM_ACTOR = "system:reapproval";
|
|
3607
|
+
var AUTO_REVOKE_NOTE = "Auto-revoked: 12-month reapproval was not completed within the grace period.";
|
|
3608
|
+
var log14 = (msg, extra = {}) => console.log(JSON.stringify({ msg, ...extra }));
|
|
3609
|
+
function addMonths(iso, months) {
|
|
3610
|
+
const d = new Date(iso);
|
|
3611
|
+
d.setMonth(d.getMonth() + months);
|
|
3612
|
+
return d.getTime();
|
|
3613
|
+
}
|
|
3614
|
+
var pendingKey = (r) => r.kind === "GROUP" ? `${r.userId}#GROUP` : `${r.groupId ?? r.userId}#${groupTargetOf(r)}`;
|
|
3615
|
+
async function sweepReapprovals(now = Date.now()) {
|
|
3616
|
+
const pending = (await listRequests("PENDING")).filter((r) => r.type === "REAPPROVAL");
|
|
3617
|
+
const pendingByKey = /* @__PURE__ */ new Map();
|
|
3618
|
+
for (const r of pending) {
|
|
3619
|
+
if (r.kind === "ADMIN" || r.kind === "SERVICE_USER") continue;
|
|
3620
|
+
pendingByKey.set(pendingKey(r), r);
|
|
3621
|
+
}
|
|
3622
|
+
const graceMs = REAPPROVAL_GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
3623
|
+
const lapsed = (req) => Boolean(req.createdAt) && now >= Date.parse(req.createdAt) + graceMs;
|
|
3624
|
+
const rows = await scanAll();
|
|
3625
|
+
const expired = await logExpiries(rows, now);
|
|
3626
|
+
const plugins = await loadPluginContext();
|
|
3627
|
+
const direct = standingDirectGrants(rows, plugins);
|
|
3628
|
+
const groups = standingGroups(rows, await listGroupRows());
|
|
3629
|
+
let raised = 0;
|
|
3630
|
+
let revoked = 0;
|
|
3631
|
+
const review = async (items, msg) => {
|
|
3632
|
+
for (const g of items) {
|
|
3633
|
+
if (!g.grantedAt) continue;
|
|
3634
|
+
if (now < addMonths(g.grantedAt, REAPPROVAL_INTERVAL_MONTHS)) continue;
|
|
3635
|
+
const req = pendingByKey.get(g.key);
|
|
3636
|
+
if (req) {
|
|
3637
|
+
if (!lapsed(req)) continue;
|
|
3638
|
+
try {
|
|
3639
|
+
await denyRequest(req.requestId, SYSTEM_ACTOR, AUTO_REVOKE_NOTE);
|
|
3640
|
+
} catch (err) {
|
|
3641
|
+
if (!(err instanceof NotFoundError) && !(err instanceof ConflictError)) throw err;
|
|
3642
|
+
continue;
|
|
3643
|
+
}
|
|
3644
|
+
revoked++;
|
|
3645
|
+
log14(msg.lapsed, { key: g.key });
|
|
3646
|
+
} else if (g.available) {
|
|
3647
|
+
const created = await g.raise();
|
|
3648
|
+
if (created) {
|
|
3649
|
+
raised++;
|
|
3650
|
+
log14(msg.raised, { key: g.key });
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
};
|
|
3655
|
+
await review(direct.roles, { raised: "reapproval raised", lapsed: "reapproval lapsed \u2014 access revoked" });
|
|
3656
|
+
await review(direct.resources, { raised: "resource reapproval raised", lapsed: "resource reapproval lapsed \u2014 access revoked" });
|
|
3657
|
+
await review(direct.plugins, { raised: "plugin reapproval raised", lapsed: "plugin reapproval lapsed \u2014 access revoked" });
|
|
3658
|
+
await review(groups, { raised: "group review raised", lapsed: "group review lapsed \u2014 group removed" });
|
|
3659
|
+
const pendingReviewSubjects = new Set(
|
|
3660
|
+
pending.filter((r) => r.kind === "ADMIN" || r.kind === "SERVICE_USER").map((r) => `${r.kind}#${r.userId}`)
|
|
3661
|
+
);
|
|
3662
|
+
for (const su of await reviewableServiceUsers()) {
|
|
3663
|
+
if (now < addMonths(su.since, REAPPROVAL_INTERVAL_MONTHS)) continue;
|
|
3664
|
+
if (pendingReviewSubjects.has(`SERVICE_USER#${su.clientId}`)) continue;
|
|
3665
|
+
const created = await createReviewRequest(su.clientId, "SERVICE_USER");
|
|
3666
|
+
if (created) {
|
|
3667
|
+
raised++;
|
|
3668
|
+
log14("service-user review raised", { clientId: su.clientId });
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
for (const sub of await listAdminSubs()) {
|
|
3672
|
+
if (pendingReviewSubjects.has(`ADMIN#${sub}`)) continue;
|
|
3673
|
+
const stamp = await getRow(userPrincipal(sub), ADMIN_REVIEW_TARGET);
|
|
3674
|
+
const since = typeof stamp?.reviewedAt === "string" ? stamp.reviewedAt : void 0;
|
|
3675
|
+
if (since && now < addMonths(since, REAPPROVAL_INTERVAL_MONTHS)) continue;
|
|
3676
|
+
const created = await createReviewRequest(sub, "ADMIN");
|
|
3677
|
+
if (created) {
|
|
3678
|
+
raised++;
|
|
3679
|
+
log14("admin review raised", { userId: sub });
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
const all = [...direct.roles, ...direct.resources, ...direct.plugins, ...groups];
|
|
3683
|
+
const result = { scanned: all.filter((g) => g.available).length, raised, revoked, expired };
|
|
3684
|
+
log14("reapproval sweep complete", result);
|
|
3685
|
+
return result;
|
|
3686
|
+
}
|
|
3687
|
+
async function logExpiries(rows, now) {
|
|
3688
|
+
const due = rows.filter(
|
|
3689
|
+
(r) => parsePrincipal(r.principal)?.kind === "user" && isGrantTarget(r.target) && parseTarget(r.target)?.kind !== "share" && r.expiryLoggedAt === void 0 && isExpired(r, now)
|
|
3690
|
+
);
|
|
3691
|
+
if (!due.length) return 0;
|
|
3692
|
+
await runAsActor(
|
|
3693
|
+
systemActor("expiry", "expiry sweep"),
|
|
3694
|
+
() => recordEvents(
|
|
3695
|
+
due.map((r) => {
|
|
3696
|
+
const t = parseTarget(r.target);
|
|
3697
|
+
return {
|
|
3698
|
+
category: "access",
|
|
3699
|
+
action: "access.expire",
|
|
3700
|
+
subject: { kind: "user", id: r.principal.slice("user#".length) },
|
|
3701
|
+
target: r.target,
|
|
3702
|
+
targetName: t?.kind === "role" ? t.roleName : void 0,
|
|
3703
|
+
details: { via: "EXPIRY", expiresAt: r.expiresAt }
|
|
3704
|
+
};
|
|
3705
|
+
})
|
|
3706
|
+
)
|
|
3707
|
+
);
|
|
3708
|
+
const loggedAt = new Date(now).toISOString();
|
|
3709
|
+
for (const r of due) await updateExisting(r.principal, r.target, { expiryLoggedAt: loggedAt });
|
|
3710
|
+
log14("expired grants logged", { count: due.length });
|
|
3711
|
+
return due.length;
|
|
3712
|
+
}
|
|
3713
|
+
function standingDirectGrants(rows, plugins) {
|
|
3714
|
+
const roles = [];
|
|
3715
|
+
const resources = [];
|
|
3716
|
+
for (const r of rows) {
|
|
3717
|
+
if (r.expiresAt) continue;
|
|
3718
|
+
const p = parsePrincipal(r.principal);
|
|
3719
|
+
const t = parseTarget(r.target);
|
|
3720
|
+
if (p?.kind !== "user") continue;
|
|
3721
|
+
const key = `${p.userId}#${r.target}`;
|
|
3722
|
+
if (t?.kind === "role") {
|
|
3723
|
+
roles.push({ key, grantedAt: r.grantedAt, available: true, raise: () => createReapprovalRequest(p.userId, t.accountNumber, t.roleName) });
|
|
3724
|
+
} else if (t?.kind === "resource") {
|
|
3725
|
+
resources.push({ key, grantedAt: r.grantedAt, available: true, raise: () => createResourceReapprovalRequest(p.userId, t.resourceId) });
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
const pluginRows = standingPluginGrants(rows, plugins).map((g) => ({
|
|
3729
|
+
key: `${g.userId}#${g.target}`,
|
|
3730
|
+
grantedAt: g.grantedAt,
|
|
3731
|
+
available: g.available,
|
|
3732
|
+
raise: () => createPluginReapprovalRequest(g.userId, g.pluginId, g.accountNumber, g.permission)
|
|
3733
|
+
}));
|
|
3734
|
+
return { roles, resources, plugins: pluginRows };
|
|
3735
|
+
}
|
|
3736
|
+
function standingGroups(rows, groups) {
|
|
3737
|
+
const newestRow = /* @__PURE__ */ new Map();
|
|
3738
|
+
for (const r of rows) {
|
|
3739
|
+
const p = parsePrincipal(r.principal);
|
|
3740
|
+
if (p?.kind !== "group" || !isGrantTarget(r.target) || !r.grantedAt) continue;
|
|
3741
|
+
const seen = newestRow.get(p.groupId);
|
|
3742
|
+
if (!seen || r.grantedAt > seen) newestRow.set(p.groupId, r.grantedAt);
|
|
3743
|
+
}
|
|
3744
|
+
const latest = (...stamps) => stamps.filter((s) => Boolean(s)).sort().at(-1);
|
|
3745
|
+
return groups.filter((g) => newestRow.has(g.groupId)).map((g) => ({
|
|
3746
|
+
key: `${g.groupId}#GROUP`,
|
|
3747
|
+
grantedAt: latest(g.reviewedAt, newestRow.get(g.groupId)) ?? g.createdAt,
|
|
3748
|
+
available: true,
|
|
3749
|
+
raise: () => createReviewRequest(g.groupId, "GROUP")
|
|
3750
|
+
}));
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
// lambdacode/reapproval/handler.ts
|
|
3754
|
+
init_actorContext();
|
|
3755
|
+
var handler = async () => {
|
|
3756
|
+
return runAsActor(systemActor("reapproval", "yearly review"), () => sweepReapprovals());
|
|
3757
|
+
};
|
|
3758
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3759
|
+
0 && (module.exports = {
|
|
3760
|
+
handler
|
|
3761
|
+
});
|