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