cyberia 2.8.885
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/.dockerignore +15 -0
- package/.env.development +45 -0
- package/.env.production +50 -0
- package/.env.test +45 -0
- package/.github/workflows/engine-cyberia.cd.yml +31 -0
- package/.github/workflows/engine-cyberia.ci.yml +60 -0
- package/.github/workflows/ghpkg.ci.yml +87 -0
- package/.github/workflows/npmpkg.ci.yml +72 -0
- package/.github/workflows/publish.ci.yml +84 -0
- package/.github/workflows/publish.cyberia.ci.yml +84 -0
- package/.github/workflows/pwa-microservices-template-page.cd.yml +72 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +33 -0
- package/.github/workflows/release.cd.yml +37 -0
- package/.nycrc +9 -0
- package/.prettierignore +13 -0
- package/.prettierrc +9 -0
- package/.vscode/extensions.json +51 -0
- package/.vscode/settings.json +87 -0
- package/AUTHORS.md +21 -0
- package/CHANGELOG.md +205 -0
- package/Dockerfile +28 -0
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/bin/build.js +209 -0
- package/bin/cron.js +47 -0
- package/bin/cyberia.js +145 -0
- package/bin/db.js +199 -0
- package/bin/deploy.js +1293 -0
- package/bin/file.js +197 -0
- package/bin/hwt.js +49 -0
- package/bin/index.js +145 -0
- package/bin/ssl.js +63 -0
- package/bin/util.js +80 -0
- package/bin/vs.js +74 -0
- package/cli.md +714 -0
- package/conf.js +204 -0
- package/deployment.yaml +138 -0
- package/jsconfig.json +7 -0
- package/jsdoc.json +32 -0
- package/manifests/deployment/adminer/deployment.yaml +32 -0
- package/manifests/deployment/adminer/kustomization.yaml +7 -0
- package/manifests/deployment/adminer/service.yaml +13 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +167 -0
- package/manifests/deployment/dd-default-development/proxy.yaml +46 -0
- package/manifests/deployment/dd-test-development/deployment.yaml +174 -0
- package/manifests/deployment/dd-test-development/proxy.yaml +51 -0
- package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
- package/manifests/deployment/fastapi/backend-service.yml +19 -0
- package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
- package/manifests/deployment/fastapi/frontend-service.yml +15 -0
- package/manifests/deployment/fastapi/initial_data.sh +56 -0
- package/manifests/deployment/kafka/deployment.yaml +69 -0
- package/manifests/deployment/mongo-express/deployment.yaml +60 -0
- package/manifests/deployment/phpmyadmin/deployment.yaml +54 -0
- package/manifests/deployment/spark/spark-pi-py.yaml +21 -0
- package/manifests/deployment/tensorflow/tf-gpu-test.yaml +65 -0
- package/manifests/envoy-service-nodeport.yaml +23 -0
- package/manifests/grafana/deployment.yaml +57 -0
- package/manifests/grafana/kustomization.yaml +7 -0
- package/manifests/grafana/pvc.yaml +12 -0
- package/manifests/grafana/service.yaml +14 -0
- package/manifests/kind-config-dev.yaml +12 -0
- package/manifests/kind-config.yaml +12 -0
- package/manifests/kubeadm-calico-config.yaml +119 -0
- package/manifests/kubelet-config.yaml +65 -0
- package/manifests/letsencrypt-prod.yaml +15 -0
- package/manifests/lxd/lxd-admin-profile.yaml +17 -0
- package/manifests/lxd/lxd-preseed.yaml +30 -0
- package/manifests/lxd/underpost-setup.sh +163 -0
- package/manifests/mariadb/config.yaml +10 -0
- package/manifests/mariadb/kustomization.yaml +9 -0
- package/manifests/mariadb/pv.yaml +12 -0
- package/manifests/mariadb/pvc.yaml +10 -0
- package/manifests/mariadb/secret.yaml +8 -0
- package/manifests/mariadb/service.yaml +10 -0
- package/manifests/mariadb/statefulset.yaml +56 -0
- package/manifests/mariadb/storage-class.yaml +10 -0
- package/manifests/mongodb/backup-access.yaml +16 -0
- package/manifests/mongodb/backup-cronjob.yaml +42 -0
- package/manifests/mongodb/backup-pv-pvc.yaml +22 -0
- package/manifests/mongodb/configmap.yaml +26 -0
- package/manifests/mongodb/headless-service.yaml +10 -0
- package/manifests/mongodb/kustomization.yaml +11 -0
- package/manifests/mongodb/pv-pvc.yaml +23 -0
- package/manifests/mongodb/statefulset.yaml +126 -0
- package/manifests/mongodb/storage-class.yaml +9 -0
- package/manifests/mongodb-4.4/kustomization.yaml +7 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +23 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
- package/manifests/mysql/kustomization.yaml +7 -0
- package/manifests/mysql/pv-pvc.yaml +27 -0
- package/manifests/mysql/statefulset.yaml +55 -0
- package/manifests/postgresql/configmap.yaml +9 -0
- package/manifests/postgresql/kustomization.yaml +10 -0
- package/manifests/postgresql/pv.yaml +15 -0
- package/manifests/postgresql/pvc.yaml +13 -0
- package/manifests/postgresql/service.yaml +10 -0
- package/manifests/postgresql/statefulset.yaml +37 -0
- package/manifests/prometheus/deployment.yaml +82 -0
- package/manifests/valkey/kustomization.yaml +7 -0
- package/manifests/valkey/service.yaml +11 -0
- package/manifests/valkey/statefulset.yaml +38 -0
- package/nodemon.json +6 -0
- package/package.json +118 -0
- package/proxy.yaml +35 -0
- package/scripts/device-scan.sh +43 -0
- package/scripts/gpu-diag.sh +19 -0
- package/scripts/maas-setup.sh +120 -0
- package/scripts/nat-iptables.sh +26 -0
- package/scripts/nvim.sh +91 -0
- package/scripts/snap-clean.sh +26 -0
- package/scripts/ssh-cluster-info.sh +14 -0
- package/scripts/ssl.sh +164 -0
- package/src/api/blockchain/blockchain.controller.js +51 -0
- package/src/api/blockchain/blockchain.model.js +90 -0
- package/src/api/blockchain/blockchain.router.js +21 -0
- package/src/api/blockchain/blockchain.service.js +24 -0
- package/src/api/core/core.controller.js +69 -0
- package/src/api/core/core.model.js +11 -0
- package/src/api/core/core.router.js +24 -0
- package/src/api/core/core.service.js +35 -0
- package/src/api/crypto/crypto.controller.js +51 -0
- package/src/api/crypto/crypto.model.js +23 -0
- package/src/api/crypto/crypto.router.js +20 -0
- package/src/api/crypto/crypto.service.js +64 -0
- package/src/api/default/default.controller.js +74 -0
- package/src/api/default/default.model.js +20 -0
- package/src/api/default/default.router.js +27 -0
- package/src/api/default/default.service.js +40 -0
- package/src/api/document/document.controller.js +66 -0
- package/src/api/document/document.model.js +51 -0
- package/src/api/document/document.router.js +24 -0
- package/src/api/document/document.service.js +133 -0
- package/src/api/file/file.controller.js +67 -0
- package/src/api/file/file.model.js +19 -0
- package/src/api/file/file.router.js +22 -0
- package/src/api/file/file.service.js +100 -0
- package/src/api/instance/instance.controller.js +69 -0
- package/src/api/instance/instance.model.js +40 -0
- package/src/api/instance/instance.router.js +34 -0
- package/src/api/instance/instance.service.js +70 -0
- package/src/api/ipfs/ipfs.controller.js +51 -0
- package/src/api/ipfs/ipfs.model.js +17 -0
- package/src/api/ipfs/ipfs.router.js +20 -0
- package/src/api/ipfs/ipfs.service.js +25 -0
- package/src/api/object-layer/README.md +85 -0
- package/src/api/object-layer/object-layer.controller.js +69 -0
- package/src/api/object-layer/object-layer.model.js +181 -0
- package/src/api/object-layer/object-layer.router.js +29 -0
- package/src/api/object-layer/object-layer.service.js +49 -0
- package/src/api/test/test.controller.js +59 -0
- package/src/api/test/test.model.js +14 -0
- package/src/api/test/test.router.js +21 -0
- package/src/api/test/test.service.js +35 -0
- package/src/api/user/postman_collection.json +216 -0
- package/src/api/user/user.build.js +16 -0
- package/src/api/user/user.controller.js +35 -0
- package/src/api/user/user.model.js +100 -0
- package/src/api/user/user.router.js +400 -0
- package/src/api/user/user.service.js +500 -0
- package/src/api.js +23 -0
- package/src/cli/baremetal.js +1310 -0
- package/src/cli/cloud-init.js +548 -0
- package/src/cli/cluster.js +834 -0
- package/src/cli/cron.js +95 -0
- package/src/cli/db.js +414 -0
- package/src/cli/deploy.js +661 -0
- package/src/cli/env.js +101 -0
- package/src/cli/fs.js +256 -0
- package/src/cli/image.js +156 -0
- package/src/cli/index.js +436 -0
- package/src/cli/lxd.js +402 -0
- package/src/cli/monitor.js +260 -0
- package/src/cli/repository.js +274 -0
- package/src/cli/run.js +728 -0
- package/src/cli/script.js +85 -0
- package/src/cli/secrets.js +71 -0
- package/src/cli/ssh.js +46 -0
- package/src/cli/test.js +159 -0
- package/src/client/Cyberia.index.js +50 -0
- package/src/client/CyberiaAdmin.index.js +34 -0
- package/src/client/CyberiaPortal.index.js +36 -0
- package/src/client/Default.index.js +84 -0
- package/src/client/components/core/404.js +20 -0
- package/src/client/components/core/500.js +20 -0
- package/src/client/components/core/Account.js +326 -0
- package/src/client/components/core/AgGrid.js +191 -0
- package/src/client/components/core/Alert.js +77 -0
- package/src/client/components/core/Auth.js +342 -0
- package/src/client/components/core/Badge.js +32 -0
- package/src/client/components/core/Blockchain.js +41 -0
- package/src/client/components/core/Blog.js +9 -0
- package/src/client/components/core/BtnIcon.js +111 -0
- package/src/client/components/core/CalendarCore.js +464 -0
- package/src/client/components/core/Chat.js +64 -0
- package/src/client/components/core/ColorPalette.js +5267 -0
- package/src/client/components/core/CommonJs.js +1010 -0
- package/src/client/components/core/Content.js +196 -0
- package/src/client/components/core/Css.js +1099 -0
- package/src/client/components/core/CssCore.js +882 -0
- package/src/client/components/core/D3Chart.js +44 -0
- package/src/client/components/core/Docs.js +376 -0
- package/src/client/components/core/DropDown.js +223 -0
- package/src/client/components/core/EventsUI.js +133 -0
- package/src/client/components/core/FileExplorer.js +707 -0
- package/src/client/components/core/FullScreen.js +36 -0
- package/src/client/components/core/Input.js +383 -0
- package/src/client/components/core/JoyStick.js +80 -0
- package/src/client/components/core/Keyboard.js +73 -0
- package/src/client/components/core/LoadingAnimation.js +159 -0
- package/src/client/components/core/LogIn.js +190 -0
- package/src/client/components/core/LogOut.js +63 -0
- package/src/client/components/core/Logger.js +29 -0
- package/src/client/components/core/Modal.js +2494 -0
- package/src/client/components/core/NotificationManager.js +84 -0
- package/src/client/components/core/ObjectLayerEngine.js +1229 -0
- package/src/client/components/core/ObjectLayerEngineModal.js +443 -0
- package/src/client/components/core/Pagination.js +207 -0
- package/src/client/components/core/Panel.js +772 -0
- package/src/client/components/core/PanelForm.js +627 -0
- package/src/client/components/core/Polyhedron.js +162 -0
- package/src/client/components/core/Recover.js +207 -0
- package/src/client/components/core/Responsive.js +82 -0
- package/src/client/components/core/RichText.js +43 -0
- package/src/client/components/core/Router.js +317 -0
- package/src/client/components/core/Scroll.js +76 -0
- package/src/client/components/core/SignUp.js +125 -0
- package/src/client/components/core/SocketIo.js +74 -0
- package/src/client/components/core/Stream.js +113 -0
- package/src/client/components/core/ToggleSwitch.js +101 -0
- package/src/client/components/core/ToolTip.js +90 -0
- package/src/client/components/core/Translate.js +522 -0
- package/src/client/components/core/Validator.js +115 -0
- package/src/client/components/core/VanillaJs.js +423 -0
- package/src/client/components/core/Wallet.js +106 -0
- package/src/client/components/core/WebComponent.js +44 -0
- package/src/client/components/core/Webhook.js +25 -0
- package/src/client/components/core/Worker.js +371 -0
- package/src/client/components/core/windowGetDimensions.js +269 -0
- package/src/client/components/cyberia/BagCyberia.js +1253 -0
- package/src/client/components/cyberia/BiomeCyberia.js +130 -0
- package/src/client/components/cyberia/CharacterCyberia.js +321 -0
- package/src/client/components/cyberia/CommonCyberia.js +1834 -0
- package/src/client/components/cyberia/CssCyberia.js +816 -0
- package/src/client/components/cyberia/ElementPreviewCyberia.js +183 -0
- package/src/client/components/cyberia/ElementsCyberia.js +146 -0
- package/src/client/components/cyberia/InteractionPanelCyberia.js +1043 -0
- package/src/client/components/cyberia/JoyStickCyberia.js +53 -0
- package/src/client/components/cyberia/LogInCyberia.js +68 -0
- package/src/client/components/cyberia/LogOutCyberia.js +24 -0
- package/src/client/components/cyberia/MainUserCyberia.js +424 -0
- package/src/client/components/cyberia/MapCyberia.js +160 -0
- package/src/client/components/cyberia/MatrixCyberia.js +147 -0
- package/src/client/components/cyberia/MenuCyberia.js +575 -0
- package/src/client/components/cyberia/PixiCyberia.js +1639 -0
- package/src/client/components/cyberia/PointAndClickMovementCyberia.js +146 -0
- package/src/client/components/cyberia/QuestCyberia.js +1420 -0
- package/src/client/components/cyberia/RoutesCyberia.js +47 -0
- package/src/client/components/cyberia/SettingsCyberia.js +16 -0
- package/src/client/components/cyberia/SignUpCyberia.js +14 -0
- package/src/client/components/cyberia/SkillCyberia.js +124 -0
- package/src/client/components/cyberia/SocketIoCyberia.js +211 -0
- package/src/client/components/cyberia/TileCyberia.js +685 -0
- package/src/client/components/cyberia/TranslateCyberia.js +96 -0
- package/src/client/components/cyberia/UniverseCyberia.js +14 -0
- package/src/client/components/cyberia/WebhookCyberia.js +13 -0
- package/src/client/components/cyberia/WikiCyberia.js +144 -0
- package/src/client/components/cyberia/WorldCyberia.js +680 -0
- package/src/client/components/cyberia-admin/BiomeCyberiaAdmin.js +978 -0
- package/src/client/components/cyberia-admin/CommonCyberiaAdmin.js +29 -0
- package/src/client/components/cyberia-admin/CssCyberiaAdmin.js +15 -0
- package/src/client/components/cyberia-admin/ElementsCyberiaAdmin.js +38 -0
- package/src/client/components/cyberia-admin/InstanceEngineCyberiaAdmin.js +180 -0
- package/src/client/components/cyberia-admin/LogInCyberiaAdmin.js +34 -0
- package/src/client/components/cyberia-admin/LogOutCyberiaAdmin.js +24 -0
- package/src/client/components/cyberia-admin/MenuCyberiaAdmin.js +660 -0
- package/src/client/components/cyberia-admin/RoutesCyberiaAdmin.js +57 -0
- package/src/client/components/cyberia-admin/ServerCyberiaAdmin.js +129 -0
- package/src/client/components/cyberia-admin/SettingsCyberiaAdmin.js +16 -0
- package/src/client/components/cyberia-admin/SignUpCyberiaAdmin.js +11 -0
- package/src/client/components/cyberia-admin/SocketIoCyberiaAdmin.js +53 -0
- package/src/client/components/cyberia-admin/TranslateCyberiaAdmin.js +7 -0
- package/src/client/components/cyberia-biome/CityCyberiaBiome.js +209 -0
- package/src/client/components/cyberia-biome/CityInteriorCyberiaBiome.js +253 -0
- package/src/client/components/cyberia-biome/ColorChaosCyberiaBiome.js +26 -0
- package/src/client/components/cyberia-biome/ForestCyberiaBiome.js +191 -0
- package/src/client/components/cyberia-biome/GridBaseCyberiaBiome.js +364 -0
- package/src/client/components/cyberia-biome/SeedCityCyberiaBiome.js +347 -0
- package/src/client/components/cyberia-biome/ShopCyberiaBiome.js +12 -0
- package/src/client/components/cyberia-biome/SpaceCyberiaBiome.js +58 -0
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +29 -0
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +132 -0
- package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +38 -0
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +18 -0
- package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +12 -0
- package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +487 -0
- package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +45 -0
- package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +136 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +16 -0
- package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +11 -0
- package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +52 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +12 -0
- package/src/client/components/default/CommonDefault.js +29 -0
- package/src/client/components/default/CssDefault.js +27 -0
- package/src/client/components/default/ElementsDefault.js +38 -0
- package/src/client/components/default/LogInDefault.js +14 -0
- package/src/client/components/default/LogOutDefault.js +10 -0
- package/src/client/components/default/MenuDefault.js +743 -0
- package/src/client/components/default/RoutesDefault.js +48 -0
- package/src/client/components/default/SettingsDefault.js +16 -0
- package/src/client/components/default/SignUpDefault.js +9 -0
- package/src/client/components/default/SocketIoDefault.js +54 -0
- package/src/client/components/default/TranslateDefault.js +7 -0
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/dark.svg +557 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/default/assets/logo/base-icon.png +0 -0
- package/src/client/public/default/assets/logo/underpost.gif +0 -0
- package/src/client/public/default/assets/mailer/api-user-check.png +0 -0
- package/src/client/public/default/assets/mailer/api-user-default-avatar.png +0 -0
- package/src/client/public/default/assets/mailer/api-user-invalid-token.png +0 -0
- package/src/client/public/default/assets/mailer/api-user-recover.png +0 -0
- package/src/client/public/default/browserconfig.xml +12 -0
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +69 -0
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/plantuml/client-conf.svg +1 -0
- package/src/client/public/default/plantuml/client-schema.svg +1 -0
- package/src/client/public/default/plantuml/cron-conf.svg +1 -0
- package/src/client/public/default/plantuml/cron-schema.svg +1 -0
- package/src/client/public/default/plantuml/server-conf.svg +1 -0
- package/src/client/public/default/plantuml/server-schema.svg +1 -0
- package/src/client/public/default/plantuml/ssr-conf.svg +1 -0
- package/src/client/public/default/plantuml/ssr-schema.svg +1 -0
- package/src/client/public/default/safari-pinned-tab.svg +24 -0
- package/src/client/public/default/site.webmanifest +69 -0
- package/src/client/public/default/sitemap +148 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +9 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +148 -0
- package/src/client/public/test/favicon.ico +0 -0
- package/src/client/public/test/sitemap +148 -0
- package/src/client/services/blockchain/blockchain.service.js +73 -0
- package/src/client/services/core/core.service.js +165 -0
- package/src/client/services/crypto/crypto.service.js +73 -0
- package/src/client/services/default/default.management.js +450 -0
- package/src/client/services/default/default.service.js +98 -0
- package/src/client/services/document/document.service.js +97 -0
- package/src/client/services/file/file.service.js +72 -0
- package/src/client/services/instance/instance.management.js +78 -0
- package/src/client/services/instance/instance.service.js +97 -0
- package/src/client/services/ipfs/ipfs.service.js +73 -0
- package/src/client/services/object-layer/object-layer.service.js +93 -0
- package/src/client/services/test/test.service.js +73 -0
- package/src/client/services/user/user.management.js +56 -0
- package/src/client/services/user/user.service.js +108 -0
- package/src/client/ssr/Render.js +237 -0
- package/src/client/ssr/body/404.js +73 -0
- package/src/client/ssr/body/500.js +72 -0
- package/src/client/ssr/body/CacheControl.js +114 -0
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +90 -0
- package/src/client/ssr/body/CyberiaSplashScreenLore.js +424 -0
- package/src/client/ssr/body/DefaultSplashScreen.js +90 -0
- package/src/client/ssr/email/DefaultRecoverEmail.js +21 -0
- package/src/client/ssr/email/DefaultVerifyEmail.js +17 -0
- package/src/client/ssr/head/Css.js +241 -0
- package/src/client/ssr/head/CyberiaAdminScripts.js +6 -0
- package/src/client/ssr/head/CyberiaPortalScripts.js +6 -0
- package/src/client/ssr/head/CyberiaScripts.js +6 -0
- package/src/client/ssr/head/DefaultScripts.js +6 -0
- package/src/client/ssr/head/Microdata.js +11 -0
- package/src/client/ssr/head/Production.js +1 -0
- package/src/client/ssr/head/Pwa.js +146 -0
- package/src/client/ssr/head/Seo.js +15 -0
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +21 -0
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +17 -0
- package/src/client/ssr/offline/Maintenance.js +63 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +67 -0
- package/src/client/ssr/pages/404.js +12 -0
- package/src/client/ssr/pages/500.js +12 -0
- package/src/client/ssr/pages/Test.js +198 -0
- package/src/client/ssr/pages/maintenance.js +14 -0
- package/src/client/ssr/pages/offline.js +21 -0
- package/src/client/sw/default.sw.js +108 -0
- package/src/client/sw/template.sw.js +84 -0
- package/src/client.build.js +22 -0
- package/src/client.dev.js +24 -0
- package/src/db/DataBaseProvider.js +98 -0
- package/src/db/mariadb/MariaDB.js +66 -0
- package/src/db/mongo/MongooseDB.js +70 -0
- package/src/index.js +198 -0
- package/src/mailer/EmailRender.js +116 -0
- package/src/mailer/MailerProvider.js +213 -0
- package/src/monitor.js +24 -0
- package/src/proxy.js +22 -0
- package/src/runtime/express/Express.js +256 -0
- package/src/runtime/lampp/Dockerfile +50 -0
- package/src/runtime/lampp/Lampp.js +343 -0
- package/src/server/auth.js +689 -0
- package/src/server/backup.js +96 -0
- package/src/server/client-build-docs.js +205 -0
- package/src/server/client-build-live.js +109 -0
- package/src/server/client-build.js +690 -0
- package/src/server/client-dev-server.js +87 -0
- package/src/server/client-formatted.js +87 -0
- package/src/server/client-icons.js +108 -0
- package/src/server/conf.js +1071 -0
- package/src/server/crypto.js +210 -0
- package/src/server/dns.js +276 -0
- package/src/server/downloader.js +74 -0
- package/src/server/json-schema.js +77 -0
- package/src/server/logger.js +197 -0
- package/src/server/network.js +72 -0
- package/src/server/object-layer.js +294 -0
- package/src/server/peer.js +69 -0
- package/src/server/process.js +171 -0
- package/src/server/proxy.js +110 -0
- package/src/server/runtime.js +170 -0
- package/src/server/ssr.js +127 -0
- package/src/server/start.js +161 -0
- package/src/server/tls.js +251 -0
- package/src/server/valkey.js +293 -0
- package/src/server.js +25 -0
- package/src/ws/IoInterface.js +139 -0
- package/src/ws/IoServer.js +88 -0
- package/src/ws/core/channels/core.ws.chat.js +23 -0
- package/src/ws/core/channels/core.ws.mailer.js +35 -0
- package/src/ws/core/channels/core.ws.stream.js +31 -0
- package/src/ws/core/core.ws.connection.js +62 -0
- package/src/ws/core/core.ws.emit.js +53 -0
- package/src/ws/core/core.ws.server.js +76 -0
- package/src/ws/core/management/core.ws.chat.js +8 -0
- package/src/ws/core/management/core.ws.mailer.js +16 -0
- package/src/ws/core/management/core.ws.stream.js +8 -0
- package/src/ws/cyberia/channels/cyberia.ws.bot.js +56 -0
- package/src/ws/cyberia/channels/cyberia.ws.skill.js +51 -0
- package/src/ws/cyberia/channels/cyberia.ws.user.js +437 -0
- package/src/ws/cyberia/cyberia.ws.connection.js +36 -0
- package/src/ws/cyberia/cyberia.ws.emit.js +14 -0
- package/src/ws/cyberia/cyberia.ws.server.js +67 -0
- package/src/ws/cyberia/management/cyberia.ws.bot.js +669 -0
- package/src/ws/cyberia/management/cyberia.ws.skill.js +441 -0
- package/src/ws/cyberia/management/cyberia.ws.user.js +188 -0
- package/src/ws/default/channels/default.ws.main.js +16 -0
- package/src/ws/default/default.ws.connection.js +22 -0
- package/src/ws/default/default.ws.emit.js +14 -0
- package/src/ws/default/default.ws.server.js +20 -0
- package/src/ws/default/management/default.ws.main.js +8 -0
- package/test/api.test.js +53 -0
- package/test/crypto.test.js +117 -0
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module for managing identity and authorization
|
|
3
|
+
* @module src/server/auth.js
|
|
4
|
+
* @namespace Auth
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import jwt from 'jsonwebtoken';
|
|
9
|
+
import { loggerFactory } from './logger.js';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
import { UserDto } from '../api/user/user.model.js';
|
|
13
|
+
import { commonAdminGuard, commonModeratorGuard, validatePassword } from '../client/components/core/CommonJs.js';
|
|
14
|
+
import helmet from 'helmet';
|
|
15
|
+
import rateLimit from 'express-rate-limit';
|
|
16
|
+
import slowDown from 'express-slow-down';
|
|
17
|
+
import cors from 'cors';
|
|
18
|
+
import cookieParser from 'cookie-parser';
|
|
19
|
+
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
20
|
+
|
|
21
|
+
dotenv.config();
|
|
22
|
+
const logger = loggerFactory(import.meta);
|
|
23
|
+
|
|
24
|
+
// Promisified crypto functions
|
|
25
|
+
const pbkdf2 = promisify(crypto.pbkdf2);
|
|
26
|
+
|
|
27
|
+
// Config with sane defaults and parsing
|
|
28
|
+
const config = {
|
|
29
|
+
hashBytes: Number(process.env.PBKDF2_HASH_BYTES) || 32,
|
|
30
|
+
saltBytes: Number(process.env.PBKDF2_SALT_BYTES) || 16,
|
|
31
|
+
iterations: Number(process.env.PBKDF2_ITERATIONS) || 150_000,
|
|
32
|
+
digest: process.env.PBKDF2_DIGEST || 'sha512',
|
|
33
|
+
refreshTokenBytes: Number(process.env.REFRESH_TOKEN_BYTES) || 48,
|
|
34
|
+
jwtAlgorithm: process.env.JWT_ALGORITHM || 'HS512', // consider RS256 with keys
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ---------- Password hashing (async) ----------
|
|
38
|
+
/**
|
|
39
|
+
* Hash password asynchronously using PBKDF2.
|
|
40
|
+
* Stored format: iterations$salt$hash
|
|
41
|
+
* @param {string} password The password to hash.
|
|
42
|
+
* @returns {Promise<string>} The hashed password string.
|
|
43
|
+
* @memberof Auth
|
|
44
|
+
*/
|
|
45
|
+
async function hashPassword(password) {
|
|
46
|
+
const salt = crypto.randomBytes(config.saltBytes).toString('hex');
|
|
47
|
+
const derived = await pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest);
|
|
48
|
+
return `${config.iterations}$${salt}$${derived.toString('hex')}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Verify password using constant-time comparison
|
|
53
|
+
* @param {string} password The password to verify.
|
|
54
|
+
* @param {string} combined The stored hashed password string (iterations$salt$hash).
|
|
55
|
+
* @returns {Promise<boolean>} True if the password is valid, false otherwise.
|
|
56
|
+
* @memberof Auth
|
|
57
|
+
*/
|
|
58
|
+
async function verifyPassword(password, combined) {
|
|
59
|
+
if (!combined) return false;
|
|
60
|
+
const parts = combined.split('$');
|
|
61
|
+
if (parts.length !== 3) return false;
|
|
62
|
+
const [itersStr, salt, originalHex] = parts;
|
|
63
|
+
const iterations = parseInt(itersStr, 10);
|
|
64
|
+
const derived = await pbkdf2(password, salt, iterations, Buffer.from(originalHex, 'hex').length, config.digest);
|
|
65
|
+
const original = Buffer.from(originalHex, 'hex');
|
|
66
|
+
const ok = crypto.timingSafeEqual(derived, original);
|
|
67
|
+
return ok;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------- Token hashing & utilities ----------
|
|
71
|
+
/**
|
|
72
|
+
* Hashes a token using SHA256.
|
|
73
|
+
* @param {string} token The token to hash.
|
|
74
|
+
* @returns {string|null} The hashed token as a hex string, or null if token is falsy.
|
|
75
|
+
* @memberof Auth
|
|
76
|
+
*/
|
|
77
|
+
function hashToken(token) {
|
|
78
|
+
if (!token) return null;
|
|
79
|
+
return crypto.createHash('sha256').update(token).digest('hex');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generates a cryptographically secure random hex string.
|
|
84
|
+
* @param {number} [bytes=config.refreshTokenBytes] The number of bytes to generate.
|
|
85
|
+
* @returns {string} The random hex string.
|
|
86
|
+
* @memberof Auth
|
|
87
|
+
*/
|
|
88
|
+
function generateRandomHex(bytes = config.refreshTokenBytes) {
|
|
89
|
+
return crypto.randomBytes(bytes).toString('hex');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generates a JWT issuer and audience based on the host and path.
|
|
94
|
+
* @param {object} options The options object.
|
|
95
|
+
* @param {string} options.host The host name.
|
|
96
|
+
* @param {string} options.path The path name.
|
|
97
|
+
* @returns {object} The issuer and audience.
|
|
98
|
+
* @memberof Auth
|
|
99
|
+
*/
|
|
100
|
+
function jwtIssuerAudienceFactory(options = { host: '', path: '' }) {
|
|
101
|
+
const audience = `${options.host}${options.path === '/' ? '' : options.path}`;
|
|
102
|
+
const issuer = `${audience}/api`;
|
|
103
|
+
return { issuer, audience };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------- JWT helpers ----------
|
|
107
|
+
/**
|
|
108
|
+
* Signs a JWT payload.
|
|
109
|
+
* @param {object} payload The payload to sign.
|
|
110
|
+
* @param {object} [options={}] Additional JWT sign options.
|
|
111
|
+
* @param {string} options.host The host name.
|
|
112
|
+
* @param {string} options.path The path name.
|
|
113
|
+
* @param {number} accessExpireMinutes The access token expiration in minutes.
|
|
114
|
+
* @param {number} refreshExpireMinutes The refresh token expiration in minutes.
|
|
115
|
+
* @returns {string} The signed JWT.
|
|
116
|
+
* @throws {Error} If JWT key is not configured.
|
|
117
|
+
* @memberof Auth
|
|
118
|
+
*/
|
|
119
|
+
function jwtSign(
|
|
120
|
+
payload,
|
|
121
|
+
options = { host: '', path: '' },
|
|
122
|
+
accessExpireMinutes = process.env.ACCESS_EXPIRE_MINUTES,
|
|
123
|
+
refreshExpireMinutes = process.env.REFRESH_EXPIRE_MINUTES,
|
|
124
|
+
) {
|
|
125
|
+
const { issuer, audience } = jwtIssuerAudienceFactory(options);
|
|
126
|
+
const signOptions = {
|
|
127
|
+
algorithm: config.jwtAlgorithm,
|
|
128
|
+
expiresIn: `${accessExpireMinutes}m`,
|
|
129
|
+
issuer,
|
|
130
|
+
audience,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (!payload.jwtid) signOptions.jwtid = generateRandomHex();
|
|
134
|
+
|
|
135
|
+
if (!process.env.JWT_SECRET) throw new Error('JWT key not configured');
|
|
136
|
+
|
|
137
|
+
// Add refreshExpiresAt to the payload, which is the same as the token's own expiry.
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
payload.refreshExpiresAt = now + refreshExpireMinutes * 60 * 1000;
|
|
140
|
+
|
|
141
|
+
logger.info('JWT signed', { payload, signOptions, accessExpireMinutes, refreshExpireMinutes });
|
|
142
|
+
|
|
143
|
+
return jwt.sign(payload, process.env.JWT_SECRET, signOptions);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Verifies a JWT.
|
|
148
|
+
* @param {string} token The JWT to verify.
|
|
149
|
+
* @param {object} [options={}] Additional JWT verify options.
|
|
150
|
+
* @param {string} options.host The host name.
|
|
151
|
+
* @param {string} options.path The path name.
|
|
152
|
+
* @returns {object} The decoded payload.
|
|
153
|
+
* @throws {jwt.JsonWebTokenError} If the token is invalid or expired.
|
|
154
|
+
* @memberof Auth
|
|
155
|
+
*/
|
|
156
|
+
function jwtVerify(token, options = { host: '', path: '' }) {
|
|
157
|
+
try {
|
|
158
|
+
const { issuer, audience } = jwtIssuerAudienceFactory(options);
|
|
159
|
+
const verifyOptions = {
|
|
160
|
+
algorithms: [config.jwtAlgorithm],
|
|
161
|
+
issuer,
|
|
162
|
+
audience,
|
|
163
|
+
};
|
|
164
|
+
return jwt.verify(token, process.env.JWT_SECRET, verifyOptions);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------- Request helpers ----------
|
|
171
|
+
/**
|
|
172
|
+
* Extracts the Bearer token from the request headers.
|
|
173
|
+
* @param {import('express').Request} req The Express request object.
|
|
174
|
+
* @returns {string} The token, or an empty string if not found.
|
|
175
|
+
* @memberof Auth
|
|
176
|
+
*/
|
|
177
|
+
const getBearerToken = (req) => {
|
|
178
|
+
const header = String(req.headers['authorization'] || req.headers['Authorization'] || '');
|
|
179
|
+
if (header.startsWith('Bearer ')) return header.slice(7).trim();
|
|
180
|
+
return '';
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Checks if the request is a refresh token request.
|
|
185
|
+
* @param {import('express').Request} req The Express request object.
|
|
186
|
+
* @returns {boolean} True if the request is a refresh token request, false otherwise.
|
|
187
|
+
* @memberof Auth
|
|
188
|
+
*/
|
|
189
|
+
const isRefreshTokenReq = (req) => req.method === 'GET' && req.params.id === 'auth';
|
|
190
|
+
|
|
191
|
+
// ---------- Middleware ----------
|
|
192
|
+
/**
|
|
193
|
+
* Creates a middleware to authenticate requests using a JWT Bearer token.
|
|
194
|
+
* @param {object} options The options object.
|
|
195
|
+
* @param {string} options.host The host name.
|
|
196
|
+
* @param {string} options.path The path name.
|
|
197
|
+
* @returns {function} The middleware function.
|
|
198
|
+
* @memberof Auth
|
|
199
|
+
*/
|
|
200
|
+
const authMiddlewareFactory = (options = { host: '', path: '' }) => {
|
|
201
|
+
/**
|
|
202
|
+
* Express middleware to authenticate requests using a JWT Bearer token.
|
|
203
|
+
* @param {import('express').Request} req The Express request object.
|
|
204
|
+
* @param {import('express').Response} res The Express response object.
|
|
205
|
+
* @param {import('express').NextFunction} next The next middleware function.
|
|
206
|
+
* @memberof Auth
|
|
207
|
+
*/
|
|
208
|
+
const authMiddleware = async (req, res, next) => {
|
|
209
|
+
try {
|
|
210
|
+
const token = getBearerToken(req);
|
|
211
|
+
if (!token) return res.status(401).json({ status: 'error', message: 'unauthorized: token missing' });
|
|
212
|
+
|
|
213
|
+
const payload = jwtVerify(token, options);
|
|
214
|
+
|
|
215
|
+
// Validate IP and User-Agent to mitigate token theft
|
|
216
|
+
if (payload.ip && payload.ip !== req.ip) {
|
|
217
|
+
logger.warn(`IP mismatch for ${payload._id}: jwt(${payload.ip}) !== req(${req.ip})`);
|
|
218
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: ip mismatch' });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (payload.userAgent && payload.userAgent !== req.headers['user-agent']) {
|
|
222
|
+
logger.warn(`UA mismatch for ${payload._id}`);
|
|
223
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: user-agent mismatch' });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Non-guest verify session exists
|
|
227
|
+
if (payload.jwtid && payload.role !== 'guest') {
|
|
228
|
+
const User = DataBaseProvider.instance[`${payload.host}${payload.path}`].mongoose.models.User;
|
|
229
|
+
const user = await User.findOne({ _id: payload._id, 'activeSessions._id': payload.jwtid }).lean();
|
|
230
|
+
|
|
231
|
+
if (!user) {
|
|
232
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: invalid session' });
|
|
233
|
+
}
|
|
234
|
+
const session = user.activeSessions.find((s) => s._id.toString() === payload.jwtid);
|
|
235
|
+
|
|
236
|
+
if (!session) {
|
|
237
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: invalid session' });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// check session ip
|
|
241
|
+
if (session.ip !== req.ip) {
|
|
242
|
+
logger.warn(`IP mismatch for ${payload._id}: jwt(${session.ip}) !== req(${req.ip})`);
|
|
243
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: ip mismatch' });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// check session userAgent
|
|
247
|
+
if (session.userAgent !== req.headers['user-agent']) {
|
|
248
|
+
logger.warn(`UA mismatch for ${payload._id}`);
|
|
249
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: user-agent mismatch' });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// compare payload host and path with session host and path
|
|
253
|
+
if (payload.host !== session.host || payload.path !== session.path) {
|
|
254
|
+
logger.warn(`Host or path mismatch for ${payload._id}`);
|
|
255
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: host or path mismatch' });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!isRefreshTokenReq(req) && session.expiresAt < new Date()) {
|
|
259
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized: session expired' });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
req.auth = { user: payload };
|
|
264
|
+
return next();
|
|
265
|
+
} catch (err) {
|
|
266
|
+
logger.warn('authMiddleware error', err && err.message);
|
|
267
|
+
return res.status(401).json({ status: 'error', message: 'unauthorized' });
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
return authMiddleware;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Express middleware to guard routes for admin users.
|
|
275
|
+
* @param {import('express').Request} req The Express request object.
|
|
276
|
+
* @param {import('express').Response} res The Express response object.
|
|
277
|
+
* @param {import('express').NextFunction} next The next middleware function.
|
|
278
|
+
* @memberof Auth
|
|
279
|
+
*/
|
|
280
|
+
const adminGuard = (req, res, next) => {
|
|
281
|
+
try {
|
|
282
|
+
if (!req.auth || !commonAdminGuard(req.auth.user.role))
|
|
283
|
+
return res.status(403).json({ status: 'error', message: 'Insufficient permission' });
|
|
284
|
+
return next();
|
|
285
|
+
} catch (err) {
|
|
286
|
+
logger.error(err);
|
|
287
|
+
return res.status(400).json({ status: 'error', message: 'bad request' });
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Express middleware to guard routes for moderator or admin users.
|
|
292
|
+
* @param {import('express').Request} req The Express request object.
|
|
293
|
+
* @param {import('express').Response} res The Express response object.
|
|
294
|
+
* @param {import('express').NextFunction} next The next middleware function.
|
|
295
|
+
* @memberof Auth
|
|
296
|
+
*/
|
|
297
|
+
const moderatorGuard = (req, res, next) => {
|
|
298
|
+
try {
|
|
299
|
+
if (!req.auth || !commonModeratorGuard(req.auth.user.role))
|
|
300
|
+
return res.status(403).json({ status: 'error', message: 'Insufficient permission' });
|
|
301
|
+
return next();
|
|
302
|
+
} catch (err) {
|
|
303
|
+
logger.error(err);
|
|
304
|
+
return res.status(400).json({ status: 'error', message: 'bad request' });
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// ---------- Password validation middleware (server-side) ----------
|
|
309
|
+
/**
|
|
310
|
+
* Validates the password from the request body.
|
|
311
|
+
* @param {import('express').Request} req The Express request object.
|
|
312
|
+
* @returns {{status: 'success'}|{status: 'error', message: string}} Validation result.
|
|
313
|
+
* @memberof Auth
|
|
314
|
+
*/
|
|
315
|
+
const validatePasswordMiddleware = (req) => {
|
|
316
|
+
const errors = req.body && 'password' in req.body ? validatePassword(req.body.password) : [];
|
|
317
|
+
if (errors.length) {
|
|
318
|
+
return { status: 'error', message: 'Password: ' + errors.map((e) => e[req.lang] || e.en || e).join(', ') };
|
|
319
|
+
}
|
|
320
|
+
return { status: 'success' };
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// ---------- Session & Refresh token management ----------
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Creates cookie options for the refresh token.
|
|
327
|
+
* @param {import('express').Request} req The Express request object.
|
|
328
|
+
* @param {string} host The host name.
|
|
329
|
+
* @returns {object} Cookie options.
|
|
330
|
+
* @memberof Auth
|
|
331
|
+
*/
|
|
332
|
+
const cookieOptionsFactory = (req, host) => {
|
|
333
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
334
|
+
|
|
335
|
+
// Determine if request is secure: respect X-Forwarded-Proto when behind proxy
|
|
336
|
+
const forwardedProto = (req.headers && req.headers['x-forwarded-proto']) || '';
|
|
337
|
+
const reqIsSecure = Boolean(req.secure || forwardedProto.split(',')[0] === 'https');
|
|
338
|
+
|
|
339
|
+
// secure must be true for SameSite=None to work across sites
|
|
340
|
+
const secure = isProduction ? reqIsSecure : req.protocol === 'https';
|
|
341
|
+
const sameSite = secure ? 'None' : 'Lax';
|
|
342
|
+
|
|
343
|
+
// Safe parse of maxAge minutes
|
|
344
|
+
const maxAge = parseInt(process.env.ACCESS_EXPIRE_MINUTES) * 60 * 1000;
|
|
345
|
+
|
|
346
|
+
const opts = {
|
|
347
|
+
httpOnly: true,
|
|
348
|
+
secure,
|
|
349
|
+
sameSite,
|
|
350
|
+
path: '/',
|
|
351
|
+
domain: process.env.NODE_ENV === 'production' ? host : 'localhost',
|
|
352
|
+
maxAge,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
return opts;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Create session and set refresh cookie. Rotating and hashed stored token.
|
|
360
|
+
* @param {object} user The user object.
|
|
361
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
362
|
+
* @param {import('express').Request} req The Express request object.
|
|
363
|
+
* @param {import('express').Response} res The Express response object.
|
|
364
|
+
* @param {object} options Additional options.
|
|
365
|
+
* @param {string} options.host The host name.
|
|
366
|
+
* @param {string} options.path The path name.
|
|
367
|
+
* @returns {Promise<{jwtid: string}>} The session ID.
|
|
368
|
+
* @memberof Auth
|
|
369
|
+
*/
|
|
370
|
+
async function createSessionAndUserToken(user, User, req, res, options = { host: '', path: '' }) {
|
|
371
|
+
const refreshToken = hashToken(generateRandomHex());
|
|
372
|
+
const now = Date.now();
|
|
373
|
+
const expiresAt = new Date(now + parseInt(process.env.REFRESH_EXPIRE_MINUTES) * 60 * 1000);
|
|
374
|
+
|
|
375
|
+
const newSession = {
|
|
376
|
+
tokenHash: refreshToken,
|
|
377
|
+
ip: req.ip,
|
|
378
|
+
userAgent: req.headers['user-agent'],
|
|
379
|
+
host: options.host,
|
|
380
|
+
path: options.path,
|
|
381
|
+
createdAt: new Date(now),
|
|
382
|
+
expiresAt,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// push session
|
|
386
|
+
const updatedUser = await User.findByIdAndUpdate(user._id, { $push: { activeSessions: newSession } }, { new: true });
|
|
387
|
+
const session = updatedUser.activeSessions[updatedUser.activeSessions.length - 1];
|
|
388
|
+
const jwtid = session._id.toString();
|
|
389
|
+
|
|
390
|
+
// Secure cookie settings
|
|
391
|
+
res.cookie('refreshToken', refreshToken, cookieOptionsFactory(req, options.host));
|
|
392
|
+
|
|
393
|
+
return { jwtid };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Removes a session by its ID for a given user.
|
|
398
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
399
|
+
* @param {string} userId The ID of the user.
|
|
400
|
+
* @param {string} sessionId The ID of the session to remove.
|
|
401
|
+
* @returns {Promise<void>}
|
|
402
|
+
* @memberof Auth
|
|
403
|
+
*/
|
|
404
|
+
async function removeSession(User, userId, sessionId) {
|
|
405
|
+
return await User.updateOne({ _id: userId }, { $pull: { activeSessions: { _id: sessionId } } });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Logs out a user session by removing it from the database and clearing the refresh token cookie.
|
|
410
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
411
|
+
* @param {import('express').Request} req The Express request object.
|
|
412
|
+
* @param {import('express').Response} res The Express response object.
|
|
413
|
+
* @returns {Promise<boolean>} True if a session was found and removed, false otherwise.
|
|
414
|
+
* @memberof Auth
|
|
415
|
+
*/
|
|
416
|
+
async function logoutSession(User, req, res) {
|
|
417
|
+
const refreshToken = req.cookies?.refreshToken;
|
|
418
|
+
|
|
419
|
+
if (!refreshToken) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const user = await User.findOne({ 'activeSessions.tokenHash': refreshToken });
|
|
424
|
+
|
|
425
|
+
if (!user) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const session = user.activeSessions.find((s) => s.tokenHash === refreshToken);
|
|
430
|
+
|
|
431
|
+
const result = await removeSession(User, user._id, session._id);
|
|
432
|
+
|
|
433
|
+
res.clearCookie('refreshToken', { path: '/' });
|
|
434
|
+
|
|
435
|
+
return result.modifiedCount > 0;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Create user and immediate session + access token
|
|
440
|
+
* @param {import('express').Request} req The Express request object.
|
|
441
|
+
* @param {import('express').Response} res The Express response object.
|
|
442
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
443
|
+
* @param {import('mongoose').Model} File The Mongoose File model.
|
|
444
|
+
* @param {object} [options={}] Additional options.
|
|
445
|
+
* @param {Function} options.getDefaultProfileImageId Function to get the default profile image ID.
|
|
446
|
+
* @param {string} options.host The host name.
|
|
447
|
+
* @param {string} options.path The path name.
|
|
448
|
+
* @returns {Promise<{token: string, user: object}>} The access token and user object.
|
|
449
|
+
* @throws {Error} If password validation fails.
|
|
450
|
+
* @memberof Auth
|
|
451
|
+
*/
|
|
452
|
+
async function createUserAndSession(req, res, User, File, options = { host: '', path: '' }) {
|
|
453
|
+
const pwdCheck = validatePasswordMiddleware(req);
|
|
454
|
+
if (pwdCheck.status === 'error') throw new Error(pwdCheck.message);
|
|
455
|
+
|
|
456
|
+
req.body.password = await hashPassword(req.body.password);
|
|
457
|
+
req.body.role = req.body.role === 'guest' ? 'guest' : 'user';
|
|
458
|
+
req.body.profileImageId = await options.getDefaultProfileImageId(File);
|
|
459
|
+
|
|
460
|
+
const saved = await new User(req.body).save();
|
|
461
|
+
const user = await User.findOne({ _id: saved._id }).select(UserDto.select.get());
|
|
462
|
+
|
|
463
|
+
const { jwtid } = await createSessionAndUserToken(user, User, req, res, options);
|
|
464
|
+
const token = jwtSign(
|
|
465
|
+
UserDto.auth.payload(user, jwtid, req.ip, req.headers['user-agent'], options.host, options.path),
|
|
466
|
+
options,
|
|
467
|
+
);
|
|
468
|
+
return { token, user };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Refresh session and rotate refresh token.
|
|
473
|
+
* Detect token reuse: if a refresh token is presented but not found, consider
|
|
474
|
+
* it a possible theft and revoke all sessions for that user.
|
|
475
|
+
* @param {import('express').Request} req The Express request object.
|
|
476
|
+
* @param {import('express').Response} res The Express response object.
|
|
477
|
+
* @param {import('mongoose').Model} User The Mongoose User model.
|
|
478
|
+
* @param {object} options Additional options.
|
|
479
|
+
* @param {string} options.host The host name.
|
|
480
|
+
* @param {string} options.path The path name.
|
|
481
|
+
* @returns {Promise<{token: string}>} The new access token.
|
|
482
|
+
* @throws {Error} If the refresh token is missing, invalid, or expired.
|
|
483
|
+
* @memberof Auth
|
|
484
|
+
*/
|
|
485
|
+
async function refreshSessionAndToken(req, res, User, options = { host: '', path: '' }) {
|
|
486
|
+
const currentRefreshToken = req.cookies.refreshToken;
|
|
487
|
+
if (!currentRefreshToken) throw new Error('Refresh token missing');
|
|
488
|
+
|
|
489
|
+
// Find user owning that token
|
|
490
|
+
const user = await User.findOne({ 'activeSessions.tokenHash': currentRefreshToken });
|
|
491
|
+
|
|
492
|
+
if (!user) {
|
|
493
|
+
// Possible token reuse: look up user by some other signals? If not possible, log and throw.
|
|
494
|
+
// TODO: on cors requests, this will throw an error, because the cookie is not sent.
|
|
495
|
+
logger.warn('Refresh token reuse or invalid token detected');
|
|
496
|
+
// Optional: revoke by clearing cookie and returning unauthorized
|
|
497
|
+
res.clearCookie('refreshToken', { path: '/' });
|
|
498
|
+
throw new Error('Invalid refresh token');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Locate session
|
|
502
|
+
const session = user.activeSessions.find((s) => s.tokenHash === currentRefreshToken);
|
|
503
|
+
if (!session) {
|
|
504
|
+
// Shouldn't happen, but safe-guard
|
|
505
|
+
res.clearCookie('refreshToken', { path: '/' });
|
|
506
|
+
throw new Error('Session not found');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Check expiry
|
|
510
|
+
if (!isRefreshTokenReq(req) && session.expiresAt && session.expiresAt < new Date()) {
|
|
511
|
+
const result = await removeSession(User, user._id, session._id);
|
|
512
|
+
if (result) throw new Error('Refresh token expired');
|
|
513
|
+
else throw new Error('Session not found');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Rotate: generate new token, update stored hash and metadata
|
|
517
|
+
const refreshToken = hashToken(generateRandomHex());
|
|
518
|
+
session.tokenHash = refreshToken;
|
|
519
|
+
session.expiresAt = new Date(Date.now() + parseInt(process.env.REFRESH_EXPIRE_MINUTES) * 60 * 1000);
|
|
520
|
+
session.ip = req.ip;
|
|
521
|
+
session.userAgent = req.headers['user-agent'];
|
|
522
|
+
await user.save({ validateBeforeSave: false });
|
|
523
|
+
|
|
524
|
+
logger.warn('Refreshed session for user ' + user.email);
|
|
525
|
+
|
|
526
|
+
res.cookie('refreshToken', refreshToken, cookieOptionsFactory(req, options.host));
|
|
527
|
+
|
|
528
|
+
return jwtSign(
|
|
529
|
+
UserDto.auth.payload(user, session._id.toString(), req.ip, req.headers['user-agent'], options.host, options.path),
|
|
530
|
+
options,
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ---------- Security middleware composition ----------
|
|
535
|
+
/**
|
|
536
|
+
* Applies a set of security-related middleware to an Express app.
|
|
537
|
+
* @param {import('express').Application} app The Express application.
|
|
538
|
+
* @param {object} [opts={}] Options for security middleware.
|
|
539
|
+
* @param {string[]} opts.origin Allowed origins for CORS.
|
|
540
|
+
* @param {object} opts.rate Rate limiting options for `express-rate-limit`.
|
|
541
|
+
* @param {object} opts.slowdown Slow down options for `express-slow-down`.
|
|
542
|
+
* @param {object} opts.cookie Cookie options.
|
|
543
|
+
* @param {string[]} opts.frameAncestors Allowed frame ancestors for CSP.
|
|
544
|
+
* @memberof Auth
|
|
545
|
+
*/
|
|
546
|
+
function applySecurity(app, opts = {}) {
|
|
547
|
+
const {
|
|
548
|
+
origin,
|
|
549
|
+
rate = { windowMs: 15 * 60 * 1000, max: 500 },
|
|
550
|
+
slowdown = { windowMs: 15 * 60 * 1000, delayAfter: 50, delayMs: () => 500 },
|
|
551
|
+
cookie = { secure: process.env.NODE_ENV === 'production', sameSite: 'Strict' },
|
|
552
|
+
frameAncestors = ["'self'"],
|
|
553
|
+
} = opts;
|
|
554
|
+
|
|
555
|
+
app.disable('x-powered-by');
|
|
556
|
+
|
|
557
|
+
// Generate nonce per request and attach to res.locals for templates
|
|
558
|
+
app.use((req, res, next) => {
|
|
559
|
+
res.locals.nonce = crypto.randomBytes(16).toString('base64');
|
|
560
|
+
next();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// Basic header hardening with Helmet
|
|
564
|
+
app.use(
|
|
565
|
+
helmet({
|
|
566
|
+
// We'll customize CSP below because many apps need tailored directives
|
|
567
|
+
crossOriginEmbedderPolicy: true,
|
|
568
|
+
crossOriginOpenerPolicy: { policy: 'same-origin' },
|
|
569
|
+
crossOriginResourcePolicy: { policy: 'same-origin' },
|
|
570
|
+
originAgentCluster: false,
|
|
571
|
+
// Permissions-Policy (formerly Feature-Policy) — limit powerful features
|
|
572
|
+
permissionsPolicy: {
|
|
573
|
+
// example: disable geolocation, camera, microphone, payment
|
|
574
|
+
features: {
|
|
575
|
+
fullscreen: ["'self'"],
|
|
576
|
+
geolocation: [],
|
|
577
|
+
camera: [],
|
|
578
|
+
microphone: [],
|
|
579
|
+
payment: [],
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
}),
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// Strict HSTS (only enable in production over TLS)
|
|
586
|
+
// maxAge in seconds (e.g. 31536000 = 1 year). Use includeSubDomains and preload carefully.
|
|
587
|
+
if (process.env.NODE_ENV === 'production') {
|
|
588
|
+
app.use(
|
|
589
|
+
helmet.hsts({
|
|
590
|
+
maxAge: 31536000,
|
|
591
|
+
includeSubDomains: true,
|
|
592
|
+
preload: true,
|
|
593
|
+
}),
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Other helpful Helmet policies
|
|
598
|
+
app.use(helmet.noSniff()); // X-Content-Type-Options: nosniff
|
|
599
|
+
app.use(helmet.frameguard({ action: 'deny' })); // X-Frame-Options: DENY
|
|
600
|
+
app.use(helmet.referrerPolicy({ policy: 'no-referrer-when-downgrade' }));
|
|
601
|
+
|
|
602
|
+
// Content-Security-Policy: include nonce from res.locals
|
|
603
|
+
// Note: We avoid 'unsafe-inline' on script/style. Use nonces or hashes.
|
|
604
|
+
const httpDirective = process.env.NODE_ENV === 'production' ? 'https:' : 'http:';
|
|
605
|
+
app.use(
|
|
606
|
+
helmet.contentSecurityPolicy({
|
|
607
|
+
useDefaults: true,
|
|
608
|
+
directives: {
|
|
609
|
+
defaultSrc: ["'self'"],
|
|
610
|
+
baseUri: ["'self'"],
|
|
611
|
+
blockAllMixedContent: [],
|
|
612
|
+
fontSrc: ["'self'", httpDirective, 'data:'],
|
|
613
|
+
frameAncestors: frameAncestors,
|
|
614
|
+
imgSrc: ["'self'", 'data:', httpDirective, 'https:', 'blob:'],
|
|
615
|
+
objectSrc: ["'none'"],
|
|
616
|
+
// script-src and script-src-elem include dynamic nonce
|
|
617
|
+
scriptSrc: [
|
|
618
|
+
"'self'",
|
|
619
|
+
(req, res) => `'nonce-${res.locals.nonce}'`,
|
|
620
|
+
(req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : ''),
|
|
621
|
+
],
|
|
622
|
+
scriptSrcElem: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
|
|
623
|
+
// style-src: avoid 'unsafe-inline' when possible; if you must inline styles,
|
|
624
|
+
// use a nonce for them too (or hash).
|
|
625
|
+
styleSrc: [
|
|
626
|
+
"'self'",
|
|
627
|
+
httpDirective,
|
|
628
|
+
(req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : `'nonce-${res.locals.nonce}'`),
|
|
629
|
+
],
|
|
630
|
+
// deny plugins
|
|
631
|
+
objectSrc: ["'none'"],
|
|
632
|
+
},
|
|
633
|
+
}),
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// CORS - be explicit. In production, pass allowed origin(s) as opts.origin
|
|
637
|
+
app.use(
|
|
638
|
+
cors({
|
|
639
|
+
origin: origin || false,
|
|
640
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
641
|
+
credentials: true,
|
|
642
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'withcredentials', 'credentials'],
|
|
643
|
+
maxAge: 600,
|
|
644
|
+
}),
|
|
645
|
+
);
|
|
646
|
+
logger.info('Cors origin', origin);
|
|
647
|
+
|
|
648
|
+
// Rate limiting + slow down
|
|
649
|
+
const limiter = rateLimit({
|
|
650
|
+
windowMs: rate.windowMs,
|
|
651
|
+
max: rate.max,
|
|
652
|
+
standardHeaders: true,
|
|
653
|
+
legacyHeaders: false,
|
|
654
|
+
message: { error: 'Too many requests, please try again later.' },
|
|
655
|
+
});
|
|
656
|
+
app.use(limiter);
|
|
657
|
+
|
|
658
|
+
const speedLimiter = slowDown({
|
|
659
|
+
windowMs: slowdown.windowMs,
|
|
660
|
+
delayAfter: slowdown.delayAfter,
|
|
661
|
+
delayMs: () => slowdown.delayMs,
|
|
662
|
+
});
|
|
663
|
+
app.use(speedLimiter);
|
|
664
|
+
|
|
665
|
+
// Cookie parsing
|
|
666
|
+
app.use(cookieParser(process.env.JWT_SECRET));
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export {
|
|
670
|
+
authMiddlewareFactory,
|
|
671
|
+
hashPassword,
|
|
672
|
+
verifyPassword,
|
|
673
|
+
hashToken,
|
|
674
|
+
jwtSign,
|
|
675
|
+
jwtVerify,
|
|
676
|
+
jwtSign as hashJWT,
|
|
677
|
+
jwtVerify as verifyJWT,
|
|
678
|
+
adminGuard,
|
|
679
|
+
moderatorGuard,
|
|
680
|
+
validatePasswordMiddleware,
|
|
681
|
+
getBearerToken,
|
|
682
|
+
createSessionAndUserToken,
|
|
683
|
+
createUserAndSession,
|
|
684
|
+
refreshSessionAndToken,
|
|
685
|
+
logoutSession,
|
|
686
|
+
removeSession,
|
|
687
|
+
applySecurity,
|
|
688
|
+
isRefreshTokenReq,
|
|
689
|
+
};
|