mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Merge remote-tracking branch 'origin/main' into fix/security-hardening-review
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Normalize line endings: store LF in the repo and check out LF on every
|
||||
# platform, so prettier/gofmt see identical bytes locally and in CI.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Binary assets
|
||||
*.png binary
|
||||
*.ico binary
|
||||
*.wasm binary
|
||||
*.exe binary
|
||||
@@ -52,6 +52,12 @@ jobs:
|
||||
- name: Go vulnerability check
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 && govulncheck ./...
|
||||
|
||||
# Generated sqlc output must never drift from db/queries/. One leg of
|
||||
# the matrix is enough; make is not guaranteed on the Windows runner.
|
||||
- name: Verify generated sqlc output (make sqlc-verify)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: make sqlc-install sqlc-verify
|
||||
|
||||
- name: Run tests with race detection and coverage
|
||||
run: go test -race -timeout 20m ./... -coverprofile=coverage.out -cover
|
||||
|
||||
@@ -73,7 +79,7 @@ jobs:
|
||||
working-directory: Server/
|
||||
|
||||
client-check:
|
||||
name: Client Typecheck & Test
|
||||
name: Client Static Checks
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -101,6 +107,7 @@ jobs:
|
||||
if (fs.existsSync(p)) {
|
||||
let c = fs.readFileSync(p, 'utf8');
|
||||
c = c.replace(/^type Event\b/gm, 'type _Event').replace(/^interface Event\b/gm, 'interface _Event');
|
||||
c = c.replace(/,\s*type Event\s*(?=\})/g, ' '); // unused named import from @tauri-apps/api/event
|
||||
fs.writeFileSync(p, c);
|
||||
console.log('Patched: renamed Event -> _Event in generated/events.ts');
|
||||
} else {
|
||||
@@ -126,6 +133,28 @@ jobs:
|
||||
- name: Knip (unused code & deps)
|
||||
run: npx knip || true
|
||||
|
||||
# Unit tests live in their own job so a red suite is visible as exactly one
|
||||
# failing check instead of masking the static gates above. The suite is
|
||||
# KNOWN RED pending the reboot-plan P2 triage — do not "fix" tests here by
|
||||
# editing assertions; see docs/plans and the P2 triage rules.
|
||||
client-tests:
|
||||
name: Client Unit Tests
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: Client/tauri-client/
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: Client/tauri-client/package-lock.json
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run unit tests with coverage
|
||||
run: npx vitest run --coverage --reporter=default
|
||||
|
||||
@@ -192,7 +221,8 @@ jobs:
|
||||
libasound2-dev \
|
||||
libssl-dev \
|
||||
patchelf \
|
||||
librsvg2-dev
|
||||
librsvg2-dev \
|
||||
xdg-utils
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
||||
|
||||
@@ -83,7 +83,8 @@ jobs:
|
||||
libasound2-dev \
|
||||
libssl-dev \
|
||||
patchelf \
|
||||
librsvg2-dev
|
||||
librsvg2-dev \
|
||||
xdg-utils
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -211,7 +212,8 @@ jobs:
|
||||
libasound2-dev \
|
||||
libssl-dev \
|
||||
patchelf \
|
||||
librsvg2-dev
|
||||
librsvg2-dev \
|
||||
xdg-utils
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -361,9 +363,21 @@ jobs:
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Generate SHA256 checksums
|
||||
- name: Create source snapshot (AGPL source availability)
|
||||
shell: bash
|
||||
run: |
|
||||
find windows linux -type f -exec sha256sum {} \; > checksums.sha256
|
||||
git archive --format=tar.gz --prefix="OwnCord-${VERSION}/" \
|
||||
-o "owncord-src-${{ github.ref_name }}.tar.gz" HEAD
|
||||
|
||||
# Checksum lines must use bare asset filenames: the v1.0.0 updater's
|
||||
# ParseChecksumFile does an exact match on the last field, so a
|
||||
# "windows/" prefix would strand every deployed server on 1.0.0.
|
||||
- name: Generate SHA256 checksums
|
||||
shell: bash
|
||||
run: |
|
||||
(cd windows && sha256sum *) > checksums.sha256
|
||||
(cd linux && sha256sum *) >> checksums.sha256
|
||||
sha256sum owncord-src-*.tar.gz >> checksums.sha256
|
||||
|
||||
- name: Generate server update manifest
|
||||
shell: bash
|
||||
@@ -382,8 +396,22 @@ jobs:
|
||||
printf '%s' "$SERVER_UPDATE_SIGNING_PRIVATE_KEY" > "$KEY_PATH"
|
||||
trap 'rm -f "$KEY_PATH"' EXIT
|
||||
npm ci
|
||||
npx tauri signer sign -k "$KEY_PATH" -p "$SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD" ../../windows/chatserver.exe
|
||||
npx tauri signer sign -k "$KEY_PATH" -p "$SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD" ../../windows/server-update-manifest.json
|
||||
npx tauri signer sign -f "$KEY_PATH" -p "$SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD" ../../windows/chatserver.exe
|
||||
npx tauri signer sign -f "$KEY_PATH" -p "$SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD" ../../windows/server-update-manifest.json
|
||||
|
||||
# Fail closed before publishing: prove the freshly signed assets verify
|
||||
# against the pinned public key that ships inside the server binary.
|
||||
# Catches key/pubkey mismatch, signature format drift, and signer flag
|
||||
# regressions — each of which has silently broken this pipeline before.
|
||||
- name: Verify signed assets against pinned server update key
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y minisign
|
||||
base64 -d Server/updater/server_update_public_key.txt > "$RUNNER_TEMP/server_update.pub"
|
||||
for f in windows/chatserver.exe windows/server-update-manifest.json; do
|
||||
base64 -d "$f.sig" > "$RUNNER_TEMP/asset.minisig"
|
||||
minisign -Vm "$f" -x "$RUNNER_TEMP/asset.minisig" -p "$RUNNER_TEMP/server_update.pub"
|
||||
done
|
||||
|
||||
- name: Install root dependencies (changelogen)
|
||||
run: npm ci
|
||||
@@ -397,7 +425,33 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mapfile -t assets < <(find windows linux -type f)
|
||||
assets+=(checksums.sha256)
|
||||
assets+=(checksums.sha256 owncord-src-*.tar.gz)
|
||||
gh release create "${{ github.ref_name }}" \
|
||||
--notes-file CHANGELOG.md \
|
||||
"${assets[@]}"
|
||||
|
||||
# The public releases repo is what deployed servers and clients poll for
|
||||
# updates, and it carries the AGPL source snapshot while the source repo
|
||||
# is private. Publishing there must never be skipped silently once the
|
||||
# source repo is private.
|
||||
- name: Publish to public releases repo
|
||||
shell: bash
|
||||
env:
|
||||
RELEASES_TOKEN: ${{ secrets.RELEASES_REPO_TOKEN }}
|
||||
SOURCE_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$RELEASES_TOKEN" ]; then
|
||||
PRIVATE=$(GH_TOKEN="$SOURCE_REPO_TOKEN" gh api "repos/$GITHUB_REPOSITORY" --jq .private)
|
||||
if [ "$PRIVATE" = "true" ]; then
|
||||
echo "::error::Source repo is private and RELEASES_REPO_TOKEN is unset — binaries would ship with no public source or update feed (AGPL violation, broken updater)."
|
||||
exit 1
|
||||
fi
|
||||
echo "::warning::RELEASES_REPO_TOKEN not set — skipping publish to J3vb/OwnCord-releases."
|
||||
exit 0
|
||||
fi
|
||||
mapfile -t assets < <(find windows linux -type f)
|
||||
assets+=(checksums.sha256 owncord-src-*.tar.gz)
|
||||
GH_TOKEN="$RELEASES_TOKEN" gh release create "${{ github.ref_name }}" \
|
||||
--repo J3vb/OwnCord-releases \
|
||||
--notes-file CHANGELOG.md \
|
||||
"${assets[@]}"
|
||||
|
||||
@@ -71,3 +71,9 @@ TODOS.md
|
||||
CLAUDE.md
|
||||
DESIGN.md
|
||||
Client/CLIENT-REVIEW.md
|
||||
|
||||
# Agent tooling state
|
||||
.serena/
|
||||
|
||||
# Client env (holds API keys - never commit)
|
||||
Client/tauri-client/.env
|
||||
|
||||
+16
-15
@@ -5,7 +5,13 @@ tooling (`npm run changelog`) auto-generates entries from commit messages
|
||||
on each release; this file is the curated counterpart that calls out
|
||||
behavioural changes operators must know about.
|
||||
|
||||
## Unreleased — Phase B + C
|
||||
## Unreleased — v1.1.0-alpha series (Phase B + C)
|
||||
|
||||
> **Project reset note:** OwnCord has re-entered alpha. The `v1.0.0` release is
|
||||
> superseded; versioning continues forward as `v1.1.0-alpha.N` so deployed
|
||||
> servers and clients keep receiving updates. Releases are published to the
|
||||
> public [`OwnCord-releases`](https://github.com/J3vb/OwnCord-releases)
|
||||
> repository, including a full source snapshot with every release.
|
||||
|
||||
### Phase B — Acceleration
|
||||
|
||||
@@ -29,8 +35,8 @@ behavioural changes operators must know about.
|
||||
`VoiceService.JoinChannel`, `InviteService.CreateInvite`,
|
||||
`ModerationService.BanUser`, `BlockService.BlockUser`,
|
||||
`UserService.UpdateProfile`. The real OTel SDK is gated behind
|
||||
`-tags otel` and is currently a placeholder; wiring the upstream
|
||||
modules is tracked in `PHASE_BC_LOCAL_TODO.md`.
|
||||
`-tags otel` and is currently a placeholder; completing it is
|
||||
deferred until after the beta reset.
|
||||
- **Solid.js proof of concept (Step 6).** Two leaf components migrated
|
||||
(`Badge`, `ChannelListItem`), Vite + JSX configured, store→signal
|
||||
adapter landed. The remaining vanilla components remain in place;
|
||||
@@ -93,16 +99,11 @@ behavioural changes operators must know about.
|
||||
the existing IP restriction.** A previous prerelease shipped with only
|
||||
the IP gate; that has been corrected.
|
||||
|
||||
### Known follow-up work (local toolchain required)
|
||||
### Deferred work
|
||||
|
||||
See `PHASE_BC_LOCAL_TODO.md` for the full list. Highlights:
|
||||
|
||||
- Real OpenTelemetry SDK wiring (needs `go get` of the upstream modules)
|
||||
- Real Wazero runtime construction (needs `go get github.com/tetratelabs/wazero`)
|
||||
- Postgres backend implementation (needs `make sqlc-generate`)
|
||||
- Tinygo `.wasm` build of the example hello plugin
|
||||
- Migration of the remaining vanilla TypeScript components to Solid.js
|
||||
- Slash-command dispatcher in the WS layer (design TBD)
|
||||
|
||||
These items each need a real developer machine with network access; no
|
||||
in-sandbox pass can land them.
|
||||
The project is under a feature freeze until the beta reset completes.
|
||||
Explicitly deferred (not abandoned unless noted): real OpenTelemetry SDK
|
||||
wiring, the Postgres backend (scaffolding removed pending real demand),
|
||||
the slash-command dispatcher (`docs/plans/slash-commands.md`), and the
|
||||
Solid.js migration (abandoned — the experiment is being removed in favor
|
||||
of the established vanilla component pattern).
|
||||
|
||||
Generated
+211
-197
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "owncord-client",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0-alpha.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "owncord-client",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0-alpha.1",
|
||||
"dependencies": {
|
||||
"@jitsi/rnnoise-wasm": "^0.2.1",
|
||||
"@tauri-apps/api": "^2.10.1",
|
||||
@@ -121,13 +121,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
@@ -143,9 +143,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
|
||||
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
||||
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -153,21 +153,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-compilation-targets": "^7.28.6",
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helpers": "^7.28.6",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/traverse": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
"@babel/helper-compilation-targets": "^7.29.7",
|
||||
"@babel/helper-module-transforms": "^7.29.7",
|
||||
"@babel/helpers": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/traverse": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
@@ -194,14 +194,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
||||
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
@@ -224,14 +224,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
||||
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
||||
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.28.6",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
"@babel/compat-data": "^7.29.7",
|
||||
"@babel/helper-validator-option": "^7.29.7",
|
||||
"browserslist": "^4.24.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.1"
|
||||
@@ -293,9 +293,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
||||
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -317,29 +317,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
||||
"integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
"@babel/traverse": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
|
||||
"integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
"@babel/helper-module-imports": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"@babel/traverse": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -404,9 +404,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -414,9 +414,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -424,9 +424,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
||||
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
||||
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -434,27 +434,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
|
||||
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
||||
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0"
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
|
||||
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -631,33 +631,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
||||
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
|
||||
"integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
"@babel/helper-globals": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -665,14 +665,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
"@babel/helper-string-parser": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -4341,16 +4341,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.4.tgz",
|
||||
"integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.7.tgz",
|
||||
"integrity": "sha512-gIzazUkbQfv6T1rJHOLhMMKQnplKAvvQ7QNGaFwI6oCsp4z2aSDZCojGpX3QX3+MYsvJdyy/8BRIYVEbAkMkEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@vitest/mocker": "3.2.4",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"@vitest/mocker": "3.2.7",
|
||||
"@vitest/utils": "3.2.7",
|
||||
"magic-string": "^0.30.17",
|
||||
"sirv": "^3.0.1",
|
||||
"tinyrainbow": "^2.0.0",
|
||||
@@ -4361,7 +4361,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"playwright": "*",
|
||||
"vitest": "3.2.4",
|
||||
"vitest": "3.2.7",
|
||||
"webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -4377,9 +4377,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
|
||||
"integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.7.tgz",
|
||||
"integrity": "sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4401,8 +4401,8 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "3.2.4",
|
||||
"vitest": "3.2.4"
|
||||
"@vitest/browser": "3.2.7",
|
||||
"vitest": "3.2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
@@ -4411,15 +4411,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
|
||||
"integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "3.2.4",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"@vitest/spy": "3.2.7",
|
||||
"@vitest/utils": "3.2.7",
|
||||
"chai": "^5.2.0",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
@@ -4428,13 +4428,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
|
||||
"integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
|
||||
"integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "3.2.4",
|
||||
"@vitest/spy": "3.2.7",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.17"
|
||||
},
|
||||
@@ -4455,9 +4455,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
|
||||
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
|
||||
"integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4468,13 +4468,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
|
||||
"integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
|
||||
"integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "3.2.4",
|
||||
"@vitest/utils": "3.2.7",
|
||||
"pathe": "^2.0.3",
|
||||
"strip-literal": "^3.0.0"
|
||||
},
|
||||
@@ -4483,13 +4483,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
|
||||
"integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
|
||||
"integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.4",
|
||||
"@vitest/pretty-format": "3.2.7",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -4498,9 +4498,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
|
||||
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
|
||||
"integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4511,13 +4511,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
|
||||
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
|
||||
"integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.4",
|
||||
"@vitest/pretty-format": "3.2.7",
|
||||
"loupe": "^3.1.4",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
@@ -4713,9 +4713,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.13",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz",
|
||||
"integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==",
|
||||
"version": "2.10.43",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
|
||||
"integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -4736,9 +4736,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4762,9 +4762,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
||||
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
||||
"version": "4.28.6",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz",
|
||||
"integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4782,10 +4782,10 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
"electron-to-chromium": "^1.5.328",
|
||||
"node-releases": "^2.0.36",
|
||||
"baseline-browser-mapping": "^2.10.42",
|
||||
"caniuse-lite": "^1.0.30001803",
|
||||
"electron-to-chromium": "^1.5.389",
|
||||
"node-releases": "^2.0.51",
|
||||
"update-browserslist-db": "^1.2.3"
|
||||
},
|
||||
"bin": {
|
||||
@@ -4847,9 +4847,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001784",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz",
|
||||
"integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==",
|
||||
"version": "1.0.30001806",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
|
||||
"integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5137,9 +5137,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.330",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.330.tgz",
|
||||
"integrity": "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA==",
|
||||
"version": "1.5.393",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz",
|
||||
"integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -5191,9 +5191,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5578,9 +5578,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5977,9 +5977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6300,10 +6300,20 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
|
||||
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -6798,9 +6808,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -6824,11 +6834,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.36",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
|
||||
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
|
||||
"version": "2.0.51",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
|
||||
"integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "6.0.0",
|
||||
@@ -7206,9 +7219,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.8",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
||||
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
||||
"version": "8.5.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
||||
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -7226,7 +7239,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
@@ -7335,13 +7348,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
||||
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
"es-define-property": "^1.0.1",
|
||||
"side-channel": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -7591,15 +7605,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"side-channel-list": "^1.0.1",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
@@ -7611,14 +7625,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
"object-inspect": "^1.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -8233,9 +8247,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.24.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.3.tgz",
|
||||
"integrity": "sha512-eJdUmK/Wrx2d+mnWWmwwLRyA7OQCkLap60sk3dOK4ViZR7DKwwptwuIvFBg2HaiP9ESaEdhtpSymQPvytpmkCA==",
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
|
||||
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -8297,9 +8311,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
||||
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
||||
"version": "6.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
||||
"integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8440,20 +8454,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
|
||||
"integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
"@vitest/mocker": "3.2.4",
|
||||
"@vitest/pretty-format": "^3.2.4",
|
||||
"@vitest/runner": "3.2.4",
|
||||
"@vitest/snapshot": "3.2.4",
|
||||
"@vitest/spy": "3.2.4",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"@vitest/expect": "3.2.7",
|
||||
"@vitest/mocker": "3.2.7",
|
||||
"@vitest/pretty-format": "^3.2.7",
|
||||
"@vitest/runner": "3.2.7",
|
||||
"@vitest/snapshot": "3.2.7",
|
||||
"@vitest/spy": "3.2.7",
|
||||
"@vitest/utils": "3.2.7",
|
||||
"chai": "^5.2.0",
|
||||
"debug": "^4.4.1",
|
||||
"expect-type": "^1.2.1",
|
||||
@@ -8483,8 +8497,8 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"@vitest/browser": "3.2.4",
|
||||
"@vitest/ui": "3.2.4",
|
||||
"@vitest/browser": "3.2.7",
|
||||
"@vitest/ui": "3.2.7",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
@@ -8732,9 +8746,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||
"version": "8.21.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
||||
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "owncord-client",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0-alpha.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[advisories]
|
||||
ignore = [
|
||||
# quick-xml 0.37 is pinned by tauri-winrt-notification 0.7 (via
|
||||
# tauri-plugin-notification -> notify-rust); no semver-compatible route
|
||||
# to the fixed 0.41 exists yet. It only parses toast-notification XML
|
||||
# templates the library itself constructs — never attacker-controlled
|
||||
# input — so these parser-DoS advisories are not reachable here.
|
||||
# Drop both entries when the notification chain moves to quick-xml >= 0.41.
|
||||
"RUSTSEC-2026-0194",
|
||||
"RUSTSEC-2026-0195",
|
||||
]
|
||||
Generated
+63
-28
@@ -519,6 +519,17 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
@@ -745,6 +756,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -775,9 +795,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
@@ -1599,11 +1619,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1613,10 +1631,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2019,7 +2040,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.62.2",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2942,7 +2963,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owncord-client"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0-alpha.1"
|
||||
dependencies = [
|
||||
"device_query",
|
||||
"env_logger",
|
||||
@@ -3532,14 +3553,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.14"
|
||||
version = "0.11.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||
checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.2",
|
||||
"lru-slab",
|
||||
"rand 0.9.2",
|
||||
"rand 0.10.2",
|
||||
"rand_pcg 0.10.2",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
@@ -3597,7 +3619,7 @@ dependencies = [
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
"rand_pcg",
|
||||
"rand_pcg 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3621,6 +3643,17 @@ dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.2",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@@ -3678,6 +3711,12 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@@ -3696,6 +3735,15 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||
dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -4005,9 +4053,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.10"
|
||||
version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -4363,7 +4411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
@@ -4374,7 +4422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
@@ -6194,19 +6242,6 @@ dependencies = [
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.2",
|
||||
"windows-interface 0.59.3",
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owncord-client"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0-alpha.1"
|
||||
edition = "2021"
|
||||
description = "OwnCord Desktop Client"
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ pub fn store_cert_fingerprint(
|
||||
// existed, or delete if there was none. Without this, a failed save
|
||||
// during cert rotation would silently lose the previously trusted cert.
|
||||
match old_value {
|
||||
Some(v) => { let _ = store.set(&host, v); }
|
||||
Some(v) => { store.set(&host, v); }
|
||||
None => { let _ = store.delete(&host); }
|
||||
}
|
||||
return Err(format!("failed to persist cert fingerprint: {e}"));
|
||||
@@ -150,12 +150,10 @@ pub fn get_cert_fingerprint(
|
||||
// DevTools command
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "devtools")]
|
||||
#[tauri::command]
|
||||
pub fn open_devtools(_window: tauri::WebviewWindow) {
|
||||
#[cfg(feature = "devtools")]
|
||||
{
|
||||
_window.open_devtools();
|
||||
}
|
||||
pub fn open_devtools(window: tauri::WebviewWindow) {
|
||||
window.open_devtools();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -74,7 +74,7 @@ pub fn run() {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
rfd::MessageDialog::new()
|
||||
.set_title("OwnCord failed to start")
|
||||
.set_description(&format!(
|
||||
.set_description(format!(
|
||||
"The application encountered a startup error and cannot continue.\n\n{e}"
|
||||
))
|
||||
.set_level(rfd::MessageLevel::Error)
|
||||
|
||||
@@ -167,7 +167,7 @@ fn tofu_check<R: Runtime>(
|
||||
// Restore previous in-memory state: put back old value or delete
|
||||
// if there was none, keeping in-memory consistent with on-disk.
|
||||
match old_value {
|
||||
Some(v) => { let _ = store.set(host, v); }
|
||||
Some(v) => { store.set(host, v); }
|
||||
None => { let _ = store.delete(host); }
|
||||
}
|
||||
return Err(format!("failed to persist cert fingerprint: {e}"));
|
||||
@@ -451,7 +451,7 @@ pub fn accept_cert_fingerprint<R: Runtime>(
|
||||
// fingerprint would be trusted in-process even though it was never
|
||||
// persisted to certs.json.
|
||||
match old_value {
|
||||
Some(v) => { let _ = store.set(&host, v); }
|
||||
Some(v) => { store.set(&host, v); }
|
||||
None => { let _ = store.delete(&host); }
|
||||
}
|
||||
return Err(format!("failed to persist cert fingerprint: {e}"));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "OwnCord",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0-alpha.1",
|
||||
"identifier": "com.owncord.client",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
@@ -54,7 +54,7 @@
|
||||
"bundleMediaFramework": true
|
||||
}
|
||||
},
|
||||
"category": "Network",
|
||||
"category": "SocialNetworking",
|
||||
"shortDescription": "Self-hosted chat platform",
|
||||
"longDescription": "OwnCord is a self-hosted voice and text chat platform with end-to-end encryption and full media support.",
|
||||
"windows": {
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import * as types from "./types";
|
||||
|
||||
export async function startLivekitProxy(
|
||||
params: types.StartLivekitProxyParams,
|
||||
): Promise<number> {
|
||||
export async function startLivekitProxy(params: types.StartLivekitProxyParams): Promise<number> {
|
||||
return invoke("start_livekit_proxy", params);
|
||||
}
|
||||
|
||||
@@ -52,9 +50,7 @@ export async function pttListenForKey(): Promise<number> {
|
||||
return invoke("ptt_listen_for_key");
|
||||
}
|
||||
|
||||
export async function saveCredential(
|
||||
params: types.SaveCredentialParams,
|
||||
): Promise<void> {
|
||||
export async function saveCredential(params: types.SaveCredentialParams): Promise<void> {
|
||||
return invoke("save_credential", params);
|
||||
}
|
||||
|
||||
@@ -64,9 +60,7 @@ export async function loadCredential(
|
||||
return invoke("load_credential", params);
|
||||
}
|
||||
|
||||
export async function deleteCredential(
|
||||
params: types.DeleteCredentialParams,
|
||||
): Promise<void> {
|
||||
export async function deleteCredential(params: types.DeleteCredentialParams): Promise<void> {
|
||||
return invoke("delete_credential", params);
|
||||
}
|
||||
|
||||
@@ -92,9 +86,7 @@ export async function getSettings(): Promise<types.Value> {
|
||||
return invoke("get_settings");
|
||||
}
|
||||
|
||||
export async function saveSettings(
|
||||
params: types.SaveSettingsParams,
|
||||
): Promise<void> {
|
||||
export async function saveSettings(params: types.SaveSettingsParams): Promise<void> {
|
||||
return invoke("save_settings", params);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* Event Listeners
|
||||
* Type-safe event listener helpers for Tauri events
|
||||
*/
|
||||
import { listen, type UnlistenFn, type Event } from "@tauri-apps/api/event";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import * as types from "./types";
|
||||
|
||||
/**
|
||||
@@ -19,9 +19,7 @@ import * as types from "./types";
|
||||
* @param handler - Callback function to handle the event
|
||||
* @returns Promise that resolves to an unlisten function
|
||||
*/
|
||||
export async function onStatusChange(
|
||||
handler: (payload: string) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
export async function onStatusChange(handler: (payload: string) => void): Promise<UnlistenFn> {
|
||||
return listen<string>("status-change", (event) => {
|
||||
handler(event.payload);
|
||||
});
|
||||
@@ -32,9 +30,7 @@ export async function onStatusChange(
|
||||
* @param handler - Callback function to handle the event
|
||||
* @returns Promise that resolves to an unlisten function
|
||||
*/
|
||||
export async function onWsState(
|
||||
handler: (payload: string) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
export async function onWsState(handler: (payload: string) => void): Promise<UnlistenFn> {
|
||||
return listen<string>("ws-state", (event) => {
|
||||
handler(event.payload);
|
||||
});
|
||||
@@ -45,9 +41,7 @@ export async function onWsState(
|
||||
* @param handler - Callback function to handle the event
|
||||
* @returns Promise that resolves to an unlisten function
|
||||
*/
|
||||
export async function onCertTofu(
|
||||
handler: (payload: types.Value) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
export async function onCertTofu(handler: (payload: types.Value) => void): Promise<UnlistenFn> {
|
||||
return listen<types.Value>("cert-tofu", (event) => {
|
||||
handler(event.payload);
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ It includes real-time messaging, voice/video via LiveKit, file sharing, and a we
|
||||
|
||||
### Option A: Prebuilt binaries
|
||||
|
||||
1. Download assets from [GitHub Releases](https://github.com/J3vb/OwnCord/releases).
|
||||
1. Download assets from [OwnCord-releases](https://github.com/J3vb/OwnCord-releases/releases) (binaries, checksums, signatures, and a full source snapshot per release).
|
||||
2. Run the server binary:
|
||||
- Windows: `chatserver.exe`
|
||||
- Linux: `./chatserver`
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
OwnCord is in alpha. Only the **latest release** receives security fixes.
|
||||
There are no backports.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| Latest release (see [OwnCord-releases](https://github.com/J3vb/OwnCord-releases/releases)) | Yes |
|
||||
| Anything older | No |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
**Do not open a public issue for security bugs.**
|
||||
|
||||
Report vulnerabilities privately via GitHub Security Advisories on the
|
||||
[OwnCord-releases](https://github.com/J3vb/OwnCord-releases/security/advisories/new)
|
||||
repository ("Report a vulnerability"). This channel works even while the
|
||||
source repository is private.
|
||||
|
||||
Please include:
|
||||
|
||||
- Affected component (server, desktop client, admin panel, plugin host)
|
||||
- Reproduction steps or a proof of concept
|
||||
- The release version (or source snapshot) you tested against
|
||||
|
||||
You will get an initial response within 7 days. Coordinated disclosure is
|
||||
appreciated; fixes ship in the next release with credit unless you prefer
|
||||
otherwise.
|
||||
|
||||
## Hardening documentation
|
||||
|
||||
Operator-facing hardening notes live in [docs/security.md](docs/security.md).
|
||||
+6
-1
@@ -17,9 +17,14 @@ sqlc-install:
|
||||
sqlc-generate:
|
||||
sqlc generate
|
||||
|
||||
# Verify only db/dbgen: the committed db/pgdbgen files carry hand-added
|
||||
# `//go:build postgres` tags that `sqlc generate` strips, so a pgdbgen diff
|
||||
# is expected noise. pgdbgen is scheduled for removal with the Postgres
|
||||
# scaffolding; restore it after generating so verify leaves a clean tree.
|
||||
sqlc-verify:
|
||||
sqlc generate
|
||||
@git diff --exit-code db/dbgen db/pgdbgen || ( \
|
||||
@git checkout -- db/pgdbgen
|
||||
@git diff --exit-code db/dbgen || ( \
|
||||
echo "ERROR: generated sqlc output is stale. Run 'make sqlc-generate' and commit the result." ; \
|
||||
exit 1 ; \
|
||||
)
|
||||
|
||||
@@ -232,7 +232,7 @@ func NewRouter(cfg *config.Config, database *db.DB, ver string, logBuf *admin.Ri
|
||||
|
||||
// Admin panel: static files + REST API (Phase 6).
|
||||
// Restrict /admin to configured CIDRs (default: private networks only).
|
||||
u := updater.NewUpdater(ver, cfg.GitHub.Token, "J3vb", "OwnCord")
|
||||
u := updater.NewUpdater(ver, cfg.GitHub.Token, cfg.GitHub.Owner, cfg.GitHub.Repo)
|
||||
adminHandler := admin.NewHandler(database, ver, hub, u, logBuf, cfg.Server.AllowedOrigins, svc.Permissions)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(AdminIPRestrict(cfg.Server.AdminAllowedCIDRs, cfg.Server.TrustedProxies))
|
||||
|
||||
+10
-1
@@ -79,8 +79,14 @@ type PluginsConfig struct {
|
||||
}
|
||||
|
||||
// GitHubConfig holds GitHub API settings for update checking.
|
||||
//
|
||||
// Owner/Repo point at the public releases repository. Server and client
|
||||
// update checks fetch release assets from this repo, so it must stay
|
||||
// publicly readable even when the source repository is private.
|
||||
type GitHubConfig struct {
|
||||
Token string `koanf:"token"`
|
||||
Owner string `koanf:"owner"`
|
||||
Repo string `koanf:"repo"`
|
||||
}
|
||||
|
||||
// VoiceConfig holds LiveKit server connection and voice quality settings.
|
||||
@@ -188,7 +194,10 @@ func defaults() Config {
|
||||
LiveKitURL: "ws://localhost:7880",
|
||||
Quality: "medium",
|
||||
},
|
||||
GitHub: GitHubConfig{},
|
||||
GitHub: GitHubConfig{
|
||||
Owner: "J3vb",
|
||||
Repo: "OwnCord-releases",
|
||||
},
|
||||
EventPersistence: EventPersistenceConfig{
|
||||
Enabled: true,
|
||||
RetentionHours: 24,
|
||||
|
||||
@@ -61,14 +61,14 @@ func (q *Queries) GetEventsSince(ctx context.Context, arg GetEventsSinceParams)
|
||||
}
|
||||
|
||||
const getMaxEventSeq = `-- name: GetMaxEventSeq :one
|
||||
SELECT COALESCE(MAX(seq), 0) FROM events
|
||||
SELECT CAST(COALESCE(MAX(seq), 0) AS INTEGER) AS max_seq FROM events
|
||||
`
|
||||
|
||||
func (q *Queries) GetMaxEventSeq(ctx context.Context) (interface{}, error) {
|
||||
func (q *Queries) GetMaxEventSeq(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getMaxEventSeq)
|
||||
var coalesce interface{}
|
||||
err := row.Scan(&coalesce)
|
||||
return coalesce, err
|
||||
var max_seq int64
|
||||
err := row.Scan(&max_seq)
|
||||
return max_seq, err
|
||||
}
|
||||
|
||||
const persistEvent = `-- name: PersistEvent :exec
|
||||
|
||||
@@ -61,7 +61,7 @@ type Querier interface {
|
||||
GetEventsSince(ctx context.Context, arg GetEventsSinceParams) ([]GetEventsSinceRow, error)
|
||||
GetInvite(ctx context.Context, code string) (GetInviteRow, error)
|
||||
GetLatestMessageID(ctx context.Context, channelID int64) (interface{}, error)
|
||||
GetMaxEventSeq(ctx context.Context) (interface{}, error)
|
||||
GetMaxEventSeq(ctx context.Context) (int64, error)
|
||||
GetMessage(ctx context.Context, id int64) (Message, error)
|
||||
GetMessagesByChannel(ctx context.Context, arg GetMessagesByChannelParams) ([]GetMessagesByChannelRow, error)
|
||||
GetMessagesByChannelBeforeCursor(ctx context.Context, arg GetMessagesByChannelBeforeCursorParams) ([]GetMessagesByChannelBeforeCursorRow, error)
|
||||
|
||||
+6
-6
@@ -30,8 +30,8 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/mod v0.34.0
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/mod v0.35.0
|
||||
modernc.org/sqlite v1.48.0
|
||||
nhooyr.io/websocket v1.8.17
|
||||
)
|
||||
@@ -57,7 +57,7 @@ require (
|
||||
github.com/frostbyte73/core v0.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gammazero/deque v1.2.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
@@ -135,10 +135,10 @@ require (
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
|
||||
+14
-14
@@ -83,8 +83,8 @@ github.com/gammazero/deque v1.2.1 h1:9fnQVFCCZ9/NOc7ccTNqzoKd1tCWOqeI05/lPqFPMGQ
|
||||
github.com/gammazero/deque v1.2.1/go.mod h1:5nSFkzVm+afG9+gy0VIowlqVAW4N8zNcMne+CMQVD2g=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=
|
||||
github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -356,21 +356,21 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -385,8 +385,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -398,16 +398,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Plugins get a per-plugin namespaced KV store backed by the PluginStore
|
||||
// rows in the events/plugin schema. Capacity caps and value-size caps are
|
||||
// enforced here so a misbehaving plugin can't fill the database.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// A plugin that declares the `ui` capability ships HTML/CSS/JS assets and a
|
||||
// list of tabs. The host serves those assets at /api/v1/plugins/<name>/ui/...
|
||||
// and the Solid.js client bridge renders each tab inside a sandboxed iframe.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
//
|
||||
// Loader walks the directory, parses every plugin.json, and returns a slice
|
||||
// of foundPlugin records. The Registry then persists each into the store.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//go:build !wazero
|
||||
|
||||
// Default build stub — TOML manifest parsing is not compiled in without -tags wazero.
|
||||
|
||||
package plugin
|
||||
|
||||
// tryLoadPluginTOML always reports "not present" in the default build so the
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// directory and persists each manifest into the PluginStore so admins can see
|
||||
// what is "installed", but the .wasm files are NOT executed. Calling
|
||||
// Dispatch() in the default build returns ErrRuntimeUnavailable.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Default plugin runtime: no Wazero. Plugin manifests are still discovered,
|
||||
// persisted, and surfaced through the admin API, but `.wasm` modules are not
|
||||
// executed. To enable real WASM execution build with `-tags wazero`.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -1 +1 @@
|
||||
dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEFCQjA3OEZEOEVCRkY1RkEKUldUNjliK08vWGl3cStHamIrVHhNbWNLT3Bwb3ppeTIwdDBkQkFlaytHSWVqZkExSmFxRHZDVVoK
|
||||
dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFEMUUzM0FDNTBCMTFCQzIKUldUQ0c3RlFyRE1lSFUvK1M1Wk1PZFcwVmJMMnZLc0o3TThjSnNVZEY3NDFaVkxPekUyemJRVzAK
|
||||
@@ -564,7 +564,7 @@ func (u *Updater) verifySignatureReader(reader io.Reader, signatureText []byte,
|
||||
return fmt.Errorf("reading file for signature verification: %w", err)
|
||||
}
|
||||
|
||||
normalizedSig := []byte(strings.TrimSpace(string(signatureText)))
|
||||
normalizedSig := normalizeSignatureText(signatureText)
|
||||
var parsedSig minisign.Signature
|
||||
if err := parsedSig.UnmarshalText(normalizedSig); err != nil {
|
||||
return fmt.Errorf("invalid update signature format: %w", err)
|
||||
@@ -576,6 +576,21 @@ func (u *Updater) verifySignatureReader(reader io.Reader, signatureText []byte,
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeSignatureText returns the raw minisign signature document from
|
||||
// signatureText. `tauri signer sign` emits .sig files that are base64-wrapped
|
||||
// minisign documents (the same wrapping used for the pinned public key file);
|
||||
// raw minisign documents pass through unchanged.
|
||||
func normalizeSignatureText(signatureText []byte) []byte {
|
||||
trimmed := []byte(strings.TrimSpace(string(signatureText)))
|
||||
if bytes.HasPrefix(trimmed, []byte("untrusted comment:")) {
|
||||
return trimmed
|
||||
}
|
||||
if decoded, err := base64.StdEncoding.DecodeString(string(trimmed)); err == nil {
|
||||
return []byte(strings.TrimSpace(string(decoded)))
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func (u *Updater) serverSignaturePublicKey() (minisign.PublicKey, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(u.signingKeyText)
|
||||
if err != nil {
|
||||
|
||||
@@ -1130,3 +1130,38 @@ func (rt *rewriteTransport) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
newReq, _ := http.NewRequestWithContext(req.Context(), req.Method, newURL, req.Body)
|
||||
return http.DefaultTransport.RoundTrip(newReq)
|
||||
}
|
||||
|
||||
// TestVerifySignature_TauriBase64WrappedFormat locks in support for the .sig
|
||||
// format that `tauri signer sign` produces in the release pipeline: a
|
||||
// base64-wrapped minisign document. Raw minisign documents must keep working.
|
||||
func TestVerifySignature_TauriBase64WrappedFormat(t *testing.T) {
|
||||
u, privateKey := newSignedTestUpdater(t, "", "1.0.0")
|
||||
content := []byte("tauri wrapped signature test")
|
||||
rawSig := signTestAsset(t, privateKey, content)
|
||||
wrappedSig := []byte(base64.StdEncoding.EncodeToString(rawSig))
|
||||
|
||||
path := filepath.Join(t.TempDir(), "chatserver.exe")
|
||||
if err := os.WriteFile(path, content, 0o600); err != nil {
|
||||
t.Fatalf("writing test asset: %v", err)
|
||||
}
|
||||
|
||||
if err := u.VerifySignature(path, wrappedSig); err != nil {
|
||||
t.Errorf("base64-wrapped tauri signature should verify: %v", err)
|
||||
}
|
||||
if err := u.VerifySignature(path, rawSig); err != nil {
|
||||
t.Errorf("raw minisign signature should verify: %v", err)
|
||||
}
|
||||
// Valid base64 that decodes to garbage must fail cleanly, not verify.
|
||||
garbage := []byte(base64.StdEncoding.EncodeToString([]byte("not a signature")))
|
||||
if err := u.VerifySignature(path, garbage); err == nil {
|
||||
t.Error("garbage base64 signature should fail verification")
|
||||
}
|
||||
// Wrapped signature over different content must fail verification.
|
||||
otherPath := filepath.Join(t.TempDir(), "other.bin")
|
||||
if err := os.WriteFile(otherPath, []byte("tampered"), 0o600); err != nil {
|
||||
t.Fatalf("writing tampered asset: %v", err)
|
||||
}
|
||||
if err := u.VerifySignature(otherPath, wrappedSig); err == nil {
|
||||
t.Error("wrapped signature must not verify tampered content")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ func TestDM_ChatEdit_ParticipantCanEdit(t *testing.T) {
|
||||
hub.HandleMessageForTest(cAlice, dmChatEditMsg(msgID, "edited"))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Alice should receive the chat_edited broadcast (via broadcastToDMParticipants).
|
||||
// Alice should receive the chat_edited broadcast (via the sequenced DM event path).
|
||||
msgs := dmDrainAll(sendAlice)
|
||||
edited := dmFindMsgType(msgs, "chat_edited")
|
||||
if edited == nil {
|
||||
|
||||
@@ -34,6 +34,7 @@ func newEmitTestHub() *Hub {
|
||||
pubsub: NewPubSub(),
|
||||
replayBuf: NewEventRingBuffer(100),
|
||||
voiceKeyHolders: make(map[int64]int64),
|
||||
topicLimiter: NewTopicRateLimiter(topicRateLimitPerSecond, time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +186,7 @@ func TestEmitEvents_ExcludeSenderEvent(t *testing.T) {
|
||||
|
||||
h.EmitEvents(events)
|
||||
|
||||
// broadcastExclude is synchronous — check immediately.
|
||||
// broadcastExcludeLow is synchronous — check immediately.
|
||||
senderMsgs := drainChan(sendSender, 50*time.Millisecond)
|
||||
otherMsgs := drainChan(sendOther, 50*time.Millisecond)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// StartEventPruner runs a background goroutine that deletes events older than
|
||||
// the configured retention window. It is the bounded-storage half of the
|
||||
// event persistence design: the persister appends, the pruner trims.
|
||||
|
||||
package ws
|
||||
|
||||
import (
|
||||
|
||||
@@ -93,8 +93,9 @@ func (p *LiveKitProcess) SetProcessStoppedForTest() {
|
||||
// NewHubForTest creates a minimal Hub with no DB or limiter for webhook testing.
|
||||
func NewHubForTest() *Hub {
|
||||
return &Hub{
|
||||
clients: make(map[int64]*Client),
|
||||
pubsub: NewPubSub(),
|
||||
clients: make(map[int64]*Client),
|
||||
pubsub: NewPubSub(),
|
||||
topicLimiter: NewTopicRateLimiter(topicRateLimitPerSecond, time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-62
@@ -5,30 +5,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/owncord/server/auth"
|
||||
"github.com/owncord/server/db"
|
||||
)
|
||||
|
||||
// Rate limit windows.
|
||||
const (
|
||||
chatRateLimit = 10
|
||||
chatWindow = time.Second
|
||||
typingRateLimit = 1
|
||||
typingWindow = 3 * time.Second
|
||||
presenceRateLimit = 1
|
||||
presenceWindow = 10 * time.Second
|
||||
reactionRateLimit = 5
|
||||
reactionWindow = time.Second
|
||||
)
|
||||
|
||||
// maxMessageLen is the maximum allowed message length in runes (Unicode code points).
|
||||
const maxMessageLen = 4000
|
||||
|
||||
var sanitizer = bluemonday.StrictPolicy()
|
||||
|
||||
// HandleMessageForTest dispatches a raw WebSocket message from client c.
|
||||
// Exported so ws_test package can invoke it directly without a real connection.
|
||||
func (h *Hub) HandleMessageForTest(c *Client, raw []byte) {
|
||||
@@ -226,21 +207,11 @@ func (h *Hub) requireChannelPerm(c *Client, channelID int64, perm int64, permLab
|
||||
return false
|
||||
}
|
||||
|
||||
// broadcastExclude sends a message to all clients in the sender's channel
|
||||
// EXCEPT the sender. Unlike hub.BroadcastToChannel, messages sent via this
|
||||
// function are NOT stored in the replay ring buffer — they are ephemeral.
|
||||
// This is correct for typing indicators but would be incorrect for messages
|
||||
// that should survive reconnection replay.
|
||||
func (h *Hub) broadcastExclude(channelID, excludeUserID int64, msg []byte) {
|
||||
if channelID == 0 {
|
||||
h.pubsub.Publish(TopicGlobal, msg, excludeUserID)
|
||||
return
|
||||
}
|
||||
h.pubsub.Publish(ChannelTopic(channelID), msg, excludeUserID)
|
||||
}
|
||||
|
||||
// broadcastExcludeLow is like broadcastExclude but at low priority.
|
||||
// Used for typing indicators — dropped on overflow instead of disconnecting.
|
||||
// broadcastExcludeLow sends a message at low priority to all clients in the
|
||||
// sender's channel EXCEPT the sender. Messages sent via this function are NOT
|
||||
// stored in the replay ring buffer — they are ephemeral. This is correct for
|
||||
// typing indicators (dropped on overflow instead of disconnecting) but would
|
||||
// be incorrect for messages that should survive reconnection replay.
|
||||
func (h *Hub) broadcastExcludeLow(channelID, excludeUserID int64, msg []byte) {
|
||||
if channelID == 0 {
|
||||
h.pubsub.PublishLow(TopicGlobal, msg, excludeUserID)
|
||||
@@ -249,31 +220,3 @@ func (h *Hub) broadcastExcludeLow(channelID, excludeUserID int64, msg []byte) {
|
||||
h.pubsub.PublishLow(ChannelTopic(channelID), msg, excludeUserID)
|
||||
}
|
||||
|
||||
// broadcastToDMParticipants sends a message to all participants of a DM channel
|
||||
// while preserving DM semantics (delivery is by participant, not channel focus).
|
||||
// Unlike broadcastToDMParticipantsExclude, this path is sequenced and replayable.
|
||||
func (h *Hub) broadcastToDMParticipants(channelID int64, msg []byte) {
|
||||
participantIDs, err := h.db.GetDMParticipantIDs(channelID)
|
||||
if err != nil {
|
||||
slog.Error("broadcastToDMParticipants GetDMParticipantIDs", "err", err, "channel_id", channelID)
|
||||
return
|
||||
}
|
||||
h.sendSequencedToUsers(channelID, participantIDs, msg)
|
||||
}
|
||||
|
||||
// broadcastToDMParticipantsExclude sends a message to all participants of a DM
|
||||
// channel EXCEPT the specified user. Used for ephemeral events like typing
|
||||
// indicators where echoing back to the sender is undesirable.
|
||||
func (h *Hub) broadcastToDMParticipantsExclude(channelID, excludeUserID int64, msg []byte) {
|
||||
participantIDs, err := h.db.GetDMParticipantIDs(channelID)
|
||||
if err != nil {
|
||||
slog.Error("broadcastToDMParticipantsExclude GetDMParticipantIDs", "err", err, "channel_id", channelID)
|
||||
return
|
||||
}
|
||||
for _, pid := range participantIDs {
|
||||
if pid == excludeUserID {
|
||||
continue
|
||||
}
|
||||
h.SendToUser(pid, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// plugin returns a Reply, it is sent only to the invoking client (ephemeral).
|
||||
// If the plugin returns a Broadcast string, it is broadcast to the channel
|
||||
// only after verifying the invoking client holds SEND_MESSAGES permission.
|
||||
|
||||
package ws
|
||||
|
||||
import (
|
||||
|
||||
@@ -1696,7 +1696,7 @@ func TestTyping_RateLimited_SilentlyDropped(t *testing.T) {
|
||||
|
||||
// TestBroadcastExclude_SendsToOthersNotSelf verifies that broadcastExclude
|
||||
// delivers to all channel members except the excluded user.
|
||||
// This is exercised indirectly via typing_start (which calls broadcastExclude).
|
||||
// This is exercised indirectly via typing_start (which calls broadcastExcludeLow).
|
||||
func TestBroadcastExclude_SendsToOthersNotSelf(t *testing.T) {
|
||||
hub, database := newHandlerHub(t)
|
||||
chID := seedTestChannel(t, database, "excl-chan1")
|
||||
|
||||
+3
-20
@@ -555,26 +555,9 @@ func (h *Hub) BroadcastToAllLow(msg []byte) {
|
||||
h.pubsub.PublishGlobalLow(msg)
|
||||
}
|
||||
|
||||
// sendSequencedToUsers stamps msg with a monotonic seq, stores it in the replay
|
||||
// buffer under channelID, and fanouts the wrapped payload to the provided users.
|
||||
func (h *Hub) sendSequencedToUsers(channelID int64, userIDs []int64, msg []byte) {
|
||||
h.seqMu.Lock()
|
||||
defer h.seqMu.Unlock()
|
||||
|
||||
seq := h.nextSeq()
|
||||
wrapped := wrapWithSeq(msg, seq)
|
||||
|
||||
// Store DM event for reconnect replay; filtering is channel-based and uses
|
||||
// allowed channel IDs computed at auth time (including open DMs).
|
||||
h.replayBuf.Push(seq, channelID, wrapped)
|
||||
h.persistEvent(seq, channelID, wrapped)
|
||||
|
||||
for _, userID := range userIDs {
|
||||
h.SendToUser(userID, wrapped)
|
||||
}
|
||||
}
|
||||
|
||||
// sendSequencedToUsersHigh is like sendSequencedToUsers but uses high-priority delivery.
|
||||
// sendSequencedToUsersHigh stamps msg with a monotonic seq, stores it in the
|
||||
// replay buffer under channelID, and fans the wrapped payload out to the
|
||||
// provided users with high-priority delivery.
|
||||
func (h *Hub) sendSequencedToUsersHigh(channelID int64, userIDs []int64, msg []byte) {
|
||||
h.seqMu.Lock()
|
||||
defer h.seqMu.Unlock()
|
||||
|
||||
@@ -489,7 +489,12 @@ func TestHub_ConcurrentRegisterUnregister(t *testing.T) {
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// The hub loop drains register/unregister asynchronously; poll instead of
|
||||
// a fixed sleep, which flakes under -race on slow runners.
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for hub.ClientCount() != 0 && time.Now().Before(deadline) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if hub.ClientCount() != 0 {
|
||||
t.Errorf("expected 0 clients after concurrent churn, got %d", hub.ClientCount())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package ws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -217,16 +216,3 @@ func (ps *PubSub) TopicsForClient(userID int64) []Topic {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// debugDump logs the current subscription state. For development use only.
|
||||
func (ps *PubSub) debugDump() {
|
||||
ps.mu.RLock()
|
||||
defer ps.mu.RUnlock()
|
||||
for topic, subs := range ps.topics {
|
||||
ids := make([]int64, 0, len(subs))
|
||||
for uid := range subs {
|
||||
ids = append(ids, uid)
|
||||
}
|
||||
slog.Debug("pubsub: topic", "topic", string(topic), "subscribers", ids)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -411,9 +411,13 @@ func readPump(ctx context.Context, conn *websocket.Conn, hub *Hub, c *Client) {
|
||||
voiceChID := c.getVoiceChID()
|
||||
replaced := hub.unregisterNow(c)
|
||||
if c.user != nil {
|
||||
// Always clean up voice state — LeaveVoiceChannelIfMatch uses a
|
||||
// join_token guard so it won't remove a replacement client's session.
|
||||
if voiceChID != 0 {
|
||||
// Clean up voice state only when this was the user's final
|
||||
// connection. A replacement connection owns the (transferred)
|
||||
// voice session, and the join_token guard cannot tell the
|
||||
// difference — the transfer keeps the same joined_at — so
|
||||
// cleaning here would delete the replacement's DB row whenever
|
||||
// teardown snapshots voiceChID before the transfer zeroes it.
|
||||
if voiceChID != 0 && !replaced {
|
||||
hub.handleVoiceLeave(ctx, c)
|
||||
}
|
||||
c.mu.Lock()
|
||||
|
||||
@@ -5,6 +5,29 @@
|
||||
|
||||
---
|
||||
|
||||
## Finding closure status (maintained; last updated 2026-07-18)
|
||||
|
||||
Every CRITICAL/HIGH below must end with a closing commit link or an explicit
|
||||
mitigation before the beta gate. Standing rule: any plugin CRITICAL still
|
||||
OPEN at the beta gate → plugins ship default-disabled (they already default
|
||||
to `plugins.enabled: false`).
|
||||
|
||||
| # | Sev | Finding | Status |
|
||||
|---|-----|---------|--------|
|
||||
| 1 | CRITICAL | Plugin `invokeCommand` has no timeout | IN PROGRESS — CPU budget added on `fix/security-hardening-review`; regression fix (module bricking, W1-1) required before merge |
|
||||
| 2 | CRITICAL | Plugin storage has no per-plugin key isolation | OPEN — verify/close in P3 |
|
||||
| 3 | CRITICAL | Plugin per-command ACL missing (auto-registration) | OPEN — verify/close in P3 |
|
||||
| 4 | CRITICAL | No rate limit on event delivery to plugins | OPEN — verify/close in P3 |
|
||||
| 5 | CRITICAL | Plugin HTTP capability allows data exfiltration to allowlisted hosts | OPEN — partially mitigated by SSRF hardening + allowlist; document residual risk in P3 |
|
||||
| 6 | HIGH | `Server/store/` untested | SUPERSEDED — `store/` package is being removed in P4 (single data layer); tests move to in-memory SQLite |
|
||||
| 7 | HIGH | Client `src/lib`/`src/stores` <10% unit coverage | CLOSED since audit — large vitest suite exists (113 files); suite health tracked in P2 |
|
||||
| 8 | HIGH | Unpinned critical npm packages | OPEN — review in P2 |
|
||||
| 9 | MEDIUM | auth_handler bypasses service layer | OPEN — P4 consolidation candidate |
|
||||
| 10 | MEDIUM | Audit-trail write failures silently ignored | OPEN — cheap fix, fold into P1 |
|
||||
| 11 | MEDIUM | E2E not in CI / no .nvmrc | IN PROGRESS — nightly non-blocking e2e job planned in P2 |
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Architecture](#1-architecture)
|
||||
2. [Code Quality](#2-code-quality)
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# Phase A Implementation Status
|
||||
|
||||
**Branch:** `claude/phase-a-foundation-plan-eUys1`
|
||||
|
||||
This document tracks what shipped during Phase A (Foundation) and what is still pending. The original Phase A design brief (`phase-a-foundation.md`) was removed from the repo root on `dev`; this status doc preserves the actionable follow-up work.
|
||||
|
||||
## Done
|
||||
|
||||
- **Step 1 — Service layer + permission cache.** `Server/service/` contains 12 service files covering message, channel, permission, moderation, user, dm, block, invite, voice. Services depend on `store.Store`, not `*db.DB`. PermissionService maintains a per-user cache. REST and WS handlers (auth, channel, dm, profile, upload, chat, reaction, presence, voice) are migrated. Service tests live in `service/message_test.go` and `service/permission_test.go`.
|
||||
- **Step 2 — sqlc adoption.** `Server/sqlc.yaml` configures both the SQLite engine (queries in `Server/db/queries/sqlite/`, generated output in `Server/db/dbgen/`) and the PostgreSQL engine (queries in `Server/db/queries/postgres/`, output in `Server/db/pgdbgen/`). sqlc v1.30.0 is pinned in `Server/sqlc.version`. `Server/Makefile` exposes `sqlc-install`, `sqlc-generate`, `sqlc-verify` targets covering both engines. 14 SQL query files per engine cover all DB domains; the SQLite `dbgen` package is committed, the PostgreSQL `pgdbgen` package will be generated on the next `make sqlc-generate` run. FTS search queries remain hand-written; transactional multi-step operations are unchanged.
|
||||
- **Step 3 (partial) — Store interface + SQLiteStore + MemStore.** `Server/store/store.go` defines the full Store interface (12 sub-interfaces). `Server/store/sqlite.go` wraps `*db.DB`. `Server/store/memstore.go` provides an in-memory implementation used by service tests.
|
||||
- **Step 4 — Logging consolidation.** OwnCord code uses `log/slog` exclusively. `go.uber.org/zap` and `rs/zerolog` appear only as transitive dependencies of livekit and are not imported by any OwnCord `.go` file. The plan's "pick one and find-and-replace" item is satisfied.
|
||||
- **Step 5 — Pub/sub broadcast model.** `Server/ws/pubsub.go`, `topic_rate_limiter.go`, `ringbuffer.go`, and the three-tier priority queue (commit `7ccc93f`) implement the topic-based broadcast model with global rate limits, backpressure, and priority tiers.
|
||||
|
||||
## Pending
|
||||
|
||||
### Step 3 — PostgreSQL backend (final wiring)
|
||||
|
||||
Scaffolding has landed on this branch:
|
||||
|
||||
- `Server/migrations/postgres/001_initial_schema.sql` — consolidated postgres schema with `tsvector` + GIN full-text search, `CITEXT` usernames, native CHECK constraints replacing SQLite triggers, and seeded role/setting rows.
|
||||
- `Server/migrations/postgres/migrations.go` — embed FS for the postgres migration set.
|
||||
- `Server/db/queries/postgres/*.sql` — 14 query files (users, sessions, roles, invites, channels, messages, reactions, voice, attachments, admin, dm, blocks, lockouts, profile) translated to postgres dialect: `$N` placeholders, `NOW()`/native `TIMESTAMPTZ`, `TRUE`/`FALSE` for BOOLEAN columns, `ON CONFLICT ... DO UPDATE` for upserts, `RETURNING id` for creates (postgres has no `LastInsertId`), `:execrows` for mutations that need rows-affected.
|
||||
- `Server/sqlc.yaml` — second engine entry (`engine: "postgresql"`, `sql_package: "pgx/v5"`) generating into the `pgdbgen` package under `Server/db/pgdbgen/`.
|
||||
- `Server/Makefile` — `sqlc-generate` and `sqlc-verify` cover both engines.
|
||||
- `Server/store/postgres.go` — `PostgresStore` type behind the `//go:build postgres` tag, implementing the full `store.Store` interface. Connection lifecycle (`OpenPostgres`, `Close`, `SQLDb`, `WithTx`) is fully implemented using `database/sql` + the pgx stdlib driver. Query methods are stubs that return `ErrPostgresNotImplemented`, waiting for the `pgdbgen` querier to land so they can be replaced with wrappers around generated code. The build tag keeps pgx out of the default build; operators who want to enable postgres run `go get github.com/jackc/pgx/v5 && go build -tags postgres ./...`.
|
||||
- `Server/config/config.go` — `DatabaseConfig` extended with `Type`, `Host`, `Port`, `User`, `Password`, `Name`, `SSLMode`, `MaxConns`. Defaults set to `type: "sqlite"` so existing operators are unaffected.
|
||||
- `Server/main.go` — explicit dispatch on `database.type`. Selecting `postgres` produces a clear startup error pointing at the remaining work, instead of silently falling through to sqlite.
|
||||
|
||||
What still needs to land for postgres to be runnable:
|
||||
|
||||
1. Add `github.com/jackc/pgx/v5` to `go.mod` and run `go mod tidy`. This happens naturally the first time an operator runs `go get github.com/jackc/pgx/v5` — the package only needs to be in the module graph when building with `-tags postgres`.
|
||||
2. Run `make sqlc-generate` to produce `Server/db/pgdbgen/` from the committed query files. This requires either network access to download sqlc or a pre-installed `sqlc` binary at v1.30.0.
|
||||
3. Replace the stub query methods in `Server/store/postgres.go` with real implementations that wrap the generated `pgdbgen` querier. The connection lifecycle and interface assertion are already in place; each stub carries the same method signature as the sqlite version, so the migration is mechanical. Where the schema produces different Go types than sqlite (e.g. `bool` vs `int64` for boolean columns, `time.Time` vs `string` for timestamps), the store wrapper performs the conversion so services see a uniform API.
|
||||
4. Refactor `main.go` and `Server/api/router.go` to thread `store.Store` through the boundary instead of `*db.DB`. Today the router constructs `dbstore.NewSQLiteStore(database)` inline, so services are store-aware but everything else still consumes `*db.DB` directly. The store-everywhere migration is the gating step before either backend can be swapped at runtime.
|
||||
5. FTS dispatch in `MessageStore.SearchMessages` / `SearchMessagesInChannels` — sqlite uses `MATCH` against the FTS5 virtual table, postgres uses `@@ to_tsquery(...)` against the `messages.fts` tsvector column. Both remain hand-written (outside the sqlc-generated set) for their respective backends.
|
||||
6. CI matrix to run the test suite against both backends.
|
||||
|
||||
### One-way sqlite → postgres data migration
|
||||
|
||||
Operators who start a community on the default sqlite backend and later outgrow it must be able to carry their history over. The migration must be forward-only (once postgres is selected, the server stays on postgres unless the operator deliberately wipes the postgres database and re-initialises), both to keep the contract simple and to avoid the support burden of "I reverted to sqlite yesterday and now my data is gone".
|
||||
|
||||
Proposed design:
|
||||
|
||||
- A one-shot CLI flag, e.g. `chatserver --migrate-to-postgres`, that exits after completion. No continuous sync.
|
||||
- Pre-flight checks: `database.type` in config is already `postgres`; postgres connection succeeds; the target postgres database contains no rows in `users` (or equivalent sentinel) — if it does, refuse to run, printing the exact row count so the operator can confirm they meant to target this database.
|
||||
- Open sqlite read-only alongside postgres. Begin one postgres transaction for the entire migration so partial failures roll back cleanly.
|
||||
- Copy rows in foreign-key order (`roles` → `users` → `channels` → `channel_overrides` → `messages` → `reactions` → `attachments` → `invites` → `sessions` → `voice_states` → `dm_participants` → `dm_open_state` → `read_states` → `audit_log` → `user_blocks` → `rate_lockouts` → `settings` → `emoji` → `sounds`). Disable the `trg_messages_fts_update` trigger for the bulk copy and re-populate `messages.fts` in a single `UPDATE messages SET fts = to_tsvector('simple', content)` at the end, so FTS doesn't fire per row.
|
||||
- Convert types at the boundary: sqlite RFC3339 strings → postgres `TIMESTAMPTZ` via `time.Parse`, sqlite `INTEGER` boolean (0/1) → postgres `BOOLEAN`. Drop the preserved `id` values straight through since both schemas are `BIGINT`-compatible.
|
||||
- After copying, reset every postgres sequence with `SELECT setval('<table>_id_seq', COALESCE(MAX(id), 1)) FROM <table>` so subsequent inserts don't collide with migrated IDs.
|
||||
- Write a marker row into `settings`: `migrated_from_sqlite = <RFC3339 timestamp>`. On subsequent startups with `type: postgres`, the marker's presence (or simply the presence of a non-empty `users` table) is the signal that the migration has already run and must not be repeated.
|
||||
- Uploaded files on disk (`data/uploads/`) are not touched — the migration only moves database rows. Attachments reference filenames, not blobs, so the filesystem copy is a separate `cp -a data/uploads old-host:/new-path` step the operator does out-of-band.
|
||||
- **No reverse migration.** The code does not include a postgres → sqlite path. If an operator wants to return to sqlite, they stop the server, restore a sqlite backup, edit `config.yaml` back to `type: sqlite`, and start fresh. This is deliberately inconvenient.
|
||||
|
||||
This feature is gated on PostgresStore's query methods being real (pending item 3). The migration implementation itself lives in a new `Server/migrate/sqlite_to_postgres.go` file and is invoked from `main.go` before the normal startup path.
|
||||
|
||||
## Verification status
|
||||
|
||||
`go build ./...` and `go test ./...` were not run in the session that produced this branch — the development environment lacked network access to fetch the Go 1.25.0 toolchain required by `go.mod`. Manual audit of the touched Go files (the `go-build-check` skill) found no compile errors. The branch should be verified locally by the next operator before merge.
|
||||
|
||||
## Actionable TODOs
|
||||
|
||||
Scannable checklist extracted from the prose above. Work top-to-bottom; most items unblock the ones below them.
|
||||
|
||||
### Verification (do first, cheap)
|
||||
|
||||
- [ ] Run `go build ./...` in `Server/` to confirm the branch compiles
|
||||
- [ ] Run `go test ./...` in `Server/` and fix any regressions
|
||||
- [ ] Run `go build -tags postgres ./...` after adding pgx (below) to verify `store/postgres.go` compiles under the postgres tag
|
||||
|
||||
### Postgres enablement (Step 3 final wiring)
|
||||
|
||||
- [ ] `cd Server && go get github.com/jackc/pgx/v5 && go mod tidy` — add pgx to the module graph
|
||||
- [ ] `make sqlc-generate` — produce `Server/db/pgdbgen/` from the committed `db/queries/postgres/*.sql`
|
||||
- [ ] Commit the generated `Server/db/pgdbgen/` output
|
||||
- [ ] Replace each stub in `Server/store/postgres.go` with a real implementation wrapping `pgdbgen`; keep the `//go:build postgres` tag. Expect per-method type conversion (sqlite `string` timestamps vs postgres `time.Time`, sqlite `int64` bools vs postgres `bool`)
|
||||
- [ ] Implement FTS dispatch: `SearchMessages` and `SearchMessagesInChannels` currently use sqlite `MATCH`; add a postgres branch using `@@ to_tsquery(...)` against `messages.fts`
|
||||
- [ ] Remove `ErrPostgresNotImplemented` once every method is real
|
||||
|
||||
### Store-everywhere boundary refactor (unblocks runtime backend selection)
|
||||
|
||||
- [ ] Audit every `*db.DB` parameter in `Server/api/` and `Server/ws/`; replace with `store.Store` where possible, using `store.Store.SQLDb()` at the leaves that truly need `*sql.DB` (backups, migrations)
|
||||
- [ ] Update `Server/api/router.go`'s `NewRouter` signature: take `store.Store` instead of `*db.DB`
|
||||
- [ ] Update every `Mount*Routes(r, database, …)` call site to pass the store
|
||||
- [ ] Update `ws.NewHub` and `auth.NewPersistentRateLimiter` to accept `store.Store`
|
||||
- [ ] Update `Server/admin/handler.go` (`admin.NewHandler`) the same way
|
||||
- [ ] Update `Server/main.go` to construct the store via a factory, `store.Open(&cfg.Database)`, that dispatches on `cfg.Database.Type`
|
||||
- [ ] Delete the explicit postgres error in `main.go`'s switch — selecting postgres should Just Work once PostgresStore is real
|
||||
- [ ] Fix all handler tests that construct handlers from `*db.DB` — they now take `store.Store` (use `store.MemStore` for unit tests)
|
||||
|
||||
### Data migration (one-way sqlite → postgres)
|
||||
|
||||
- [ ] Create `Server/migrate/sqlite_to_postgres.go`
|
||||
- [ ] Add `--migrate-to-postgres` CLI flag to `main.go`, parsed before normal startup
|
||||
- [ ] Pre-flight: verify `database.type == postgres`, verify target postgres `users` table is empty, refuse with exact row count if not
|
||||
- [ ] Implement bulk copy in FK order: `roles → users → channels → channel_overrides → messages → reactions → attachments → invites → sessions → voice_states → dm_participants → dm_open_state → read_states → audit_log → user_blocks → rate_lockouts → settings → emoji → sounds`
|
||||
- [ ] Disable `trg_messages_fts_update` during bulk message copy; repopulate `messages.fts` in one statement at the end
|
||||
- [ ] Convert types at the boundary: `time.Parse(time.RFC3339, …)` for timestamps, `int != 0` for booleans
|
||||
- [ ] Reset every `<table>_id_seq` with `SELECT setval(…, COALESCE(MAX(id), 1))` after copy
|
||||
- [ ] Write marker row: `INSERT INTO settings (key, value) VALUES ('migrated_from_sqlite', NOW()::text)`
|
||||
- [ ] Wrap everything in one postgres transaction so partial failures roll back
|
||||
- [ ] **Do NOT implement a reverse migration** — documented as deliberately unavailable
|
||||
|
||||
### CI
|
||||
|
||||
- [ ] Add a postgres job to `.github/workflows/*.yml` that spins up a postgres service container and runs `go test -tags postgres ./...`
|
||||
- [ ] Keep the existing sqlite job unchanged
|
||||
- [ ] Add a `sqlc-verify` job that runs `make sqlc-verify` to catch stale generated code
|
||||
|
||||
### Hygiene (optional, do whenever)
|
||||
|
||||
- [ ] Expand `Server/service/*_test.go` coverage — currently only `message_test.go` and `permission_test.go` exist; add channel, dm, voice, invite, moderation tests using `store.MemStore`
|
||||
- [ ] Split the postgres migration file if it grows beyond ~300 lines; for now it's consolidated intentionally
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Status:** design only, not implemented
|
||||
**Owner:** TBD
|
||||
**Tracks:** PHASE_D_PARITY_TODO.md item #1
|
||||
**Tracks:** deferred feature backlog (post-beta; see CHANGELOG "Deferred work")
|
||||
**Estimated effort:** 1–2 weeks of focused work
|
||||
|
||||
## Why
|
||||
|
||||
@@ -66,6 +66,8 @@ Configuration is loaded in three layers (later layers override earlier ones):
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `github.token` | string | `""` | Optional GitHub API token for higher rate limits on update checks (5000 req/hr vs 60) |
|
||||
| `github.owner` | string | `"J3vb"` | Owner of the GitHub repository server and client updates are fetched from |
|
||||
| `github.repo` | string | `"OwnCord-releases"` | Public releases repository. Must stay publicly readable — both the server self-update and the client auto-update chain fetch release assets from it |
|
||||
|
||||
### Event Persistence (`event_persistence`)
|
||||
|
||||
@@ -178,6 +180,8 @@ voice:
|
||||
|
||||
github:
|
||||
token: "" # optional GitHub PAT for update check rate limits
|
||||
owner: "J3vb" # update source repo owner
|
||||
repo: "OwnCord-releases" # public releases repo (binaries + source snapshots)
|
||||
|
||||
# Event persistence (tiered reconnect replay)
|
||||
event_persistence:
|
||||
|
||||
Reference in New Issue
Block a user