gina 0.5.26 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +34 -22
- package/ROADMAP.md +43 -16
- package/bin/cli +12 -1
- package/bin/gina-init +15 -1
- package/framework/v0.6.0/VERSION +1 -0
- package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/js/gina.js +36 -1
- package/framework/v0.6.0/core/asset/plugin/dist/vendor/gina/js/gina.min.js +601 -0
- package/framework/v0.6.0/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- package/framework/v0.6.0/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/session-store.v3.js +52 -84
- package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/session-store.v4.js +58 -84
- package/framework/{v0.5.26 → v0.6.0}/core/connectors/mongodb/lib/session-store.js +5 -2
- package/framework/{v0.5.26 → v0.6.0}/core/connectors/redis/lib/session-store.js +5 -2
- package/framework/{v0.5.26 → v0.6.0}/core/connectors/scylladb/lib/session-store.js +5 -2
- package/framework/{v0.5.26 → v0.6.0}/core/connectors/sqlite/lib/session-store.js +17 -3
- package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/session/src/main.js +142 -9
- package/framework/{v0.5.26 → v0.6.0}/core/router.js +101 -11
- package/framework/{v0.5.26 → v0.6.0}/core/server.js +91 -1
- package/framework/{v0.5.26 → v0.6.0}/core/template/conf/settings.json +3 -0
- package/framework/v0.6.0/lib/audit/src/main.js +916 -0
- package/framework/v0.6.0/lib/authn/package.json +7 -0
- package/framework/v0.6.0/lib/authn/src/lockout.js +615 -0
- package/framework/v0.6.0/lib/authn/src/main.js +899 -0
- package/framework/v0.6.0/lib/authn/src/totp.js +418 -0
- package/framework/v0.6.0/lib/cmd/audit/arguments.json +5 -0
- package/framework/v0.6.0/lib/cmd/audit/help.txt +50 -0
- package/framework/v0.6.0/lib/cmd/audit/verify.js +294 -0
- package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/init.js +11 -0
- package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/tail.js +5 -1
- package/framework/{v0.5.26 → v0.6.0}/lib/cmd/gina.1.md +1 -0
- package/framework/{v0.5.26 → v0.6.0}/lib/index.js +13 -0
- package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/file/index.js +11 -1
- package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/mq/speaker.js +8 -1
- package/framework/v0.6.0/lib/net-locality/package.json +17 -0
- package/framework/v0.6.0/lib/net-locality/src/main.js +174 -0
- package/framework/{v0.5.26 → v0.6.0}/lib/nunjucks-filters/src/main.js +13 -0
- package/framework/{v0.5.26 → v0.6.0}/lib/routing/src/main.js +36 -1
- package/framework/{v0.5.26 → v0.6.0}/lib/swig-filters/src/main.js +13 -0
- package/framework/{v0.5.26 → v0.6.0}/package.json +1 -1
- package/gna.js +4 -4
- package/llms.txt +19 -2
- package/package.json +3 -3
- package/schema/connectors.json +2 -3
- package/schema/settings.json +101 -0
- package/types/index.d.ts +4 -0
- package/framework/v0.5.26/VERSION +0 -1
- package/framework/v0.5.26/core/asset/plugin/dist/vendor/gina/js/gina.min.js +0 -599
- package/framework/v0.5.26/core/asset/plugin/dist/vendor/gina/js/gina.min.js.br +0 -0
- package/framework/v0.5.26/core/asset/plugin/dist/vendor/gina/js/gina.min.js.gz +0 -0
- package/framework/v0.5.26/lib/audit/src/main.js +0 -456
- /package/framework/{v0.5.26 → v0.6.0}/AUTHORS +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/LICENSE +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/html/nolayout.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/html/static.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/img/android-chrome-192x192.png +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/img/android-chrome-512x512.png +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/img/apple-touch-icon.png +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/img/favicon-16x16.png +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/img/favicon-32x32.png +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/img/favicon.ico +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.css +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/beemaster/beemaster.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/beemaster/index.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/css/gina.min.css +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.br +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/css/gina.min.css.gz +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/html/statusbar.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.br +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/html/statusbar.html.gz +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/inspector/have_heart_one-webfont.woff2 +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/inspector/index.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/inspector/inspector.css +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/inspector/inspector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/inspector/logo.svg +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.br +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/asset/plugin/dist/vendor/gina/js/gina.onload.min.js.gz +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/config.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/ai/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/ai/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/connector.v3.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/connector.v4.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/n1ql.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/couchbase/lib/session-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/mongodb/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/mongodb/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/mongodb/lib/job-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/mongodb/lib/pipeline-loader.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/mysql/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/mysql/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/postgresql/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/postgresql/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/redis/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/redis/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/redis/lib/job-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/redis/lib/render-cache-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/scylladb/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/scylladb/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/sql-parser.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/sqlite/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/sqlite/lib/connector.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/connectors/sqlite/lib/job-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/content.encoding +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.framework.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-json.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-nunjucks-async.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-nunjucks.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-stream.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-swig-async.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-swig.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/controller.render-v1.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/inspector-window-emit.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/controller/release-banner.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/dev/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/dev/lib/class.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/dev/lib/factory.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/dev/lib/tools.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/gna.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/currency.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/dist/language/en.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/dist/language/fr.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/dist/region/en.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/dist/region/fr.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/locales/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/mime.types +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/model/entity.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/model/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/model/template/entityFactory.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/model/template/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/csrf/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/csrf/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/csrf/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/coep/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/coep/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/coep/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/coop/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/coop/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/coop/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/corp/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/corp/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/corp/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/csp/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/csp/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/csp/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/hide-powered-by/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/hide-powered-by/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/hide-powered-by/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/hsts/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/hsts/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/hsts/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/origin-agent-cluster/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/origin-agent-cluster/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/origin-agent-cluster/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/referrer-policy/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/referrer-policy/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/referrer-policy/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-content-type-options/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-content-type-options/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-content-type-options/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-dns-prefetch-control/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-dns-prefetch-control/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-dns-prefetch-control/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-download-options/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-download-options/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-download-options/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-frame-options/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-frame-options/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-frame-options/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-permitted-cross-domain-policies/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-xss-protection/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-xss-protection/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/security-headers/x-xss-protection/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/session/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/session/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/storage/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/storage/build.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/storage/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/storage/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/validator/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/validator/build.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/validator/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/validator/src/form-validator.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/plugins/lib/validator/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/server.express.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/server.isaac.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/status.codes +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/_gitignore +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/app.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/connectors.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/routing.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/settings.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/settings.server.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/templates.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/config/watchers.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/controllers/controller.content.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/controllers/controller.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/controllers/setup.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle/locales/en.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_namespace/controllers/controller.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/css/default.css +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/css/home.css +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/css/vendor/readme.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/favicon.ico +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/js/components/x-checklist.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/js/vendor/readme.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/manifest.webmanifest +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/readme.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_public/sw.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_templates/handlers/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_templates/html/content/homepage.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_templates/html/includes/error-msg-noscript.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_templates/html/includes/error-msg-outdated-browser.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_templates/html/includes/x-checklist.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/boilerplate/bundle_templates/html/layouts/main.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/command/gina.bat.tpl +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/command/gina.tpl +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/conf/env.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/conf/manifest.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/conf/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/conf/statics.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/conf/templates.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/error/client/json/401.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/error/client/json/403.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/error/client/json/404.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/error/server/html/50x.html +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/error/server/json/500.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/error/server/json/503.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/core/template/extensions/logger/config.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/console.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/context.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/data/LICENSE +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/data/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/data/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/data/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/dateFormat.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/json/LICENSE +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/json/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/json/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/json/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/path.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/plugins/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/plugins/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/plugins/src/api-error.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/plugins/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/prototypes.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/task.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/helpers/text.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/admin/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/admin/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/archiver/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/archiver/build.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/archiver/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/archiver/src/dep/jszip.min.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/archiver/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/async/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/async/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/audit/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/audit-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/authz-gate/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/authz-gate/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cache/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cache/build.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cache/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cache/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/aliases.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/build.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/copy.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/cp.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/inc/name-rewrite.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/man.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/mcp-start.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/mcp.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/oas.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/openapi.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/rename.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/restart.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/start.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/status.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/stop.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/bundle/types.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/cache/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/cache/clear.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/cache/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/cache/stats.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/infer.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/migrate.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/models.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/connector/test.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/container/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/container/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/container/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/container/man.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/container/ps.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/container/stop.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/inc/args.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/inc/namespace.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/inc/reference-rewrite.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/inc/reference-scan.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/inc/scaffold.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/rename.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/controller/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/get.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/link-dev.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/set.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/unset.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/env/use.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/build.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/dot.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/get.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/link-node-modules.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/link.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/man.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/msg.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/open.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/reset.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/restart.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/set.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/start.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/status.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/stop.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/update.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/framework/version.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/gina-dev.1.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/gina-framework.1.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/helper.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/export.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/import.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/i18n/scan.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/_host.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/build.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/man.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/image/run.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/inspector/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/inspector/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/inspector/open.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/man-render.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/minion/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/minion/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/minion/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/minion/kill.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/minion/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/msg.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/port/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/port/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/port/inc/scan.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/port/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/port/reset.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/port/set.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/backup.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/build.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/import.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/man.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/move.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/rename.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/restart.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/restore.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/start.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/status.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/project/stop.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/protocol/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/protocol/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/protocol/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/protocol/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/protocol/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/protocol/set.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/link-local.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/link-production.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/remove.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/rm.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/scope/use.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/secrets/arguments.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/secrets/check.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/secrets/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/secrets/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/secrets/scan.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/service/help.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/service/help.txt +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/service/list.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/service/man.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/service/start.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd/view/add.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd-status-format/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cmd-status-format/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/collection/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/collection/build.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/collection/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/collection/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/config.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/connector-config/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/connector-config/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/connector-error/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/connector-error/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/connector-registry/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/connector-registry/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cron/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cron/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/cron/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/domain/LICENSE +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/domain/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/domain/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/domain/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/dto/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/dto/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/dto-pipe/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/dto-pipe/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/dto-types/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/dto-types/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/generator/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/i18n/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/i18n/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/image-build/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/image-build/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inherits/LICENSE +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inherits/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inherits/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inherits/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inspector-events/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inspector-events/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inspector-redact/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/inspector-redact/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/instrument/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/instrument/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/job/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/job/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/job-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/json-config-header/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/json-config-header/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/default/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/file/lib/logrotator/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/file/lib/logrotator/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/mq/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/containers/mq/listener.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/helper.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/logger/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/math/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/mcp-dispatch/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/mcp-dispatch/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/mcp-http/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/mcp-http/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/mcp-server/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/mcp-server/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/merge/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/merge/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/merge/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/metrics/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/metrics/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/model.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/nunjucks-filters/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/nunjucks-filters/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/nunjucks-resolver/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/nunjucks-resolver/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/proc.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/release-watch/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/release-watch/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/render-cache/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/render-cache/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/render-cache-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/routing/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/routing/build.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/routing/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/routing/src/radix.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/routing-introspect/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/routing-introspect/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/secrets/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/secrets/src/backends/env.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/secrets/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/session-store.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/shell.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/state.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/swig-filters/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/swig-filters/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/swig-resolver/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/swig-resolver/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/template-loaders/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/template-loaders/src/loaders/http.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/template-loaders/src/loaders/memory.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/template-loaders/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/url/README.md +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/url/index.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/url/routing.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/uuid/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/uuid/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/validator.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/watcher/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/watcher/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/ws-framing/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/ws-framing/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/ws-query/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/ws-query/src/main.js +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/ws-session/package.json +0 -0
- /package/framework/{v0.5.26 → v0.6.0}/lib/ws-session/src/main.js +0 -0
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the gina package.
|
|
3
|
+
* Copyright (c) 2009-2026 Rhinostone <contact@gina.io>
|
|
4
|
+
*
|
|
5
|
+
* For the full copyright and license information, please view the LICENSE
|
|
6
|
+
* file that was distributed with this source code.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @module gina/lib/audit
|
|
13
|
+
*
|
|
14
|
+
* The #COMPLY2 audit-trail primitive — a user-attributed, append-only record
|
|
15
|
+
* of "who did what to which record when", DISTINCT from `lib/logger`
|
|
16
|
+
* (severity-level app logging): the trail has its own store and never rides
|
|
17
|
+
* the logger sinks.
|
|
18
|
+
*
|
|
19
|
+
* - **Module-singleton.** Configuration and the store live at module scope and
|
|
20
|
+
* are adopted ONCE at bundle boot (`core/server.js` `init()` calls
|
|
21
|
+
* {@link start} after the fail-fast lint). Registered in `lib/index.js` with
|
|
22
|
+
* a plain `require` (never `_require`) — like `lib/job` / `State` / logger,
|
|
23
|
+
* `refreshCore()` re-runs `Lib()` on every dev-mode HTTP request, and
|
|
24
|
+
* `_require` would discard the adopted store + counters on each request.
|
|
25
|
+
* - **File JSONL backend by default.** `audit.enabled: true` with no
|
|
26
|
+
* `audit.store` writes one JSON line per record to an append-only file
|
|
27
|
+
* (O_APPEND fd opened at boot — which also proves writability at boot).
|
|
28
|
+
* Writes are SERIALIZED through a FIFO queue: parallel `fs.write` calls on
|
|
29
|
+
* one fd complete in threadpool order, so without the queue the on-disk
|
|
30
|
+
* order could diverge from emit order — harmless for JSONL, fatal for the
|
|
31
|
+
* slice-3 HMAC hash chain (`hash = HMAC(key, prevHash + canonical(record))`),
|
|
32
|
+
* which is why the queue ships in the MVP.
|
|
33
|
+
* - **Fire-and-forget by design.** A write failure is counted + logged
|
|
34
|
+
* (`console.error`) and NEVER thrown into the request path; the optional
|
|
35
|
+
* per-call callback is for callers that must confirm the write.
|
|
36
|
+
* - **Tamper-evidence (opt-in, `audit.chain`).** Every record gains a `hash`
|
|
37
|
+
* chaining from its predecessor (HMAC-SHA256 over {@link canonicalV1}, hex),
|
|
38
|
+
* verified offline by `gina audit:verify` / {@link verifyChain}. Detects
|
|
39
|
+
* post-hoc edit, deletion, insertion and reordering by anyone WITHOUT the
|
|
40
|
+
* key. Explicit boundaries, stated rather than implied: truncation at the
|
|
41
|
+
* exact tail is invisible (nothing after it commits to it); a compromised
|
|
42
|
+
* writing process holds the key and can forge freely — stream the file to
|
|
43
|
+
* WORM storage (the documented isolation control) for that adversary; and
|
|
44
|
+
* without fsync, acknowledged records can be lost on a host crash (the
|
|
45
|
+
* chain stays consistent — loss is from the tail).
|
|
46
|
+
* - **Correlation, not attribution.** `requestId` mirrors `req._ginaReqId`
|
|
47
|
+
* (always-on since #COMPLY2 slice 1) and honours a sanitized inbound
|
|
48
|
+
* `X-Request-Id`, so it is client-influenceable BY DESIGN — attribution is
|
|
49
|
+
* the `actor` snapshot (session-derived), never the id.
|
|
50
|
+
* - **No require-time app dependency.** Node builtins only — the module loads
|
|
51
|
+
* standalone (the types-runtime-parity gate requires the whole lib registry
|
|
52
|
+
* at test load time).
|
|
53
|
+
*
|
|
54
|
+
* @package gina.framework
|
|
55
|
+
* @namespace lib.audit
|
|
56
|
+
* @author Rhinostone <contact@gina.io>
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
var fs = require('fs');
|
|
60
|
+
var path = require('path');
|
|
61
|
+
var crypto = require('crypto');
|
|
62
|
+
var StringDecoder = require('string_decoder').StringDecoder;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Chain genesis value — the `prevHash` of the very first chained record.
|
|
66
|
+
* 64 zeros: the same width as a real hex digest, self-describing in a dump.
|
|
67
|
+
*
|
|
68
|
+
* @constant
|
|
69
|
+
* @inner
|
|
70
|
+
* @type {string}
|
|
71
|
+
*/
|
|
72
|
+
var CHAIN_GENESIS = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Upper bound (bytes) on the boot-time backwards tail read. A single trail
|
|
76
|
+
* line larger than this is pathological (records are a few hundred bytes);
|
|
77
|
+
* beyond it the resume gives up gracefully (GENESIS + a torn marker) rather
|
|
78
|
+
* than scanning an unbounded file.
|
|
79
|
+
*
|
|
80
|
+
* @constant
|
|
81
|
+
* @inner
|
|
82
|
+
* @type {number}
|
|
83
|
+
*/
|
|
84
|
+
var CHAIN_TAIL_MAX = 1048576;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The audit store seam — what a backend must implement. The default file
|
|
88
|
+
* backend ({@link createFileStore}) serializes records to JSONL; a connector
|
|
89
|
+
* store (resolved by `lib/audit-store`, demand-gated) would persist the same
|
|
90
|
+
* record object its own way.
|
|
91
|
+
*
|
|
92
|
+
* @typedef {object} AuditStore
|
|
93
|
+
* @property {function(AuditRecord, function(?Error)=): void} append - Persist one record. MUST be append-only (never update/delete) and MUST report failure through the callback, never by throwing.
|
|
94
|
+
* @property {function(): void} close - Release the backend handle. NOT called on the request path — provided for tests and explicit teardown.
|
|
95
|
+
* @property {?string} [path] - The destination path when the backend is file-based (surfaced by {@link stats} and the boot log).
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* One audit record (schema v1 — the locked #COMPLY2 shape).
|
|
100
|
+
*
|
|
101
|
+
* @typedef {object} AuditRecord
|
|
102
|
+
* @property {string} id - Record id (`crypto.randomUUID()`).
|
|
103
|
+
* @property {number} ts - Epoch ms at emit time.
|
|
104
|
+
* @property {?string} requestId - `req._ginaReqId` (always-on since slice 1); null off-request.
|
|
105
|
+
* @property {{key: *, roles: string[], machine: boolean=}} actor - Snapshot: `session.user[actorKey]` + a COPY of `user.roles`. NEVER the whole user object (PII). For a #MS3 machine caller (no session): the caller NAME as `key`, its configured roles, plus `machine: true`.
|
|
106
|
+
* @property {string} action - App-defined verb, e.g. `"invoice.delete"`; framework auto-events use `"authz.denied"`.
|
|
107
|
+
* @property {*} [resource] - App-defined subject, e.g. an id — present only when the caller passed one.
|
|
108
|
+
* @property {object} [meta] - App-defined extras — present only when passed. Framework authz auto-events carry `{ outcome: '401'|'login-bounce'|'403-roles'|'403-policy' }` here.
|
|
109
|
+
* @property {?string} ip - Socket remoteAddress, `::ffff:`-normalized. `X-Forwarded-For` is NEVER read (the #OBS1 rule — that header is attacker-writable).
|
|
110
|
+
* @property {?string} rule - `req.routing.rule` (cardinality-safe, the metrics precedent).
|
|
111
|
+
* @property {?string} method - HTTP method.
|
|
112
|
+
* @property {?string} bundle - The booted bundle (captured at {@link start}).
|
|
113
|
+
* @property {?string} env - The booted env (captured at {@link start}).
|
|
114
|
+
* @property {string} [hash] - Tamper-evidence chain digest (present only when `audit.chain` is enabled): 64-char lowercase hex of `HMAC-SHA256(secret, prevHash + ':' + canonicalV1(record-without-hash))`, where `prevHash` is the previous record's `hash` (or {@link CHAIN_GENESIS} for the first). Appended LAST at write time; the canonical form is key-sorted, so field position never affects the digest.
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Resolved configuration — adopted once by {@link start}, null until then.
|
|
119
|
+
* `null` doubles as the "audit disabled" state: `core/server.js` only calls
|
|
120
|
+
* `start()` when `settings.json > audit.enabled === true`.
|
|
121
|
+
*
|
|
122
|
+
* @inner
|
|
123
|
+
* @type {?{bundle: ?string, env: ?string, actorKey: string, eventsAuthz: boolean}}
|
|
124
|
+
*/
|
|
125
|
+
var _conf = null;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The adopted store ({@link AuditStore}), null until {@link start}.
|
|
129
|
+
* @inner
|
|
130
|
+
* @type {?AuditStore}
|
|
131
|
+
*/
|
|
132
|
+
var _store = null;
|
|
133
|
+
|
|
134
|
+
/** Records successfully persisted since boot. @inner @type {number} */
|
|
135
|
+
var _written = 0;
|
|
136
|
+
|
|
137
|
+
/** Records dropped (serialization or write failure, or a bad `action`). @inner @type {number} */
|
|
138
|
+
var _dropped = 0;
|
|
139
|
+
|
|
140
|
+
/** @inner */
|
|
141
|
+
function noop() {}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build the default append-only JSONL file backend.
|
|
145
|
+
*
|
|
146
|
+
* Creates the parent directory (recursive) and opens the file with the
|
|
147
|
+
* `'a'` flag at BOOT — so an unwritable destination fails the boot (the
|
|
148
|
+
* caller's #B57 catch), never the first request. Each record is one
|
|
149
|
+
* `JSON.stringify(record) + '\n'` line; a record whose `meta` cannot be
|
|
150
|
+
* stringified (circular) fails through the callback and is dropped by the
|
|
151
|
+
* caller — other records are unaffected.
|
|
152
|
+
*
|
|
153
|
+
* Writes are serialized (one in-flight `fs.write` at a time, FIFO): O_APPEND
|
|
154
|
+
* makes each line land whole, but only the queue makes on-disk ORDER match
|
|
155
|
+
* emit order — the property the slice-3 hash chain needs.
|
|
156
|
+
*
|
|
157
|
+
* @inner
|
|
158
|
+
* @param {string} filePath - Absolute destination (`.jsonl`).
|
|
159
|
+
* @returns {AuditStore}
|
|
160
|
+
* @throws {Error} When the directory cannot be created or the file cannot be opened for append.
|
|
161
|
+
*/
|
|
162
|
+
function createFileStore(filePath) {
|
|
163
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
164
|
+
var fd = fs.openSync(filePath, 'a');
|
|
165
|
+
|
|
166
|
+
var queue = [];
|
|
167
|
+
var writing = false;
|
|
168
|
+
|
|
169
|
+
var drain = function() {
|
|
170
|
+
if (writing || queue.length === 0) return;
|
|
171
|
+
writing = true;
|
|
172
|
+
var item = queue.shift();
|
|
173
|
+
fs.write(fd, item.line, function(err) {
|
|
174
|
+
writing = false;
|
|
175
|
+
item.cb(err || null);
|
|
176
|
+
drain();
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
path: filePath,
|
|
182
|
+
append: function(record, cb) {
|
|
183
|
+
cb = (typeof cb === 'function') ? cb : noop;
|
|
184
|
+
var line;
|
|
185
|
+
try {
|
|
186
|
+
line = JSON.stringify(record) + '\n';
|
|
187
|
+
} catch (serr) {
|
|
188
|
+
return cb(serr);
|
|
189
|
+
}
|
|
190
|
+
queue.push({ line: line, cb: cb });
|
|
191
|
+
drain();
|
|
192
|
+
},
|
|
193
|
+
close: function() {
|
|
194
|
+
try { fs.closeSync(fd); } catch (cerr) { /* best-effort teardown */ }
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Canonical serialization v1 — the deterministic form the chain digest
|
|
201
|
+
* commits to. `JSON.stringify`'s output depends on key insertion order
|
|
202
|
+
* (`resource`/`meta` are conditionally inserted mid-record), so it is NOT a
|
|
203
|
+
* canonicalisation; this one is: object keys are sorted (UTF-16 code-unit
|
|
204
|
+
* order, `Array.prototype.sort` default), arrays keep their order, primitives
|
|
205
|
+
* serialize as `JSON.stringify` does.
|
|
206
|
+
*
|
|
207
|
+
* ⚠️ Operates on JSON VALUE SPACE — callers must project the input through a
|
|
208
|
+
* `JSON.parse(JSON.stringify(x))` round-trip first (the chain store does, and
|
|
209
|
+
* the verifier reads parsed-from-disk values, which are projected by
|
|
210
|
+
* construction). Skipping the projection breaks write/verify symmetry for
|
|
211
|
+
* `toJSON` types: a live `Date` would canonicalise as `{}` here while the
|
|
212
|
+
* disk record holds its ISO string.
|
|
213
|
+
*
|
|
214
|
+
* Versioned by NAME: a future change to the canonical form ships as
|
|
215
|
+
* `canonicalV2` + a records marker, never as a silent edit — every existing
|
|
216
|
+
* trail's digests depend on this exact byte behaviour.
|
|
217
|
+
*
|
|
218
|
+
* @memberof module:gina/lib/audit
|
|
219
|
+
* @param {*} value - A JSON-projected value (object, array, or primitive).
|
|
220
|
+
* @returns {string|undefined} The canonical string; `undefined` for `undefined` input (mirroring `JSON.stringify`).
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* canonicalV1({ b: 1, a: { d: null, c: [2, 1] } });
|
|
224
|
+
* // -> '{"a":{"c":[2,1],"d":null},"b":1}' — identical whatever the insertion order was
|
|
225
|
+
*/
|
|
226
|
+
function canonicalV1(value) {
|
|
227
|
+
if (value === null || typeof value !== 'object') {
|
|
228
|
+
return JSON.stringify(value);
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(value)) {
|
|
231
|
+
var parts = [];
|
|
232
|
+
for (var i = 0; i < value.length; i++) {
|
|
233
|
+
var av = canonicalV1(value[i]);
|
|
234
|
+
// JSON semantics: an un-serializable array slot becomes null
|
|
235
|
+
parts.push(typeof av === 'undefined' ? 'null' : av);
|
|
236
|
+
}
|
|
237
|
+
return '[' + parts.join(',') + ']';
|
|
238
|
+
}
|
|
239
|
+
var keys = Object.keys(value).sort();
|
|
240
|
+
var out = [];
|
|
241
|
+
for (var k = 0; k < keys.length; k++) {
|
|
242
|
+
var ov = canonicalV1(value[keys[k]]);
|
|
243
|
+
// JSON semantics: an un-serializable object member is dropped
|
|
244
|
+
if (typeof ov === 'undefined') continue;
|
|
245
|
+
out.push(JSON.stringify(keys[k]) + ':' + ov);
|
|
246
|
+
}
|
|
247
|
+
return '{' + out.join(',') + '}';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Compute one chain digest.
|
|
252
|
+
*
|
|
253
|
+
* @inner
|
|
254
|
+
* @param {string} secret - The HMAC key.
|
|
255
|
+
* @param {string} prevHash - The previous record's `hash` (or {@link CHAIN_GENESIS}).
|
|
256
|
+
* @param {string} canonical - {@link canonicalV1} of the record WITHOUT its `hash` field.
|
|
257
|
+
* @returns {string} 64-char lowercase hex. Hex (not base64url, the CSRF token
|
|
258
|
+
* choice) because the digest lives in a log file read by humans and
|
|
259
|
+
* SIEM tooling, where hex is the convention — base64url's rationale
|
|
260
|
+
* (URL-safe compactness in a transported token) does not apply here.
|
|
261
|
+
*/
|
|
262
|
+
function chainDigest(secret, prevHash, canonical) {
|
|
263
|
+
return crypto.createHmac('sha256', secret)
|
|
264
|
+
.update(prevHash + ':' + canonical)
|
|
265
|
+
.digest('hex');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Wrap a store with the tamper-evidence hash chain. The seam is unchanged —
|
|
270
|
+
* the wrapper IS an {@link AuditStore} — and the inner store stays dumb.
|
|
271
|
+
*
|
|
272
|
+
* Ordering is enforced HERE, by construction: one record in flight at a time,
|
|
273
|
+
* the digest computed at DEQUEUE (so hash order == append order == emit
|
|
274
|
+
* order), and `prevHash` advanced ONLY on a successful inner append — a
|
|
275
|
+
* dropped record (disk full, serialization failure) never forks the on-disk
|
|
276
|
+
* chain; the next record chains from the last record that actually landed.
|
|
277
|
+
* The drop itself is still counted + logged by {@link write}.
|
|
278
|
+
*
|
|
279
|
+
* Caller callbacks are invoked through a guard, so a throwing consumer
|
|
280
|
+
* callback can never stall the queue.
|
|
281
|
+
*
|
|
282
|
+
* @inner
|
|
283
|
+
* @param {AuditStore} inner - The wrapped backend (the file store today).
|
|
284
|
+
* @param {{secret: string, prevHash: string}} chainOpts - Key + resume point ({@link readChainTail}).
|
|
285
|
+
* @returns {AuditStore}
|
|
286
|
+
*/
|
|
287
|
+
function createChainStore(inner, chainOpts) {
|
|
288
|
+
var secret = chainOpts.secret;
|
|
289
|
+
var prev = chainOpts.prevHash || CHAIN_GENESIS;
|
|
290
|
+
|
|
291
|
+
var queue = [];
|
|
292
|
+
var busy = false;
|
|
293
|
+
|
|
294
|
+
var guardedCb = function(cb, err) {
|
|
295
|
+
try { cb(err); } catch (cerr) {
|
|
296
|
+
try { console.error('[audit] a write callback threw (the chain queue is unaffected): '+ (cerr.message || cerr)); } catch (e2) { /* never escalate */ }
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
var drain = function() {
|
|
301
|
+
if (busy || queue.length === 0) return;
|
|
302
|
+
busy = true;
|
|
303
|
+
var item = queue.shift();
|
|
304
|
+
var record = item.record;
|
|
305
|
+
try {
|
|
306
|
+
// Project into JSON value space FIRST (see canonicalV1's contract):
|
|
307
|
+
// the digest must commit to what the DISK will hold, not to live
|
|
308
|
+
// object graphs (Date & friends serialize through toJSON).
|
|
309
|
+
var projected = JSON.parse(JSON.stringify(record));
|
|
310
|
+
record.hash = chainDigest(secret, prev, canonicalV1(projected));
|
|
311
|
+
} catch (serr) {
|
|
312
|
+
busy = false;
|
|
313
|
+
guardedCb(item.cb, serr);
|
|
314
|
+
return drain();
|
|
315
|
+
}
|
|
316
|
+
inner.append(record, function(err) {
|
|
317
|
+
if (!err) {
|
|
318
|
+
prev = record.hash; // advance ONLY on success — a drop never forks the chain
|
|
319
|
+
}
|
|
320
|
+
busy = false;
|
|
321
|
+
guardedCb(item.cb, err || null);
|
|
322
|
+
drain();
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
path: inner.path,
|
|
328
|
+
append: function(record, cb) {
|
|
329
|
+
queue.push({ record: record, cb: (typeof cb === 'function') ? cb : noop });
|
|
330
|
+
drain();
|
|
331
|
+
},
|
|
332
|
+
close: function() {
|
|
333
|
+
if (typeof inner.close === 'function') inner.close();
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* O(1) boot-time resume: read the TAIL of an existing trail to recover the
|
|
340
|
+
* chain anchor. Never scans the whole file — a bounded backwards window
|
|
341
|
+
* ({@link CHAIN_TAIL_MAX}); whole-file integrity is `gina audit:verify`'s job.
|
|
342
|
+
*
|
|
343
|
+
* Anchor rule: the LAST complete line that parses as JSON and carries a
|
|
344
|
+
* 64-hex `hash` (walking back through at most a handful of damaged lines).
|
|
345
|
+
* A parseable hashless tail (a pre-chain trail) anchors at
|
|
346
|
+
* {@link CHAIN_GENESIS}. Trailing garbage — a torn partial line from a crash
|
|
347
|
+
* mid-write, or an unparseable complete line — is reported so {@link start}
|
|
348
|
+
* can acknowledge it with a chained `audit.chain.break` record.
|
|
349
|
+
*
|
|
350
|
+
* @inner
|
|
351
|
+
* @param {string} filePath
|
|
352
|
+
* @returns {{prevHash: string, torn: ?{reason: string, bytes: number}}}
|
|
353
|
+
*/
|
|
354
|
+
function readChainTail(filePath) {
|
|
355
|
+
var fd = null;
|
|
356
|
+
try {
|
|
357
|
+
fd = fs.openSync(filePath, 'r');
|
|
358
|
+
} catch (oerr) {
|
|
359
|
+
return { prevHash: CHAIN_GENESIS, torn: null }; // no trail yet
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
var size = fs.fstatSync(fd).size;
|
|
363
|
+
if (size === 0) {
|
|
364
|
+
return { prevHash: CHAIN_GENESIS, torn: null };
|
|
365
|
+
}
|
|
366
|
+
var want = Math.min(size, CHAIN_TAIL_MAX);
|
|
367
|
+
var buf = Buffer.alloc(want);
|
|
368
|
+
fs.readSync(fd, buf, 0, want, size - want);
|
|
369
|
+
var text = buf.toString('utf8');
|
|
370
|
+
|
|
371
|
+
var endsWithNL = text.charCodeAt(text.length - 1) === 10;
|
|
372
|
+
var lines = text.split('\n');
|
|
373
|
+
var tornPartial = null;
|
|
374
|
+
if (endsWithNL) {
|
|
375
|
+
lines.pop(); // the empty tail after the final newline
|
|
376
|
+
} else if (size > want) {
|
|
377
|
+
// The window itself starts mid-line AND ends mid-line — a single
|
|
378
|
+
// line larger than the whole window: give up gracefully.
|
|
379
|
+
return { prevHash: CHAIN_GENESIS, torn: { reason: 'oversized-tail-line', bytes: want } };
|
|
380
|
+
} else {
|
|
381
|
+
tornPartial = lines.pop(); // a crash mid-write left a partial line
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Walk back to the last parseable line carrying a valid chain hash.
|
|
385
|
+
var torn = (tornPartial !== null && tornPartial.length > 0)
|
|
386
|
+
? { reason: 'torn-tail', bytes: Buffer.byteLength(tornPartial) }
|
|
387
|
+
: null;
|
|
388
|
+
var WALK_MAX = 8; // a real crash tears at most one line; more damage is verify's to report
|
|
389
|
+
var walked = 0;
|
|
390
|
+
for (var i = lines.length - 1; i >= 0 && walked < WALK_MAX; i--) {
|
|
391
|
+
var line = lines[i];
|
|
392
|
+
if (line.length === 0) continue; // empty lines carry nothing
|
|
393
|
+
walked++;
|
|
394
|
+
var parsed = null;
|
|
395
|
+
try { parsed = JSON.parse(line); } catch (perr) {
|
|
396
|
+
if (!torn) {
|
|
397
|
+
torn = { reason: 'unparseable-tail', bytes: Buffer.byteLength(line) };
|
|
398
|
+
}
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (parsed && typeof parsed.hash === 'string' && /^[0-9a-f]{64}$/.test(parsed.hash)) {
|
|
402
|
+
return { prevHash: parsed.hash, torn: torn };
|
|
403
|
+
}
|
|
404
|
+
// Parseable but hashless: a pre-chain trail — the chain starts fresh.
|
|
405
|
+
return { prevHash: CHAIN_GENESIS, torn: torn };
|
|
406
|
+
}
|
|
407
|
+
return { prevHash: CHAIN_GENESIS, torn: torn };
|
|
408
|
+
} finally {
|
|
409
|
+
if (fd !== null) {
|
|
410
|
+
try { fs.closeSync(fd); } catch (cerr) { /* best-effort */ }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Iterate a file line-by-line, synchronously, in bounded memory (64 KB
|
|
417
|
+
* chunks + a UTF-8-safe decoder). Sync is deliberate: the only consumers are
|
|
418
|
+
* the offline CLI and tests. Returning `false` from `onLine` stops early.
|
|
419
|
+
*
|
|
420
|
+
* @inner
|
|
421
|
+
* @param {string} filePath
|
|
422
|
+
* @param {function(string, number): (boolean|void)} onLine - `(line, lineNo)`, 1-indexed.
|
|
423
|
+
*/
|
|
424
|
+
function eachLineSync(filePath, onLine) {
|
|
425
|
+
var fd = fs.openSync(filePath, 'r');
|
|
426
|
+
try {
|
|
427
|
+
var decoder = new StringDecoder('utf8');
|
|
428
|
+
var buf = Buffer.alloc(65536);
|
|
429
|
+
var rem = '', pos = 0, n, lineNo = 0, idx;
|
|
430
|
+
while ((n = fs.readSync(fd, buf, 0, buf.length, pos)) > 0) {
|
|
431
|
+
pos += n;
|
|
432
|
+
rem += decoder.write(buf.subarray(0, n));
|
|
433
|
+
while ((idx = rem.indexOf('\n')) >= 0) {
|
|
434
|
+
lineNo++;
|
|
435
|
+
if (onLine(rem.slice(0, idx), lineNo) === false) return;
|
|
436
|
+
rem = rem.slice(idx + 1);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
rem += decoder.end();
|
|
440
|
+
if (rem.length > 0) {
|
|
441
|
+
lineNo++;
|
|
442
|
+
onLine(rem, lineNo);
|
|
443
|
+
}
|
|
444
|
+
} finally {
|
|
445
|
+
try { fs.closeSync(fd); } catch (cerr) { /* best-effort */ }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @typedef {object} ChainVerifyResult
|
|
451
|
+
* @property {boolean} ok - `true` when the chain is intact (warnings allowed — read them).
|
|
452
|
+
* @property {number} records - Chained records verified.
|
|
453
|
+
* @property {number} unchained - Pre-chain (hashless) records in the leading prefix.
|
|
454
|
+
* @property {?{line: number, reason: string}} breakAt - First break, when `ok` is false.
|
|
455
|
+
* @property {Array<{line: number, type: string}>} warnings - Non-fatal findings (acknowledged breaks, an unacknowledged torn tail).
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Walk a trail file and verify its hash chain — the engine behind
|
|
460
|
+
* `gina audit:verify`.
|
|
461
|
+
*
|
|
462
|
+
* Rules (each one closes a hole, none is cosmetic):
|
|
463
|
+
* - Hashless records are legal ONLY as a contiguous leading prefix (a trail
|
|
464
|
+
* that predates the chain). Anywhere later they FAIL — otherwise inserting
|
|
465
|
+
* unhashed lines would be free tampering.
|
|
466
|
+
* - An unparseable line FAILS unless the next record is a chained
|
|
467
|
+
* `audit.chain.break` acknowledgment ({@link start} writes one when it
|
|
468
|
+
* resumes over a torn tail) — reported as a warning, never silently passed.
|
|
469
|
+
* Only ONE damaged line per acknowledgment; consecutive garbage FAILS.
|
|
470
|
+
* - Empty lines are skipped (they carry nothing and cannot smuggle content).
|
|
471
|
+
* - A trailing unparseable line with no acknowledgment yet (the
|
|
472
|
+
* crash-just-happened state) is a warning: the chain is intact up to the
|
|
473
|
+
* last complete record, and the next boot will acknowledge.
|
|
474
|
+
* - Digest comparison is constant-time (`crypto.timingSafeEqual`).
|
|
475
|
+
*
|
|
476
|
+
* What an intact chain does NOT prove (documented, by design): truncation at
|
|
477
|
+
* the exact tail is invisible (nothing after it commits to it); an empty or
|
|
478
|
+
* absent file verifies trivially (`records: 0` — read the count); and the
|
|
479
|
+
* process holding the key can forge any chain. Stream the file to WORM
|
|
480
|
+
* storage for the stronger adversary.
|
|
481
|
+
*
|
|
482
|
+
* @memberof module:gina/lib/audit
|
|
483
|
+
* @param {string} filePath - The trail (JSONL).
|
|
484
|
+
* @param {string} secret - The chain HMAC key.
|
|
485
|
+
* @returns {ChainVerifyResult}
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* var result = lib.audit.verifyChain('/srv/app/logs/audit-web-prod.jsonl', process.env.MY_AUDIT_KEY);
|
|
489
|
+
* // -> { ok: true, records: 1042, unchained: 17, breakAt: null, warnings: [] }
|
|
490
|
+
*/
|
|
491
|
+
function verifyChain(filePath, secret) {
|
|
492
|
+
var result = { ok: true, records: 0, unchained: 0, breakAt: null, warnings: [] };
|
|
493
|
+
var prev = CHAIN_GENESIS;
|
|
494
|
+
var chainStarted = false;
|
|
495
|
+
var pendingGarbage = null; // {line}
|
|
496
|
+
|
|
497
|
+
var fail = function(line, reason) {
|
|
498
|
+
result.ok = false;
|
|
499
|
+
result.breakAt = { line: line, reason: reason };
|
|
500
|
+
return false; // stops eachLineSync
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
eachLineSync(filePath, function(line, lineNo) {
|
|
504
|
+
if (line.length === 0) return; // empty lines carry nothing
|
|
505
|
+
|
|
506
|
+
var parsed = null;
|
|
507
|
+
try { parsed = JSON.parse(line); } catch (perr) {
|
|
508
|
+
if (pendingGarbage) {
|
|
509
|
+
return fail(lineNo, 'unparseable line (second consecutive — a crash tears at most one)');
|
|
510
|
+
}
|
|
511
|
+
pendingGarbage = { line: lineNo };
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
var hasHash = (parsed && typeof parsed.hash === 'string');
|
|
516
|
+
if (!hasHash) {
|
|
517
|
+
if (pendingGarbage) {
|
|
518
|
+
return fail(pendingGarbage.line, 'unparseable line not followed by a chained acknowledgment');
|
|
519
|
+
}
|
|
520
|
+
if (!chainStarted) {
|
|
521
|
+
result.unchained++;
|
|
522
|
+
return; // pre-chain prefix
|
|
523
|
+
}
|
|
524
|
+
return fail(lineNo, 'unchained record after chain start');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!/^[0-9a-f]{64}$/.test(parsed.hash)) {
|
|
528
|
+
return fail(lineNo, 'malformed hash field');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
var recordHash = parsed.hash;
|
|
532
|
+
delete parsed.hash;
|
|
533
|
+
var expected = chainDigest(secret, prev, canonicalV1(parsed));
|
|
534
|
+
var okDigest = false;
|
|
535
|
+
try {
|
|
536
|
+
okDigest = crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(recordHash, 'hex'));
|
|
537
|
+
} catch (terr) {
|
|
538
|
+
okDigest = false;
|
|
539
|
+
}
|
|
540
|
+
if (!okDigest) {
|
|
541
|
+
if (pendingGarbage) {
|
|
542
|
+
return fail(pendingGarbage.line, 'unparseable line not followed by a chained acknowledgment');
|
|
543
|
+
}
|
|
544
|
+
return fail(lineNo, 'hash mismatch — the record does not chain from its predecessor');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (pendingGarbage) {
|
|
548
|
+
if (parsed.action === 'audit.chain.break') {
|
|
549
|
+
result.warnings.push({ line: pendingGarbage.line, type: 'acknowledged-break' });
|
|
550
|
+
pendingGarbage = null;
|
|
551
|
+
} else {
|
|
552
|
+
return fail(pendingGarbage.line, 'unparseable line not followed by a chained acknowledgment');
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
prev = recordHash;
|
|
557
|
+
chainStarted = true;
|
|
558
|
+
result.records++;
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
if (result.ok && pendingGarbage) {
|
|
562
|
+
// The crash-just-happened state: torn tail, no boot has acknowledged yet.
|
|
563
|
+
result.warnings.push({ line: pendingGarbage.line, type: 'torn-tail-unacknowledged' });
|
|
564
|
+
}
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Snapshot the acting user off the request session.
|
|
570
|
+
*
|
|
571
|
+
* Reads `req.session.user` (the framework's authenticated contract — the
|
|
572
|
+
* #COMPLY1 `isAuthenticated` predicate) and keeps exactly TWO things: the
|
|
573
|
+
* configured `actorKey` property and a COPY of `user.roles`. The whole user
|
|
574
|
+
* object is never stored (PII — the COMPLY5 synergy); `roles` is `.slice()`d
|
|
575
|
+
* so a later in-request mutation cannot rewrite an already-emitted record.
|
|
576
|
+
*
|
|
577
|
+
* @inner
|
|
578
|
+
* @param {?object} req
|
|
579
|
+
* @returns {{key: *, roles: string[], machine: boolean=}} `{ key: null, roles: [] }` when
|
|
580
|
+
* unauthenticated or off-request; `{ key: <callerName>, roles, machine: true }`
|
|
581
|
+
* for a #MS3 machine caller (no session, `req.machineCaller` stamped by the gate).
|
|
582
|
+
*/
|
|
583
|
+
function deriveActor(req) {
|
|
584
|
+
var user = (
|
|
585
|
+
req && req.session && typeof req.session === 'object'
|
|
586
|
+
&& req.session.user && typeof req.session.user === 'object'
|
|
587
|
+
) ? req.session.user : null;
|
|
588
|
+
|
|
589
|
+
if (!user) {
|
|
590
|
+
// #MS3 — a machine caller authenticated by the authz gate (Bearer key)
|
|
591
|
+
// carries its identity on `req.machineCaller`, never on the session.
|
|
592
|
+
// The caller NAME is the actor key (`actorKey` is a session-user
|
|
593
|
+
// concept — a machine principal has no configurable id property), the
|
|
594
|
+
// roles are COPIED for the same mutation-isolation reason as the
|
|
595
|
+
// session path, and the record is marked `machine: true` so a reader
|
|
596
|
+
// can tell machine actors from human ones. Session wins by
|
|
597
|
+
// construction: this branch is only reached when no session user
|
|
598
|
+
// exists — mirroring the gate's own precedence.
|
|
599
|
+
var machine = (
|
|
600
|
+
req && req.machineCaller && typeof req.machineCaller === 'object'
|
|
601
|
+
&& typeof req.machineCaller.name === 'string' && req.machineCaller.name
|
|
602
|
+
) ? req.machineCaller : null;
|
|
603
|
+
if (machine) {
|
|
604
|
+
return {
|
|
605
|
+
key : machine.name,
|
|
606
|
+
roles : Array.isArray(machine.roles) ? machine.roles.slice() : [],
|
|
607
|
+
machine : true
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return { key: null, roles: [] };
|
|
611
|
+
}
|
|
612
|
+
var key = (typeof user[_conf.actorKey] !== 'undefined' && user[_conf.actorKey] !== null)
|
|
613
|
+
? user[_conf.actorKey]
|
|
614
|
+
: null;
|
|
615
|
+
return {
|
|
616
|
+
key : key,
|
|
617
|
+
roles : Array.isArray(user.roles) ? user.roles.slice() : []
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Resolve the client IP from the SOCKET, never from headers.
|
|
623
|
+
* `X-Forwarded-For` is deliberately not read (the #OBS1 rule — the header is
|
|
624
|
+
* attacker-writable, and an audit trail must not let the audited party choose
|
|
625
|
+
* its own recorded address). IPv6-mapped IPv4 (`::ffff:10.0.0.1`) is
|
|
626
|
+
* normalized to the bare IPv4 form, mirroring `lib/metrics`.
|
|
627
|
+
*
|
|
628
|
+
* @inner
|
|
629
|
+
* @param {?object} req
|
|
630
|
+
* @returns {?string}
|
|
631
|
+
*/
|
|
632
|
+
function resolveIp(req) {
|
|
633
|
+
var ip = null;
|
|
634
|
+
if (req && req.socket && req.socket.remoteAddress) {
|
|
635
|
+
ip = req.socket.remoteAddress;
|
|
636
|
+
} else if (req && req.connection && req.connection.remoteAddress) {
|
|
637
|
+
ip = req.connection.remoteAddress;
|
|
638
|
+
}
|
|
639
|
+
if (!ip) return null;
|
|
640
|
+
return ip.replace(/^::ffff:/i, '');
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Build one {@link AuditRecord}. Every request-derived read is null-safe by
|
|
645
|
+
* construction, so a released request (`local.req` nulled at a terminal exit —
|
|
646
|
+
* the #B35 class) still yields a DEGRADED record (`requestId`/`ip`/`rule`/
|
|
647
|
+
* `method` null, `actor` `{key:null, roles:[]}`) instead of a crash or a
|
|
648
|
+
* dropped record — for a compliance trail, present-but-degraded beats absent.
|
|
649
|
+
*
|
|
650
|
+
* @inner
|
|
651
|
+
* @param {string} action
|
|
652
|
+
* @param {{req: ?object, resource: *, meta: *, actor: *}} fields
|
|
653
|
+
* @returns {AuditRecord}
|
|
654
|
+
*/
|
|
655
|
+
function buildRecord(action, fields) {
|
|
656
|
+
var req = fields.req || null;
|
|
657
|
+
|
|
658
|
+
var record = {
|
|
659
|
+
id : crypto.randomUUID(),
|
|
660
|
+
ts : Date.now(),
|
|
661
|
+
requestId : (req && typeof req._ginaReqId === 'string') ? req._ginaReqId : null,
|
|
662
|
+
actor : (fields.actor && typeof fields.actor === 'object') ? fields.actor : deriveActor(req),
|
|
663
|
+
action : action
|
|
664
|
+
};
|
|
665
|
+
if (typeof fields.resource !== 'undefined') {
|
|
666
|
+
record.resource = fields.resource;
|
|
667
|
+
}
|
|
668
|
+
if (typeof fields.meta !== 'undefined') {
|
|
669
|
+
record.meta = fields.meta;
|
|
670
|
+
}
|
|
671
|
+
record.ip = resolveIp(req);
|
|
672
|
+
record.rule = (req && req.routing && typeof req.routing.rule === 'string') ? req.routing.rule : null;
|
|
673
|
+
record.method = (req && typeof req.method === 'string') ? req.method : null;
|
|
674
|
+
record.bundle = _conf.bundle;
|
|
675
|
+
record.env = _conf.env;
|
|
676
|
+
|
|
677
|
+
return record;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Adopt the boot-resolved configuration + store. Called ONCE by
|
|
682
|
+
* `core/server.js` `init()` when `settings.json > audit.enabled === true`,
|
|
683
|
+
* AFTER the fail-fast lint. Adoption is once-only (the `lib/job` precedent):
|
|
684
|
+
* a second call is refused loudly — silently swapping the store mid-flight
|
|
685
|
+
* would strand the earlier records.
|
|
686
|
+
*
|
|
687
|
+
* Building the default file store here (rather than at the first write) is
|
|
688
|
+
* deliberate: `mkdirSync` + `openSync` throw on an unwritable destination, and
|
|
689
|
+
* the caller's enclosing #B57 catch turns that into a boot refusal — an audit
|
|
690
|
+
* trail that silently cannot write is the quietly-OFF class the lint exists
|
|
691
|
+
* to prevent.
|
|
692
|
+
*
|
|
693
|
+
* When `opts.chain` is given, the file store is wrapped with the
|
|
694
|
+
* tamper-evidence chain ({@link createChainStore}): the resume anchor is
|
|
695
|
+
* recovered from the trail's TAIL in O(1) ({@link readChainTail}), and a torn
|
|
696
|
+
* tail (crash mid-write) is acknowledged — `'\n'` is appended so the damaged
|
|
697
|
+
* bytes end as their own line (append-only is preserved; nothing is deleted),
|
|
698
|
+
* a loud `console.warn` fires, and a chained `audit.chain.break` record goes
|
|
699
|
+
* out as the first write, carrying the damage report in its `meta`.
|
|
700
|
+
* `gina audit:verify` reports that acknowledgment as a warning, never a pass.
|
|
701
|
+
*
|
|
702
|
+
* @memberof module:gina/lib/audit
|
|
703
|
+
* @param {object} opts
|
|
704
|
+
* @param {?string} [opts.bundle] - The booted bundle name (stamped on every record).
|
|
705
|
+
* @param {?string} [opts.env] - The booted env (stamped on every record).
|
|
706
|
+
* @param {string} [opts.actorKey=id] - `session.user` property snapshotted as `actor.key`.
|
|
707
|
+
* @param {boolean} [opts.eventsAuthz=true] - Framework authz auto-events ({@link emitAuthzDenied}).
|
|
708
|
+
* @param {AuditStore} [opts.store] - A pre-built store (the `lib/audit-store` dispatcher path).
|
|
709
|
+
* @param {string} [opts.file] - JSONL destination for the default file backend (required when no `store`).
|
|
710
|
+
* @param {{secret: string}} [opts.chain] - Enable the tamper-evidence chain (file backend only).
|
|
711
|
+
* @returns {boolean} `true` when adopted, `false` when already configured.
|
|
712
|
+
* @throws {Error} When neither `store` nor `file` is given, when the file destination cannot be opened for append, when `chain` is combined with `store` (a connector store cannot guarantee ordered append), or when `chain` lacks a non-empty `secret`.
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* lib.audit.start({ bundle: 'web', env: 'prod', actorKey: 'id', file: '/srv/app/logs/audit-web-prod.jsonl' });
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* // Tamper-evident: every record carries a `hash` chaining from its predecessor.
|
|
719
|
+
* lib.audit.start({ bundle: 'web', env: 'prod', file: '/srv/app/logs/audit-web-prod.jsonl', chain: { secret: process.env.MY_AUDIT_KEY } });
|
|
720
|
+
*/
|
|
721
|
+
function start(opts) {
|
|
722
|
+
opts = opts || {};
|
|
723
|
+
if (_conf) {
|
|
724
|
+
console.warn('[audit] start: the audit trail is already configured — the new options were ignored.');
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
var chainOpts = null;
|
|
729
|
+
if (opts.chain && typeof opts.chain === 'object') {
|
|
730
|
+
if (typeof opts.chain.secret !== 'string' || opts.chain.secret.length === 0) {
|
|
731
|
+
throw new Error('[audit] `chain` requires a non-empty `secret` — tamper-evidence without a key would be silently OFF.');
|
|
732
|
+
}
|
|
733
|
+
chainOpts = { secret: opts.chain.secret, prevHash: CHAIN_GENESIS };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
var store = null;
|
|
737
|
+
var torn = null;
|
|
738
|
+
if (opts.store && typeof opts.store === 'object') {
|
|
739
|
+
if (chainOpts) {
|
|
740
|
+
// Defensive twin of the boot lint: the seam carries NO ordering
|
|
741
|
+
// obligation, and a chain without ordered append forks silently.
|
|
742
|
+
throw new Error('[audit] the hash chain requires the file backend — a connector `store` cannot guarantee ordered append.');
|
|
743
|
+
}
|
|
744
|
+
store = opts.store;
|
|
745
|
+
} else if (typeof opts.file === 'string' && opts.file.length > 0) {
|
|
746
|
+
if (chainOpts) {
|
|
747
|
+
var tail = readChainTail(opts.file);
|
|
748
|
+
chainOpts.prevHash = tail.prevHash;
|
|
749
|
+
torn = tail.torn;
|
|
750
|
+
if (torn && torn.reason === 'torn-tail') {
|
|
751
|
+
// Terminate the partial line so the next record starts clean.
|
|
752
|
+
// Append-only is preserved: the damaged bytes stay on file as
|
|
753
|
+
// their own (unparseable) line, acknowledged below.
|
|
754
|
+
fs.appendFileSync(opts.file, '\n');
|
|
755
|
+
}
|
|
756
|
+
if (torn) {
|
|
757
|
+
console.warn('[audit] chain resume: the trail tail is damaged ('+ torn.reason +', '+ torn.bytes +' byte(s)) — resuming from the last intact record and appending a chained `audit.chain.break` acknowledgment. Run `gina audit:verify` for the full report.');
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
store = createFileStore(opts.file); // throws on unwritable — the caller's #B57 catch owns it
|
|
761
|
+
if (chainOpts) {
|
|
762
|
+
store = createChainStore(store, chainOpts);
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
throw new Error('[audit] start needs a `store` or a `file` destination.');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
_conf = {
|
|
769
|
+
bundle : opts.bundle || null,
|
|
770
|
+
env : opts.env || null,
|
|
771
|
+
actorKey : (typeof opts.actorKey === 'string' && opts.actorKey.length > 0) ? opts.actorKey : 'id',
|
|
772
|
+
eventsAuthz : (opts.eventsAuthz !== false),
|
|
773
|
+
chain : !!chainOpts
|
|
774
|
+
};
|
|
775
|
+
_store = store;
|
|
776
|
+
|
|
777
|
+
if (torn) {
|
|
778
|
+
// The acknowledgment is a REAL chained record through the normal
|
|
779
|
+
// write path — unforgeable without the key, visible to verify.
|
|
780
|
+
write('audit.chain.break', { meta: { reason: torn.reason, damagedBytes: torn.bytes } });
|
|
781
|
+
}
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Whether the trail is active (configured + store adopted). `false` means
|
|
787
|
+
* every {@link write} is a benign no-op — `enabled` is deployment config, so
|
|
788
|
+
* application code never needs to branch on it.
|
|
789
|
+
*
|
|
790
|
+
* @memberof module:gina/lib/audit
|
|
791
|
+
* @returns {boolean}
|
|
792
|
+
*/
|
|
793
|
+
function isEnabled() {
|
|
794
|
+
return !!(_conf && _store);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Emit one audit record — the single write entry point (`self.audit()` and
|
|
799
|
+
* the framework auto-events both land here).
|
|
800
|
+
*
|
|
801
|
+
* Fire-and-forget by default: a failure is counted ({@link stats} `dropped`)
|
|
802
|
+
* and logged via `console.error`, never thrown into the request path. Pass a
|
|
803
|
+
* callback to observe the write result (`cb(err)`); when the trail is
|
|
804
|
+
* disabled the callback gets `cb(null)` — a vacuous success, so callers stay
|
|
805
|
+
* config-agnostic.
|
|
806
|
+
*
|
|
807
|
+
* @memberof module:gina/lib/audit
|
|
808
|
+
* @param {string} action - App-defined verb (`"invoice.delete"`). Non-empty string, else the record is dropped (counted + logged, never thrown).
|
|
809
|
+
* @param {object} [fields] - `{ req, resource, meta, actor }` — all optional; `actor` overrides the session-derived snapshot.
|
|
810
|
+
* @param {function(?Error)} [cb] - Optional write confirmation.
|
|
811
|
+
* @returns {void}
|
|
812
|
+
*
|
|
813
|
+
* @example
|
|
814
|
+
* lib.audit.write('invoice.delete', { req: req, resource: invoiceId });
|
|
815
|
+
*/
|
|
816
|
+
function write(action, fields, cb) {
|
|
817
|
+
if (typeof fields === 'function') { cb = fields; fields = null; }
|
|
818
|
+
cb = (typeof cb === 'function') ? cb : noop;
|
|
819
|
+
fields = (fields && typeof fields === 'object') ? fields : {};
|
|
820
|
+
|
|
821
|
+
if ( !isEnabled() ) {
|
|
822
|
+
return cb(null);
|
|
823
|
+
}
|
|
824
|
+
if (typeof action !== 'string' || action.length === 0) {
|
|
825
|
+
_dropped++;
|
|
826
|
+
console.error('[audit] dropped a record: `action` must be a non-empty string (got: '+ JSON.stringify(action) +')');
|
|
827
|
+
return cb(new Error('[audit] `action` must be a non-empty string'));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
var record = buildRecord(action, fields);
|
|
831
|
+
_store.append(record, function(err) {
|
|
832
|
+
if (err) {
|
|
833
|
+
_dropped++;
|
|
834
|
+
console.error('[audit] write failed — the record was dropped (dropped so far: '+ _dropped +'): '+ (err.message || err));
|
|
835
|
+
return cb(err);
|
|
836
|
+
}
|
|
837
|
+
_written++;
|
|
838
|
+
return cb(null);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Framework auto-event for an authorization denial (#COMPLY2 locked decision
|
|
844
|
+
* 3 — ON by default when the trail is enabled; `settings.json >
|
|
845
|
+
* audit.events.authz: false` opts out). Called by `lib/authz-gate`'s two deny
|
|
846
|
+
* writers; the outcome (`'401'` / `'login-bounce'` / `'403-roles'` /
|
|
847
|
+
* `'403-policy'`) rides in `meta.outcome`, so the record schema stays v1.
|
|
848
|
+
*
|
|
849
|
+
* The WHOLE body is contained: an audit failure must never change an
|
|
850
|
+
* authorization outcome, so nothing here can throw into the gate.
|
|
851
|
+
*
|
|
852
|
+
* Flood caveat (documented, accepted): unauthenticated 401s make this an
|
|
853
|
+
* attacker-writable line — like any log line; throttling is COMPLY6's job.
|
|
854
|
+
*
|
|
855
|
+
* @memberof module:gina/lib/audit
|
|
856
|
+
* @param {?object} req - The denied request (actor derives from its session when present).
|
|
857
|
+
* @param {string} outcome - `'401' | 'login-bounce' | '403-roles' | '403-policy'`.
|
|
858
|
+
* @returns {void}
|
|
859
|
+
*/
|
|
860
|
+
function emitAuthzDenied(req, outcome) {
|
|
861
|
+
try {
|
|
862
|
+
if ( !isEnabled() || _conf.eventsAuthz === false ) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
write('authz.denied', { req: req, meta: { outcome: String(outcome || 'denied') } });
|
|
866
|
+
} catch (err) {
|
|
867
|
+
try { console.error('[audit] authz auto-event failed (the denial itself is unaffected): '+ (err.message || err)); } catch (e2) { /* never escalate */ }
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Counters + destination snapshot — for tests and the boot log.
|
|
873
|
+
*
|
|
874
|
+
* @memberof module:gina/lib/audit
|
|
875
|
+
* @returns {{enabled: boolean, chain: boolean, written: number, dropped: number, path: ?string}}
|
|
876
|
+
*/
|
|
877
|
+
function stats() {
|
|
878
|
+
return {
|
|
879
|
+
enabled : isEnabled(),
|
|
880
|
+
chain : !!(_conf && _conf.chain),
|
|
881
|
+
written : _written,
|
|
882
|
+
dropped : _dropped,
|
|
883
|
+
path : (_store && _store.path) || null
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Test-only teardown: close the store, reset config + counters so the next
|
|
889
|
+
* `start()` adopts fresh. Never called on a production path (adoption is
|
|
890
|
+
* once-per-boot by design).
|
|
891
|
+
*
|
|
892
|
+
* @memberof module:gina/lib/audit
|
|
893
|
+
* @returns {void}
|
|
894
|
+
*/
|
|
895
|
+
function _resetForTest() {
|
|
896
|
+
if (_store && typeof _store.close === 'function') {
|
|
897
|
+
try { _store.close(); } catch (err) { /* best-effort */ }
|
|
898
|
+
}
|
|
899
|
+
_conf = null;
|
|
900
|
+
_store = null;
|
|
901
|
+
_written = 0;
|
|
902
|
+
_dropped = 0;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
module.exports = {
|
|
906
|
+
start : start,
|
|
907
|
+
isEnabled : isEnabled,
|
|
908
|
+
write : write,
|
|
909
|
+
emitAuthzDenied : emitAuthzDenied,
|
|
910
|
+
stats : stats,
|
|
911
|
+
canonicalV1 : canonicalV1,
|
|
912
|
+
verifyChain : verifyChain,
|
|
913
|
+
_resetForTest : _resetForTest,
|
|
914
|
+
_readChainTail : readChainTail, // test surface only — resume edge cases (torn/oversized tails)
|
|
915
|
+
_createChainStore : createChainStore // test surface only — the drop-never-forks + queue-liveness contracts need a failable inner store
|
|
916
|
+
};
|