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