@venizia/ignis 0.2.0-9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +0 -1
- package/README.md +118 -158
- package/dist/base/applications/base.d.ts +19 -13
- package/dist/base/applications/base.d.ts.map +1 -1
- package/dist/base/applications/base.js +95 -74
- package/dist/base/applications/base.js.map +1 -1
- package/dist/base/applications/boot-steps.d.ts +13 -0
- package/dist/base/applications/boot-steps.d.ts.map +1 -0
- package/dist/base/applications/boot-steps.js +23 -0
- package/dist/base/applications/boot-steps.js.map +1 -0
- package/dist/base/applications/common/index.d.ts +2 -0
- package/dist/base/applications/common/index.d.ts.map +1 -0
- package/dist/base/applications/common/index.js +18 -0
- package/dist/base/applications/common/index.js.map +1 -0
- package/dist/base/applications/{types.d.ts → common/types.d.ts} +11 -0
- package/dist/base/applications/common/types.d.ts.map +1 -0
- package/dist/base/applications/common/types.js.map +1 -0
- package/dist/base/applications/index.d.ts +2 -1
- package/dist/base/applications/index.d.ts.map +1 -1
- package/dist/base/applications/index.js +2 -1
- package/dist/base/applications/index.js.map +1 -1
- package/dist/base/applications/server.d.ts +6 -2
- package/dist/base/applications/server.d.ts.map +1 -1
- package/dist/base/applications/server.js +34 -2
- package/dist/base/applications/server.js.map +1 -1
- package/dist/base/controllers/grpc/adapter.js +2 -2
- package/dist/base/controllers/grpc/adapter.js.map +1 -1
- package/dist/base/middlewares/app-error/app-error.middleware.d.ts +3 -2
- package/dist/base/middlewares/app-error/app-error.middleware.d.ts.map +1 -1
- package/dist/base/middlewares/app-error/app-error.middleware.js +4 -3
- package/dist/base/middlewares/app-error/app-error.middleware.js.map +1 -1
- package/dist/base/middlewares/request-spy/request-spy.middleware.d.ts +1 -1
- package/dist/base/middlewares/request-spy/request-spy.middleware.d.ts.map +1 -1
- package/dist/base/middlewares/request-spy/request-spy.middleware.js +13 -4
- package/dist/base/middlewares/request-spy/request-spy.middleware.js.map +1 -1
- package/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +2 -1
- package/dist/common/constants.js.map +1 -1
- package/dist/common/environments.d.ts +31 -10
- package/dist/common/environments.d.ts.map +1 -1
- package/dist/common/environments.js +33 -10
- package/dist/common/environments.js.map +1 -1
- package/dist/components/api-reference/component.d.ts.map +1 -1
- package/dist/components/api-reference/component.js +4 -3
- package/dist/components/api-reference/component.js.map +1 -1
- package/dist/components/api-reference/index.d.ts +1 -1
- package/dist/components/api-reference/index.d.ts.map +1 -1
- package/dist/components/api-reference/index.js +1 -1
- package/dist/components/api-reference/index.js.map +1 -1
- package/dist/components/api-reference/ui/factory.d.ts +14 -0
- package/dist/components/api-reference/ui/factory.d.ts.map +1 -0
- package/dist/components/api-reference/ui/factory.js +54 -0
- package/dist/components/api-reference/ui/factory.js.map +1 -0
- package/dist/components/api-reference/ui/index.d.ts +4 -0
- package/dist/components/api-reference/ui/index.d.ts.map +1 -0
- package/dist/components/api-reference/ui/index.js +20 -0
- package/dist/components/api-reference/ui/index.js.map +1 -0
- package/dist/components/api-reference/ui/scalar.d.ts +8 -0
- package/dist/components/api-reference/ui/scalar.d.ts.map +1 -0
- package/dist/components/api-reference/ui/scalar.js +28 -0
- package/dist/components/api-reference/ui/scalar.js.map +1 -0
- package/dist/components/api-reference/ui/swagger.d.ts +8 -0
- package/dist/components/api-reference/ui/swagger.d.ts.map +1 -0
- package/dist/components/api-reference/ui/swagger.js +28 -0
- package/dist/components/api-reference/ui/swagger.js.map +1 -0
- package/dist/components/auth/authenticate/component.d.ts +5 -0
- package/dist/components/auth/authenticate/component.d.ts.map +1 -1
- package/dist/components/auth/authenticate/component.js +23 -0
- package/dist/components/auth/authenticate/component.js.map +1 -1
- package/dist/components/auth/authenticate/controllers/factory.d.ts +35 -3
- package/dist/components/auth/authenticate/controllers/factory.d.ts.map +1 -1
- package/dist/components/auth/authenticate/controllers/jwks/definitions.d.ts +40 -20
- package/dist/components/auth/authenticate/controllers/jwks/definitions.d.ts.map +1 -1
- package/dist/components/auth/authenticate/controllers/service-certs/definitions.d.ts +40 -20
- package/dist/components/auth/authenticate/controllers/service-certs/definitions.d.ts.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.d.ts +2 -2
- package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.d.ts.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js +12 -8
- package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.d.ts +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.d.ts.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.js +5 -3
- package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.js.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jws.service.d.ts +1 -2
- package/dist/components/auth/authenticate/services/bearer/jws.service.d.ts.map +1 -1
- package/dist/components/auth/authenticate/services/bearer/jws.service.js +5 -3
- package/dist/components/auth/authenticate/services/bearer/jws.service.js.map +1 -1
- package/dist/components/auth/authenticate/services/jose-loader.d.ts +16 -0
- package/dist/components/auth/authenticate/services/jose-loader.d.ts.map +1 -0
- package/dist/components/auth/authenticate/services/jose-loader.js +25 -0
- package/dist/components/auth/authenticate/services/jose-loader.js.map +1 -0
- package/dist/components/auth/authenticate/services/service/signer.service.d.ts.map +1 -1
- package/dist/components/auth/authenticate/services/service/signer.service.js +8 -5
- package/dist/components/auth/authenticate/services/service/signer.service.js.map +1 -1
- package/dist/components/auth/authenticate/services/service/verifier.service.d.ts.map +1 -1
- package/dist/components/auth/authenticate/services/service/verifier.service.js +10 -9
- package/dist/components/auth/authenticate/services/service/verifier.service.js.map +1 -1
- package/dist/components/auth/authenticate/strategies/index.d.ts +1 -1
- package/dist/components/auth/authenticate/strategies/index.d.ts.map +1 -1
- package/dist/components/auth/authenticate/strategies/index.js +1 -1
- package/dist/components/auth/authenticate/strategies/index.js.map +1 -1
- package/dist/components/auth/authenticate/strategies/jwks/index.d.ts +3 -0
- package/dist/components/auth/authenticate/strategies/jwks/index.d.ts.map +1 -0
- package/dist/components/auth/authenticate/strategies/jwks/index.js +19 -0
- package/dist/components/auth/authenticate/strategies/jwks/index.js.map +1 -0
- package/dist/components/auth/authenticate/strategies/jwks/issuer.d.ts +13 -0
- package/dist/components/auth/authenticate/strategies/jwks/issuer.d.ts.map +1 -0
- package/dist/components/auth/authenticate/strategies/{jwks.strategy.js → jwks/issuer.js} +4 -27
- package/dist/components/auth/authenticate/strategies/jwks/issuer.js.map +1 -0
- package/dist/components/auth/authenticate/strategies/{jwks.strategy.d.ts → jwks/verifier.d.ts} +2 -10
- package/dist/components/auth/authenticate/strategies/jwks/verifier.d.ts.map +1 -0
- package/dist/components/auth/authenticate/strategies/jwks/verifier.js +45 -0
- package/dist/components/auth/authenticate/strategies/jwks/verifier.js.map +1 -0
- package/dist/components/auth/authorize/adapters/base-filtered.d.ts +1 -1
- package/dist/components/auth/authorize/adapters/base-filtered.d.ts.map +1 -1
- package/dist/components/auth/authorize/adapters/common/index.d.ts +2 -0
- package/dist/components/auth/authorize/adapters/common/index.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/common/index.js +18 -0
- package/dist/components/auth/authorize/adapters/common/index.js.map +1 -0
- package/dist/components/auth/authorize/adapters/{types.d.ts → common/types.d.ts} +1 -1
- package/dist/components/auth/authorize/adapters/common/types.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/common/types.js.map +1 -0
- package/dist/components/auth/authorize/adapters/connector.d.ts +1 -1
- package/dist/components/auth/authorize/adapters/connector.d.ts.map +1 -1
- package/dist/components/auth/authorize/adapters/custom-grant-expander.d.ts +1 -1
- package/dist/components/auth/authorize/adapters/custom-grant-expander.d.ts.map +1 -1
- package/dist/components/auth/authorize/adapters/index.d.ts +2 -2
- package/dist/components/auth/authorize/adapters/index.d.ts.map +1 -1
- package/dist/components/auth/authorize/adapters/index.js +2 -2
- package/dist/components/auth/authorize/adapters/index.js.map +1 -1
- package/dist/components/auth/authorize/adapters/{scoped-casbin.adapter.d.ts → scoped-casbin/adapter.d.ts} +11 -55
- package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/{scoped-casbin.adapter.js → scoped-casbin/adapter.js} +27 -22
- package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.js.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.d.ts +2 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.js +5 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.js.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.d.ts +3 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.js +19 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.js.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.d.ts +50 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.js +3 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.js.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/index.d.ts +4 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/index.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/index.js +19 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/index.js.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.d.ts +17 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.d.ts.map +1 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.js +11 -0
- package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.js.map +1 -0
- package/dist/components/auth/authorize/enforcers/policy-line-codec.d.ts.map +1 -1
- package/dist/components/auth/authorize/enforcers/policy-line-codec.js +4 -2
- package/dist/components/auth/authorize/enforcers/policy-line-codec.js.map +1 -1
- package/dist/components/auth/authorize/role-managers/base.d.ts.map +1 -1
- package/dist/components/auth/authorize/role-managers/base.js +2 -1
- package/dist/components/auth/authorize/role-managers/base.js.map +1 -1
- package/dist/components/auth/models/entities/policy-definition.model.d.ts +30 -9
- package/dist/components/auth/models/entities/policy-definition.model.d.ts.map +1 -1
- package/dist/components/auth/models/entities/policy-definition.model.js +36 -10
- package/dist/components/auth/models/entities/policy-definition.model.js.map +1 -1
- package/dist/components/auth/models/entities/user.model.d.ts +2 -2
- package/dist/components/controller/grpc/grpc.component.d.ts.map +1 -1
- package/dist/components/controller/grpc/grpc.component.js +1 -5
- package/dist/components/controller/grpc/grpc.component.js.map +1 -1
- package/dist/components/health-check/common/constants.d.ts +5 -0
- package/dist/components/health-check/common/constants.d.ts.map +1 -0
- package/dist/components/health-check/common/constants.js +9 -0
- package/dist/components/health-check/common/constants.js.map +1 -0
- package/dist/components/health-check/common/index.d.ts +1 -0
- package/dist/components/health-check/common/index.d.ts.map +1 -1
- package/dist/components/health-check/common/index.js +1 -0
- package/dist/components/health-check/common/index.js.map +1 -1
- package/dist/components/health-check/common/keys.d.ts +2 -0
- package/dist/components/health-check/common/keys.d.ts.map +1 -1
- package/dist/components/health-check/common/keys.js +2 -0
- package/dist/components/health-check/common/keys.js.map +1 -1
- package/dist/components/health-check/common/rest-paths.d.ts +1 -0
- package/dist/components/health-check/common/rest-paths.d.ts.map +1 -1
- package/dist/components/health-check/common/rest-paths.js +1 -0
- package/dist/components/health-check/common/rest-paths.js.map +1 -1
- package/dist/components/health-check/common/types.d.ts +22 -0
- package/dist/components/health-check/common/types.d.ts.map +1 -1
- package/dist/components/health-check/component.d.ts +9 -3
- package/dist/components/health-check/component.d.ts.map +1 -1
- package/dist/components/health-check/component.js +38 -1
- package/dist/components/health-check/component.js.map +1 -1
- package/dist/components/health-check/controller.d.ts +10 -2
- package/dist/components/health-check/controller.d.ts.map +1 -1
- package/dist/components/health-check/controller.js +79 -13
- package/dist/components/health-check/controller.js.map +1 -1
- package/dist/components/health-check/index.d.ts +1 -0
- package/dist/components/health-check/index.d.ts.map +1 -1
- package/dist/components/health-check/index.js +1 -0
- package/dist/components/health-check/index.js.map +1 -1
- package/dist/components/health-check/reporter.d.ts +98 -0
- package/dist/components/health-check/reporter.d.ts.map +1 -0
- package/dist/components/health-check/reporter.js +138 -0
- package/dist/components/health-check/reporter.js.map +1 -0
- package/dist/components/mail/common/constants.d.ts +8 -0
- package/dist/components/mail/common/constants.d.ts.map +1 -1
- package/dist/components/mail/common/constants.js +9 -1
- package/dist/components/mail/common/constants.js.map +1 -1
- package/dist/components/mail/common/types/index.d.ts +6 -0
- package/dist/components/mail/common/types/index.d.ts.map +1 -0
- package/dist/components/mail/common/types/index.js +22 -0
- package/dist/components/mail/common/types/index.js.map +1 -0
- package/dist/components/mail/common/types/message.d.ts +49 -0
- package/dist/components/mail/common/types/message.d.ts.map +1 -0
- package/dist/components/mail/common/types/message.js +3 -0
- package/dist/components/mail/common/types/message.js.map +1 -0
- package/dist/components/mail/common/types/options.d.ts +65 -0
- package/dist/components/mail/common/types/options.d.ts.map +1 -0
- package/dist/components/mail/common/types/options.js +3 -0
- package/dist/components/mail/common/types/options.js.map +1 -0
- package/dist/components/mail/common/types/queue.d.ts +56 -0
- package/dist/components/mail/common/types/queue.d.ts.map +1 -0
- package/dist/components/mail/common/types/queue.js +3 -0
- package/dist/components/mail/common/types/queue.js.map +1 -0
- package/dist/components/mail/common/types/template.d.ts +33 -0
- package/dist/components/mail/common/types/template.d.ts.map +1 -0
- package/dist/components/mail/common/types/template.js +3 -0
- package/dist/components/mail/common/types/template.js.map +1 -0
- package/dist/components/mail/common/types/verification.d.ts +26 -0
- package/dist/components/mail/common/types/verification.d.ts.map +1 -0
- package/dist/components/mail/common/types/verification.js +3 -0
- package/dist/components/mail/common/types/verification.js.map +1 -0
- package/dist/components/mail/component.d.ts +4 -1
- package/dist/components/mail/component.d.ts.map +1 -1
- package/dist/components/mail/component.js +12 -0
- package/dist/components/mail/component.js.map +1 -1
- package/dist/components/mail/helpers/executors/bull-mq-executor.helper.d.ts +1 -0
- package/dist/components/mail/helpers/executors/bull-mq-executor.helper.d.ts.map +1 -1
- package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js +3 -0
- package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js.map +1 -1
- package/dist/components/mail/helpers/executors/internal-queue-executor.helper.d.ts.map +1 -1
- package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js +2 -1
- package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js.map +1 -1
- package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts +19 -0
- package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts.map +1 -0
- package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.js +112 -0
- package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.js.map +1 -0
- package/dist/components/mail/helpers/transporters/index.d.ts +2 -1
- package/dist/components/mail/helpers/transporters/index.d.ts.map +1 -1
- package/dist/components/mail/helpers/transporters/index.js +2 -1
- package/dist/components/mail/helpers/transporters/index.js.map +1 -1
- package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.d.ts.map +1 -1
- package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js +4 -3
- package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js.map +1 -1
- package/dist/components/mail/providers/mail-transporter.provider.d.ts +2 -0
- package/dist/components/mail/providers/mail-transporter.provider.d.ts.map +1 -1
- package/dist/components/mail/providers/mail-transporter.provider.js +35 -17
- package/dist/components/mail/providers/mail-transporter.provider.js.map +1 -1
- package/dist/components/mail/services/generators/index.d.ts +4 -0
- package/dist/components/mail/services/generators/index.d.ts.map +1 -0
- package/dist/components/mail/services/generators/index.js +20 -0
- package/dist/components/mail/services/generators/index.js.map +1 -0
- package/dist/components/mail/services/generators/numeric-code.d.ts +7 -0
- package/dist/components/mail/services/generators/numeric-code.d.ts.map +1 -0
- package/dist/components/mail/services/generators/numeric-code.js +20 -0
- package/dist/components/mail/services/generators/numeric-code.js.map +1 -0
- package/dist/components/mail/services/generators/random-token.d.ts +7 -0
- package/dist/components/mail/services/generators/random-token.d.ts.map +1 -0
- package/dist/components/mail/services/generators/random-token.js +18 -0
- package/dist/components/mail/services/generators/random-token.js.map +1 -0
- package/dist/components/mail/services/{generator.service.d.ts → generators/verification-data.d.ts} +2 -10
- package/dist/components/mail/services/generators/verification-data.d.ts.map +1 -0
- package/dist/components/mail/services/{generator.service.js → generators/verification-data.js} +5 -29
- package/dist/components/mail/services/generators/verification-data.js.map +1 -0
- package/dist/components/mail/services/index.d.ts +1 -1
- package/dist/components/mail/services/index.d.ts.map +1 -1
- package/dist/components/mail/services/index.js +1 -1
- package/dist/components/mail/services/index.js.map +1 -1
- package/dist/components/mail/services/mail.service.d.ts.map +1 -1
- package/dist/components/mail/services/mail.service.js +18 -17
- package/dist/components/mail/services/mail.service.js.map +1 -1
- package/dist/components/mail/utilities/address.utility.d.ts +9 -0
- package/dist/components/mail/utilities/address.utility.d.ts.map +1 -0
- package/dist/components/mail/utilities/address.utility.js +36 -0
- package/dist/components/mail/utilities/address.utility.js.map +1 -0
- package/dist/components/mail/utilities/index.d.ts +2 -0
- package/dist/components/mail/utilities/index.d.ts.map +1 -1
- package/dist/components/mail/utilities/index.js +2 -0
- package/dist/components/mail/utilities/index.js.map +1 -1
- package/dist/components/mail/utilities/mime.utility.d.ts +19 -0
- package/dist/components/mail/utilities/mime.utility.d.ts.map +1 -0
- package/dist/components/mail/utilities/mime.utility.js +414 -0
- package/dist/components/mail/utilities/mime.utility.js.map +1 -0
- package/dist/components/static-asset/common/commit-token.d.ts +23 -0
- package/dist/components/static-asset/common/commit-token.d.ts.map +1 -0
- package/dist/components/static-asset/common/commit-token.js +40 -0
- package/dist/components/static-asset/common/commit-token.js.map +1 -0
- package/dist/components/static-asset/common/constants.d.ts +13 -1
- package/dist/components/static-asset/common/constants.d.ts.map +1 -1
- package/dist/components/static-asset/common/constants.js +29 -2
- package/dist/components/static-asset/common/constants.js.map +1 -1
- package/dist/components/static-asset/common/errors.d.ts +43 -0
- package/dist/components/static-asset/common/errors.d.ts.map +1 -1
- package/dist/components/static-asset/common/errors.js +37 -0
- package/dist/components/static-asset/common/errors.js.map +1 -1
- package/dist/components/static-asset/common/index.d.ts +1 -0
- package/dist/components/static-asset/common/index.d.ts.map +1 -1
- package/dist/components/static-asset/common/index.js +1 -0
- package/dist/components/static-asset/common/index.js.map +1 -1
- package/dist/components/static-asset/common/keys.d.ts +1 -0
- package/dist/components/static-asset/common/keys.d.ts.map +1 -1
- package/dist/components/static-asset/common/keys.js +1 -0
- package/dist/components/static-asset/common/keys.js.map +1 -1
- package/dist/components/static-asset/common/types.d.ts +87 -30
- package/dist/components/static-asset/common/types.d.ts.map +1 -1
- package/dist/components/static-asset/component.d.ts +4 -1
- package/dist/components/static-asset/component.d.ts.map +1 -1
- package/dist/components/static-asset/component.js +21 -10
- package/dist/components/static-asset/component.js.map +1 -1
- package/dist/components/static-asset/controller/base.definition.d.ts +27 -554
- package/dist/components/static-asset/controller/base.definition.d.ts.map +1 -1
- package/dist/components/static-asset/controller/base.definition.js +250 -286
- package/dist/components/static-asset/controller/base.definition.js.map +1 -1
- package/dist/components/static-asset/controller/factory.d.ts +76 -7
- package/dist/components/static-asset/controller/factory.d.ts.map +1 -1
- package/dist/components/static-asset/controller/factory.js +565 -138
- package/dist/components/static-asset/controller/factory.js.map +1 -1
- package/dist/components/static-asset/index.d.ts +1 -0
- package/dist/components/static-asset/index.d.ts.map +1 -1
- package/dist/components/static-asset/index.js +1 -0
- package/dist/components/static-asset/index.js.map +1 -1
- package/dist/components/static-asset/ingest.d.ts +58 -0
- package/dist/components/static-asset/ingest.d.ts.map +1 -0
- package/dist/components/static-asset/ingest.js +82 -0
- package/dist/components/static-asset/ingest.js.map +1 -0
- package/dist/components/static-asset/models/base.model.d.ts +44 -6
- package/dist/components/static-asset/models/base.model.d.ts.map +1 -1
- package/dist/components/static-asset/models/base.model.js +7 -1
- package/dist/components/static-asset/models/base.model.js.map +1 -1
- package/dist/components/websocket/handlers/bun.handler.d.ts.map +1 -1
- package/dist/components/websocket/handlers/bun.handler.js +2 -1
- package/dist/components/websocket/handlers/bun.handler.js.map +1 -1
- package/dist/connectors/postgres/drivers/postgres-js.d.mts +3 -0
- package/dist/connectors/postgres/drivers/postgres-js.d.mts.map +1 -0
- package/dist/connectors/postgres/drivers/postgres-js.mjs +3 -0
- package/dist/connectors/postgres/drivers/postgres-js.mjs.map +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +29 -26
- package/dist/base/applications/types.d.ts.map +0 -1
- package/dist/base/applications/types.js.map +0 -1
- package/dist/components/api-reference/ui-factory.d.ts +0 -24
- package/dist/components/api-reference/ui-factory.d.ts.map +0 -1
- package/dist/components/api-reference/ui-factory.js +0 -97
- package/dist/components/api-reference/ui-factory.js.map +0 -1
- package/dist/components/auth/authenticate/strategies/jwks.strategy.d.ts.map +0 -1
- package/dist/components/auth/authenticate/strategies/jwks.strategy.js.map +0 -1
- package/dist/components/auth/authorize/adapters/scoped-casbin.adapter.d.ts.map +0 -1
- package/dist/components/auth/authorize/adapters/scoped-casbin.adapter.js.map +0 -1
- package/dist/components/auth/authorize/adapters/types.d.ts.map +0 -1
- package/dist/components/auth/authorize/adapters/types.js.map +0 -1
- package/dist/components/mail/common/types.d.ts +0 -201
- package/dist/components/mail/common/types.d.ts.map +0 -1
- package/dist/components/mail/common/types.js +0 -10
- package/dist/components/mail/common/types.js.map +0 -1
- package/dist/components/mail/services/generator.service.d.ts.map +0 -1
- package/dist/components/mail/services/generator.service.js.map +0 -1
- /package/dist/base/applications/{types.js → common/types.js} +0 -0
- /package/dist/components/auth/authorize/adapters/{types.js → common/types.js} +0 -0
|
@@ -9,22 +9,133 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.AssetControllerFactory = void 0;
|
|
12
|
+
exports.AssetControllerFactory = exports.resolveServedContentType = exports.buildObjectLink = exports.findLabelsInQuery = void 0;
|
|
13
13
|
const controllers_1 = require("../../../base/controllers");
|
|
14
14
|
const metadata_1 = require("../../../base/metadata");
|
|
15
|
-
const core_1 = require("@venizia/ignis-helpers/core");
|
|
16
|
-
const common_1 = require("@venizia/ignis-helpers/common");
|
|
17
15
|
const ignis_helpers_1 = require("@venizia/ignis-helpers");
|
|
16
|
+
const common_1 = require("@venizia/ignis-helpers/common");
|
|
17
|
+
const core_1 = require("@venizia/ignis-helpers/core");
|
|
18
18
|
const node_fs_1 = require("node:fs");
|
|
19
19
|
const common_2 = require("../common");
|
|
20
|
+
const common_3 = require("../common");
|
|
20
21
|
const base_definition_1 = require("./base.definition");
|
|
21
22
|
/** Hono ALREADY percent-decodes path params - a second decodeURIComponent throws on `report_100%.pdf` and turns `a%2Fb.png` into a DIFFERENT object; `isValidName`/`isValidPath` still run on this value, so traversal is still rejected. */
|
|
23
|
+
/** The upload labels, read off the multipart form. Every value arrives as text, so `sequence` is the one that needs converting. */
|
|
24
|
+
/** One list: the reader below and the stale-caller warning must agree. */
|
|
25
|
+
const UPLOAD_LABEL_NAMES = [
|
|
26
|
+
'principalType',
|
|
27
|
+
'principalId',
|
|
28
|
+
'variant',
|
|
29
|
+
'sequence',
|
|
30
|
+
'folderPath',
|
|
31
|
+
];
|
|
32
|
+
/** Names any label a caller put in the query - where the upload routes refuse it. */
|
|
33
|
+
const findLabelsInQuery = (opts) => (opts.names ?? UPLOAD_LABEL_NAMES).filter(name => opts.query[name] !== undefined);
|
|
34
|
+
exports.findLabelsInQuery = findLabelsInQuery;
|
|
35
|
+
/** Refused before the body is read: a route that ignored them would store an unlabelled row. */
|
|
36
|
+
const refuseLabelsInQuery = (opts) => {
|
|
37
|
+
const names = (0, exports.findLabelsInQuery)(opts);
|
|
38
|
+
if (names.length > 0) {
|
|
39
|
+
throw (0, core_1.getError)({
|
|
40
|
+
error: common_2.StaticAssetErrors.LABELS_IN_QUERY,
|
|
41
|
+
message: `${opts.reason ?? 'Upload labels belong in the request body, not the query'} | names: ${names.join(', ')}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** The commit body carries four of the five: the object key - and so `folderPath` - was fixed when the policy was issued. */
|
|
46
|
+
const COMMIT_LABEL_NAMES = UPLOAD_LABEL_NAMES.filter(name => name !== 'folderPath');
|
|
47
|
+
// A multipart form hands strings; a JSON commit body may hand `sequence` as a number.
|
|
48
|
+
const readUploadLabels = (opts) => {
|
|
49
|
+
const { fields } = opts;
|
|
50
|
+
const read = (name) => {
|
|
51
|
+
const value = fields[name];
|
|
52
|
+
return value === undefined || value === '' ? undefined : String(value);
|
|
53
|
+
};
|
|
54
|
+
const sequence = read('sequence');
|
|
55
|
+
return {
|
|
56
|
+
principalType: read('principalType'),
|
|
57
|
+
principalId: read('principalId'),
|
|
58
|
+
variant: read('variant'),
|
|
59
|
+
sequence: sequence === undefined ? undefined : Number(sequence),
|
|
60
|
+
folderPath: read('folderPath'),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
22
63
|
const readObjectName = rawObjectName => rawObjectName;
|
|
23
64
|
/** Encodes an object path into a SINGLE url segment: `{objectName}` matches one segment only, so `/` must be percent-encoded too (Hono decodes it back before the handler reads the param). */
|
|
24
65
|
const encodeObjectPath = objectPath => {
|
|
25
66
|
return encodeURIComponent(objectPath);
|
|
26
67
|
};
|
|
27
|
-
/**
|
|
68
|
+
/** The bucket one request works on: a configured value wins - called on every request, so an application may read an environment variable lazily - else the path param, which is only registered when there is no configured bucket. */
|
|
69
|
+
const resolveBucket = async (opts) => {
|
|
70
|
+
const { configured, param } = opts;
|
|
71
|
+
if (configured === undefined) {
|
|
72
|
+
return param ?? '';
|
|
73
|
+
}
|
|
74
|
+
return (0, common_1.resolveValueAsync)({ value: configured });
|
|
75
|
+
};
|
|
76
|
+
/** The URL of one object, in the shape its controller serves: no `/buckets/<name>` with a configured bucket, and a raw path with `rawObjectPath`. */
|
|
77
|
+
const buildObjectLink = (opts) => {
|
|
78
|
+
const { basePath, bucket, object } = opts;
|
|
79
|
+
const { hasConfiguredBucket = false, rawObjectPath = false } = opts;
|
|
80
|
+
const bucketSegment = hasConfiguredBucket ? '' : `/buckets/${bucket.name}`;
|
|
81
|
+
const objectSegment = rawObjectPath ? object.key : encodeObjectPath(object.key);
|
|
82
|
+
return `${basePath}${bucketSegment}/objects/${objectSegment}`;
|
|
83
|
+
};
|
|
84
|
+
exports.buildObjectLink = buildObjectLink;
|
|
85
|
+
/** Mirrors `BaseStorageHelper.normalizeObjectName`, which is protected: `resolveObjectName` is offered the very name the helper would otherwise have written. */
|
|
86
|
+
const defaultObjectName = ({ file }) => {
|
|
87
|
+
const { originalName, folderPath } = file;
|
|
88
|
+
const normalizedFileName = originalName.toLowerCase().replace(/ /g, '_');
|
|
89
|
+
if (!folderPath) {
|
|
90
|
+
return normalizedFileName;
|
|
91
|
+
}
|
|
92
|
+
return `${folderPath.toLowerCase().replace(/ /g, '_')}/${normalizedFileName}`;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The served type comes from the object NAME, never the backend or the uploader: a renderable type on
|
|
96
|
+
* the API origin is stored XSS. Exported so a hand-written route makes the same decision - the
|
|
97
|
+
* backend's own content-type is dropped by `WHITELIST_HEADERS`.
|
|
98
|
+
*/
|
|
99
|
+
const resolveServedContentType = (opts) => {
|
|
100
|
+
const { helper, object } = opts;
|
|
101
|
+
const candidate = helper.getMimeType({ filename: object.key }).toLowerCase().split(';')[0].trim();
|
|
102
|
+
if (!common_2.RENDERABLE_CONTENT_TYPES.has(candidate)) {
|
|
103
|
+
return { contentType: common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM, isRenderable: false };
|
|
104
|
+
}
|
|
105
|
+
return { contentType: candidate, isRenderable: true };
|
|
106
|
+
};
|
|
107
|
+
exports.resolveServedContentType = resolveServedContentType;
|
|
108
|
+
/**
|
|
109
|
+
* Reads one `Range` header. Only a single byte range is honoured - multipart ranges need a multipart
|
|
110
|
+
* body no browser asks for here. An unsatisfiable or malformed range returns `null`, and the caller
|
|
111
|
+
* serves the whole object, which is what RFC 9110 allows.
|
|
112
|
+
*/
|
|
113
|
+
const readByteRange = (opts) => {
|
|
114
|
+
const { header, size } = opts;
|
|
115
|
+
if (!header || size <= 0) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
119
|
+
if (!match) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const [, rawStart, rawEnd] = match;
|
|
123
|
+
// `bytes=-500` means the LAST 500 bytes, not "from 0 to 500".
|
|
124
|
+
if (!rawStart) {
|
|
125
|
+
const suffixLength = Number(rawEnd);
|
|
126
|
+
if (!Number.isInteger(suffixLength) || suffixLength <= 0) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return { start: Math.max(size - suffixLength, 0), end: size - 1 };
|
|
130
|
+
}
|
|
131
|
+
const start = Number(rawStart);
|
|
132
|
+
const end = rawEnd ? Number(rawEnd) : size - 1;
|
|
133
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start > end || start >= size) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return { start, end: Math.min(end, size - 1) };
|
|
137
|
+
};
|
|
138
|
+
/** Sets whitelisted metadata headers on the response context. `content-type` is not among them. */
|
|
28
139
|
const applyMetadataHeaders = ({ ctx, metadata }) => {
|
|
29
140
|
Object.entries(metadata).forEach(([key, value]) => {
|
|
30
141
|
if (!common_2.WHITELIST_HEADERS.includes(key.toLowerCase())) {
|
|
@@ -33,16 +144,63 @@ const applyMetadataHeaders = ({ ctx, metadata }) => {
|
|
|
33
144
|
ctx.header(key.toLowerCase(), String(value).replace(/[\r\n]/g, ''));
|
|
34
145
|
});
|
|
35
146
|
};
|
|
147
|
+
/** The MetaLink row for one uploaded object. Both upload paths go through here, so a direct upload lands exactly the row an ordinary one does. */
|
|
148
|
+
const createMetaLinkRow = async (opts) => {
|
|
149
|
+
const { metaLink, helper, storage, uploadResult, fileStat, query } = opts;
|
|
150
|
+
if (metaLink.createMetaLink) {
|
|
151
|
+
const created = await metaLink.createMetaLink({ uploadResult, fileStat, query });
|
|
152
|
+
return created.data;
|
|
153
|
+
}
|
|
154
|
+
// Resolved per call so an application that swaps the repository is not pinned to the first one.
|
|
155
|
+
const repository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
|
|
156
|
+
const created = await repository.create({
|
|
157
|
+
data: {
|
|
158
|
+
bucketName: uploadResult.bucket.name,
|
|
159
|
+
objectName: uploadResult.object.key,
|
|
160
|
+
link: uploadResult.link,
|
|
161
|
+
// The column is NOT NULL and a backend may report nothing.
|
|
162
|
+
mimetype: fileStat.metadata?.mimetype ?? helper.getMimeType({ filename: uploadResult.object.key }),
|
|
163
|
+
size: fileStat.size,
|
|
164
|
+
etag: fileStat.etag,
|
|
165
|
+
metadata: fileStat.metadata,
|
|
166
|
+
storageType: storage,
|
|
167
|
+
isSynced: true,
|
|
168
|
+
principalId: query.principalId ? String(query.principalId) : undefined,
|
|
169
|
+
principalType: query.principalType ? String(query.principalType) : undefined,
|
|
170
|
+
variant: query.variant ? String(query.variant) : undefined,
|
|
171
|
+
// Absent leaves the column default rather than writing 0, so a row the caller never
|
|
172
|
+
// ordered is indistinguishable from a legacy one.
|
|
173
|
+
sequence: query.sequence === undefined ? undefined : Number(query.sequence),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return created.data;
|
|
177
|
+
};
|
|
36
178
|
class AssetControllerFactory extends core_1.BaseHelper {
|
|
37
179
|
constructor() {
|
|
38
180
|
super({ scope: AssetControllerFactory.name });
|
|
39
181
|
}
|
|
40
182
|
static defineAssetController(opts) {
|
|
41
183
|
const { controller, helper, options, useMetaLink, metaLink, storage } = opts;
|
|
42
|
-
const {
|
|
184
|
+
const { resolveObjectName, defineRoutesBefore, defineExtraRoutes } = opts;
|
|
185
|
+
const { directUpload } = controller;
|
|
186
|
+
const { name, basePath, routes, isStrict = true, bucket, rawObjectPath = false } = controller;
|
|
43
187
|
const maxFolderDepth = options?.maxFolderDepth ?? ignis_helpers_1.BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH;
|
|
188
|
+
const maxBytes = options?.maxBytes;
|
|
189
|
+
const hasConfiguredBucket = bucket !== undefined;
|
|
190
|
+
const definitions = (0, base_definition_1.buildAssetDefinitions)({ hasConfiguredBucket, rawObjectPath });
|
|
44
191
|
// The mount path may be declared with or without a leading slash; a link must carry exactly one.
|
|
45
192
|
const normalizedBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;
|
|
193
|
+
/** Only when the URL shape deviates: with neither option the storage helper's own link is left untouched. */
|
|
194
|
+
const shapedLinkFn = hasConfiguredBucket || rawObjectPath
|
|
195
|
+
? (linkOptions) => (0, exports.buildObjectLink)({
|
|
196
|
+
basePath: normalizedBasePath,
|
|
197
|
+
bucket: linkOptions.bucket,
|
|
198
|
+
object: linkOptions.object,
|
|
199
|
+
hasConfiguredBucket,
|
|
200
|
+
rawObjectPath,
|
|
201
|
+
})
|
|
202
|
+
: undefined;
|
|
203
|
+
const normalizeLinkFn = options?.normalizeLinkFn ?? shapedLinkFn;
|
|
46
204
|
let GeneratedStaticAssetController = class GeneratedStaticAssetController extends controllers_1.BaseRestController {
|
|
47
205
|
constructor() {
|
|
48
206
|
super({
|
|
@@ -65,79 +223,161 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
65
223
|
}
|
|
66
224
|
}
|
|
67
225
|
binding() {
|
|
226
|
+
// First, so a literal path can win over the built-in catch-all `rawObjectPath` registers.
|
|
227
|
+
defineRoutesBefore?.({ controller: this, helper, basePath: normalizedBasePath });
|
|
228
|
+
// A single-bucket application has no reason to expose bucket creation or deletion, so the
|
|
229
|
+
// four bucket-management routes are not registered once `bucket` is configured.
|
|
230
|
+
if (!hasConfiguredBucket) {
|
|
231
|
+
this.bindRoute({
|
|
232
|
+
configs: { ...definitions.GET_BUCKETS, ...routes?.getBuckets },
|
|
233
|
+
}).to({
|
|
234
|
+
handler: async (ctx) => {
|
|
235
|
+
const buckets = await helper.getBuckets();
|
|
236
|
+
return ctx.json(buckets, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
this.bindRoute({
|
|
240
|
+
configs: { ...definitions.GET_BUCKET_BY_NAME, ...routes?.getBucketByName },
|
|
241
|
+
}).to({
|
|
242
|
+
handler: async (ctx) => {
|
|
243
|
+
const params = ctx.req.valid('param');
|
|
244
|
+
const bucketName = await resolveBucket({
|
|
245
|
+
configured: bucket,
|
|
246
|
+
param: params.bucketName,
|
|
247
|
+
});
|
|
248
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
249
|
+
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
250
|
+
}
|
|
251
|
+
const found = await helper.getBucket({ bucket: { name: bucketName } });
|
|
252
|
+
return ctx.json(found, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
this.bindRoute({
|
|
256
|
+
configs: { ...definitions.CREATE_BUCKET, ...routes?.createBucket },
|
|
257
|
+
}).to({
|
|
258
|
+
handler: async (ctx) => {
|
|
259
|
+
const params = ctx.req.valid('param');
|
|
260
|
+
const bucketName = await resolveBucket({
|
|
261
|
+
configured: bucket,
|
|
262
|
+
param: params.bucketName,
|
|
263
|
+
});
|
|
264
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
265
|
+
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
266
|
+
}
|
|
267
|
+
const createdBucket = await helper.createBucket({ bucket: { name: bucketName } });
|
|
268
|
+
return ctx.json(createdBucket, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
this.bindRoute({
|
|
272
|
+
configs: { ...definitions.DELETE_BUCKET, ...routes?.deleteBucket },
|
|
273
|
+
}).to({
|
|
274
|
+
handler: async (ctx) => {
|
|
275
|
+
const params = ctx.req.valid('param');
|
|
276
|
+
const bucketName = await resolveBucket({
|
|
277
|
+
configured: bucket,
|
|
278
|
+
param: params.bucketName,
|
|
279
|
+
});
|
|
280
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
281
|
+
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
282
|
+
}
|
|
283
|
+
const isRemovedBucket = await helper.removeBucket({
|
|
284
|
+
bucket: { name: bucketName },
|
|
285
|
+
});
|
|
286
|
+
return ctx.json({ isDeleted: isRemovedBucket }, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
68
290
|
this.bindRoute({
|
|
69
|
-
configs: { ...
|
|
70
|
-
}).to({
|
|
71
|
-
handler: async (ctx) => {
|
|
72
|
-
const bucket = await helper.getBuckets();
|
|
73
|
-
return ctx.json(bucket, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
this.bindRoute({
|
|
77
|
-
configs: { ...base_definition_1.StaticAssetDefinitions.GET_BUCKET_BY_NAME, ...routes?.getBucketByName },
|
|
78
|
-
}).to({
|
|
79
|
-
handler: async (ctx) => {
|
|
80
|
-
const { bucketName } = ctx.req.valid('param');
|
|
81
|
-
if (!helper.isValidName(bucketName)) {
|
|
82
|
-
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
83
|
-
}
|
|
84
|
-
const bucket = await helper.getBucket({ name: bucketName });
|
|
85
|
-
return ctx.json(bucket, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
this.bindRoute({
|
|
89
|
-
configs: { ...base_definition_1.StaticAssetDefinitions.GET_OBJECT_BY_NAME, ...routes?.getObjectByName },
|
|
291
|
+
configs: { ...definitions.GET_OBJECT_BY_NAME, ...routes?.getObjectByName },
|
|
90
292
|
}).to({
|
|
91
293
|
handler: async (ctx) => {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
294
|
+
const params = ctx.req.valid('param');
|
|
295
|
+
const bucketName = await resolveBucket({
|
|
296
|
+
configured: bucket,
|
|
297
|
+
param: params.bucketName,
|
|
298
|
+
});
|
|
299
|
+
const objectName = readObjectName(params.objectName);
|
|
300
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
95
301
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
96
302
|
}
|
|
97
|
-
if (!helper.
|
|
303
|
+
if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
|
|
98
304
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
|
|
99
305
|
}
|
|
100
|
-
const fileStat = await helper.getStat({
|
|
306
|
+
const fileStat = await helper.getStat({
|
|
307
|
+
bucket: { name: bucketName },
|
|
308
|
+
object: { key: objectName },
|
|
309
|
+
});
|
|
101
310
|
const { size, metadata } = fileStat;
|
|
102
311
|
applyMetadataHeaders({ ctx, metadata });
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
|
|
312
|
+
const served = (0, exports.resolveServedContentType)({ helper, object: { key: objectName } });
|
|
313
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, served.contentType);
|
|
107
314
|
ctx.header('x-content-type-options', 'nosniff');
|
|
108
|
-
|
|
315
|
+
// Sandboxed even when renderable, so an unforeseen renderable type cannot reach this origin.
|
|
316
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_SECURITY_POLICY, 'sandbox');
|
|
317
|
+
// Advertised unconditionally: a player only offers seeking once it sees this.
|
|
318
|
+
ctx.header('accept-ranges', 'bytes');
|
|
319
|
+
// A type outside the allow-list downloads instead of rendering; that is the XSS boundary.
|
|
320
|
+
if (!served.isRenderable) {
|
|
321
|
+
const inlineName = objectName.split('/').pop() ?? objectName;
|
|
322
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: inlineName, type: 'attachment' }));
|
|
323
|
+
}
|
|
324
|
+
const range = readByteRange({ header: ctx.req.header('range'), size });
|
|
325
|
+
if (range) {
|
|
326
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, String(range.end - range.start + 1));
|
|
327
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_RANGE, `bytes ${range.start}-${range.end}/${size}`);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
|
|
331
|
+
}
|
|
332
|
+
// The web stream is what a Response body wants, so no Node Readable sits in between.
|
|
333
|
+
const stream = await helper.getObjectStream({
|
|
334
|
+
bucket: { name: bucketName },
|
|
335
|
+
object: { key: objectName },
|
|
336
|
+
...(range ? { range } : {}),
|
|
337
|
+
});
|
|
109
338
|
return new Response(stream, {
|
|
110
339
|
headers: ctx.res.headers,
|
|
111
|
-
status: common_1.HTTP.ResultCodes.RS_2.Ok,
|
|
340
|
+
status: range ? common_1.HTTP.ResultCodes.RS_2.PartialContent : common_1.HTTP.ResultCodes.RS_2.Ok,
|
|
112
341
|
});
|
|
113
342
|
},
|
|
114
343
|
});
|
|
115
344
|
this.bindRoute({
|
|
116
345
|
configs: {
|
|
117
|
-
...
|
|
346
|
+
...definitions.DOWNLOAD_OBJECT_BY_NAME,
|
|
118
347
|
...routes?.downloadObjectByName,
|
|
119
348
|
},
|
|
120
349
|
}).to({
|
|
121
350
|
handler: async (ctx) => {
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
351
|
+
const params = ctx.req.valid('param');
|
|
352
|
+
const bucketName = await resolveBucket({
|
|
353
|
+
configured: bucket,
|
|
354
|
+
param: params.bucketName,
|
|
355
|
+
});
|
|
356
|
+
const objectName = readObjectName(params.objectName);
|
|
357
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
125
358
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
126
359
|
}
|
|
127
|
-
if (!helper.
|
|
360
|
+
if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
|
|
128
361
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
|
|
129
362
|
}
|
|
130
|
-
const fileStat = await helper.getStat({
|
|
363
|
+
const fileStat = await helper.getStat({
|
|
364
|
+
bucket: { name: bucketName },
|
|
365
|
+
object: { key: objectName },
|
|
366
|
+
});
|
|
131
367
|
const { size, metadata } = fileStat;
|
|
132
368
|
applyMetadataHeaders({ ctx, metadata });
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
369
|
+
// Download always attaches, so the served type only has to be honest, never renderable.
|
|
370
|
+
const served = (0, exports.resolveServedContentType)({ helper, object: { key: objectName } });
|
|
371
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, served.contentType);
|
|
136
372
|
ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
|
|
137
373
|
const fileName = objectName.split('/').pop() ?? objectName;
|
|
138
374
|
ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: fileName, type: 'attachment' }));
|
|
139
375
|
ctx.header('x-content-type-options', 'nosniff');
|
|
140
|
-
|
|
376
|
+
ctx.header(common_1.HTTP.Headers.CONTENT_SECURITY_POLICY, 'sandbox');
|
|
377
|
+
const stream = await helper.getObjectStream({
|
|
378
|
+
bucket: { name: bucketName },
|
|
379
|
+
object: { key: objectName },
|
|
380
|
+
});
|
|
141
381
|
return new Response(stream, {
|
|
142
382
|
headers: ctx.res.headers,
|
|
143
383
|
status: common_1.HTTP.ResultCodes.RS_2.Ok,
|
|
@@ -145,28 +385,18 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
145
385
|
},
|
|
146
386
|
});
|
|
147
387
|
this.bindRoute({
|
|
148
|
-
configs: { ...
|
|
149
|
-
}).to({
|
|
150
|
-
handler: async (ctx) => {
|
|
151
|
-
const { bucketName } = ctx.req.valid('param');
|
|
152
|
-
if (!helper.isValidName(bucketName)) {
|
|
153
|
-
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
154
|
-
}
|
|
155
|
-
const createdBucket = await helper.createBucket({ name: bucketName });
|
|
156
|
-
return ctx.json(createdBucket, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
this.bindRoute({
|
|
160
|
-
configs: { ...base_definition_1.StaticAssetDefinitions.UPLOAD, ...routes?.upload },
|
|
388
|
+
configs: { ...definitions.UPLOAD, ...routes?.upload },
|
|
161
389
|
}).to({
|
|
162
390
|
handler: async (ctx) => {
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
391
|
+
// A configured bucket drops `params` from this route entirely, so it is read only when it exists.
|
|
392
|
+
const bucketParam = hasConfiguredBucket
|
|
393
|
+
? undefined
|
|
394
|
+
: ctx.req.valid('param').bucketName;
|
|
395
|
+
const bucketName = await resolveBucket({ configured: bucket, param: bucketParam });
|
|
396
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
166
397
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
167
398
|
}
|
|
168
|
-
const
|
|
169
|
-
if (folderPath) {
|
|
399
|
+
const validateFolderPath = (folderPath) => {
|
|
170
400
|
const normalizedFolder = folderPath.replace(/^\/+|\/+$/g, '');
|
|
171
401
|
if (!normalizedFolder) {
|
|
172
402
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.FOLDER_PATH_INVALID });
|
|
@@ -179,19 +409,37 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
179
409
|
message: `Folder path exceeds max depth of ${maxFolderDepth}`,
|
|
180
410
|
});
|
|
181
411
|
}
|
|
182
|
-
const invalidSegmentIndex = folderSegments.findIndex(segment => !helper.
|
|
412
|
+
const invalidSegmentIndex = folderSegments.findIndex(segment => !helper.isValidSegment({ segment }));
|
|
183
413
|
if (invalidSegmentIndex !== -1) {
|
|
184
414
|
throw (0, core_1.getError)({
|
|
185
415
|
error: common_2.StaticAssetErrors.FOLDER_SEGMENT_INVALID,
|
|
186
416
|
message: `Invalid folder path segment: ${folderSegments[invalidSegmentIndex]}`,
|
|
187
417
|
});
|
|
188
418
|
}
|
|
419
|
+
};
|
|
420
|
+
refuseLabelsInQuery({ query: ctx.req.query() });
|
|
421
|
+
// The early exit, before the body is spooled. Not authoritative: a multipart envelope is
|
|
422
|
+
// larger than its files, so the per-file check below is the one that decides.
|
|
423
|
+
if (maxBytes !== undefined) {
|
|
424
|
+
const declaredLength = Number(ctx.req.header(common_1.HTTP.Headers.CONTENT_LENGTH));
|
|
425
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
426
|
+
throw (0, core_1.getError)({
|
|
427
|
+
error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
|
|
428
|
+
message: `Request body of ${declaredLength} bytes exceeds the maximum of ${maxBytes}`,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
189
431
|
}
|
|
190
|
-
const filesArray = await (0, ignis_helpers_1.parseMultipartBody)({
|
|
432
|
+
const { files: filesArray, fields } = await (0, ignis_helpers_1.parseMultipartBody)({
|
|
191
433
|
context: ctx,
|
|
192
434
|
storage: options?.parseMultipartBody?.storage,
|
|
193
435
|
uploadDir: options?.parseMultipartBody?.uploadDir,
|
|
194
436
|
});
|
|
437
|
+
// Labels travel with the payload, not the URL, so the folder check waits for the parse.
|
|
438
|
+
const query = readUploadLabels({ fields });
|
|
439
|
+
const folderPath = query.folderPath;
|
|
440
|
+
if (folderPath) {
|
|
441
|
+
validateFolderPath(folderPath);
|
|
442
|
+
}
|
|
195
443
|
const spoolPaths = filesArray
|
|
196
444
|
.map(file => file.path)
|
|
197
445
|
.filter((filePath) => Boolean(filePath));
|
|
@@ -206,6 +454,13 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
206
454
|
message: `Empty file content | name: ${file.originalname}`,
|
|
207
455
|
});
|
|
208
456
|
}
|
|
457
|
+
// Authoritative: `buffer.length`, not the client-declared `size`.
|
|
458
|
+
if (maxBytes !== undefined && buffer.length > maxBytes) {
|
|
459
|
+
throw (0, core_1.getError)({
|
|
460
|
+
error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
|
|
461
|
+
message: `File ${file.originalname} of ${buffer.length} bytes exceeds the maximum of ${maxBytes}`,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
209
464
|
return {
|
|
210
465
|
originalName: file.originalname,
|
|
211
466
|
mimetype: file.mimetype,
|
|
@@ -215,11 +470,25 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
215
470
|
folderPath: folderPath ?? undefined,
|
|
216
471
|
};
|
|
217
472
|
});
|
|
473
|
+
// Built per request: the hook is offered the bucket, which only the route knows. No
|
|
474
|
+
// hook leaves `normalizeNameFn` exactly as configured, so the stored name is unchanged.
|
|
475
|
+
const normalizeNameFn = resolveObjectName
|
|
476
|
+
? (naming) => {
|
|
477
|
+
const defaultKey = options?.normalizeNameFn
|
|
478
|
+
? options.normalizeNameFn(naming)
|
|
479
|
+
: defaultObjectName(naming);
|
|
480
|
+
return resolveObjectName({
|
|
481
|
+
bucket: { name: bucketName },
|
|
482
|
+
file: naming.file,
|
|
483
|
+
defaultKey,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
: options?.normalizeNameFn;
|
|
218
487
|
uploaded = await helper.upload({
|
|
219
|
-
bucket: bucketName,
|
|
488
|
+
bucket: { name: bucketName },
|
|
220
489
|
files: modifiedFiles,
|
|
221
|
-
normalizeNameFn
|
|
222
|
-
normalizeLinkFn
|
|
490
|
+
normalizeNameFn,
|
|
491
|
+
normalizeLinkFn,
|
|
223
492
|
// Without this the helper re-validates against its own hard default of 2, so an app configured for a deeper tree spools the body and only then fails inside the helper.
|
|
224
493
|
maxFolderDepth,
|
|
225
494
|
});
|
|
@@ -234,47 +503,30 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
234
503
|
for (const uploadResult of uploaded) {
|
|
235
504
|
try {
|
|
236
505
|
const fileStat = await helper.getStat({
|
|
237
|
-
bucket: uploadResult.
|
|
238
|
-
|
|
506
|
+
bucket: uploadResult.bucket,
|
|
507
|
+
object: uploadResult.object,
|
|
239
508
|
});
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
objectName: uploadResult.objectName,
|
|
250
|
-
link: uploadResult.link,
|
|
251
|
-
mimetype: fileStat.metadata?.['mimetype'],
|
|
252
|
-
size: fileStat.size,
|
|
253
|
-
etag: fileStat.etag,
|
|
254
|
-
metadata: fileStat.metadata,
|
|
255
|
-
storageType: storage,
|
|
256
|
-
isSynced: true,
|
|
257
|
-
principalId: query.principalId ? String(query.principalId) : undefined,
|
|
258
|
-
principalType: query.principalType
|
|
259
|
-
? String(query.principalType)
|
|
260
|
-
: undefined,
|
|
261
|
-
variant: query.variant ? String(query.variant) : undefined,
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
results.push({ ...uploadResult, metaLink: createdMetaLink });
|
|
509
|
+
const createdMetaLink = await createMetaLinkRow({
|
|
510
|
+
metaLink,
|
|
511
|
+
helper,
|
|
512
|
+
storage,
|
|
513
|
+
uploadResult,
|
|
514
|
+
fileStat,
|
|
515
|
+
query,
|
|
516
|
+
});
|
|
517
|
+
results.push({ ...uploadResult, metaLink: { data: createdMetaLink } });
|
|
265
518
|
}
|
|
266
519
|
catch (error) {
|
|
267
520
|
this.logger
|
|
268
521
|
.for('UPLOAD')
|
|
269
|
-
.error('Failed to create MetaLink | objectName: %s | Error: %s', uploadResult.
|
|
522
|
+
.error('Failed to create MetaLink | objectName: %s | Error: %s', uploadResult.object.key, error);
|
|
270
523
|
results.push({
|
|
271
524
|
...uploadResult,
|
|
272
|
-
metaLink: null,
|
|
273
525
|
// A CODE, never the driver text. This handler returns 200, so it bypasses the
|
|
274
526
|
// error middleware and `database.handler` - the two places that strip
|
|
275
527
|
// `detail`/`table`/`constraint` - and shipped raw constraint names to the client.
|
|
276
528
|
// The real error is already logged in full immediately above.
|
|
277
|
-
|
|
529
|
+
metaLink: { error: common_2.META_LINK_CREATE_FAILED },
|
|
278
530
|
});
|
|
279
531
|
}
|
|
280
532
|
}
|
|
@@ -282,36 +534,39 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
282
534
|
},
|
|
283
535
|
});
|
|
284
536
|
this.bindRoute({
|
|
285
|
-
configs: { ...
|
|
537
|
+
configs: { ...definitions.DELETE_OBJECT, ...routes?.deleteObject },
|
|
286
538
|
}).to({
|
|
287
539
|
handler: async (ctx) => {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const isRemovedBucket = await helper.removeBucket({
|
|
293
|
-
name: bucketName,
|
|
540
|
+
const params = ctx.req.valid('param');
|
|
541
|
+
const bucketName = await resolveBucket({
|
|
542
|
+
configured: bucket,
|
|
543
|
+
param: params.bucketName,
|
|
294
544
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
});
|
|
298
|
-
this.bindRoute({
|
|
299
|
-
configs: { ...base_definition_1.StaticAssetDefinitions.DELETE_OBJECT, ...routes?.deleteObject },
|
|
300
|
-
}).to({
|
|
301
|
-
handler: async (ctx) => {
|
|
302
|
-
const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
|
|
303
|
-
const objectName = readObjectName(rawObjectName);
|
|
304
|
-
if (!helper.isValidName(bucketName)) {
|
|
545
|
+
const objectName = readObjectName(params.objectName);
|
|
546
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
305
547
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
306
548
|
}
|
|
307
|
-
if (!helper.
|
|
549
|
+
if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
|
|
308
550
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
|
|
309
551
|
}
|
|
310
|
-
|
|
552
|
+
// Deliberately idempotent: S3 answers 200 for a key that was never there, and consumers
|
|
553
|
+
// already depend on it. `disk` throws instead, so the two are reconciled here, not below.
|
|
554
|
+
try {
|
|
555
|
+
await helper.removeObject({
|
|
556
|
+
bucket: { name: bucketName },
|
|
557
|
+
object: { key: objectName },
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
if (!(0, ignis_helpers_1.isNotFoundError)({ error })) {
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
311
565
|
if (!useMetaLink || !metaLink) {
|
|
312
566
|
return ctx.json({ success: true }, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
313
567
|
}
|
|
314
|
-
metaLink.repository
|
|
568
|
+
const deleteRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
|
|
569
|
+
deleteRepository
|
|
315
570
|
.deleteAll({
|
|
316
571
|
where: {
|
|
317
572
|
bucketName,
|
|
@@ -332,12 +587,16 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
332
587
|
},
|
|
333
588
|
});
|
|
334
589
|
this.bindRoute({
|
|
335
|
-
configs: { ...
|
|
590
|
+
configs: { ...definitions.LIST_OBJECTS, ...routes?.listObjects },
|
|
336
591
|
}).to({
|
|
337
592
|
handler: async (ctx) => {
|
|
338
|
-
|
|
593
|
+
// A configured bucket drops `params` from this route entirely, so it is read only when it exists.
|
|
594
|
+
const bucketParam = hasConfiguredBucket
|
|
595
|
+
? undefined
|
|
596
|
+
: ctx.req.valid('param').bucketName;
|
|
597
|
+
const bucketName = await resolveBucket({ configured: bucket, param: bucketParam });
|
|
339
598
|
const { prefix, recursive, maxKeys } = ctx.req.valid('query');
|
|
340
|
-
if (!helper.
|
|
599
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
341
600
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
342
601
|
}
|
|
343
602
|
// A NaN or 0 maxKeys is silently treated as "unlimited" by the storage backends, so an unparsable value must be rejected instead of forwarded.
|
|
@@ -352,7 +611,7 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
352
611
|
}
|
|
353
612
|
}
|
|
354
613
|
const objects = await helper.listObjects({
|
|
355
|
-
bucket: bucketName,
|
|
614
|
+
bucket: { name: bucketName },
|
|
356
615
|
prefix,
|
|
357
616
|
useRecursive: recursive === 'true',
|
|
358
617
|
maxKeys: resolvedMaxKeys,
|
|
@@ -360,27 +619,190 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
360
619
|
return ctx.json(objects, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
361
620
|
},
|
|
362
621
|
});
|
|
622
|
+
if (directUpload) {
|
|
623
|
+
if (!hasConfiguredBucket) {
|
|
624
|
+
throw (0, core_1.getError)({
|
|
625
|
+
message: `[${name}] directUpload needs controller.bucket | a policy names one bucket, and a bucket in the URL is a bucket the caller chooses`,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
const pendingPrefix = directUpload.pendingPrefix ?? common_2.DEFAULT_PENDING_PREFIX;
|
|
629
|
+
this.bindRoute({
|
|
630
|
+
configs: { ...definitions.UPLOAD_POLICY, ...routes?.uploadPolicy },
|
|
631
|
+
}).to({
|
|
632
|
+
handler: async (ctx) => {
|
|
633
|
+
if (!(await directUpload.authorize({ context: ctx }))) {
|
|
634
|
+
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.UPLOAD_NOT_AUTHORIZED });
|
|
635
|
+
}
|
|
636
|
+
const { files } = ctx.req.valid('json');
|
|
637
|
+
const bucketName = await resolveBucket({ configured: bucket });
|
|
638
|
+
const policies = [];
|
|
639
|
+
for (const file of files) {
|
|
640
|
+
if (file.size !== undefined && file.size > directUpload.maxBytes) {
|
|
641
|
+
throw (0, core_1.getError)({
|
|
642
|
+
error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
|
|
643
|
+
message: `File '${file.fileName}' is ${file.size} bytes, over the ${directUpload.maxBytes} the policy allows`,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
// Generated, never taken from the caller: a key the caller picks is a key the caller
|
|
647
|
+
// can collide with, and the pending object is the one thing a policy authorizes.
|
|
648
|
+
const objectName = `${pendingPrefix}${core_1.UuidHelper.getInstance().v4()}/${file.fileName}`;
|
|
649
|
+
const policy = await helper.presignPost({
|
|
650
|
+
bucket: { name: bucketName },
|
|
651
|
+
keyPrefix: pendingPrefix,
|
|
652
|
+
maxBytes: directUpload.maxBytes,
|
|
653
|
+
contentType: file.contentType,
|
|
654
|
+
expiresIn: directUpload.expiresIn,
|
|
655
|
+
});
|
|
656
|
+
policies.push({
|
|
657
|
+
...policy,
|
|
658
|
+
objectName,
|
|
659
|
+
commitToken: (0, common_3.buildCommitToken)({
|
|
660
|
+
payload: {
|
|
661
|
+
bucket: bucketName,
|
|
662
|
+
key: objectName,
|
|
663
|
+
expiresAt: new Date(policy.expiresAt).getTime(),
|
|
664
|
+
},
|
|
665
|
+
secretKey: directUpload.secretKey,
|
|
666
|
+
}),
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
return ctx.json(policies, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
this.bindRoute({
|
|
673
|
+
configs: { ...definitions.UPLOAD_COMMIT, ...routes?.uploadCommit },
|
|
674
|
+
}).to({
|
|
675
|
+
handler: async (ctx) => {
|
|
676
|
+
const commitQuery = ctx.req.query();
|
|
677
|
+
refuseLabelsInQuery({ query: commitQuery, names: COMMIT_LABEL_NAMES });
|
|
678
|
+
refuseLabelsInQuery({
|
|
679
|
+
query: commitQuery,
|
|
680
|
+
names: ['folderPath'],
|
|
681
|
+
reason: 'folderPath was decided when the upload policy was issued; the commit route cannot move the object',
|
|
682
|
+
});
|
|
683
|
+
const { commitToken, ...labels } = ctx.req.valid('json');
|
|
684
|
+
// Verified BEFORE any storage call. That is what keeps this route from being a name
|
|
685
|
+
// prober: a forged token fails without a round trip, so timing says nothing.
|
|
686
|
+
const payload = (0, common_3.readCommitToken)({
|
|
687
|
+
token: commitToken,
|
|
688
|
+
secretKey: directUpload.secretKey,
|
|
689
|
+
});
|
|
690
|
+
// Labels, not authorization - the token deliberately carries no business fields, so
|
|
691
|
+
// they travel beside it rather than inside it.
|
|
692
|
+
const query = readUploadLabels({ fields: labels });
|
|
693
|
+
const bucketRef = { name: payload.bucket };
|
|
694
|
+
const pendingObject = { key: payload.key };
|
|
695
|
+
const object = { key: payload.key.slice(pendingPrefix.length) };
|
|
696
|
+
// Before the copy, so a hook that throws leaves the object under the pending prefix
|
|
697
|
+
// for a lifecycle rule, rather than stranding it at the final key.
|
|
698
|
+
await directUpload.onCommit?.({ bucket: bucketRef, pendingObject, object });
|
|
699
|
+
await helper.copyObject({
|
|
700
|
+
bucket: bucketRef,
|
|
701
|
+
source: pendingObject,
|
|
702
|
+
destination: object,
|
|
703
|
+
});
|
|
704
|
+
// The copy IS the commit; removing the temporary object is cleanup. A failure here
|
|
705
|
+
// leaves one object for the lifecycle rule, and failing the request would tell the
|
|
706
|
+
// caller their upload did not work when it did.
|
|
707
|
+
await helper
|
|
708
|
+
.removeObject({ bucket: bucketRef, object: pendingObject })
|
|
709
|
+
.catch(error => {
|
|
710
|
+
this.logger
|
|
711
|
+
.for('UPLOAD_COMMIT')
|
|
712
|
+
.warn('Committed, but the pending object remains | key: %s | error: %s', pendingObject.key, error);
|
|
713
|
+
});
|
|
714
|
+
const link = (0, exports.buildObjectLink)({
|
|
715
|
+
basePath: normalizedBasePath,
|
|
716
|
+
bucket: bucketRef,
|
|
717
|
+
object,
|
|
718
|
+
hasConfiguredBucket,
|
|
719
|
+
rawObjectPath,
|
|
720
|
+
});
|
|
721
|
+
// The two paths have to agree: without this a client reading `metaLink` off the
|
|
722
|
+
// response renders an empty state with no error - the failure nobody reports.
|
|
723
|
+
let metaLinkResult;
|
|
724
|
+
if (useMetaLink && metaLink) {
|
|
725
|
+
try {
|
|
726
|
+
const fileStat = await helper.getStat({ bucket: bucketRef, object });
|
|
727
|
+
const data = await createMetaLinkRow({
|
|
728
|
+
metaLink,
|
|
729
|
+
helper,
|
|
730
|
+
storage,
|
|
731
|
+
uploadResult: {
|
|
732
|
+
bucket: bucketRef,
|
|
733
|
+
object: {
|
|
734
|
+
key: object.key,
|
|
735
|
+
size: fileStat.size,
|
|
736
|
+
contentType: fileStat.metadata?.mimetype ??
|
|
737
|
+
helper.getMimeType({ filename: object.key }),
|
|
738
|
+
},
|
|
739
|
+
link,
|
|
740
|
+
},
|
|
741
|
+
fileStat,
|
|
742
|
+
query,
|
|
743
|
+
});
|
|
744
|
+
metaLinkResult = { data };
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
// The object IS committed by now. A 500 would send the caller back with a token
|
|
748
|
+
// whose source no longer exists, so this rides the body as a code, like UPLOAD.
|
|
749
|
+
this.logger
|
|
750
|
+
.for('UPLOAD_COMMIT')
|
|
751
|
+
.error('Failed to create MetaLink | objectName: %s | Error: %s', object.key, error);
|
|
752
|
+
metaLinkResult = { error: common_2.META_LINK_CREATE_FAILED };
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return ctx.json({
|
|
756
|
+
bucket: bucketRef,
|
|
757
|
+
object,
|
|
758
|
+
link,
|
|
759
|
+
...(metaLinkResult === undefined ? {} : { metaLink: metaLinkResult }),
|
|
760
|
+
}, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
761
|
+
},
|
|
762
|
+
});
|
|
763
|
+
}
|
|
363
764
|
if (useMetaLink && metaLink) {
|
|
364
765
|
this.bindRoute({
|
|
365
|
-
configs: { ...
|
|
766
|
+
configs: { ...definitions.RECREATE_METALINK, ...routes?.recreateMetaLink },
|
|
366
767
|
}).to({
|
|
367
768
|
handler: async (ctx) => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
769
|
+
// This route takes no labels anywhere: one in the query would land an unlabelled row.
|
|
770
|
+
refuseLabelsInQuery({
|
|
771
|
+
query: ctx.req.query(),
|
|
772
|
+
reason: 'This route recreates a MetaLink for an existing object and takes no labels',
|
|
773
|
+
});
|
|
774
|
+
const params = ctx.req.valid('param');
|
|
775
|
+
const bucketName = await resolveBucket({
|
|
776
|
+
configured: bucket,
|
|
777
|
+
param: params.bucketName,
|
|
778
|
+
});
|
|
779
|
+
const objectName = readObjectName(params.objectName);
|
|
780
|
+
if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
|
|
371
781
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
|
|
372
782
|
}
|
|
373
|
-
if (!helper.
|
|
783
|
+
if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
|
|
374
784
|
throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
|
|
375
785
|
}
|
|
376
786
|
const fileStat = await helper.getStat({
|
|
377
|
-
bucket: bucketName,
|
|
378
|
-
|
|
787
|
+
bucket: { name: bucketName },
|
|
788
|
+
object: { key: objectName },
|
|
379
789
|
});
|
|
380
790
|
const link = options?.normalizeLinkFn
|
|
381
|
-
? options.normalizeLinkFn({
|
|
382
|
-
|
|
383
|
-
|
|
791
|
+
? options.normalizeLinkFn({
|
|
792
|
+
bucket: { name: bucketName },
|
|
793
|
+
object: { key: objectName },
|
|
794
|
+
})
|
|
795
|
+
: (0, exports.buildObjectLink)({
|
|
796
|
+
basePath: normalizedBasePath,
|
|
797
|
+
bucket: { name: bucketName },
|
|
798
|
+
object: { key: objectName },
|
|
799
|
+
hasConfiguredBucket,
|
|
800
|
+
rawObjectPath,
|
|
801
|
+
});
|
|
802
|
+
// Read per call, never once per handler: an application may rebind the repository
|
|
803
|
+
// between two of these, and a copy held across them would answer from the old binding.
|
|
804
|
+
const findRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
|
|
805
|
+
const existing = await findRepository.findOne({
|
|
384
806
|
filter: {
|
|
385
807
|
where: {
|
|
386
808
|
bucketName,
|
|
@@ -389,7 +811,8 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
389
811
|
},
|
|
390
812
|
});
|
|
391
813
|
if (existing) {
|
|
392
|
-
await metaLink.repository
|
|
814
|
+
const updateRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
|
|
815
|
+
await updateRepository.updateById({
|
|
393
816
|
id: existing.id,
|
|
394
817
|
data: {
|
|
395
818
|
link,
|
|
@@ -401,15 +824,17 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
401
824
|
isSynced: true,
|
|
402
825
|
},
|
|
403
826
|
});
|
|
404
|
-
const
|
|
827
|
+
const readRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
|
|
828
|
+
const updatedMetaLink = await readRepository.findById({ id: existing.id });
|
|
405
829
|
return ctx.json({ success: true, metaLink: updatedMetaLink }, common_1.HTTP.ResultCodes.RS_2.Ok);
|
|
406
830
|
}
|
|
407
|
-
const
|
|
831
|
+
const createRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
|
|
832
|
+
const createdMetaLink = await createRepository.create({
|
|
408
833
|
data: {
|
|
409
834
|
bucketName,
|
|
410
835
|
objectName,
|
|
411
836
|
link,
|
|
412
|
-
mimetype: fileStat.metadata?.
|
|
837
|
+
mimetype: fileStat.metadata?.mimetype ?? helper.getMimeType({ filename: objectName }),
|
|
413
838
|
size: fileStat.size,
|
|
414
839
|
etag: fileStat.etag,
|
|
415
840
|
metadata: fileStat.metadata,
|
|
@@ -421,6 +846,8 @@ class AssetControllerFactory extends core_1.BaseHelper {
|
|
|
421
846
|
},
|
|
422
847
|
});
|
|
423
848
|
}
|
|
849
|
+
// Last, so a built-in route always wins a path collision with an application's own.
|
|
850
|
+
defineExtraRoutes?.({ controller: this, helper, basePath: normalizedBasePath });
|
|
424
851
|
}
|
|
425
852
|
};
|
|
426
853
|
GeneratedStaticAssetController = __decorate([
|