feat(dev): switch dev tokens to a real admin account with configurable duration
Requested: dev-token.sh/.ps1 tokens should last 3h by default, with an argument to override the duration. Server-verified finding: STALWART_RECOVERY_ADMIN (the break-glass account docker-compose.yml and the scripts used) always issues OAuth tokens with a fixed 1h expiry, regardless of the server's configured accessTokenExpiry — confirmed against the live container, including after changing the setting and restarting. Confirmed x:ApiKey objects, by contrast, support an arbitrary expiresAt set per request, and their secret works directly as a bearer token. Add scripts/dev-server-init.sh (+ .ps1): a one-time, idempotent setup step that completes the server's bootstrap wizard (default domain, no TLS certificate request), creates a real "devadmin" admin account, and sets the server's default OAuth token lifetime to 3h. Rework dev-token.sh/.ps1 to authenticate as devadmin and create an x:ApiKey with a caller-supplied expiry (`dev-token.sh 1800` for 30 minutes, defaults to 10800s/3h) instead of running the OAuth PKCE flow against the recovery account. Verified end-to-end against a fresh container, including a real browser session against the running WebUI. Also includes an incidental package-lock.json sync (was still pinned to v1.0.8 / stale dependency ranges from before the upstream merge).
This commit is contained in:
@@ -26,6 +26,8 @@ dist-ssr
|
||||
scripts/*
|
||||
!scripts/dev-token.ps1
|
||||
!scripts/dev-token.sh
|
||||
!scripts/dev-server-init.ps1
|
||||
!scripts/dev-server-init.sh
|
||||
*.md
|
||||
!README.md
|
||||
!CHANGELOG.md
|
||||
|
||||
@@ -9,7 +9,8 @@ Community fork of [stalwartlabs/webui](https://github.com/stalwartlabs/webui), a
|
||||
## Commands
|
||||
|
||||
- `npm run dev:server` - Start a local disposable Stalwart test server (Docker); `npm run dev:server:down` to stop it
|
||||
- `bash scripts/dev-token.sh` (or `pwsh ./scripts/dev-token.ps1` on Windows) - Get a dev access token from that server
|
||||
- `bash scripts/dev-server-init.sh` (or `pwsh ./scripts/dev-server-init.ps1`) - One-time setup of that server (bootstrap, dev admin account, 3h default token lifetime); idempotent
|
||||
- `bash scripts/dev-token.sh [duration_seconds]` (or `pwsh ./scripts/dev-token.ps1 [-DurationSeconds N]`) - Get a dev access token from that server, 3h by default
|
||||
- `npm run dev` - Dev server (proxies `/api` and `/jmap` to the test server)
|
||||
- `npm run typecheck` - `tsc --noEmit`
|
||||
- `npm run lint` - ESLint
|
||||
|
||||
+48
-18
@@ -39,27 +39,55 @@ npm run dev:server:down # stop it (add `-v` via `docker compose down -v` to al
|
||||
The server takes a couple of seconds to come up; `docker compose logs stalwart`
|
||||
will show `Network listener started ... localPort = 8080` once it's ready.
|
||||
|
||||
## 2. Get an access token
|
||||
## 2. Initialize the server (first time only)
|
||||
|
||||
The WebUI normally authenticates through an OAuth flow in the browser, but
|
||||
for local development it's simpler to skip that and use a bearer token
|
||||
directly via `VITE_ACCESS_TOKEN` (see `.env.development`).
|
||||
The container starts in Stalwart's bootstrap mode, which only allows
|
||||
signing in as the break-glass `STALWART_RECOVERY_ADMIN` account — real
|
||||
accounts and most settings aren't usable yet. Run once per fresh volume:
|
||||
|
||||
```bash
|
||||
# Windows / PowerShell
|
||||
pwsh ./scripts/dev-token.ps1
|
||||
pwsh ./scripts/dev-server-init.ps1
|
||||
|
||||
# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
|
||||
bash ./scripts/dev-token.sh
|
||||
bash ./scripts/dev-server-init.sh
|
||||
```
|
||||
|
||||
Both scripts log in as the dev container's admin account, run the full
|
||||
OAuth PKCE flow against it, and write the resulting token to
|
||||
`.env.development.local` (gitignored, never committed). Tokens expire
|
||||
after 1 hour — re-run the script and restart `npm run dev` if the UI
|
||||
starts returning 401s.
|
||||
This completes the bootstrap wizard (default domain `example.org`, no TLS
|
||||
certificate request — safe for local/offline use), creates a real
|
||||
`devadmin@example.org` admin account, and sets the server's default OAuth
|
||||
access token lifetime to 3 hours. It's idempotent — safe to re-run, it
|
||||
no-ops once the server is already bootstrapped. You only need to re-run it
|
||||
after `docker compose down -v` (which wipes the volumes).
|
||||
|
||||
## 3. Run the WebUI
|
||||
## 3. Get an access token
|
||||
|
||||
For local development it's simpler to skip interactive login and use a
|
||||
bearer token directly via `VITE_ACCESS_TOKEN` (see `.env.development`).
|
||||
|
||||
```bash
|
||||
# Windows / PowerShell
|
||||
pwsh ./scripts/dev-token.ps1 # 3 hour token (server default)
|
||||
pwsh ./scripts/dev-token.ps1 -DurationSeconds 1800 # custom duration (30 min)
|
||||
|
||||
# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
|
||||
bash ./scripts/dev-token.sh # 3 hour token
|
||||
bash ./scripts/dev-token.sh 1800 # custom duration (30 min)
|
||||
```
|
||||
|
||||
Both scripts authenticate as the `devadmin` account created in step 2 and
|
||||
create a Stalwart API key with the requested expiry (default 3 hours,
|
||||
overridable per invocation — this is a genuine per-request duration, not
|
||||
a global setting), then write its secret to `.env.development.local`
|
||||
(gitignored, never committed) as `VITE_ACCESS_TOKEN`. Re-run the script
|
||||
and restart `npm run dev` once the token expires (the UI starts returning
|
||||
401s).
|
||||
|
||||
The `STALWART_RECOVERY_ADMIN` account is intentionally not used here: it's
|
||||
a break-glass credential and its tokens always expire in a fixed 1 hour
|
||||
regardless of server configuration, so it can't honor a custom duration.
|
||||
|
||||
## 4. Run the WebUI
|
||||
|
||||
```bash
|
||||
npm install # first time only
|
||||
@@ -69,7 +97,7 @@ npm run dev
|
||||
Open `http://localhost:5173`. You should land directly in the admin panel
|
||||
(no login screen) since `VITE_ACCESS_TOKEN` is set.
|
||||
|
||||
## 4. Verify your change
|
||||
## 5. Verify your change
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
@@ -91,15 +119,17 @@ behavior):
|
||||
npm run dev:server:down
|
||||
docker compose down -v # also removes the stalwart-etc/stalwart-data volumes
|
||||
npm run dev:server
|
||||
bash ./scripts/dev-server-init.sh # re-run: fresh volume needs bootstrapping again
|
||||
```
|
||||
|
||||
## Notes for AI agents
|
||||
|
||||
- This whole workflow (steps 1–3) is scriptable end-to-end without a
|
||||
browser: `npm run dev:server`, then `bash scripts/dev-token.sh`, then
|
||||
the app is reachable at `http://localhost:5173` with
|
||||
`VITE_ACCESS_TOKEN` already set. Verify backend connectivity directly
|
||||
with `curl`, e.g. `curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/jmap/session`.
|
||||
- This whole workflow (steps 1–4) is scriptable end-to-end without a
|
||||
browser: `npm run dev:server`, then `bash scripts/dev-server-init.sh`
|
||||
(first time only), then `bash scripts/dev-token.sh`, then the app is
|
||||
reachable at `http://localhost:5173` with `VITE_ACCESS_TOKEN` already
|
||||
set. Verify backend connectivity directly with `curl`, e.g.
|
||||
`curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/jmap/session`.
|
||||
- Read [AGENTS.md](AGENTS.md) before touching anything under `src/` —
|
||||
the schema-fidelity rule applies to all development, local test server
|
||||
or not.
|
||||
|
||||
+3
-1
@@ -22,7 +22,9 @@ services:
|
||||
- stalwart-data:/var/lib/stalwart
|
||||
environment:
|
||||
# Disposable local dev credentials — do not reuse for anything real.
|
||||
# Matches scripts/dev-token.ps1's defaults.
|
||||
# Break-glass admin, used only by scripts/dev-server-init.sh (its
|
||||
# tokens always expire in 1h regardless of server config, so it's
|
||||
# not used for day-to-day dev tokens — see scripts/dev-token.sh).
|
||||
STALWART_RECOVERY_ADMIN: "admin@example.org:c8321iEscHDy0GWV"
|
||||
|
||||
volumes:
|
||||
|
||||
Generated
+404
-97
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "stalwart-webui-fork",
|
||||
"version": "1.0.8",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stalwart-webui-fork",
|
||||
"version": "1.0.8",
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"@daypicker/react": "^10.0.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.23",
|
||||
@@ -28,7 +28,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"i18next": "^26.3.6",
|
||||
"lucide-react": "^1.27.0",
|
||||
"lucide-react": "1.28.0",
|
||||
"otpauth": "^9.5.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.2.8",
|
||||
@@ -46,7 +46,7 @@
|
||||
"@types/node": "^26.1.2",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.4",
|
||||
"@vitejs/plugin-react": "6.0.5",
|
||||
"eslint": "^10.8.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
@@ -57,7 +57,7 @@
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.65.0",
|
||||
"vite": "^8.1.5",
|
||||
"vite": "8.2.0",
|
||||
"vitest": "^4.1.10"
|
||||
}
|
||||
},
|
||||
@@ -644,22 +644,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
||||
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz",
|
||||
"integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
"@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3",
|
||||
"@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
@@ -675,9 +678,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.139.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
|
||||
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
|
||||
"version": "0.142.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz",
|
||||
"integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -1661,9 +1664,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz",
|
||||
"integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1678,9 +1681,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz",
|
||||
"integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1695,9 +1698,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz",
|
||||
"integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1712,9 +1715,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz",
|
||||
"integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1729,9 +1732,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
|
||||
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz",
|
||||
"integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1746,9 +1749,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz",
|
||||
"integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1766,9 +1769,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz",
|
||||
"integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1786,9 +1789,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz",
|
||||
"integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -1806,9 +1809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz",
|
||||
"integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -1826,9 +1829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz",
|
||||
"integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1846,9 +1849,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz",
|
||||
"integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1866,9 +1869,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz",
|
||||
"integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1883,28 +1886,59 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
|
||||
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz",
|
||||
"integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@napi-rs/wasm-runtime": "^1.1.6"
|
||||
"@emnapi/core": "2.0.0-alpha.3",
|
||||
"@emnapi/runtime": "2.0.0-alpha.3",
|
||||
"@napi-rs/wasm-runtime": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "2.0.0-alpha.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz",
|
||||
"integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "2.0.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "2.0.0-alpha.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz",
|
||||
"integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz",
|
||||
"integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz",
|
||||
"integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1919,9 +1953,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz",
|
||||
"integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2708,9 +2742,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.4.tgz",
|
||||
"integrity": "sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg==",
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz",
|
||||
"integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4609,9 +4643,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.27.0.tgz",
|
||||
"integrity": "sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw==",
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.28.0.tgz",
|
||||
"integrity": "sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
@@ -5872,13 +5906,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz",
|
||||
"integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.139.0",
|
||||
"@oxc-project/types": "=0.142.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -5888,21 +5922,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-x64": "1.1.5",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.5",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.5",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.5",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.5",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.5",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||
"@rolldown/binding-android-arm64": "1.2.1",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.1",
|
||||
"@rolldown/binding-darwin-x64": "1.2.1",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.1",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.1",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.1",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.1",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.1",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.1",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.1",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.1",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.1",
|
||||
"@rolldown/binding-wasm32-wasi": "1.2.1",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.1",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
@@ -6463,16 +6497,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
|
||||
"integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz",
|
||||
"integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.5",
|
||||
"postcss": "^8.5.17",
|
||||
"rolldown": "~1.1.5",
|
||||
"postcss": "^8.5.23",
|
||||
"rolldown": "~1.2.0",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
@@ -6489,7 +6523,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.3.0",
|
||||
"@vitejs/devtools": "^0.4.0",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
@@ -6540,6 +6574,279 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
|
||||
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.33.0",
|
||||
"lightningcss-darwin-arm64": "1.33.0",
|
||||
"lightningcss-darwin-x64": "1.33.0",
|
||||
"lightningcss-freebsd-x64": "1.33.0",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.33.0",
|
||||
"lightningcss-linux-arm64-gnu": "1.33.0",
|
||||
"lightningcss-linux-arm64-musl": "1.33.0",
|
||||
"lightningcss-linux-x64-gnu": "1.33.0",
|
||||
"lightningcss-linux-x64-musl": "1.33.0",
|
||||
"lightningcss-win32-arm64-msvc": "1.33.0",
|
||||
"lightningcss-win32-x64-msvc": "1.33.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
|
||||
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
|
||||
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
|
||||
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
|
||||
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
|
||||
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
|
||||
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
|
||||
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
|
||||
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
|
||||
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
|
||||
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
|
||||
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
One-time setup for the disposable Stalwart test server from
|
||||
docker-compose.yml: completes the bootstrap wizard, creates a real
|
||||
"devadmin" account, and sets the default OAuth access token lifetime.
|
||||
|
||||
.DESCRIPTION
|
||||
Local development only. Requires the dev container to be running
|
||||
('npm run dev:server'). The STALWART_RECOVERY_ADMIN account is
|
||||
break-glass only and always issues fixed 1h OAuth tokens regardless of
|
||||
server config, so dev-token.ps1 authenticates as "devadmin" instead,
|
||||
created here. Idempotent: safe to re-run; does nothing if the server
|
||||
already left bootstrap mode.
|
||||
#>
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://localhost:8080"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$RecoveryAccount = "admin@example.org"
|
||||
$RecoverySecret = "c8321iEscHDy0GWV"
|
||||
$DevDomain = "example.org"
|
||||
$DevHostname = "mail.example.org"
|
||||
$DevAdminName = "devadmin"
|
||||
$DevAdminSecret = "DevAdminPass123!"
|
||||
$DefaultTokenExpiryMs = 10800000 # 3 hours
|
||||
|
||||
$recoveryCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($RecoveryAccount):$($RecoverySecret)"))
|
||||
$authHeader = @{ Authorization = "Basic $recoveryCreds" }
|
||||
|
||||
function Invoke-Jmap($body) {
|
||||
Invoke-RestMethod -Uri "$ApiBaseUrl/jmap/" -Method Post -ContentType "application/json" -Headers $authHeader -Body ($body | ConvertTo-Json -Depth 10 -Compress) -TimeoutSec 15
|
||||
}
|
||||
|
||||
Write-Host "Waiting for $ApiBaseUrl to be reachable..."
|
||||
$ready = $false
|
||||
for ($i = 0; $i -lt 30; $i++) {
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$ApiBaseUrl/jmap/session" -Headers $authHeader -TimeoutSec 5 | Out-Null
|
||||
$ready = $true
|
||||
break
|
||||
} catch { Start-Sleep -Seconds 1 }
|
||||
}
|
||||
if (-not $ready) { throw "Server did not become reachable at $ApiBaseUrl" }
|
||||
|
||||
$session = Invoke-RestMethod -Uri "$ApiBaseUrl/jmap/session" -Headers $authHeader -TimeoutSec 15
|
||||
$accountId = $session.primaryAccounts.'urn:stalwart:jmap'
|
||||
|
||||
$queryResult = Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:Domain/query", @{ accountId = $accountId }, "0"))
|
||||
}
|
||||
$alreadyBootstrapped = -not ($queryResult.methodResponses[0][1].type -eq "forbidden")
|
||||
|
||||
if ($alreadyBootstrapped) {
|
||||
Write-Host "Server already bootstrapped, skipping setup. (Use 'docker compose down -v; npm run dev:server' to start fresh.)"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Completing server bootstrap (domain: $DevDomain, no TLS certificate request)..."
|
||||
Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:Bootstrap/set", @{
|
||||
accountId = $accountId
|
||||
update = @{ singleton = @{ defaultDomain = $DevDomain; serverHostname = $DevHostname; requestTlsCertificate = $false } }
|
||||
}, "0"))
|
||||
} | Out-Null
|
||||
|
||||
Write-Host "Restarting the container to apply bootstrap config (one-time only)..."
|
||||
docker compose restart stalwart | Out-Null
|
||||
$ready = $false
|
||||
for ($i = 0; $i -lt 30; $i++) {
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$ApiBaseUrl/jmap/session" -Headers $authHeader -TimeoutSec 5 | Out-Null
|
||||
$ready = $true
|
||||
break
|
||||
} catch { Start-Sleep -Seconds 1 }
|
||||
}
|
||||
if (-not $ready) { throw "Server did not come back up after restart" }
|
||||
|
||||
$domainResult = Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:Domain/query", @{ accountId = $accountId }, "0"))
|
||||
}
|
||||
$domainId = $domainResult.methodResponses[0][1].ids[0]
|
||||
|
||||
Write-Host "Creating devadmin account ($DevAdminName@$DevDomain)..."
|
||||
$createResult = Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:Account/set", @{
|
||||
accountId = $accountId
|
||||
create = @{ u1 = @{ "@type" = "User"; name = $DevAdminName; domainId = $domainId; roles = @{ "@type" = "Admin" } } }
|
||||
}, "0"))
|
||||
}
|
||||
$devAdminId = $createResult.methodResponses[0][1].created.u1.id
|
||||
|
||||
Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:Account/set", @{
|
||||
accountId = $accountId
|
||||
update = @{ $devAdminId = @{ credentials = @{ "0" = @{ "@type" = "Password"; secret = $DevAdminSecret } } } }
|
||||
}, "0"))
|
||||
} | Out-Null
|
||||
|
||||
Write-Host "Setting default OAuth access token lifetime to 3 hours..."
|
||||
Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:OidcProvider/set", @{
|
||||
accountId = $accountId
|
||||
update = @{ singleton = @{ accessTokenExpiry = $DefaultTokenExpiryMs } }
|
||||
}, "0"))
|
||||
} | Out-Null
|
||||
try {
|
||||
Invoke-Jmap @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:Action/set", @{ accountId = $accountId; create = @{ a1 = @{ "@type" = "ReloadSettings" } } }, "0"))
|
||||
} | Out-Null
|
||||
} catch {}
|
||||
|
||||
Write-Host "Done. $DevAdminName@$DevDomain / $DevAdminSecret is ready - run scripts/dev-token.ps1 to get a token."
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# Local development only. One-time setup for the disposable Stalwart test
|
||||
# server from docker-compose.yml: completes the server's bootstrap wizard,
|
||||
# creates a real "devadmin" account (used by dev-token.sh/.ps1 — the
|
||||
# STALWART_RECOVERY_ADMIN account is break-glass only and always issues
|
||||
# fixed 1h OAuth tokens regardless of server config), and sets the default
|
||||
# OAuth access token lifetime to 3 hours.
|
||||
#
|
||||
# Idempotent: safe to re-run; does nothing if the server already left
|
||||
# bootstrap mode. Run this once after `npm run dev:server` on a fresh
|
||||
# volume (or after `docker compose down -v`).
|
||||
set -euo pipefail
|
||||
|
||||
API_BASE_URL="${1:-http://localhost:8080}"
|
||||
RECOVERY_ACCOUNT="admin@example.org"
|
||||
RECOVERY_SECRET="c8321iEscHDy0GWV"
|
||||
DEV_DOMAIN="example.org"
|
||||
DEV_HOSTNAME="mail.example.org"
|
||||
DEVADMIN_NAME="devadmin"
|
||||
DEVADMIN_SECRET="DevAdminPass123!"
|
||||
DEFAULT_TOKEN_EXPIRY_MS=10800000 # 3 hours
|
||||
|
||||
jmap() {
|
||||
curl -sf --compressed -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" \
|
||||
-X POST -H "Content-Type: application/json" -d "$1" "$API_BASE_URL/jmap/"
|
||||
}
|
||||
|
||||
echo "Waiting for $API_BASE_URL to be reachable..."
|
||||
for _ in $(seq 1 30); do
|
||||
curl -sf -o /dev/null -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" "$API_BASE_URL/jmap/session" && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
SESSION=$(curl -sf --compressed -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" "$API_BASE_URL/jmap/session")
|
||||
ACCOUNT_ID=$(printf '%s' "$SESSION" | grep -o '"urn:stalwart:jmap":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
QUERY_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Domain/query\",{\"accountId\":\"$ACCOUNT_ID\"},\"0\"]]}")
|
||||
if ! printf '%s' "$QUERY_RESULT" | grep -q '"forbidden"'; then
|
||||
echo "Server already bootstrapped, skipping setup. (Use 'docker compose down -v && npm run dev:server' to start fresh.)"
|
||||
else
|
||||
echo "Completing server bootstrap (domain: $DEV_DOMAIN, no TLS certificate request)..."
|
||||
jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Bootstrap/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"singleton\":{\"defaultDomain\":\"$DEV_DOMAIN\",\"serverHostname\":\"$DEV_HOSTNAME\",\"requestTlsCertificate\":false}}},\"0\"]]}" > /dev/null
|
||||
|
||||
echo "Restarting the container to apply bootstrap config (one-time only)..."
|
||||
docker compose restart stalwart > /dev/null
|
||||
for _ in $(seq 1 30); do
|
||||
curl -sf -o /dev/null -u "$RECOVERY_ACCOUNT:$RECOVERY_SECRET" "$API_BASE_URL/jmap/session" && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
DOMAIN_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Domain/query\",{\"accountId\":\"$ACCOUNT_ID\"},\"0\"]]}")
|
||||
DOMAIN_ID=$(printf '%s' "$DOMAIN_RESULT" | grep -o '"ids":\["[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
echo "Creating devadmin account ($DEVADMIN_NAME@$DEV_DOMAIN)..."
|
||||
CREATE_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Account/set\",{\"accountId\":\"$ACCOUNT_ID\",\"create\":{\"u1\":{\"@type\":\"User\",\"name\":\"$DEVADMIN_NAME\",\"domainId\":\"$DOMAIN_ID\",\"roles\":{\"@type\":\"Admin\"}}}},\"0\"]]}")
|
||||
DEVADMIN_ID=$(printf '%s' "$CREATE_RESULT" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Account/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"$DEVADMIN_ID\":{\"credentials\":{\"0\":{\"@type\":\"Password\",\"secret\":\"$DEVADMIN_SECRET\"}}}}},\"0\"]]}" > /dev/null
|
||||
|
||||
echo "Setting default OAuth access token lifetime to 3 hours..."
|
||||
jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:OidcProvider/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"singleton\":{\"accessTokenExpiry\":$DEFAULT_TOKEN_EXPIRY_MS}}},\"0\"]]}" > /dev/null
|
||||
jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Action/set\",{\"accountId\":\"$ACCOUNT_ID\",\"create\":{\"a1\":{\"@type\":\"ReloadSettings\"}}},\"0\"]]}" > /dev/null 2>&1 || true
|
||||
|
||||
echo "Done. devadmin@$DEV_DOMAIN / $DEVADMIN_SECRET is ready — run scripts/dev-token.sh to get a token."
|
||||
fi
|
||||
+47
-36
@@ -1,60 +1,71 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a fresh OAuth access token from the local Stalwart dev container
|
||||
and writes it to .env.development.local (gitignored).
|
||||
Generates a fresh access token from the local Stalwart dev container and
|
||||
writes it to .env.development.local (gitignored).
|
||||
|
||||
.DESCRIPTION
|
||||
Local development only. Requires the dev container from docker-compose.yml
|
||||
(`docker compose up -d`) to be running. Tokens expire after 1 hour; re-run
|
||||
this script and restart "npm run dev" when the UI starts returning 401s.
|
||||
The credentials below belong to the disposable local Stalwart container.
|
||||
See DEVELOPMENT.md for the full workflow. Non-Windows shells (and AI
|
||||
agents without PowerShell) can use scripts/dev-token.sh instead.
|
||||
Local development only. Requires scripts/dev-server-init.ps1 to have been
|
||||
run once first (creates the "devadmin" account this script authenticates
|
||||
as — the STALWART_RECOVERY_ADMIN account is break-glass only and always
|
||||
issues fixed 1h tokens regardless of server config, so it can't honor a
|
||||
custom duration). See DEVELOPMENT.md for the full workflow. Non-Windows
|
||||
shells (and AI agents without PowerShell) can use scripts/dev-token.sh
|
||||
instead.
|
||||
|
||||
.PARAMETER DurationSeconds
|
||||
How long the token should stay valid, in seconds. Defaults to 3 hours
|
||||
(10800), matching the server default set by dev-server-init.ps1.
|
||||
|
||||
.EXAMPLE
|
||||
./dev-token.ps1 # 3 hour token
|
||||
.EXAMPLE
|
||||
./dev-token.ps1 -DurationSeconds 1800 # 30 minute token
|
||||
#>
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://localhost:8080",
|
||||
[string]$AccountName = "admin@example.org",
|
||||
[string]$AccountSecret = "c8321iEscHDy0GWV"
|
||||
[int]$DurationSeconds = 10800,
|
||||
[string]$ApiBaseUrl = "http://localhost:8080"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
# PKCE pair (S256)
|
||||
$chars = (48..57) + (65..90) + (97..122)
|
||||
$verifier = -join ($chars | Get-Random -Count 64 | ForEach-Object { [char]$_ })
|
||||
$sha = [System.Security.Cryptography.SHA256]::Create()
|
||||
$challenge = [Convert]::ToBase64String($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($verifier))).Replace('+', '-').Replace('/', '_').TrimEnd('=')
|
||||
$DevAdminAccount = "devadmin@example.org"
|
||||
$DevAdminSecret = "DevAdminPass123!"
|
||||
|
||||
$redirectUri = "http://localhost:3005/oauth/callback"
|
||||
$creds = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($DevAdminAccount):$($DevAdminSecret)"))
|
||||
$authHeader = @{ Authorization = "Basic $creds" }
|
||||
|
||||
$authPayload = @{
|
||||
type = "authCode"
|
||||
accountName = $AccountName
|
||||
accountSecret = $AccountSecret
|
||||
clientId = "stalwart-webui"
|
||||
redirectUri = $redirectUri
|
||||
scope = "openid email profile offline_access"
|
||||
state = [guid]::NewGuid().ToString("N")
|
||||
codeChallenge = $challenge
|
||||
codeChallengeMethod = "S256"
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
$auth = Invoke-RestMethod -Uri "$ApiBaseUrl/api/auth" -Method Post -ContentType "application/json" -Body $authPayload -TimeoutSec 15
|
||||
if ($auth.type -ne "authenticated" -or -not $auth.client_code) {
|
||||
throw "Unexpected /api/auth response: $($auth | ConvertTo-Json -Compress)"
|
||||
try {
|
||||
$session = Invoke-RestMethod -Uri "$ApiBaseUrl/jmap/session" -Headers $authHeader -TimeoutSec 15
|
||||
} catch {
|
||||
throw "Could not reach $ApiBaseUrl as $DevAdminAccount. Is the server running ('npm run dev:server') and initialized ('scripts/dev-server-init.ps1')?"
|
||||
}
|
||||
$accountId = $session.primaryAccounts.'urn:stalwart:jmap'
|
||||
|
||||
$tokenBody = "grant_type=authorization_code&code=$($auth.client_code)&code_verifier=$verifier&client_id=stalwart-webui&redirect_uri=$([uri]::EscapeDataString($redirectUri))"
|
||||
$token = Invoke-RestMethod -Uri "$ApiBaseUrl/auth/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -TimeoutSec 15
|
||||
$expiresAt = [DateTime]::UtcNow.AddSeconds($DurationSeconds).ToString("yyyy-MM-ddTHH:mm:ssZ")
|
||||
|
||||
$request = @{
|
||||
using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap")
|
||||
methodCalls = @(, @("x:ApiKey/set", @{
|
||||
accountId = $accountId
|
||||
create = @{ k1 = @{ description = "dev-token.ps1"; expiresAt = $expiresAt } }
|
||||
}, "0"))
|
||||
} | ConvertTo-Json -Depth 10 -Compress
|
||||
|
||||
$response = Invoke-RestMethod -Uri "$ApiBaseUrl/jmap/" -Method Post -ContentType "application/json" -Headers $authHeader -Body $request -TimeoutSec 15
|
||||
$secret = $response.methodResponses[0][1].created.k1.secret
|
||||
|
||||
if (-not $secret) {
|
||||
throw "Unexpected x:ApiKey/set response: $($response | ConvertTo-Json -Depth 10 -Compress)"
|
||||
}
|
||||
|
||||
$envPath = Join-Path $root ".env.development.local"
|
||||
@"
|
||||
# Generated by scripts/dev-token.ps1 - gitignored, do not commit.
|
||||
# Empty base URL: API calls stay same-origin and go through the Vite proxy.
|
||||
VITE_API_BASE_URL=
|
||||
VITE_ACCESS_TOKEN=$($token.access_token)
|
||||
VITE_ACCESS_TOKEN=$secret
|
||||
"@ | Set-Content -Path $envPath -Encoding ascii
|
||||
|
||||
Write-Host "Token written to $envPath (expires in $($token.expires_in)s). Restart 'npm run dev' to pick it up."
|
||||
Write-Host "Token written to $envPath (expires $expiresAt, in ${DurationSeconds}s). Restart 'npm run dev' to pick it up."
|
||||
|
||||
+32
-38
@@ -1,54 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# Local development only. Generates a fresh OAuth access token from the local
|
||||
# Local development only. Generates a fresh access token from the local
|
||||
# Stalwart dev container (see docker-compose.yml) and writes it to
|
||||
# .env.development.local (gitignored). Bash equivalent of dev-token.ps1, for
|
||||
# non-Windows shells (and AI agents without PowerShell).
|
||||
#
|
||||
# Tokens expire after 1 hour; re-run this script and restart "npm run dev"
|
||||
# when the UI starts returning 401s.
|
||||
# The credentials below belong to the disposable local Stalwart container.
|
||||
# Requires scripts/dev-server-init.sh to have been run once first (creates
|
||||
# the "devadmin" account this script authenticates as — the
|
||||
# STALWART_RECOVERY_ADMIN account is break-glass only and always issues
|
||||
# fixed 1h tokens regardless of server config, so it can't honor a custom
|
||||
# duration).
|
||||
#
|
||||
# Usage: dev-token.sh [duration_seconds] [api_base_url]
|
||||
# dev-token.sh # 3 hour token (server default, see dev-server-init.sh)
|
||||
# dev-token.sh 1800 # 30 minute token
|
||||
set -euo pipefail
|
||||
|
||||
API_BASE_URL="${1:-http://localhost:8080}"
|
||||
ACCOUNT_NAME="${2:-admin@example.org}"
|
||||
ACCOUNT_SECRET="${3:-c8321iEscHDy0GWV}"
|
||||
REDIRECT_URI="http://localhost:3005/oauth/callback"
|
||||
DURATION_SECONDS="${1:-10800}"
|
||||
API_BASE_URL="${2:-http://localhost:8080}"
|
||||
DEVADMIN_ACCOUNT="devadmin@example.org"
|
||||
DEVADMIN_SECRET="DevAdminPass123!"
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
b64url() {
|
||||
base64 | tr '+/' '-_' | tr -d '=\n'
|
||||
if ! date -u -d "+1 minute" +"%Y-%m-%dT%H:%M:%SZ" >/dev/null 2>&1; then
|
||||
EXPIRES_AT=$(date -u -v+"${DURATION_SECONDS}"S +"%Y-%m-%dT%H:%M:%SZ") # BSD/macOS date
|
||||
else
|
||||
EXPIRES_AT=$(date -u -d "+${DURATION_SECONDS} seconds" +"%Y-%m-%dT%H:%M:%SZ") # GNU date
|
||||
fi
|
||||
|
||||
SESSION=$(curl -sf --compressed -u "$DEVADMIN_ACCOUNT:$DEVADMIN_SECRET" "$API_BASE_URL/jmap/session") || {
|
||||
echo "Could not reach $API_BASE_URL as $DEVADMIN_ACCOUNT. Is the server running ('npm run dev:server') and initialized ('scripts/dev-server-init.sh')?" >&2
|
||||
exit 1
|
||||
}
|
||||
ACCOUNT_ID=$(printf '%s' "$SESSION" | grep -o '"urn:stalwart:jmap":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
VERIFIER="$(head -c 48 /dev/urandom | b64url | head -c 64)"
|
||||
CHALLENGE="$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | b64url)"
|
||||
STATE="$(head -c 16 /dev/urandom | xxd -p)"
|
||||
|
||||
AUTH_PAYLOAD=$(cat <<JSON
|
||||
{"type":"authCode","accountName":"$ACCOUNT_NAME","accountSecret":"$ACCOUNT_SECRET","clientId":"stalwart-webui","redirectUri":"$REDIRECT_URI","scope":"openid email profile offline_access","state":"$STATE","codeChallenge":"$CHALLENGE","codeChallengeMethod":"S256"}
|
||||
REQ=$(cat <<JSON
|
||||
{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:ApiKey/set",{"accountId":"$ACCOUNT_ID","create":{"k1":{"description":"dev-token.sh","expiresAt":"$EXPIRES_AT"}}},"0"]]}
|
||||
JSON
|
||||
)
|
||||
|
||||
AUTH_RESPONSE=$(curl -sf "$API_BASE_URL/api/auth" -X POST -H "Content-Type: application/json" -d "$AUTH_PAYLOAD")
|
||||
CLIENT_CODE=$(printf '%s' "$AUTH_RESPONSE" | grep -o '"client_code":"[^"]*"' | cut -d'"' -f4)
|
||||
RESPONSE=$(curl -sf --compressed -u "$DEVADMIN_ACCOUNT:$DEVADMIN_SECRET" -X POST -H "Content-Type: application/json" -d "$REQ" "$API_BASE_URL/jmap/")
|
||||
TOKEN=$(printf '%s' "$RESPONSE" | grep -o '"secret":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$CLIENT_CODE" ]; then
|
||||
echo "Unexpected /api/auth response: $AUTH_RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN_RESPONSE=$(curl -sf "$API_BASE_URL/auth/token" -X POST \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
--data-urlencode "grant_type=authorization_code" \
|
||||
--data-urlencode "code=$CLIENT_CODE" \
|
||||
--data-urlencode "code_verifier=$VERIFIER" \
|
||||
--data-urlencode "client_id=stalwart-webui" \
|
||||
--data-urlencode "redirect_uri=$REDIRECT_URI")
|
||||
|
||||
ACCESS_TOKEN=$(printf '%s' "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
|
||||
EXPIRES_IN=$(printf '%s' "$TOKEN_RESPONSE" | grep -o '"expires_in":[0-9]*' | cut -d':' -f2)
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
echo "Unexpected /auth/token response: $TOKEN_RESPONSE" >&2
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "Unexpected x:ApiKey/set response: $RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -57,7 +51,7 @@ cat > "$ENV_PATH" <<EOF
|
||||
# Generated by scripts/dev-token.sh - gitignored, do not commit.
|
||||
# Empty base URL: API calls stay same-origin and go through the Vite proxy.
|
||||
VITE_API_BASE_URL=
|
||||
VITE_ACCESS_TOKEN=$ACCESS_TOKEN
|
||||
VITE_ACCESS_TOKEN=$TOKEN
|
||||
EOF
|
||||
|
||||
echo "Token written to $ENV_PATH (expires in ${EXPIRES_IN}s). Restart 'npm run dev' to pick it up."
|
||||
echo "Token written to $ENV_PATH (expires $EXPIRES_AT, in ${DURATION_SECONDS}s). Restart 'npm run dev' to pick it up."
|
||||
|
||||
Reference in New Issue
Block a user