Merge branch 'next' into dependabot/npm_and_yarn/integration-tests/next/selenium-webdriver-4.41.0

This commit is contained in:
James Read 2026-03-01 17:16:37 -05:00 committed by GitHub
commit a8c6366bdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 654 additions and 518 deletions

View File

@ -7,14 +7,20 @@ updates:
interval: "weekly" interval: "weekly"
target-branch: "next" target-branch: "next"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "3k"
- "dependencies"
# npm updates for frontend - targeting "release/2k" branch # npm updates for frontend - targeting "release/2k" branch (security updates only)
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/frontend" directory: "/frontend"
schedule: schedule:
interval: "weekly" interval: "weekly"
target-branch: "release/2k" target-branch: "release/2k"
open-pull-requests-limit: 10 open-pull-requests-limit: 0
labels:
- "2k"
- "dependencies"
# npm updates for integration-tests - targeting "next" branch # npm updates for integration-tests - targeting "next" branch
- package-ecosystem: "npm" - package-ecosystem: "npm"
@ -23,14 +29,20 @@ updates:
interval: "weekly" interval: "weekly"
target-branch: "next" target-branch: "next"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "3k"
- "dependencies"
# npm updates for integration-tests - targeting "release/2k" branch # npm updates for integration-tests - targeting "release/2k" branch (security updates only)
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/integration-tests" directory: "/integration-tests"
schedule: schedule:
interval: "weekly" interval: "weekly"
target-branch: "release/2k" target-branch: "release/2k"
open-pull-requests-limit: 10 open-pull-requests-limit: 0
labels:
- "2k"
- "dependencies"
# Go modules updates for service - targeting "next" branch # Go modules updates for service - targeting "next" branch
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
@ -39,14 +51,20 @@ updates:
interval: "weekly" interval: "weekly"
target-branch: "next" target-branch: "next"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "3k"
- "dependencies"
# Go modules updates for service - targeting "release/2k" branch # Go modules updates for service - targeting "release/2k" branch (security updates only)
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
directory: "/service" directory: "/service"
schedule: schedule:
interval: "weekly" interval: "weekly"
target-branch: "release/2k" target-branch: "release/2k"
open-pull-requests-limit: 10 open-pull-requests-limit: 0
labels:
- "2k"
- "dependencies"
# Go modules updates for lang - targeting "next" branch # Go modules updates for lang - targeting "next" branch
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
@ -55,14 +73,20 @@ updates:
interval: "weekly" interval: "weekly"
target-branch: "next" target-branch: "next"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "3k"
- "dependencies"
# Go modules updates for lang - targeting "release/2k" branch # Go modules updates for lang - targeting "release/2k" branch (security updates only)
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
directory: "/lang" directory: "/lang"
schedule: schedule:
interval: "weekly" interval: "weekly"
target-branch: "release/2k" target-branch: "release/2k"
open-pull-requests-limit: 10 open-pull-requests-limit: 0
labels:
- "2k"
- "dependencies"
# Docker updates - targeting "next" branch # Docker updates - targeting "next" branch
- package-ecosystem: "docker" - package-ecosystem: "docker"
@ -71,12 +95,18 @@ updates:
interval: "weekly" interval: "weekly"
target-branch: "next" target-branch: "next"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "3k"
- "dependencies"
# Docker updates - targeting "release/2k" branch # Docker updates - targeting "release/2k" branch (security updates only)
- package-ecosystem: "docker" - package-ecosystem: "docker"
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
target-branch: "release/2k" target-branch: "release/2k"
open-pull-requests-limit: 10 open-pull-requests-limit: 0
labels:
- "2k"
- "dependencies"

View File

@ -87,6 +87,9 @@ jobs:
with: with:
install-only: true install-only: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: release - name: release
if: github.ref_type != 'tag' if: github.ref_type != 'tag'
uses: cycjimmy/semantic-release-action@v4 uses: cycjimmy/semantic-release-action@v4

View File

@ -9,6 +9,19 @@ repos:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- id: check-added-large-files - id: check-added-large-files
- id: check-merge-conflict
- id: detect-private-key
- id: mixed-line-ending
args: ['--fix', 'lf']
- id: check-json
exclude: |
(?x)^(
service/internal/entities/testdata/.*\.json|
integration-tests/tests/.*/entities/.*\.json|
var/entities/.*\.json
)$
- id: check-case-conflict
- id: detect-aws-credentials
# Alternative semantic commit checker # Alternative semantic commit checker
- repo: https://github.com/compilerla/conventional-pre-commit - repo: https://github.com/compilerla/conventional-pre-commit
@ -34,9 +47,23 @@ repos:
pass_filenames: false pass_filenames: false
always_run: true always_run: true
- id: it - id: service-unittests
name: it name: service-unittests
entry: make service-codestyle frontend-codestyle entry: make service-unittests
language: system
pass_filenames: false
always_run: true
- id: service-build
name: service-build
entry: make service
language: system
pass_filenames: false
always_run: true
- id: it
name: integration-tests
entry: make it
language: system language: system
pass_filenames: false pass_filenames: false
always_run: true always_run: true

View File

@ -19,6 +19,8 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- From repo root: `go run ./service` - From repo root: `go run ./service`
- Unit tests (Go): - Unit tests (Go):
- From repo root: `cd service && make unittests` - From repo root: `cd service && make unittests`
- Code style (after editing code in `service/`):
- From repo root: `cd service && make codestyle`
- Integration tests (Mocha + Selenium): - Integration tests (Mocha + Selenium):
- Single test: `cd integration-tests && npx --yes mocha test/general.mjs` - Single test: `cd integration-tests && npx --yes mocha test/general.mjs`
- All tests: `cd integration-tests && npx --yes mocha` - All tests: `cd integration-tests && npx --yes mocha`
@ -41,6 +43,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- Do not swallow errors; propagate or log meaningfully. - Do not swallow errors; propagate or log meaningfully.
- Match existing formatting; avoid unrelated reformatting. - Match existing formatting; avoid unrelated reformatting.
- Be safe around nils in executor steps (e.g., guard `req.Binding` and `req.Binding.Action`). - Be safe around nils in executor steps (e.g., guard `req.Binding` and `req.Binding.Action`).
- Cyclomatic complexity over 4 is not permitted.
### API and Execution Flow (High-level) ### API and Execution Flow (High-level)
1. Client calls Connect RPC (e.g., `Init`, `GetDashboard`, `StartAction`). 1. Client calls Connect RPC (e.g., `Init`, `GetDashboard`, `StartAction`).

View File

@ -10,6 +10,7 @@
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5050/badge)](https://bestpractices.coreinfrastructure.org/projects/5050) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5050/badge)](https://bestpractices.coreinfrastructure.org/projects/5050)
[![Go Report Card](https://goreportcard.com/badge/github.com/Olivetin/OliveTin)](https://goreportcard.com/report/github.com/OliveTin/OliveTin) [![Go Report Card](https://goreportcard.com/badge/github.com/Olivetin/OliveTin)](https://goreportcard.com/report/github.com/OliveTin/OliveTin)
[![AI Autonomy Level](https://img.shields.io/badge/AI%20Autonomy-Level%201%20of%205%20(assistance--only)-blue)](https://blog.jread.com/posts/ai-levels-of-autonomy-in-software-engineering/)
[OliveTin 2k to 3k upgrade guide](https://docs.olivetin.app/upgrade/2k3k.html) [OliveTin 2k to 3k upgrade guide](https://docs.olivetin.app/upgrade/2k3k.html)
</div> </div>

View File

@ -15,9 +15,9 @@ To understand more about 2k vs 3k, see the following docs; https://docs.olivetin
The very purpose of OliveTin is to allow users to execute commands remotely on a machine. The very purpose of OliveTin is to allow users to execute commands remotely on a machine.
This means that, by design, OliveTin has might higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occour have the potential to be much more severe than in other types of software. This means that, by design, OliveTin has much higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occur have the potential to be much more severe than in other types of software.
We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substancially less resources than those projects. We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substantially less resources than those projects.
With that being said, OliveTin tries to follow examples of best practice, so judge the project not on if/when it has security issues, but how security issues are responded to as the measure of quality. With that being said, OliveTin tries to follow examples of best practice, so judge the project not on if/when it has security issues, but how security issues are responded to as the measure of quality.
@ -27,7 +27,7 @@ This is why we take security very seriously, and why we encourage responsible di
Please use responsible disclosure practices when reporting a vulnerability. **You will receive full credit for your discovery**, and we will work with you to ensure that the issue is resolved as quickly as **possible**. Please note that only James Read has access to security issues at the moment, so please be patient and understanding if you do not receive an immediate response. Please use responsible disclosure practices when reporting a vulnerability. **You will receive full credit for your discovery**, and we will work with you to ensure that the issue is resolved as quickly as **possible**. Please note that only James Read has access to security issues at the moment, so please be patient and understanding if you do not receive an immediate response.
* **Option A (preferred)**: GitHub Security Advisories, which allows you to report a vulnerability privately and securely. You can find the option to report a security issue in the "Issues" tab of this repository, and then select "Report a security vulnerability". This will allow you to provide details about the vulnerability without making it public. * **Option A (preferred)**: GitHub Security Advisories, which allows you to report a vulnerability privately and securely. Use this direct link to report privately: `https://github.com/OliveTin/OliveTin/security/advisories/new`. This allows you to provide details without making them public.
* **Option B**: Please email `contact@jread.com` for responsible disclosure. * **Option B**: Please email `contact@jread.com` for responsible disclosure.

View File

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@connectrpc/connect": "^2.1.1", "@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1", "@connectrpc/connect-web": "^2.1.1",
"@hugeicons/core-free-icons": "^3.1.1", "@hugeicons/core-free-icons": "^3.3.0",
"@hugeicons/vue": "^1.0.4", "@hugeicons/vue": "^1.0.4",
"@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
@ -21,13 +21,13 @@
"standard": "^17.1.2", "standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0", "unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1", "vite": "^7.3.1",
"vue": "^3.5.28", "vue": "^3.5.29",
"vue-i18n": "^11.2.8", "vue-i18n": "^11.2.8",
"vue-router": "^5.0.2" "vue-router": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"process": "^0.11.10", "process": "^0.11.10",
"stylelint": "^17.3.0", "stylelint": "^17.4.0",
"stylelint-config-standard": "^40.0.0" "stylelint-config-standard": "^40.0.0"
} }
}, },
@ -905,9 +905,9 @@
} }
}, },
"node_modules/@hugeicons/core-free-icons": { "node_modules/@hugeicons/core-free-icons": {
"version": "3.1.1", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.3.0.tgz",
"integrity": "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg==", "integrity": "sha512-qYyr4JQ2eQIHTSTbITvnJvs6ERNK64D9gpwZnf2IyuG0exzqfyABLO/oTB71FB3RZPfu1GbwycdiGSo46apjMQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@hugeicons/vue": { "node_modules/@hugeicons/vue": {
@ -1435,39 +1435,39 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
"integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.29.0", "@babel/parser": "^7.29.0",
"@vue/shared": "3.5.28", "@vue/shared": "3.5.29",
"entities": "^7.0.1", "entities": "^7.0.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz",
"integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.28", "@vue/compiler-core": "3.5.29",
"@vue/shared": "3.5.28" "@vue/shared": "3.5.29"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz",
"integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.29.0", "@babel/parser": "^7.29.0",
"@vue/compiler-core": "3.5.28", "@vue/compiler-core": "3.5.29",
"@vue/compiler-dom": "3.5.28", "@vue/compiler-dom": "3.5.29",
"@vue/compiler-ssr": "3.5.28", "@vue/compiler-ssr": "3.5.29",
"@vue/shared": "3.5.28", "@vue/shared": "3.5.29",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"postcss": "^8.5.6", "postcss": "^8.5.6",
@ -1475,13 +1475,13 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz",
"integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.28", "@vue/compiler-dom": "3.5.29",
"@vue/shared": "3.5.28" "@vue/shared": "3.5.29"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@ -1491,12 +1491,12 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/devtools-kit": { "node_modules/@vue/devtools-kit": {
"version": "8.0.5", "version": "8.0.6",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.6.tgz",
"integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", "integrity": "sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-shared": "^8.0.5", "@vue/devtools-shared": "^8.0.6",
"birpc": "^2.6.1", "birpc": "^2.6.1",
"hookable": "^5.5.3", "hookable": "^5.5.3",
"mitt": "^3.0.1", "mitt": "^3.0.1",
@ -1506,62 +1506,62 @@
} }
}, },
"node_modules/@vue/devtools-shared": { "node_modules/@vue/devtools-shared": {
"version": "8.0.5", "version": "8.0.6",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.6.tgz",
"integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", "integrity": "sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"rfdc": "^1.4.1" "rfdc": "^1.4.1"
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz",
"integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/shared": "3.5.28" "@vue/shared": "3.5.29"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz",
"integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.28", "@vue/reactivity": "3.5.29",
"@vue/shared": "3.5.28" "@vue/shared": "3.5.29"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz",
"integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.28", "@vue/reactivity": "3.5.29",
"@vue/runtime-core": "3.5.28", "@vue/runtime-core": "3.5.29",
"@vue/shared": "3.5.28", "@vue/shared": "3.5.29",
"csstype": "^3.2.3" "csstype": "^3.2.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz",
"integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.5.28", "@vue/compiler-ssr": "3.5.29",
"@vue/shared": "3.5.28" "@vue/shared": "3.5.29"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.5.28" "vue": "3.5.29"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz",
"integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@xterm/addon-fit": { "node_modules/@xterm/addon-fit": {
@ -2121,13 +2121,13 @@
} }
}, },
"node_modules/css-functions-list": { "node_modules/css-functions-list": {
"version": "3.2.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz",
"integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12 || >=16" "node": ">=12"
} }
}, },
"node_modules/css-tree": { "node_modules/css-tree": {
@ -4276,13 +4276,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/known-css-properties": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
"integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
"dev": true,
"license": "MIT"
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -5854,9 +5847,9 @@
} }
}, },
"node_modules/stylelint": { "node_modules/stylelint": {
"version": "17.3.0", "version": "17.4.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.3.0.tgz", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.4.0.tgz",
"integrity": "sha512-1POV91lcEMhj6SLVaOeA0KlS9yattS+qq+cyWqP/nYzWco7K5jznpGH1ExngvPlTM9QF1Kjd2bmuzJu9TH2OcA==", "integrity": "sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -5872,15 +5865,14 @@
"dependencies": { "dependencies": {
"@csstools/css-calc": "^3.1.1", "@csstools/css-calc": "^3.1.1",
"@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-syntax-patches-for-csstree": "^1.0.26", "@csstools/css-syntax-patches-for-csstree": "^1.0.27",
"@csstools/css-tokenizer": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0",
"@csstools/media-query-list-parser": "^5.0.0", "@csstools/media-query-list-parser": "^5.0.0",
"@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-resolve-nested": "^4.0.0",
"@csstools/selector-specificity": "^6.0.0", "@csstools/selector-specificity": "^6.0.0",
"balanced-match": "^3.0.1",
"colord": "^2.9.3", "colord": "^2.9.3",
"cosmiconfig": "^9.0.0", "cosmiconfig": "^9.0.0",
"css-functions-list": "^3.2.3", "css-functions-list": "^3.3.3",
"css-tree": "^3.1.0", "css-tree": "^3.1.0",
"debug": "^4.4.3", "debug": "^4.4.3",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
@ -5894,7 +5886,6 @@
"import-meta-resolve": "^4.2.0", "import-meta-resolve": "^4.2.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0", "is-plain-object": "^5.0.0",
"known-css-properties": "^0.37.0",
"mathml-tag-names": "^4.0.0", "mathml-tag-names": "^4.0.0",
"meow": "^14.0.0", "meow": "^14.0.0",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
@ -5979,16 +5970,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1" "url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/stylelint/node_modules/balanced-match": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz",
"integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
}
},
"node_modules/stylelint/node_modules/file-entry-cache": { "node_modules/stylelint/node_modules/file-entry-cache": {
"version": "11.1.2", "version": "11.1.2",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz",
@ -6615,16 +6596,16 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.5.28", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
"integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.28", "@vue/compiler-dom": "3.5.29",
"@vue/compiler-sfc": "3.5.28", "@vue/compiler-sfc": "3.5.29",
"@vue/runtime-dom": "3.5.28", "@vue/runtime-dom": "3.5.29",
"@vue/server-renderer": "3.5.28", "@vue/server-renderer": "3.5.29",
"@vue/shared": "3.5.28" "@vue/shared": "3.5.29"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@ -6656,14 +6637,14 @@
} }
}, },
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "5.0.2", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.2.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.3.tgz",
"integrity": "sha512-YFhwaE5c5JcJpNB1arpkl4/GnO32wiUWRB+OEj1T0DlDxEZoOfbltl2xEwktNU/9o1sGcGburIXSpbLpPFe/6w==", "integrity": "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/generator": "^7.28.6", "@babel/generator": "^7.28.6",
"@vue-macros/common": "^3.1.1", "@vue-macros/common": "^3.1.1",
"@vue/devtools-api": "^8.0.0", "@vue/devtools-api": "^8.0.6",
"ast-walker-scope": "^0.8.3", "ast-walker-scope": "^0.8.3",
"chokidar": "^5.0.0", "chokidar": "^5.0.0",
"json5": "^2.2.3", "json5": "^2.2.3",
@ -6701,12 +6682,12 @@
} }
}, },
"node_modules/vue-router/node_modules/@vue/devtools-api": { "node_modules/vue-router/node_modules/@vue/devtools-api": {
"version": "8.0.5", "version": "8.0.6",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.6.tgz",
"integrity": "sha512-DgVcW8H/Nral7LgZEecYFFYXnAvGuN9C3L3DtWekAncFBedBczpNW8iHKExfaM559Zm8wQWrwtYZ9lXthEHtDw==", "integrity": "sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-kit": "^8.0.5" "@vue/devtools-kit": "^8.0.6"
} }
}, },
"node_modules/vue-router/node_modules/json5": { "node_modules/vue-router/node_modules/json5": {

View File

@ -6,7 +6,7 @@
"source": "index.html", "source": "index.html",
"devDependencies": { "devDependencies": {
"process": "^0.11.10", "process": "^0.11.10",
"stylelint": "^17.3.0", "stylelint": "^17.4.0",
"stylelint-config-standard": "^40.0.0" "stylelint-config-standard": "^40.0.0"
}, },
"scripts": { "scripts": {
@ -24,7 +24,7 @@
"dependencies": { "dependencies": {
"@connectrpc/connect": "^2.1.1", "@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1", "@connectrpc/connect-web": "^2.1.1",
"@hugeicons/core-free-icons": "^3.1.1", "@hugeicons/core-free-icons": "^3.3.0",
"@hugeicons/vue": "^1.0.4", "@hugeicons/vue": "^1.0.4",
"@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
@ -34,8 +34,8 @@
"standard": "^17.1.2", "standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0", "unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1", "vite": "^7.3.1",
"vue": "^3.5.28", "vue": "^3.5.29",
"vue-i18n": "^11.2.8", "vue-i18n": "^11.2.8",
"vue-router": "^5.0.2" "vue-router": "^5.0.3"
} }
} }

View File

@ -210,6 +210,11 @@ export declare type EffectivePolicy = Message<"olivetin.api.v1.EffectivePolicy">
* @generated from field: bool show_log_list = 2; * @generated from field: bool show_log_list = 2;
*/ */
showLogList: boolean; showLogList: boolean;
/**
* @generated from field: bool show_version_number = 3;
*/
showVersionNumber: boolean;
}; };
/** /**
@ -495,6 +500,13 @@ export declare type GetLogsRequest = Message<"olivetin.api.v1.GetLogsRequest"> &
* @generated from field: string date_filter = 2; * @generated from field: string date_filter = 2;
*/ */
dateFilter: string; dateFilter: string;
/**
* Number of logs per page (optional; server default used if 0 or unset)
*
* @generated from field: int64 page_size = 3;
*/
pageSize: bigint;
}; };
/** /**

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@
<footer title="footer" v-if="showFooter"> <footer title="footer" v-if="showFooter">
<p> <p>
<img title="application icon" :src="logoUrl" alt="OliveTin logo" style="height: 1em;" class="logo" /> <img title="application icon" :src="logoUrl" alt="OliveTin logo" style="height: 1em;" class="logo" />
OliveTin {{ currentVersion }} OliveTin <span v-if="showVersionNumber">{{ currentVersion }}</span>
</p> </p>
<p> <p>
<span> <span>
@ -52,7 +52,7 @@
<span>{{ t('connected') }}</span> <span>{{ t('connected') }}</span>
</p> </p>
<p> <p v-if="showVersionNumber">
<a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a> <a id="available-version" href="http://olivetin.app" target="_blank" hidden>?</a>
</p> </p>
</footer> </footer>
@ -68,7 +68,7 @@
</option> </option>
</select> </select>
<p class="browser-languages"> <p class="browser-languages">
{{ t('language-dialog.browser-languages') }}: {{ t('language-dialog.browser-languages') }}:
<span v-if="browserLanguages.length > 0">{{ browserLanguages.join(', ') }}</span> <span v-if="browserLanguages.length > 0">{{ browserLanguages.join(', ') }}</span>
<span v-else>{{ t('language-dialog.not-available') }}</span> <span v-else>{{ t('language-dialog.not-available') }}</span>
</p> </p>
@ -126,6 +126,7 @@ const showFooter = ref(true)
const showNavigation = ref(true) const showNavigation = ref(true)
const showLogs = ref(true) const showLogs = ref(true)
const showDiagnostics = ref(true) const showDiagnostics = ref(true)
const showVersionNumber = ref(true)
const showLoginLink = ref(true) const showLoginLink = ref(true)
const sectionNavigationStyle = ref('sidebar') const sectionNavigationStyle = ref('sidebar')
@ -184,7 +185,7 @@ function normalizeBrowserLanguage() {
if (navigator.languages && navigator.languages.length > 0) { if (navigator.languages && navigator.languages.length > 0) {
for (const candidate of navigator.languages) { for (const candidate of navigator.languages) {
const lowerCandidate = candidate.toLowerCase() const lowerCandidate = candidate.toLowerCase()
// Try exact match (case-insensitive) // Try exact match (case-insensitive)
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate) const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
if (exact) { if (exact) {
@ -223,6 +224,7 @@ function updateHeaderFromInit() {
showNavigation.value = window.initResponse.showNavigation showNavigation.value = window.initResponse.showNavigation
showLogs.value = window.initResponse.showLogList showLogs.value = window.initResponse.showLogList
showDiagnostics.value = window.initResponse.showDiagnostics showDiagnostics.value = window.initResponse.showDiagnostics
showVersionNumber.value = window.initResponse.effectivePolicy?.showVersionNumber ?? true
sectionNavigationStyle.value = window.initResponse.sectionNavigationStyle || 'sidebar' sectionNavigationStyle.value = window.initResponse.sectionNavigationStyle || 'sidebar'
availableThemes.value = window.initResponse.availableThemes || [] availableThemes.value = window.initResponse.availableThemes || []
@ -277,7 +279,7 @@ function renderNavigation() {
function openLanguageDialog() { function openLanguageDialog() {
selectedLanguage.value = languagePreference.value selectedLanguage.value = languagePreference.value
if (typeof navigator !== 'undefined' && Array.isArray(navigator.languages)) { if (typeof navigator !== 'undefined' && Array.isArray(navigator.languages)) {
browserLanguages.value = navigator.languages browserLanguages.value = navigator.languages
} else { } else {
@ -327,7 +329,7 @@ function handleLanguageDialogClick(event) {
function openThemeDialog() { function openThemeDialog() {
selectedTheme.value = themePreference.value || '' selectedTheme.value = themePreference.value || ''
if (themeDialog.value) { if (themeDialog.value) {
themeDialog.value.showModal() themeDialog.value.showModal()
} }
@ -354,7 +356,7 @@ function changeTheme() {
function applyTheme() { function applyTheme() {
let themeStyle = document.getElementById('theme-style') let themeStyle = document.getElementById('theme-style')
if (!themeStyle) { if (!themeStyle) {
themeStyle = document.createElement('style') themeStyle = document.createElement('style')
themeStyle.id = 'theme-style' themeStyle.id = 'theme-style'
@ -404,10 +406,10 @@ window.updateHeaderFromInit = updateHeaderFromInit
onMounted(() => { onMounted(() => {
serverConnection.value = true; serverConnection.value = true;
updateHeaderFromInit() updateHeaderFromInit()
// Initialize selected language from stored preference // Initialize selected language from stored preference
selectedLanguage.value = languagePreference.value selectedLanguage.value = languagePreference.value
// Initialize selected theme from stored preference // Initialize selected theme from stored preference
selectedTheme.value = themePreference.value || '' selectedTheme.value = themePreference.value || ''

View File

@ -78,7 +78,7 @@
</tbody> </tbody>
</table> </table>
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange" class="padding" <Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" :page="currentPage" @page-change="handlePageChange" class="padding"
@page-size-change="handlePageSizeChange" itemTitle="execution logs" /> @page-size-change="handlePageSizeChange" itemTitle="execution logs" />
</div> </div>

View File

@ -162,7 +162,10 @@ async function generateBrowserInfo() {
userAgentData: userAgentData userAgentData: userAgentData
} }
const olivetinVersion = window.initResponse?.currentVersion || t('diagnostics.unknown') const showVersionNumber = window.initResponse?.effectivePolicy?.showVersionNumber ?? true
const olivetinVersion = showVersionNumber
? (window.initResponse?.currentVersion || t('diagnostics.unknown'))
: '[hidden]'
const currentLanguage = locale.value || t('diagnostics.unknown') const currentLanguage = locale.value || t('diagnostics.unknown')
let output = ''; let output = '';
@ -300,4 +303,4 @@ onMounted(() => {
flex-direction: column; flex-direction: column;
gap: 1em; gap: 1em;
} }
</style> </style>

View File

@ -68,7 +68,7 @@
</tbody> </tbody>
</table> </table>
<Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange" class = "padding" <Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" :page="currentPage" @page-change="handlePageChange" class = "padding"
@page-size-change="handlePageSizeChange" itemTitle="execution logs" /> @page-size-change="handlePageSizeChange" itemTitle="execution logs" />
</div> </div>
@ -150,6 +150,7 @@ async function fetchLogs() {
const args = { const args = {
"startOffset": BigInt(startOffset), "startOffset": BigInt(startOffset),
"pageSize": BigInt(pageSize.value),
} }
// Add date filter if selected // Add date filter if selected
@ -160,7 +161,6 @@ async function fetchLogs() {
const response = await window.client.getLogs(args) const response = await window.client.getLogs(args)
logs.value = response.logs logs.value = response.logs
pageSize.value = Number(response.pageSize) || 0
totalCount.value = Number(response.totalCount) || 0 totalCount.value = Number(response.totalCount) || 0
} catch (err) { } catch (err) {
console.error('Failed to fetch logs:', err) console.error('Failed to fetch logs:', err)

View File

@ -13,7 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"chai": "^6.2.2", "chai": "^6.2.2",
"eslint": "^9.39.2", "eslint": "^10.0.2",
"mocha": "^11.7.5", "mocha": "^11.7.5",
"selenium-webdriver": "^4.41.0" "selenium-webdriver": "^4.41.0"
} }
@ -67,9 +67,9 @@
} }
}, },
"node_modules/@eslint-community/regexpp": { "node_modules/@eslint-community/regexpp": {
"version": "4.12.1", "version": "4.12.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -77,105 +77,68 @@
} }
}, },
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.21.1", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/object-schema": "^2.1.7", "@eslint/object-schema": "^3.0.2",
"debug": "^4.3.1", "debug": "^4.3.1",
"minimatch": "^3.1.2" "minimatch": "^10.2.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
} }
}, },
"node_modules/@eslint/config-helpers": { "node_modules/@eslint/config-helpers": {
"version": "0.4.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz",
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.17.0" "@eslint/core": "^1.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.17.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.15" "@types/json-schema": "^7.0.15"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/js": {
"version": "9.39.2",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
} }
}, },
"node_modules/@eslint/object-schema": { "node_modules/@eslint/object-schema": {
"version": "2.1.7", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.4.1", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz",
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.17.0", "@eslint/core": "^1.1.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
} }
}, },
"node_modules/@hapi/address": { "node_modules/@hapi/address": {
@ -326,10 +289,17 @@
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/esrecurse": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -341,9 +311,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.15.0", "version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -364,9 +334,9 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -438,14 +408,26 @@
"dev": true "dev": true
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^4.0.2"
"concat-map": "0.0.1" },
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/brace-expansion/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
} }
}, },
"node_modules/browser-stdout": { "node_modules/browser-stdout": {
@ -467,16 +449,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase": { "node_modules/camelcase": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
@ -639,13 +611,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/core-util-is": { "node_modules/core-util-is": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@ -818,33 +783,30 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.39.2", "version": "10.0.2",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.21.1", "@eslint/config-array": "^0.23.2",
"@eslint/config-helpers": "^0.4.2", "@eslint/config-helpers": "^0.5.2",
"@eslint/core": "^0.17.0", "@eslint/core": "^1.1.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/plugin-kit": "^0.6.0",
"@eslint/js": "9.39.2",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2", "@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6", "@types/estree": "^1.0.6",
"ajv": "^6.12.4", "ajv": "^6.14.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.4.0", "eslint-scope": "^9.1.1",
"eslint-visitor-keys": "^4.2.1", "eslint-visitor-keys": "^5.0.1",
"espree": "^10.4.0", "espree": "^11.1.1",
"esquery": "^1.5.0", "esquery": "^1.7.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"file-entry-cache": "^8.0.0", "file-entry-cache": "^8.0.0",
@ -854,8 +816,7 @@
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2", "minimatch": "^10.2.1",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.3" "optionator": "^0.9.3"
}, },
@ -863,7 +824,7 @@
"eslint": "bin/eslint.js" "eslint": "bin/eslint.js"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
}, },
"funding": { "funding": {
"url": "https://eslint.org/donate" "url": "https://eslint.org/donate"
@ -878,58 +839,61 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.4.0", "version": "9.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz",
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@types/esrecurse": "^4.3.1",
"@types/estree": "^1.0.8",
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint-visitor-keys": { "node_modules/eslint-visitor-keys": {
"version": "4.2.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "10.4.0", "version": "11.1.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"acorn": "^8.15.0", "acorn": "^8.16.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^5.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^20.19.0 || ^22.13.0 || >=24"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/esquery": { "node_modules/esquery": {
"version": "1.5.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"estraverse": "^5.1.0" "estraverse": "^5.1.0"
}, },
@ -955,6 +919,7 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true, "dev": true,
"license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
} }
@ -1216,19 +1181,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -1314,23 +1266,6 @@
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true "dev": true
}, },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imurmurhash": { "node_modules/imurmurhash": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -1552,12 +1487,6 @@
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -1612,16 +1541,19 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true, "dev": true,
"license": "ISC", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^5.0.2"
}, },
"engines": { "engines": {
"node": "*" "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/minimist": { "node_modules/minimist": {
@ -1793,19 +1725,6 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true "dev": true
}, },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -1928,16 +1847,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/rxjs": { "node_modules/rxjs": {
"version": "7.8.2", "version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",

View File

@ -12,7 +12,7 @@
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"devDependencies": { "devDependencies": {
"chai": "^6.2.2", "chai": "^6.2.2",
"eslint": "^9.39.2", "eslint": "^10.0.2",
"mocha": "^11.7.5", "mocha": "^11.7.5",
"selenium-webdriver": "^4.41.0" "selenium-webdriver": "^4.41.0"
}, },

View File

@ -3,7 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG" logLevel: "DEBUG"
checkForUpdates: false checkForUpdates: false
popupOnStart: execution-dialog defaultPopupOnStart: execution-dialog
actions: actions:
- title: Test checkbox argument - title: Test checkbox argument

View File

@ -3,7 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG" logLevel: "DEBUG"
checkForUpdates: false checkForUpdates: false
popupOnStart: execution-dialog defaultPopupOnStart: execution-dialog
actions: actions:
- title: Test datetime argument - title: Test datetime argument

View File

@ -3,7 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG" logLevel: "DEBUG"
checkForUpdates: false checkForUpdates: false
popupOnStart: execution-dialog defaultPopupOnStart: execution-dialog
actions: actions:
- title: Test suggestionsBrowserKey - title: Test suggestionsBrowserKey

View File

@ -51,6 +51,7 @@ message GetDashboardResponse {
message EffectivePolicy { message EffectivePolicy {
bool show_diagnostics = 1; bool show_diagnostics = 1;
bool show_log_list = 2; bool show_log_list = 2;
bool show_version_number = 3;
} }
message GetDashboardRequest { message GetDashboardRequest {
@ -121,6 +122,7 @@ message StartActionByGetAndWaitResponse {
message GetLogsRequest{ message GetLogsRequest{
int64 start_offset = 1; int64 start_offset = 1;
string date_filter = 2; // Optional date filter in YYYY-MM-DD format string date_filter = 2; // Optional date filter in YYYY-MM-DD format
int64 page_size = 3; // Number of logs per page (optional; server default used if 0 or unset)
}; };
message LogEntry { message LogEntry {

View File

@ -410,11 +410,12 @@ func (x *GetDashboardResponse) GetDashboard() *Dashboard {
} }
type EffectivePolicy struct { type EffectivePolicy struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
ShowDiagnostics bool `protobuf:"varint,1,opt,name=show_diagnostics,json=showDiagnostics,proto3" json:"show_diagnostics,omitempty"` ShowDiagnostics bool `protobuf:"varint,1,opt,name=show_diagnostics,json=showDiagnostics,proto3" json:"show_diagnostics,omitempty"`
ShowLogList bool `protobuf:"varint,2,opt,name=show_log_list,json=showLogList,proto3" json:"show_log_list,omitempty"` ShowLogList bool `protobuf:"varint,2,opt,name=show_log_list,json=showLogList,proto3" json:"show_log_list,omitempty"`
unknownFields protoimpl.UnknownFields ShowVersionNumber bool `protobuf:"varint,3,opt,name=show_version_number,json=showVersionNumber,proto3" json:"show_version_number,omitempty"`
sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *EffectivePolicy) Reset() { func (x *EffectivePolicy) Reset() {
@ -461,6 +462,13 @@ func (x *EffectivePolicy) GetShowLogList() bool {
return false return false
} }
func (x *EffectivePolicy) GetShowVersionNumber() bool {
if x != nil {
return x.ShowVersionNumber
}
return false
}
type GetDashboardRequest struct { type GetDashboardRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
@ -1105,6 +1113,7 @@ type GetLogsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
StartOffset int64 `protobuf:"varint,1,opt,name=start_offset,json=startOffset,proto3" json:"start_offset,omitempty"` StartOffset int64 `protobuf:"varint,1,opt,name=start_offset,json=startOffset,proto3" json:"start_offset,omitempty"`
DateFilter string `protobuf:"bytes,2,opt,name=date_filter,json=dateFilter,proto3" json:"date_filter,omitempty"` // Optional date filter in YYYY-MM-DD format DateFilter string `protobuf:"bytes,2,opt,name=date_filter,json=dateFilter,proto3" json:"date_filter,omitempty"` // Optional date filter in YYYY-MM-DD format
PageSize int64 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // Number of logs per page (optional; server default used if 0 or unset)
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -1153,6 +1162,13 @@ func (x *GetLogsRequest) GetDateFilter() string {
return "" return ""
} }
func (x *GetLogsRequest) GetPageSize() int64 {
if x != nil {
return x.PageSize
}
return 0
}
type LogEntry struct { type LogEntry struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
DatetimeStarted string `protobuf:"bytes,1,opt,name=datetime_started,json=datetimeStarted,proto3" json:"datetime_started,omitempty"` DatetimeStarted string `protobuf:"bytes,1,opt,name=datetime_started,json=datetimeStarted,proto3" json:"datetime_started,omitempty"`
@ -3926,10 +3942,11 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"f\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"f\n" +
"\x14GetDashboardResponse\x12\x14\n" + "\x14GetDashboardResponse\x12\x14\n" +
"\x05title\x18\x01 \x01(\tR\x05title\x128\n" + "\x05title\x18\x01 \x01(\tR\x05title\x128\n" +
"\tdashboard\x18\x04 \x01(\v2\x1a.olivetin.api.v1.DashboardR\tdashboard\"`\n" + "\tdashboard\x18\x04 \x01(\v2\x1a.olivetin.api.v1.DashboardR\tdashboard\"\x90\x01\n" +
"\x0fEffectivePolicy\x12)\n" + "\x0fEffectivePolicy\x12)\n" +
"\x10show_diagnostics\x18\x01 \x01(\bR\x0fshowDiagnostics\x12\"\n" + "\x10show_diagnostics\x18\x01 \x01(\bR\x0fshowDiagnostics\x12\"\n" +
"\rshow_log_list\x18\x02 \x01(\bR\vshowLogList\"k\n" + "\rshow_log_list\x18\x02 \x01(\bR\vshowLogList\x12.\n" +
"\x13show_version_number\x18\x03 \x01(\bR\x11showVersionNumber\"k\n" +
"\x13GetDashboardRequest\x12\x14\n" + "\x13GetDashboardRequest\x12\x14\n" +
"\x05title\x18\x01 \x01(\tR\x05title\x12\x1f\n" + "\x05title\x18\x01 \x01(\tR\x05title\x12\x1f\n" +
"\ventity_type\x18\x02 \x01(\tR\n" + "\ventity_type\x18\x02 \x01(\tR\n" +
@ -3972,11 +3989,12 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
"\x1eStartActionByGetAndWaitRequest\x12\x1b\n" + "\x1eStartActionByGetAndWaitRequest\x12\x1b\n" +
"\taction_id\x18\x01 \x01(\tR\bactionId\"Y\n" + "\taction_id\x18\x01 \x01(\tR\bactionId\"Y\n" +
"\x1fStartActionByGetAndWaitResponse\x126\n" + "\x1fStartActionByGetAndWaitResponse\x126\n" +
"\tlog_entry\x18\x01 \x01(\v2\x19.olivetin.api.v1.LogEntryR\blogEntry\"T\n" + "\tlog_entry\x18\x01 \x01(\v2\x19.olivetin.api.v1.LogEntryR\blogEntry\"q\n" +
"\x0eGetLogsRequest\x12!\n" + "\x0eGetLogsRequest\x12!\n" +
"\fstart_offset\x18\x01 \x01(\x03R\vstartOffset\x12\x1f\n" + "\fstart_offset\x18\x01 \x01(\x03R\vstartOffset\x12\x1f\n" +
"\vdate_filter\x18\x02 \x01(\tR\n" + "\vdate_filter\x18\x02 \x01(\tR\n" +
"dateFilter\"\x89\x05\n" + "dateFilter\x12\x1b\n" +
"\tpage_size\x18\x03 \x01(\x03R\bpageSize\"\x89\x05\n" +
"\bLogEntry\x12)\n" + "\bLogEntry\x12)\n" +
"\x10datetime_started\x18\x01 \x01(\tR\x0fdatetimeStarted\x12!\n" + "\x10datetime_started\x18\x01 \x01(\tR\x0fdatetimeStarted\x12!\n" +
"\faction_title\x18\x02 \x01(\tR\vactionTitle\x12\x16\n" + "\faction_title\x18\x02 \x01(\tR\vactionTitle\x12\x16\n" +

View File

@ -3,6 +3,7 @@ package api
import ( import (
ctx "context" ctx "context"
"encoding/json" "encoding/json"
"errors"
"os" "os"
"path" "path"
"sort" "sort"
@ -144,6 +145,9 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
hash, err := createHash(req.Msg.Password) hash, err := createHash(req.Msg.Password)
if err != nil { if err != nil {
if errors.Is(err, ErrArgon2Busy) {
return nil, connect.NewError(connect.CodeResourceExhausted, err)
}
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("error creating hash: %w", err)) return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("error creating hash: %w", err))
} }
@ -154,52 +158,50 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
return connect.NewResponse(ret), nil return connect.NewResponse(ret), nil
} }
func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[apiv1.LocalUserLoginRequest]) (*connect.Response[apiv1.LocalUserLoginResponse], error) { func (api *oliveTinAPI) cookieSecure(header http.Header) bool {
// Check if local user authentication is enabled useTLS := header.Get("X-Forwarded-Proto") == "https"
if !api.cfg.AuthLocalUsers.Enabled { return useTLS || api.cfg.Security.ForceSecureCookies
return connect.NewResponse(&apiv1.LocalUserLoginResponse{ }
Success: false,
}), nil
}
match := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{
Success: match,
})
func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool, secure bool) {
if match { if match {
// Set authentication cookie for successful login user := api.cfg.FindUserByUsername(req.Username)
user := api.cfg.FindUserByUsername(req.Msg.Username)
if user != nil { if user != nil {
sid := uuid.NewString() sid := uuid.NewString()
// Register the session in the session storage
auth.RegisterUserSession(api.cfg, "local", sid, user.Username) auth.RegisterUserSession(api.cfg, "local", sid, user.Username)
log.WithFields(log.Fields{"username": user.Username}).Info("LocalUserLogin: Session created and registered")
log.WithFields(log.Fields{
"username": user.Username,
}).Info("LocalUserLogin: Session created and registered")
// Set the authentication cookie in the response headers
cookie := &http.Cookie{ cookie := &http.Cookie{
Name: "olivetin-sid-local", Name: "olivetin-sid-local",
Value: sid, Value: sid,
MaxAge: 31556952, // 1 year MaxAge: 31556952,
HttpOnly: true, HttpOnly: true,
Path: "/", Path: "/",
Secure: secure,
SameSite: http.SameSiteLaxMode,
} }
response.Header().Set("Set-Cookie", cookie.String()) response.Header().Set("Set-Cookie", cookie.String())
log.WithFields(log.Fields{"username": user.Username}).Info("LocalUserLogin: User logged in successfully.")
} else {
log.WithFields(log.Fields{"username": req.Username}).Warn("LocalUserLogin: Password matched but user lookup failed.")
} }
log.WithFields(log.Fields{
"username": req.Msg.Username,
}).Info("LocalUserLogin: User logged in successfully.")
} else { } else {
log.WithFields(log.Fields{ log.WithFields(log.Fields{"username": req.Username}).Warn("LocalUserLogin: User login failed.")
"username": req.Msg.Username,
}).Warn("LocalUserLogin: User login failed.")
} }
}
func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[apiv1.LocalUserLoginRequest]) (*connect.Response[apiv1.LocalUserLoginResponse], error) {
if !api.cfg.AuthLocalUsers.Enabled {
return connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: false}), nil
}
match, err := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
if err != nil {
if errors.Is(err, ErrArgon2Busy) {
return nil, connect.NewError(connect.CodeResourceExhausted, err)
}
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
}
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: match})
api.applyLocalLoginResult(req.Msg, response, match, api.cookieSecure(req.Header()))
return response, nil return response, nil
} }
@ -354,30 +356,36 @@ func getMostRecentExecutionStatusByActionId(api *oliveTinAPI, actionId string) *
return ile return ile
} }
func (api *oliveTinAPI) resolveExecutionStatusForView(msg *apiv1.ExecutionStatusRequest, user *authpublic.AuthenticatedUser) (*executor.InternalLogEntry, error) {
ile := api.getExecutionStatusByRequest(msg)
if ile == nil {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s or action ID %s", msg.ExecutionTrackingId, msg.ActionId))
}
if !isValidLogEntry(ile) || !api.isLogEntryAllowed(ile, user) {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied to view this execution"))
}
return ile, nil
}
func (api *oliveTinAPI) getExecutionStatusByRequest(msg *apiv1.ExecutionStatusRequest) *executor.InternalLogEntry {
if msg.ExecutionTrackingId != "" {
return getExecutionStatusByTrackingID(api, msg.ExecutionTrackingId)
}
return getMostRecentExecutionStatusByActionId(api, msg.ActionId)
}
func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[apiv1.ExecutionStatusRequest]) (*connect.Response[apiv1.ExecutionStatusResponse], error) { func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[apiv1.ExecutionStatusRequest]) (*connect.Response[apiv1.ExecutionStatusResponse], error) {
res := &apiv1.ExecutionStatusResponse{}
user := auth.UserFromApiCall(ctx, req, api.cfg) user := auth.UserFromApiCall(ctx, req, api.cfg)
if err := api.checkDashboardAccess(user); err != nil { if err := api.checkDashboardAccess(user); err != nil {
return nil, err return nil, err
} }
ile, err := api.resolveExecutionStatusForView(req.Msg, user)
var ile *executor.InternalLogEntry if err != nil {
return nil, err
if req.Msg.ExecutionTrackingId != "" {
ile = getExecutionStatusByTrackingID(api, req.Msg.ExecutionTrackingId)
} else {
ile = getMostRecentExecutionStatusByActionId(api, req.Msg.ActionId)
} }
res := &apiv1.ExecutionStatusResponse{
if ile == nil { LogEntry: api.internalLogEntryToPb(ile, user),
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s or action ID %s", req.Msg.ExecutionTrackingId, req.Msg.ActionId))
} else {
res.LogEntry = api.internalLogEntryToPb(ile, user)
} }
return connect.NewResponse(res), nil return connect.NewResponse(res), nil
} }
@ -390,6 +398,7 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
}).Info("Logout: User logged out") }).Info("Logout: User logged out")
response := connect.NewResponse(&apiv1.LogoutResponse{}) response := connect.NewResponse(&apiv1.LogoutResponse{})
secure := api.cookieSecure(req.Header())
// Clear the local authentication cookie by setting it to expire // Clear the local authentication cookie by setting it to expire
localCookie := &http.Cookie{ localCookie := &http.Cookie{
@ -398,6 +407,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
MaxAge: -1, // This tells the browser to delete the cookie MaxAge: -1, // This tells the browser to delete the cookie
HttpOnly: true, HttpOnly: true,
Path: "/", Path: "/",
Secure: secure,
SameSite: http.SameSiteLaxMode,
} }
response.Header().Set("Set-Cookie", localCookie.String()) response.Header().Set("Set-Cookie", localCookie.String())
@ -408,6 +419,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
MaxAge: -1, // This tells the browser to delete the cookie MaxAge: -1, // This tells the browser to delete the cookie
HttpOnly: true, HttpOnly: true,
Path: "/", Path: "/",
Secure: secure,
SameSite: http.SameSiteLaxMode,
} }
response.Header().Add("Set-Cookie", oauth2Cookie.String()) response.Header().Add("Set-Cookie", oauth2Cookie.String())
@ -494,6 +507,19 @@ func (api *oliveTinAPI) buildCustomDashboardResponse(rr *DashboardRenderRequest,
return connect.NewResponse(res), nil return connect.NewResponse(res), nil
} }
func resolveLogsPageSize(requestPageSize, defaultPageSize int64) int64 {
if requestPageSize == 0 {
return defaultPageSize
}
if requestPageSize < 10 {
return 10
}
if requestPageSize > 100 {
return 100
}
return requestPageSize
}
func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetLogsRequest]) (*connect.Response[apiv1.GetLogsResponse], error) { func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetLogsRequest]) (*connect.Response[apiv1.GetLogsResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg) user := auth.UserFromApiCall(ctx, req, api.cfg)
@ -501,12 +527,9 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetL
return nil, err return nil, err
} }
pageSize := resolveLogsPageSize(req.Msg.GetPageSize(), api.cfg.LogHistoryPageSize)
logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, pageSize, req.Msg.DateFilter)
ret := &apiv1.GetLogsResponse{} ret := &apiv1.GetLogsResponse{}
dateFilter := ""
if req.Msg.DateFilter != "" {
dateFilter = req.Msg.DateFilter
}
logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, api.cfg.LogHistoryPageSize, dateFilter)
for _, le := range logEntries { for _, le := range logEntries {
ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user)) ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
} }
@ -687,7 +710,9 @@ func (api *oliveTinAPI) WhoAmI(ctx ctx.Context, req *connect.Request[apiv1.WhoAm
} }
func (api *oliveTinAPI) SosReport(ctx ctx.Context, req *connect.Request[apiv1.SosReportRequest]) (*connect.Response[apiv1.SosReportResponse], error) { func (api *oliveTinAPI) SosReport(ctx ctx.Context, req *connect.Request[apiv1.SosReportRequest]) (*connect.Response[apiv1.SosReportResponse], error) {
sos := installationinfo.GetSosReport() user := auth.UserFromApiCall(ctx, req, api.cfg)
redactVersion := !user.EffectivePolicy.ShowVersionNumber
sos := installationinfo.GetSosReport(redactVersion)
if !api.cfg.InsecureAllowDumpSos { if !api.cfg.InsecureAllowDumpSos {
log.Info(sos) log.Info(sos)
@ -872,11 +897,17 @@ func (api *oliveTinAPI) OnExecutionFinished(ile *executor.InternalLogEntry) {
} }
func (api *oliveTinAPI) GetDiagnostics(ctx ctx.Context, req *connect.Request[apiv1.GetDiagnosticsRequest]) (*connect.Response[apiv1.GetDiagnosticsResponse], error) { func (api *oliveTinAPI) GetDiagnostics(ctx ctx.Context, req *connect.Request[apiv1.GetDiagnosticsRequest]) (*connect.Response[apiv1.GetDiagnosticsResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg)
if err := api.checkDashboardAccess(user); err != nil {
return nil, err
}
if !user.EffectivePolicy.ShowDiagnostics {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("diagnostics are not available for your account"))
}
res := &apiv1.GetDiagnosticsResponse{ res := &apiv1.GetDiagnosticsResponse{
SshFoundKey: installationinfo.Runtime.SshFoundKey, SshFoundKey: installationinfo.Runtime.SshFoundKey,
SshFoundConfig: installationinfo.Runtime.SshFoundConfig, SshFoundConfig: installationinfo.Runtime.SshFoundConfig,
} }
return connect.NewResponse(res), nil return connect.NewResponse(res), nil
} }
@ -885,12 +916,19 @@ func (api *oliveTinAPI) Init(ctx ctx.Context, req *connect.Request[apiv1.InitReq
loginRequired := user.IsGuest() && api.cfg.AuthRequireGuestsToLogin loginRequired := user.IsGuest() && api.cfg.AuthRequireGuestsToLogin
showVersion := user.EffectivePolicy.ShowVersionNumber
currentVersion := ""
availableVersion := ""
if showVersion {
currentVersion = installationinfo.Build.Version
availableVersion = installationinfo.Runtime.AvailableVersion
}
res := &apiv1.InitResponse{ res := &apiv1.InitResponse{
ShowFooter: api.cfg.ShowFooter, ShowFooter: api.cfg.ShowFooter,
ShowNavigation: api.cfg.ShowNavigation, ShowNavigation: api.cfg.ShowNavigation,
ShowNewVersions: api.cfg.ShowNewVersions, ShowNewVersions: showVersion && api.cfg.ShowNewVersions,
AvailableVersion: installationinfo.Runtime.AvailableVersion, AvailableVersion: availableVersion,
CurrentVersion: installationinfo.Build.Version, CurrentVersion: currentVersion,
PageTitle: api.cfg.PageTitle, PageTitle: api.cfg.PageTitle,
SectionNavigationStyle: api.cfg.SectionNavigationStyle, SectionNavigationStyle: api.cfg.SectionNavigationStyle,
DefaultIconForBack: api.cfg.DefaultIconForBack, DefaultIconForBack: api.cfg.DefaultIconForBack,
@ -1253,7 +1291,7 @@ func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv
return api.StartAction(ctx, &connect.Request[apiv1.StartActionRequest]{ return api.StartAction(ctx, &connect.Request[apiv1.StartActionRequest]{
Msg: &apiv1.StartActionRequest{ Msg: &apiv1.StartActionRequest{
// FIXME BindingId: execReqLogEntry.GetBindingId(),
UniqueTrackingId: req.Msg.ExecutionTrackingId, UniqueTrackingId: req.Msg.ExecutionTrackingId,
}, },
}) })

View File

@ -55,8 +55,9 @@ func matchesEntity(binding *executor.ActionBinding, entity *entities.Entity) boo
func buildEffectivePolicy(policy *config.ConfigurationPolicy) *apiv1.EffectivePolicy { func buildEffectivePolicy(policy *config.ConfigurationPolicy) *apiv1.EffectivePolicy {
ret := &apiv1.EffectivePolicy{ ret := &apiv1.EffectivePolicy{
ShowDiagnostics: policy.ShowDiagnostics, ShowDiagnostics: policy.ShowDiagnostics,
ShowLogList: policy.ShowLogList, ShowLogList: policy.ShowLogList,
ShowVersionNumber: policy.ShowVersionNumber,
} }
return ret return ret

View File

@ -57,7 +57,7 @@ func buildEntityFieldsetContents(contents []*config.DashboardComponent, ent *ent
for _, subitem := range contents { for _, subitem := range contents {
c := cloneItem(subitem, ent, entityType, rr) c := cloneItem(subitem, ent, entityType, rr)
log.Infof("cloneItem: %+v", c) log.Tracef("cloneItem: %+v", c)
if c != nil { if c != nil {
ret = append(ret, c) ret = append(ret, c)

View File

@ -1,6 +1,7 @@
package api package api
import ( import (
"errors"
"runtime" "runtime"
config "github.com/OliveTin/OliveTin/internal/config" config "github.com/OliveTin/OliveTin/internal/config"
@ -8,6 +9,12 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var ErrArgon2Busy = errors.New("too many concurrent password operations")
const argon2MaxConcurrent = 10
var argon2Sem = make(chan struct{}, argon2MaxConcurrent)
var defaultParams = argon2id.Params{ var defaultParams = argon2id.Params{
Memory: 64 * 1024, Memory: 64 * 1024,
Iterations: 4, Iterations: 4,
@ -17,10 +24,16 @@ var defaultParams = argon2id.Params{
} }
func CreateHash(password string) (string, error) { func CreateHash(password string) (string, error) {
select {
case argon2Sem <- struct{}{}:
defer func() { <-argon2Sem }()
default:
return "", ErrArgon2Busy
}
hash, err := argon2id.CreateHash(password, &defaultParams) hash, err := argon2id.CreateHash(password, &defaultParams)
if err != nil { if err != nil {
log.Fatal("Error creating hash: ", err) log.Warnf("Error creating hash: %v", err)
return "", err return "", err
} }
@ -31,37 +44,40 @@ func createHash(password string) (string, error) {
return CreateHash(password) return CreateHash(password)
} }
func comparePasswordAndHash(password, hash string) bool { func comparePasswordAndHash(password, hash string) (bool, error) {
select {
case argon2Sem <- struct{}{}:
defer func() { <-argon2Sem }()
default:
return false, ErrArgon2Busy
}
match, err := argon2id.ComparePasswordAndHash(password, hash) match, err := argon2id.ComparePasswordAndHash(password, hash)
if err != nil { if err != nil {
log.Errorf("Error comparing password and hash: %v", err) log.Errorf("Error comparing password and hash: %v", err)
return false return false, nil
} }
return match return match, nil
} }
func checkUserPassword(cfg *config.Config, username, password string) bool { func checkUserPassword(cfg *config.Config, username, password string) (bool, error) {
for _, user := range cfg.AuthLocalUsers.Users { user := cfg.FindUserByUsername(username)
if user.Username == username { if user == nil {
match := comparePasswordAndHash(password, user.Password) log.WithFields(log.Fields{"username": username}).Warn("Failed to check password for user, as username was not found")
return false, nil
if match {
return true
} else {
log.WithFields(log.Fields{
"username": username,
}).Warn("Password does not match for user")
return false
}
}
} }
return comparePasswordAndLogResult(password, user.Password, username)
log.WithFields(log.Fields{ }
"username": username,
}).Warn("Failed to check password for user, as username was not found") func comparePasswordAndLogResult(password, hash, username string) (bool, error) {
match, err := comparePasswordAndHash(password, hash)
return false if err != nil {
return false, err
}
if !match {
log.WithFields(log.Fields{"username": username}).Warn("Password does not match for user")
return false, nil
}
return true, nil
} }

View File

@ -76,8 +76,9 @@ func (u *AuthenticatedUser) BuildUserAcls(cfg *config.Config) {
func getEffectivePolicy(cfg *config.Config, u *AuthenticatedUser) *config.ConfigurationPolicy { func getEffectivePolicy(cfg *config.Config, u *AuthenticatedUser) *config.ConfigurationPolicy {
ret := &config.ConfigurationPolicy{ ret := &config.ConfigurationPolicy{
ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics, ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics,
ShowLogList: cfg.DefaultPolicy.ShowLogList, ShowLogList: cfg.DefaultPolicy.ShowLogList,
ShowVersionNumber: cfg.DefaultPolicy.ShowVersionNumber,
} }
for _, acl := range cfg.AccessControlLists { for _, acl := range cfg.AccessControlLists {
@ -98,5 +99,9 @@ func buildConfigurationPolicy(ret *config.ConfigurationPolicy, policy config.Con
ret.ShowLogList = policy.ShowLogList ret.ShowLogList = policy.ShowLogList
} }
if policy.ShowVersionNumber {
ret.ShowVersionNumber = policy.ShowVersionNumber
}
return ret return ret
} }

View File

@ -108,14 +108,20 @@ func randString(nByte int) (string, error) {
return base64.URLEncoding.EncodeToString(b), nil return base64.URLEncoding.EncodeToString(b), nil
} }
func (h *OAuth2Handler) cookieSecure(r *http.Request) bool {
useTLS := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
return useTLS || h.cfg.Security.ForceSecureCookies
}
func (h *OAuth2Handler) setOAuthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) { func (h *OAuth2Handler) setOAuthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
cookie := &http.Cookie{ cookie := &http.Cookie{
Name: name, Name: name,
Value: value, Value: value,
MaxAge: 900, // 15 minutes MaxAge: 900, // 15 minutes
Secure: r.TLS != nil, Secure: h.cookieSecure(r),
HttpOnly: true, HttpOnly: true,
Path: "/", Path: "/",
SameSite: http.SameSiteLaxMode,
} }
http.SetCookie(w, cookie) http.SetCookie(w, cookie)

View File

@ -98,8 +98,9 @@ type AccessControlList struct {
// ConfigurationPolicy defines global settings which are overridden with an ACL. // ConfigurationPolicy defines global settings which are overridden with an ACL.
type ConfigurationPolicy struct { type ConfigurationPolicy struct {
ShowDiagnostics bool `koanf:"showDiagnostics"` ShowDiagnostics bool `koanf:"showDiagnostics"`
ShowLogList bool `koanf:"showLogList"` ShowLogList bool `koanf:"showLogList"`
ShowVersionNumber bool `koanf:"showVersionNumber"`
} }
type PrometheusConfig struct { type PrometheusConfig struct {
@ -107,6 +108,16 @@ type PrometheusConfig struct {
DefaultGoMetrics bool `koanf:"defaultGoMetrics"` DefaultGoMetrics bool `koanf:"defaultGoMetrics"`
} }
// SecurityConfig allows users to fine tune the security related HTTP headers and cookie options.
type SecurityConfig struct {
HeaderContentSecurityPolicy bool `koanf:"headerContentSecurityPolicy"`
ContentSecurityPolicy string `koanf:"contentSecurityPolicy"`
HeaderXContentTypeOptions bool `koanf:"headerXContentTypeOptions"`
HeaderXFrameOptions bool `koanf:"headerXFrameOptions"`
XFrameOptions string `koanf:"xFrameOptions"`
ForceSecureCookies bool `koanf:"forceSecureCookies"`
}
// Config is the global config used through the whole app. // Config is the global config used through the whole app.
type Config struct { type Config struct {
UseSingleHTTPFrontend bool `koanf:"useSingleHTTPFrontend"` UseSingleHTTPFrontend bool `koanf:"useSingleHTTPFrontend"`
@ -160,6 +171,7 @@ type Config struct {
InsecureAllowDumpActionMap bool `koanf:"insecureAllowDumpActionMap"` InsecureAllowDumpActionMap bool `koanf:"insecureAllowDumpActionMap"`
InsecureAllowDumpJwtClaims bool `koanf:"insecureAllowDumpJwtClaims"` InsecureAllowDumpJwtClaims bool `koanf:"insecureAllowDumpJwtClaims"`
Prometheus PrometheusConfig `koanf:"prometheus"` Prometheus PrometheusConfig `koanf:"prometheus"`
Security SecurityConfig `koanf:"security"`
SaveLogs SaveLogsConfig `koanf:"saveLogs"` SaveLogs SaveLogsConfig `koanf:"saveLogs"`
DefaultIconForActions string `koanf:"defaultIconForActions"` DefaultIconForActions string `koanf:"defaultIconForActions"`
DefaultIconForDirectories string `koanf:"defaultIconForDirectories"` DefaultIconForDirectories string `koanf:"defaultIconForDirectories"`
@ -268,6 +280,11 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.InsecureAllowDumpJwtClaims = false config.InsecureAllowDumpJwtClaims = false
config.Prometheus.Enabled = false config.Prometheus.Enabled = false
config.Prometheus.DefaultGoMetrics = false config.Prometheus.DefaultGoMetrics = false
config.Security.HeaderContentSecurityPolicy = true
config.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'"
config.Security.HeaderXContentTypeOptions = true
config.Security.HeaderXFrameOptions = true
config.Security.XFrameOptions = "DENY"
config.DefaultIconForActions = "&#x1F600;" config.DefaultIconForActions = "&#x1F600;"
config.DefaultIconForDirectories = "&#128193" config.DefaultIconForDirectories = "&#128193"
config.DefaultIconForBack = "&laquo;" config.DefaultIconForBack = "&laquo;"
@ -281,6 +298,7 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.DefaultPolicy.ShowDiagnostics = true config.DefaultPolicy.ShowDiagnostics = true
config.DefaultPolicy.ShowLogList = true config.DefaultPolicy.ShowLogList = true
config.DefaultPolicy.ShowVersionNumber = true
return &config return &config
} }

View File

@ -16,6 +16,7 @@ func (cfg *Config) Sanitize() {
cfg.sanitizeAuthRequireGuestsToLogin() cfg.sanitizeAuthRequireGuestsToLogin()
cfg.sanitizeLogHistoryPageSize() cfg.sanitizeLogHistoryPageSize()
cfg.sanitizeLocalUserPasswords() cfg.sanitizeLocalUserPasswords()
cfg.sanitizeSecurityHeaders()
// log.Infof("cfg %p", cfg) // log.Infof("cfg %p", cfg)
@ -183,6 +184,25 @@ func (cfg *Config) sanitizeLocalUserPasswords() {
} }
} }
func (cfg *Config) sanitizeSecurityHeaders() {
cfg.sanitizeSecurityHeadersCSP()
cfg.sanitizeSecurityHeadersXFrameOptions()
}
func (cfg *Config) sanitizeSecurityHeadersCSP() {
if !cfg.Security.HeaderContentSecurityPolicy || cfg.Security.ContentSecurityPolicy != "" {
return
}
cfg.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'"
}
func (cfg *Config) sanitizeSecurityHeadersXFrameOptions() {
if !cfg.Security.HeaderXFrameOptions || cfg.Security.XFrameOptions != "" {
return
}
cfg.Security.XFrameOptions = "DENY"
}
// parsePasswordTemplate expands {{ .Env.VAR }} in local user password fields using the process environment. // parsePasswordTemplate expands {{ .Env.VAR }} in local user password fields using the process environment.
func parsePasswordTemplate(source string) string { func parsePasswordTemplate(source string) string {
t, err := template.New("password").Option("missingkey=error").Parse(source) t, err := template.New("password").Option("missingkey=error").Parse(source)
@ -239,8 +259,7 @@ func (arg *ActionArgument) sanitize() {
arg.sanitizeNoType() arg.sanitizeNoType()
// TODO Validate the default against the type checker, but this creates a // Default value validation runs in executor at config load (validateArgumentDefaults).
// import loop
} }
func (arg *ActionArgument) sanitizeNoType() { func (arg *ActionArgument) sanitizeNoType() {

View File

@ -1,23 +0,0 @@
package cors
import (
log "github.com/sirupsen/logrus"
"net/http"
)
// AllowCors takes a HTTP handler and adds Access-Control-Allow-Origin headers to
// responses.
//
// Note: HTTP OPTIONS requests (which need to be preflighted" for CORS) are not
// handled because this app does not use HTTP PUT/PATCH/etc.
func AllowCors(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if origin := r.Header.Get("Origin"); origin != "" {
log.Debugf("Adding CORS header origin: %q", origin)
w.Header().Set("Access-Control-Allow-Origin", origin)
}
h.ServeHTTP(w, r)
})
}

View File

@ -1,22 +0,0 @@
package cors
import (
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestCors(t *testing.T) {
req, _ := http.NewRequest("GET", "/health-check", nil)
req.Header.Add("Origin", "1.2.3.4")
blat := AllowCors(http.FileServer(http.Dir(".")))
rr := httptest.NewRecorder()
blat.ServeHTTP(rr, req)
assert.Equal(t, http.StatusNotFound, rr.Code, "HTTP 404 on CORS")
assert.Equal(t, "1.2.3.4", rr.Header().Get("Access-Control-Allow-Origin"), "CORS Header set")
}

View File

@ -603,14 +603,14 @@ func getExecutionsCount(rate config.RateSpec, req *ExecutionRequest) int {
then := time.Now().Add(-duration) then := time.Now().Add(-duration)
currentEntityPrefix := ""
if req.Binding != nil && req.Binding.Entity != nil {
currentEntityPrefix = req.Binding.Entity.UniqueKey
}
for _, logEntry := range req.executor.GetLogsByBindingId(req.Binding.ID) { for _, logEntry := range req.executor.GetLogsByBindingId(req.Binding.ID) {
// FIXME if logEntry.EntityPrefix != currentEntityPrefix {
/* continue
if logEntry.EntityPrefix != req.EntityPrefix { }
continue
}
*/
if logEntry.DatetimeStarted.After(then) && !logEntry.Blocked { if logEntry.DatetimeStarted.After(then) && !logEntry.Blocked {
executions += 1 executions += 1
@ -761,28 +761,12 @@ func fail(req *ExecutionRequest, err error) bool {
func stepRequestAction(req *ExecutionRequest) bool { func stepRequestAction(req *ExecutionRequest) bool {
metricActionsRequested.Inc() metricActionsRequested.Inc()
// If there is no binding or action, do not proceed. Leave default if !stepRequestActionHasBinding(req) {
// log entry values (icon/title/id) and stop execution gracefully.
if req.Binding == nil || req.Binding.Action == nil {
log.Warnf("Action request has no binding/action; skipping execution")
return false return false
} }
req.logEntry.Binding = req.Binding stepRequestActionPopulateLogEntry(req)
req.logEntry.ActionConfigTitle = req.Binding.Action.Title stepRequestActionRegisterLog(req)
req.logEntry.ActionTitle = tpl.ParseTemplateOfActionBeforeExec(req.Binding.Action.Title, req.Binding.Entity)
req.logEntry.ActionIcon = req.Binding.Action.Icon
req.logEntry.Tags = req.Tags
req.executor.logmutex.Lock()
if _, containsKey := req.executor.LogsByBindingId[req.Binding.ID]; !containsKey {
req.executor.LogsByBindingId[req.Binding.ID] = make([]*InternalLogEntry, 0)
}
req.executor.LogsByBindingId[req.Binding.ID] = append(req.executor.LogsByBindingId[req.Binding.ID], req.logEntry)
req.executor.logmutex.Unlock()
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"actionTitle": req.logEntry.ActionTitle, "actionTitle": req.logEntry.ActionTitle,
@ -794,6 +778,35 @@ func stepRequestAction(req *ExecutionRequest) bool {
return true return true
} }
func stepRequestActionHasBinding(req *ExecutionRequest) bool {
if req.Binding == nil || req.Binding.Action == nil {
log.Warnf("Action request has no binding/action; skipping execution")
return false
}
return true
}
func stepRequestActionPopulateLogEntry(req *ExecutionRequest) {
req.logEntry.Binding = req.Binding
req.logEntry.ActionConfigTitle = req.Binding.Action.Title
req.logEntry.ActionTitle = tpl.ParseTemplateOfActionBeforeExec(req.Binding.Action.Title, req.Binding.Entity)
req.logEntry.ActionIcon = req.Binding.Action.Icon
req.logEntry.Tags = req.Tags
if req.Binding.Entity != nil {
req.logEntry.EntityPrefix = req.Binding.Entity.UniqueKey
}
}
func stepRequestActionRegisterLog(req *ExecutionRequest) {
req.executor.logmutex.Lock()
defer req.executor.logmutex.Unlock()
if _, containsKey := req.executor.LogsByBindingId[req.Binding.ID]; !containsKey {
req.executor.LogsByBindingId[req.Binding.ID] = make([]*InternalLogEntry, 0)
}
req.executor.LogsByBindingId[req.Binding.ID] = append(req.executor.LogsByBindingId[req.Binding.ID], req.logEntry)
}
func stepLogStart(req *ExecutionRequest) bool { func stepLogStart(req *ExecutionRequest) bool {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"actionTitle": req.logEntry.ActionTitle, "actionTitle": req.logEntry.ActionTitle,

View File

@ -41,7 +41,41 @@ type RebuildActionMapRequest struct {
DashboardActionTitles []string DashboardActionTitles []string
} }
func validateArgumentDefaults(cfg *config.Config) {
if cfg == nil {
return
}
for _, action := range cfg.Actions {
validateActionArgumentDefaults(action)
}
}
func validateActionArgumentDefaults(action *config.Action) {
if action == nil {
return
}
for i := range action.Arguments {
validateArgumentDefault(action, &action.Arguments[i])
}
}
func validateArgumentDefault(action *config.Action, arg *config.ActionArgument) {
if arg.Default == "" {
return
}
if err := ValidateArgument(arg, arg.Default, action); err != nil {
log.WithFields(log.Fields{
"actionTitle": action.Title,
"argName": arg.Name,
"default": arg.Default,
"error": err,
}).Warn("Argument default value failed validation")
}
}
func (e *Executor) RebuildActionMap() { func (e *Executor) RebuildActionMap() {
validateArgumentDefaults(e.Cfg)
e.MapActionBindingsLock.Lock() e.MapActionBindingsLock.Lock()
clear(e.MapActionBindings) clear(e.MapActionBindings)

View File

@ -23,6 +23,40 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func applySecurityHeaders(cfg *config.Config, w http.ResponseWriter) {
applyCSP(cfg, w)
applyXContentTypeOptions(cfg, w)
applyXFrameOptions(cfg, w)
}
func applyCSP(cfg *config.Config, w http.ResponseWriter) {
if !cfg.Security.HeaderContentSecurityPolicy || cfg.Security.ContentSecurityPolicy == "" {
return
}
w.Header().Set("Content-Security-Policy", cfg.Security.ContentSecurityPolicy)
}
func applyXContentTypeOptions(cfg *config.Config, w http.ResponseWriter) {
if !cfg.Security.HeaderXContentTypeOptions {
return
}
w.Header().Set("X-Content-Type-Options", "nosniff")
}
func applyXFrameOptions(cfg *config.Config, w http.ResponseWriter) {
if !cfg.Security.HeaderXFrameOptions || cfg.Security.XFrameOptions == "" {
return
}
w.Header().Set("X-Frame-Options", cfg.Security.XFrameOptions)
}
func securityHeadersMiddleware(cfg *config.Config, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
applySecurityHeaders(cfg, w)
next.ServeHTTP(w, r)
})
}
func logDebugRequest(cfg *config.Config, source string, r *http.Request) { func logDebugRequest(cfg *config.Config, source string, r *http.Request) {
if cfg.LogDebugOptions.SingleFrontendRequests { if cfg.LogDebugOptions.SingleFrontendRequests {
log.Debugf("SingleFrontend HTTP Req URL %v: %q", source, r.URL) log.Debugf("SingleFrontend HTTP Req URL %v: %q", source, r.URL)
@ -96,7 +130,7 @@ func StartFrontendMux(cfg *config.Config, ex *executor.Executor) {
srv := &http.Server{ srv := &http.Server{
Addr: cfg.ListenAddressSingleHTTPFrontend, Addr: cfg.ListenAddressSingleHTTPFrontend,
Handler: mux, Handler: securityHeadersMiddleware(cfg, mux),
} }
log.Fatal(srv.ListenAndServe()) log.Fatal(srv.ListenAndServe())

View File

@ -1,8 +1,6 @@
package httpservers package httpservers
import ( import (
// cors "github.com/OliveTin/OliveTin/internal/cors"
"net/http" "net/http"
"os" "os"
"path" "path"

View File

@ -40,15 +40,23 @@ func configToSosreport(cfg *config.Config) *sosReportConfig {
} }
} }
func GetSosReport() string { func GetSosReport(redactVersion bool) string {
ret := "" ret := ""
ret += "### SOSREPORT START (copy all text to SOSREPORT END)\n" ret += "### SOSREPORT START (copy all text to SOSREPORT END)\n"
out, _ := yaml.Marshal(Build) buildForReport := *Build
if redactVersion {
buildForReport.Version = "[redacted]"
}
out, _ := yaml.Marshal(&buildForReport)
ret += fmt.Sprintf("# Build: \n%+v\n", string(out)) ret += fmt.Sprintf("# Build: \n%+v\n", string(out))
out, _ = yaml.Marshal(Runtime) runtimeForReport := *Runtime
if redactVersion {
runtimeForReport.AvailableVersion = "[redacted]"
}
out, _ = yaml.Marshal(&runtimeForReport)
ret += fmt.Sprintf("# Runtime:\n%+v\n", string(out)) ret += fmt.Sprintf("# Runtime:\n%+v\n", string(out))
out, _ = yaml.Marshal(configToSosreport(Config)) out, _ = yaml.Marshal(configToSosreport(Config))