cbacfea7f2
NexusGuard CI / server-core-test (push) Failing after 3m6s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 4s
NexusGuard CI / device-agent-cross-build (amd64, linux) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (amd64, windows) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (arm64, linux) (push) Has been skipped
NexusGuard CI / dashboard-test (push) Failing after 4s
NexusGuard CI / dashboard-dist (push) Has been skipped
1078 lines
39 KiB
Markdown
1078 lines
39 KiB
Markdown
# Share Link Fix + PresharedKey Disable Feature
|
|
|
|
## TL;DR
|
|
|
|
> **Quick Summary**: Fix share link "always expired" bug (URL mismatch) + add peer-level PresharedKey disable toggle with server default setting + fix device-agent dropped PresharedKey.
|
|
>
|
|
> **Deliverables**:
|
|
> - Share link fix: `ShareConfig.vue` — use `fetch()` at root path, not Axios
|
|
> - PSK disable: Device model `DisablePresharedKey bool`, WgServer model `DefaultDisablePresharedKey bool` + all creation/generation paths + frontend toggles
|
|
> - Agent PSK: `WireGuardConfig.PresharedKey` field + UAPI emission
|
|
>
|
|
> **Estimated Effort**: Medium (8 files backend, 4 files frontend, 2 files agent)
|
|
> **Parallel Execution**: YES — 3 waves
|
|
> **Critical Path**: Model migration → Backend handlers → Frontend toggles → Agent structs → Final QA
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
### Original Request
|
|
1. **Bug**: Share config link selalu expired — recipient always sees "Link Expired or Invalid"
|
|
2. **Feature**: Peer dapat disable PresharedKey; server default disable PSK untuk peer baru
|
|
|
|
### Metis Analysis Key Findings
|
|
- **Share bug root cause**: URL mismatch — `ShareConfig.vue` calls `api.get('/share/:token')` which adds `/api/v1` prefix via Axios baseURL, but server route is at root `GET /share/:token`
|
|
- **Share bug secondary concern**: Server returns generic 404 for both expired AND invalid — error message doesn't distinguish
|
|
- **PSK disable scope**: 3 creation paths (peer, device, provisioning) + 3 config generators (peer config, share config, provisioning response) + agent UAPI
|
|
- **Precedence**: `device.DisablePresharedKey` > `server.DefaultDisablePresharedKey` > `false` (keep PSK)
|
|
- **Migration**: GORM AutoMigrate with `default:false` tags; existing rows get `false`
|
|
- **Agent**: Missing `PresharedKey` field in `WireGuardConfig` struct; needs to handle empty gracefully
|
|
|
|
### Current State
|
|
- **Share link**: `PeerConfigModal.vue` creates share at admin dashboard → generates URL like `/share/TOKEN` → recipient opens → SPA at `/share/TOKEN` → `ShareConfig.vue` calls `api.get('/share/TOKEN')` → WRONG URL → 404 → "Expired"
|
|
- **PresharedKey**: Always generated via `wgtypes.GenerateKey()` in all 3 creation paths. Always included in config text. No toggle to disable. Device-agent `WireGuardConfig` struct doesn't parse it.
|
|
- **WireGuard kernel**: `wgmanager_linux.go` already handles empty PresharedKey correctly (zero-value key = no PSK)
|
|
|
|
---
|
|
|
|
## Work Objectives
|
|
|
|
### Core Objective
|
|
1. Fix share link so recipients can actually retrieve the config
|
|
2. Allow admin to disable PresharedKey per-peer and set server-wide default
|
|
|
|
### Concrete Deliverables
|
|
- `apps/dashboard-ui/src/views/ShareConfig.vue` — use `fetch()` instead of Axios
|
|
- `apps/server-core/internal/models/models.go` — add `DisablePresharedKey` to Device, `DefaultDisablePresharedKey` to WgServer
|
|
- `apps/server-core/api/peers.go` — conditional PSK in CreatePeer + getDeviceConfig
|
|
- `apps/server-core/api/devices.go` — DisablePresharedKey in UpdateDeviceRequest + handler
|
|
- `apps/server-core/api/share.go` — conditional PSK in share config
|
|
- `apps/server-core/api/provisioning.go` — conditional PSK generation + payload
|
|
- `apps/dashboard-ui/src/components/AddPeerModal.vue` — PSK toggle
|
|
- `apps/dashboard-ui/src/views/DeviceDetail.vue` — PSK toggle
|
|
- `apps/dashboard-ui/src/views/Servers.vue` — default PSK setting
|
|
- `apps/device-agent/internal/client/provisioning.go` — add PresharedKey field
|
|
- `apps/device-agent/internal/tunnel/wireguard.go` — conditional preshared_key in UAPI
|
|
|
|
### Definition of Done
|
|
- [ ] Share link: curl `GET /share/{VALID_TOKEN}` → 200 with config_text
|
|
- [ ] Share link: curl `GET /share/{EXPIRED_TOKEN}` → 404 with error message
|
|
- [ ] Share link: Open share URL in browser → config displays, no "Expired" error
|
|
- [ ] PSK: Create device with PSK enabled → config_text includes `PresharedKey = <key>`
|
|
- [ ] PSK: Create device with PSK disabled → config_text has NO PresharedKey line
|
|
- [ ] PSK: Toggle PSK on existing device → updates correctly
|
|
- [ ] PSK: Server default disable PSK → new devices inherit
|
|
- [ ] PSK: Per-device toggle overrides server default
|
|
- [ ] Agent: Provision device with PSK → agent applies preshared_key via UAPI
|
|
- [ ] Agent: Provision device without PSK → agent does NOT emit preshared_key
|
|
- [ ] `npm run build` passes (dashboard-ui)
|
|
- [ ] `go build ./...` passes (server-core + device-agent)
|
|
|
|
### Must Have
|
|
- Share link URL mismatch fixed — recipient can download config
|
|
- Admin can disable PSK per-peer at creation/editing
|
|
- Server can set default "disable PSK" for all new peers on that server
|
|
- Device-agent receives and applies PresharedKey when present
|
|
- Device-agent handles missing/empty PresharedKey gracefully
|
|
|
|
### Must NOT Have (Guardrails)
|
|
- Do NOT refactor Axios `api` instance or `VITE_API_BASE_URL`
|
|
- Do NOT change server routing structure (`main.go` route definitions)
|
|
- Do NOT touch `shared/crypto/encryptor.go`
|
|
- Do NOT add PSK rotation, expiry, or custom/user-defined PSK values
|
|
- Do NOT add bulk tools for mass-updating existing devices
|
|
- Do NOT add new provisioning protocols or agent restart mechanisms
|
|
- Do NOT retroactively apply server default to existing devices
|
|
- Do NOT change how existing tunnels work mid-session
|
|
|
|
---
|
|
|
|
## Verification Strategy
|
|
|
|
> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed.
|
|
|
|
### Test Decision
|
|
- **Infrastructure exists**: Yes (Go tests + test DB + miniredis)
|
|
- **Automated tests**: Tests-after (add/update tests after implementation)
|
|
- **Framework**: Go `testing` + miniredis + httptest
|
|
- **Agent-Executed QA**: curl for API, fetch for share link, npm run build, go build
|
|
|
|
### QA Policy
|
|
Every task MUST include agent-executed QA scenarios.
|
|
- **API/Backend**: curl — specific HTTP methods, paths, request bodies, expected status codes + response fields
|
|
- **Frontend**: npm run build + grep for expected patterns
|
|
- **Agent**: grep for struct fields + build verification
|
|
- **Evidence**: Saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.txt`
|
|
|
|
---
|
|
|
|
## Execution Strategy
|
|
|
|
### Parallel Execution Waves
|
|
|
|
```
|
|
Wave 1 (Foundation — 2 parallel tasks):
|
|
├── Task 1: Model migration (Device + WgServer fields) [quick]
|
|
├── Task 2: Share link fix (ShareConfig.vue) [quick]
|
|
|
|
Wave 2 (Backend — 4 parallel tasks, blocked by Task 1):
|
|
├── Task 3: peers.go — conditional PSK in CreatePeer + getDeviceConfig [quick]
|
|
├── Task 4: share.go — conditional PSK in share config [quick]
|
|
├── Task 5: provisioning.go — conditional PSK gen + payload [quick]
|
|
├── Task 6: devices.go — DisablePresharedKey in update handler [quick]
|
|
|
|
Wave 3 (Frontend + Agent — 4 parallel tasks):
|
|
├── Task 7: AddPeerModal.vue — PSK disable toggle [visual-engineering]
|
|
├── Task 8: DeviceDetail.vue — PSK disable toggle [visual-engineering]
|
|
├── Task 9: Servers.vue — default PSK disable setting [visual-engineering]
|
|
├── Task 10: Device-agent — PresharedKey struct + UAPI [quick]
|
|
|
|
Wave FINAL (Parallel reviews):
|
|
├── Task F1: Plan compliance audit (oracle)
|
|
├── Task F2: Code quality + build tests (unspecified-high)
|
|
├── Task F3: Real manual QA (unspecified-high)
|
|
├── Task F4: Scope fidelity check (deep)
|
|
```
|
|
|
|
---
|
|
|
|
## TODOs
|
|
|
|
- [x] 1. Database model migration — add DisablePresharedKey fields
|
|
|
|
**What to do**:
|
|
- In `apps/server-core/internal/models/models.go`:
|
|
1. Add to `Device` struct: `DisablePresharedKey bool \`json:"disable_preshared_key" gorm:"default:false"\``
|
|
2. Add to `WgServer` struct: `DefaultDisablePresharedKey bool \`json:"default_disable_preshared_key" gorm:"default:false"\``
|
|
- `main_dev.go` already runs GORM AutoMigrate with `-tags dev`. The `default:false` tag ensures existing rows get `false` (not NULL).
|
|
- **Note**: GORM `default:false` on bool works — existing rows will backfill correctly.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT run `ALTER TABLE` manually — AutoMigrate handles this
|
|
- Do NOT touch `models_test.go` or `migrations.go`
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: 2 lines added, zero logic change, pure schema
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Task 2)
|
|
- **Blocks**: Tasks 3-9, F1-F4
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `apps/server-core/internal/models/models.go:57-84` — Device struct (add field near line 80)
|
|
- `apps/server-core/internal/models/models.go:25-48` — WgServer struct (add field near line 45)
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `go build ./...` passes
|
|
- [ ] Grep confirms new fields exist in model
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Device model has DisablePresharedKey
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "DisablePresharedKey" apps/server-core/internal/models/models.go
|
|
Expected Result: Match found with gorm tag "default:false"
|
|
Evidence: .sisyphus/evidence/task-1-model-field.txt
|
|
|
|
Scenario: WgServer model has DefaultDisablePresharedKey
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "DefaultDisablePresharedKey" apps/server-core/internal/models/models.go
|
|
Expected Result: Match found with gorm tag "default:false"
|
|
Evidence: .sisyphus/evidence/task-1-server-default.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/server-core && go build ./...
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-1-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with Tasks 3-6)
|
|
- Message: `feat(model): add DisablePresharedKey flags to Device + WgServer`
|
|
|
|
---
|
|
|
|
- [x] 2. Fix share link URL mismatch in ShareConfig.vue
|
|
|
|
**What to do**:
|
|
- In `apps/dashboard-ui/src/views/ShareConfig.vue`:
|
|
1. Remove `import { api } from '../services/api'` (line 36)
|
|
2. Change `load()` function (lines 43-52) to use `fetch()` instead of `api.get()`:
|
|
```typescript
|
|
const load = async () => {
|
|
try {
|
|
const res = await fetch(`/share/${route.params.token}`)
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP ${res.status}`)
|
|
}
|
|
const data = await res.json()
|
|
configText.value = data.config_text
|
|
} catch (err) {
|
|
error.value = true
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
```
|
|
- **Why**: The `api` Axios instance has `baseURL = VITE_API_BASE_URL` which includes `/api/v1` prefix. Share endpoint is at root `GET /share/:token`. Using `fetch()` hits the origin domain correctly.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT import or use `api` (avoid JWT auth header on public route)
|
|
- Do NOT change router config or route definitions
|
|
- Do NOT modify server `main.go` routes
|
|
- Do NOT modify `api.ts` (Axios config)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Single file, ~15 lines change, straightforward
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Task 1)
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `apps/dashboard-ui/src/views/ShareConfig.vue:33-52` — entire script section. Line 45 is the api.get call to replace.
|
|
- `apps/dashboard-ui/src/services/api.ts:4-5` — baseURL = VITE_API_BASE_URL (contains /api/v1). Confirms why api.get is wrong.
|
|
- `apps/server-core/main.go:268` — `r.GET("/share/:token", shareHandler.GetShare)` — root level, not under /api/v1.
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `npm run build` passes
|
|
- [ ] Grep confirms no `api.get(` in ShareConfig.vue
|
|
- [ ] Grep confirms `fetch(` is used for share endpoint
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: ShareConfig no longer uses api.get
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "api\." apps/dashboard-ui/src/views/ShareConfig.vue
|
|
Expected Result: No matches (api not imported)
|
|
Evidence: .sisyphus/evidence/task-2-no-api.txt
|
|
|
|
Scenario: ShareConfig uses fetch instead
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "fetch(" apps/dashboard-ui/src/views/ShareConfig.vue
|
|
Expected Result: Match found in load() function
|
|
Evidence: .sisyphus/evidence/task-2-fetch.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/dashboard-ui && npm run build
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-2-build.txt
|
|
```
|
|
|
|
**Commit**: YES (separate)
|
|
- Message: `fix(ui): use fetch() for share endpoint to fix URL mismatch /api/v1 prefix bug`
|
|
|
|
---
|
|
|
|
- [x] 3. Conditionally generate + output PresharedKey in peers.go
|
|
|
|
**What to do**:
|
|
Changes in `apps/server-core/api/peers.go`:
|
|
|
|
**3a. `CreatePeer()` — conditional PSK generation** (lines 76-80):
|
|
```go
|
|
// OLD:
|
|
psk, err := wgtypes.GenerateKey()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate preshared key"})
|
|
return
|
|
}
|
|
|
|
// NEW: Generate PSK only if not disabled
|
|
var pskStr string
|
|
if !req.DisablePresharedKey {
|
|
psk, err := wgtypes.GenerateKey()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate preshared key"})
|
|
return
|
|
}
|
|
pskStr = psk.String()
|
|
}
|
|
```
|
|
|
|
Also add `DisablePresharedKey` to `CreatePeerRequest` struct (line 30-34):
|
|
```go
|
|
type CreatePeerRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
WgServerID string `json:"wg_server_id" binding:"required"`
|
|
AllowInternet bool `json:"allow_internet"`
|
|
DisablePresharedKey bool `json:"disable_preshared_key"`
|
|
}
|
|
```
|
|
|
|
And in the Device struct creation (line 94-107), change:
|
|
```go
|
|
// OLD:
|
|
PresharedKey: psk.String(),
|
|
|
|
// NEW:
|
|
DisablePresharedKey: req.DisablePresharedKey,
|
|
PresharedKey: pskStr,
|
|
```
|
|
|
|
**3b. `getDeviceConfig()` — conditional PSK in config text** (lines 226-246):
|
|
Change the configText format string to conditionally include PresharedKey:
|
|
```go
|
|
pskLine := fmt.Sprintf("PresharedKey = %s", device.PresharedKey)
|
|
if device.DisablePresharedKey || device.PresharedKey == "" {
|
|
pskLine = ""
|
|
}
|
|
```
|
|
Then change the format string:
|
|
```go
|
|
configText := fmt.Sprintf(`[Interface]
|
|
PrivateKey = %s
|
|
Address = %s/%s
|
|
DNS = %s%s
|
|
|
|
[Peer]
|
|
PublicKey = %s
|
|
%s
|
|
AllowedIPs = %s
|
|
Endpoint = %s%s`,
|
|
device.PrivateKey,
|
|
*device.InternalIP,
|
|
addrPrefix,
|
|
dns,
|
|
mtuLine,
|
|
wgServer.PublicKey,
|
|
pskLine,
|
|
allowedIPs,
|
|
wgServer.PublicEndpoint,
|
|
keepaliveLine,
|
|
)
|
|
```
|
|
Note: If `pskLine` is empty, the blank line with `%s` will produce `\n\n` — need to handle carefully. Better approach: build config text with a conditional block.
|
|
|
|
**Better approach**: Build config string with conditional parts:
|
|
```go
|
|
var pskBlock string
|
|
if device.PresharedKey != "" && !device.DisablePresharedKey {
|
|
pskBlock = fmt.Sprintf("PresharedKey = %s\n", device.PresharedKey)
|
|
}
|
|
|
|
configText := fmt.Sprintf(`[Interface]
|
|
PrivateKey = %s
|
|
Address = %s/%s
|
|
DNS = %s%s
|
|
|
|
[Peer]
|
|
PublicKey = %s
|
|
%sAllowedIPs = %s
|
|
Endpoint = %s%s`,
|
|
device.PrivateKey,
|
|
*device.InternalIP,
|
|
addrPrefix,
|
|
dns,
|
|
mtuLine,
|
|
wgServer.PublicKey,
|
|
pskBlock,
|
|
allowedIPs,
|
|
wgServer.PublicEndpoint,
|
|
keepaliveLine,
|
|
)
|
|
```
|
|
This way if pskBlock is empty, no blank line is inserted.
|
|
|
|
**3c. `UpdateConfig()` — handle PresharedKey validation** (lines 311-319):
|
|
Currently rejects PresharedKey changes. Keep this validation — users should use the toggle, not edit config manually.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT touch `UpdateConfig()` validation logic
|
|
- Do NOT remove the PresharedKey change rejection (users should use toggle)
|
|
- Do NOT touch `wg_test.go`
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 4, 5, 6)
|
|
- **Parallel Group**: Wave 2
|
|
- **Blocks**: Tasks 7-9, F1-F4
|
|
- **Blocked By**: Task 1
|
|
|
|
**References**:
|
|
- `apps/server-core/api/peers.go:30-34` — CreatePeerRequest struct
|
|
- `apps/server-core/api/peers.go:76-80` — PSK generation in CreatePeer
|
|
- `apps/server-core/api/peers.go:226-246` — getDeviceConfig config text generation
|
|
- `apps/server-core/api/peers.go:311-319` — UpdateConfig PresharedKey validation (keep as-is)
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] go build ./... passes
|
|
- [ ] CreatePeer with disable_preshared_key=false → PSK in response
|
|
- [ ] CreatePeer with disable_preshared_key=true → no PSK in response
|
|
- [ ] getDeviceConfig omits PresharedKey line when device has DisablePresharedKey=true
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: CreatePeerRequest has DisablePresharedKey field
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "DisablePresharedKey" apps/server-core/api/peers.go
|
|
Expected Result: 2+ matches (struct + usage)
|
|
Evidence: .sisyphus/evidence/task-3-request-struct.txt
|
|
|
|
Scenario: CreatePeer conditionally generates PSK
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -A5 "if !req.DisablePresharedKey" apps/server-core/api/peers.go
|
|
Expected Result: Shows GenerateKey() inside the conditional
|
|
Evidence: .sisyphus/evidence/task-3-conditional-gen.txt
|
|
|
|
Scenario: getDeviceConfig conditionally includes PSK
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -B2 -A3 "pskBlock\|PresharedKey =" apps/server-core/api/peers.go | head -20
|
|
Expected Result: Shows pskBlock built conditionally, used in format string
|
|
Evidence: .sisyphus/evidence/task-3-config-output.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/server-core && go build ./...
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-3-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 1, 4, 5, 6)
|
|
- Message: `feat(api): conditional PresharedKey generation and config output`
|
|
|
|
---
|
|
|
|
- [x] 4. Conditionally include PresharedKey in share config (share.go)
|
|
|
|
**What to do**:
|
|
- In `apps/server-core/api/share.go`, the `CreateShare()` function generates config text at lines 71-88.
|
|
- Replace the hardcoded `PresharedKey = %s` with conditional inclusion:
|
|
```go
|
|
var pskBlock string
|
|
if device.PresharedKey != "" && !device.DisablePresharedKey {
|
|
pskBlock = fmt.Sprintf("PresharedKey = %s\n", device.PresharedKey)
|
|
}
|
|
|
|
configText := fmt.Sprintf(`[Interface]
|
|
PrivateKey = %s
|
|
Address = %s/%s
|
|
DNS = 1.1.1.1
|
|
|
|
[Peer]
|
|
PublicKey = %s
|
|
%sAllowedIPs = %s
|
|
Endpoint = %s`,
|
|
device.PrivateKey,
|
|
*device.InternalIP,
|
|
addrPrefix,
|
|
wgServer.PublicKey,
|
|
pskBlock,
|
|
allowedIPs,
|
|
wgServer.PublicEndpoint,
|
|
)
|
|
```
|
|
|
|
**Must NOT do**:
|
|
- Do NOT change the existing config structure (Interface, Peer sections)
|
|
- Do NOT touch share_test.go
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 3, 5, 6)
|
|
- **Parallel Group**: Wave 2
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: Task 1
|
|
|
|
**References**:
|
|
- `apps/server-core/api/share.go:71-88` — current config generation (lines 78 = PresharedKey)
|
|
- `apps/server-core/api/peers.go:226-246` — same pattern to follow for conditional PSK block
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] go build ./... passes
|
|
- [ ] Share config omits PresharedKey when device has DisablePresharedKey=true
|
|
- [ ] Share config includes PresharedKey when device has DisablePresharedKey=false
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Share config has conditional PSK
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "pskBlock\|DisablePresharedKey" apps/server-core/api/share.go
|
|
Expected Result: Matches showing conditional PSK logic
|
|
Evidence: .sisyphus/evidence/task-4-share-psk.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/server-core && go build ./...
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-4-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 1, 3, 5, 6)
|
|
- Message: `feat(api): conditional PresharedKey in share config`
|
|
|
|
---
|
|
|
|
- [x] 5. Conditionally generate + output PresharedKey in provisioning.go
|
|
|
|
**What to do**:
|
|
Changes in `apps/server-core/api/provisioning.go`:
|
|
|
|
**5a. Conditional PSK generation** (lines 105-110):
|
|
```go
|
|
// OLD:
|
|
psk, err := wgtypes.GenerateKey()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate preshared key"})
|
|
return
|
|
}
|
|
...
|
|
device.PresharedKey = psk.String()
|
|
|
|
// NEW:
|
|
var pskStr string
|
|
if !device.DisablePresharedKey {
|
|
psk, err := wgtypes.GenerateKey()
|
|
if err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate preshared key"})
|
|
return
|
|
}
|
|
pskStr = psk.String()
|
|
}
|
|
...
|
|
device.PresharedKey = pskStr
|
|
```
|
|
|
|
**5b. Conditional PSK in ConfigPayload** (lines 138-145):
|
|
```go
|
|
payload := ConfigPayload{
|
|
PrivateKey: priv.String(),
|
|
InternalIP: *device.InternalIP,
|
|
ServerPub: wgServer.PublicKey,
|
|
Endpoint: wgServer.PublicEndpoint,
|
|
DNS: "1.1.1.1",
|
|
}
|
|
if pskStr != "" {
|
|
payload.PresharedKey = pskStr
|
|
}
|
|
```
|
|
|
|
**Note**: `ConfigPayload.PresharedKey` is a string — if not set, it serializes as `"preshared_key":""`. The device-agent already ignores unknown fields silently. This is fine.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT touch the provisioning test file (tests need updating but out of scope for this task)
|
|
- Do NOT touch `ConfigPayload` struct (leave as-is, empty string = no PSK)
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 3, 4, 6)
|
|
- **Parallel Group**: Wave 2
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: Task 1
|
|
|
|
**References**:
|
|
- `apps/server-core/api/provisioning.go:37-44` — ConfigPayload struct (PresharedKey field already exists)
|
|
- `apps/server-core/api/provisioning.go:105-114` — PSK generation block
|
|
- `apps/server-core/api/provisioning.go:138-145` — ConfigPayload construction
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] go build ./... passes
|
|
- [ ] Provisioning with DisablePresharedKey=true → no PSK in ConfigPayload
|
|
- [ ] Provisioning with DisablePresharedKey=false → PSK in ConfigPayload
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Provisioning conditionally generates PSK
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -B1 -A5 "if !device.DisablePresharedKey" apps/server-core/api/provisioning.go
|
|
Expected Result: Shows conditional block with GenerateKey()
|
|
Evidence: .sisyphus/evidence/task-5-conditional-gen.txt
|
|
|
|
Scenario: ConfigPayload conditionally includes PSK
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -A5 "if pskStr" apps/server-core/api/provisioning.go
|
|
Expected Result: Shows the conditional payload.PresharedKey assignment
|
|
Evidence: .sisyphus/evidence/task-5-payload.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/server-core && go build ./...
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-5-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 1, 3, 4, 6)
|
|
- Message: `feat(api): conditional PresharedKey in provisioning flow`
|
|
|
|
---
|
|
|
|
- [x] 6. Handle DisablePresharedKey in device Update handler (devices.go)
|
|
|
|
**What to do**:
|
|
Changes in `apps/server-core/api/devices.go`:
|
|
|
|
**6a. Add to UpdateDeviceRequest** (lines 166-174):
|
|
```go
|
|
type UpdateDeviceRequest struct {
|
|
Name string `json:"name"`
|
|
AllowInternet *bool `json:"allow_internet"`
|
|
EndpointAllowedIPs *string `json:"endpoint_allowed_ips"`
|
|
DNS *string `json:"dns"`
|
|
MTU *int `json:"mtu"`
|
|
PersistentKeepalive *int `json:"persistent_keepalive"`
|
|
Notes *string `json:"notes"`
|
|
DisablePresharedKey *bool `json:"disable_preshared_key"`
|
|
}
|
|
```
|
|
|
|
**6b. Handle in Update handler** (between lines 192-213):
|
|
```go
|
|
if req.DisablePresharedKey != nil {
|
|
updates["disable_preshared_key"] = *req.DisablePresharedKey
|
|
// If re-enabling PSK and no PSK exists, generate one
|
|
if !*req.DisablePresharedKey && device.PresharedKey == "" {
|
|
psk, err := wgtypes.GenerateKey()
|
|
if err == nil {
|
|
updates["preshared_key"] = psk.String()
|
|
}
|
|
}
|
|
// If disabling PSK, clear the stored key
|
|
if *req.DisablePresharedKey {
|
|
updates["preshared_key"] = ""
|
|
}
|
|
}
|
|
```
|
|
|
|
**Must NOT do**:
|
|
- Do NOT touch `Create()` — that's the peer creation path (handled by peers.go)
|
|
- Do NOT touch `List()`, `Get()`, `Delete()` handlers
|
|
- Do NOT generate PSK outside the re-enable scenario
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 3, 4, 5)
|
|
- **Parallel Group**: Wave 2
|
|
- **Blocks**: Tasks 7-9, F1-F4
|
|
- **Blocked By**: Task 1
|
|
|
|
**References**:
|
|
- `apps/server-core/api/devices.go:166-174` — UpdateDeviceRequest struct
|
|
- `apps/server-core/api/devices.go:176-235` — Update handler (lines 192-213 are the updates section)
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] go build ./... passes
|
|
- [ ] PUT /devices/:id with disable_preshared_key=true → device.PresharedKey cleared
|
|
- [ ] PUT /devices/:id with disable_preshared_key=false on device with PSK → no change to PSK
|
|
- [ ] PUT /devices/:id with disable_preshared_key=false on device without PSK → new PSK generated
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: UpdateDeviceRequest has DisablePresharedKey
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "DisablePresharedKey" apps/server-core/api/devices.go
|
|
Expected Result: 2+ matches (struct + handler)
|
|
Evidence: .sisyphus/evidence/task-6-request-field.txt
|
|
|
|
Scenario: Update handler handles PSK toggle
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -B1 -A6 "if req.DisablePresharedKey" apps/server-core/api/devices.go
|
|
Expected Result: Shows the conditional block with PSK generation/clearing
|
|
Evidence: .sisyphus/evidence/task-6-toggle-logic.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/server-core && go build ./...
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-6-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 1, 3, 4, 5)
|
|
- Message: `feat(api): handle DisablePresharedKey in device update handler`
|
|
|
|
---
|
|
|
|
- [x] 7. Add PSK disable toggle to AddPeerModal.vue
|
|
|
|
**What to do**:
|
|
In `apps/dashboard-ui/src/components/AddPeerModal.vue`:
|
|
|
|
**7a. Add reactive state**:
|
|
```typescript
|
|
const disablePresharedKey = ref(false)
|
|
```
|
|
|
|
**7b. Add checkbox to template** (after the AllowInternet field):
|
|
```vue
|
|
<div class="flex items-center space-x-3 mt-3">
|
|
<input type="checkbox" id="disable-psk" v-model="disablePresharedKey"
|
|
class="w-4 h-4 rounded border-white/20 bg-white/5 text-cyan-600 focus:ring-cyan-500" />
|
|
<label for="disable-psk" class="text-sm text-gray-300">
|
|
Disable PresharedKey
|
|
<span class="text-xs text-gray-500 ml-1">(reduces connection security)</span>
|
|
</label>
|
|
</div>
|
|
```
|
|
|
|
**7c. Include in API request** (in the submit handler):
|
|
Pass `disable_preshared_key: disablePresharedKey.value` in the createPeer payload.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT remove the existing AllowInternet toggle
|
|
- Do NOT add custom PSK input (only yes/no toggle)
|
|
- Do NOT change the existing API response handling
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 8, 9, 10)
|
|
- **Parallel Group**: Wave 3
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: Tasks 1, 3
|
|
|
|
**References**:
|
|
- `apps/dashboard-ui/src/components/AddPeerModal.vue` — full component (read first, add field + state + API passthrough)
|
|
- `apps/dashboard-ui/src/views/DeviceDetail.vue` — similar toggle pattern for reference
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] npm run build passes
|
|
- [ ] Toggle exists in template
|
|
- [ ] Toggle is passed in API request
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Toggle exists in template
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "disable-psk\|DisablePresharedKey\|disable_preshared_key" apps/dashboard-ui/src/components/AddPeerModal.vue
|
|
Expected Result: 2+ matches (template + script)
|
|
Evidence: .sisyphus/evidence/task-7-toggle-exists.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/dashboard-ui && npm run build
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-7-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 8, 9)
|
|
- Message: `feat(ui): add PresharedKey disable toggle to peer creation form`
|
|
|
|
---
|
|
|
|
- [x] 8. Add PSK disable toggle to DeviceDetail.vue
|
|
|
|
**What to do**:
|
|
In `apps/dashboard-ui/src/views/DeviceDetail.vue`:
|
|
|
|
**8a. Add reactive state**:
|
|
```typescript
|
|
const disablePresharedKey = ref(false)
|
|
```
|
|
|
|
**8b. Load current state** from device data in `onMounted` or `watch`:
|
|
```typescript
|
|
disablePresharedKey.value = device.disable_preshared_key || false
|
|
```
|
|
|
|
**8c. Add toggle to the device edit form**:
|
|
```vue
|
|
<div class="flex items-center space-x-3 mt-3">
|
|
<input type="checkbox" id="device-disable-psk" v-model="disablePresharedKey"
|
|
class="w-4 h-4 rounded border-white/20 bg-white/5 text-cyan-600 focus:ring-cyan-500" />
|
|
<label for="device-disable-psk" class="text-sm text-gray-300">
|
|
Disable PresharedKey
|
|
<span class="text-xs text-gray-500 ml-1">(reduces connection security)</span>
|
|
</label>
|
|
</div>
|
|
```
|
|
|
|
**8d. Include in update payload**:
|
|
Pass `disable_preshared_key: disablePresharedKey.value` in the device update request.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT change the existing edit form layout or other fields
|
|
- Do NOT add custom PSK input
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 7, 9, 10)
|
|
- **Parallel Group**: Wave 3
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: Tasks 1, 6
|
|
|
|
**References**:
|
|
- `apps/dashboard-ui/src/views/DeviceDetail.vue` — read expected edit form section
|
|
- `apps/server-core/api/devices.go:166-174` — UpdateDeviceRequest struct (for field name)
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] npm run build passes
|
|
- [ ] Toggle in device edit form, bound to device.disable_preshared_key
|
|
- [ ] Toggle sent in update API request
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Toggle exists in DeviceDetail
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "disable_preshared_key\|DisablePresharedKey" apps/dashboard-ui/src/views/DeviceDetail.vue
|
|
Expected Result: 2+ matches (template + script)
|
|
Evidence: .sisyphus/evidence/task-8-toggle-exists.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/dashboard-ui && npm run build
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-8-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 7, 9)
|
|
- Message: `feat(ui): add PresharedKey disable toggle to device detail edit form`
|
|
|
|
---
|
|
|
|
- [x] 9. Add default PSK disable setting to Servers.vue
|
|
|
|
**What to do**:
|
|
In `apps/dashboard-ui/src/views/Servers.vue`:
|
|
|
|
**9a. Add reactive state** in the server edit form:
|
|
```typescript
|
|
const defaultDisablePresharedKey = ref(false)
|
|
```
|
|
|
|
**9b. Load from server data**:
|
|
```typescript
|
|
defaultDisablePresharedKey.value = editingServer.default_disable_preshared_key || false
|
|
```
|
|
|
|
**9c. Add toggle to server edit form** (near peer defaults section):
|
|
```vue
|
|
<div class="flex items-center space-x-3 mt-3">
|
|
<input type="checkbox" id="server-default-disable-psk" v-model="defaultDisablePresharedKey"
|
|
class="w-4 h-4 rounded border-white/20 bg-white/5 text-cyan-600 focus:ring-cyan-500" />
|
|
<label for="server-default-disable-psk" class="text-sm text-gray-300">
|
|
Disable PresharedKey for new peers by default
|
|
<span class="text-xs text-gray-500 ml-1">(can be overridden per-peer)</span>
|
|
</label>
|
|
</div>
|
|
```
|
|
|
|
**9d. Include in update payload**:
|
|
Pass `default_disable_preshared_key: defaultDisablePresharedKey.value` in the server update request.
|
|
|
|
**Must NOT do**:
|
|
- Do NOT change existing peer default fields (DNS, MTU, Keepalive, AllowedIPs)
|
|
- Do NOT add any other default settings
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 7, 8, 10)
|
|
- **Parallel Group**: Wave 3
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: Tasks 1, 3
|
|
|
|
**References**:
|
|
- `apps/dashboard-ui/src/views/Servers.vue` — read the server edit form (peer defaults section)
|
|
- `apps/server-core/internal/models/models.go:45` — WgServer.DefaultDisablePresharedKey field
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] npm run build passes
|
|
- [ ] Toggle exists in server edit form
|
|
- [ ] Toggle sent in server update request
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Default toggle exists in Servers.vue
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "default_disable_preshared_key\|DefaultDisablePresharedKey" apps/dashboard-ui/src/views/Servers.vue
|
|
Expected Result: 2+ matches (template + script)
|
|
Evidence: .sisyphus/evidence/task-9-toggle-exists.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/dashboard-ui && npm run build
|
|
Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-9-build.txt
|
|
```
|
|
|
|
**Commit**: YES (with tasks 7, 8)
|
|
- Message: `feat(ui): add default PresharedKey disable setting to server edit form`
|
|
|
|
---
|
|
|
|
- [x] 10. Fix device-agent PresharedKey gap (provisioning.go + wireguard.go)
|
|
|
|
**What to do**:
|
|
Changes in `apps/device-agent/`:
|
|
|
|
**10a. Add PresharedKey to WireGuardConfig struct** in `apps/device-agent/internal/client/provisioning.go`:
|
|
```go
|
|
type WireGuardConfig struct {
|
|
PrivateKey string `json:"private_key"`
|
|
PresharedKey string `json:"preshared_key"`
|
|
InternalIP string `json:"internal_ip"`
|
|
ServerPub string `json:"server_pub"`
|
|
Endpoint string `json:"endpoint"`
|
|
DNS string `json:"dns"`
|
|
}
|
|
```
|
|
|
|
**10b. Update ConvertToUAPI()** in `apps/device-agent/internal/tunnel/wireguard.go`:
|
|
Change from:
|
|
```go
|
|
return fmt.Sprintf(`private_key=%s
|
|
public_key=%s
|
|
endpoint=%s
|
|
allowed_ip=%s`, ...)
|
|
```
|
|
To:
|
|
```go
|
|
pskLine := ""
|
|
if presharedKey != "" {
|
|
pskLine = fmt.Sprintf("preshared_key=%s\n", presharedKey)
|
|
}
|
|
return fmt.Sprintf(`private_key=%s
|
|
public_key=%s
|
|
%sendpoint=%s
|
|
allowed_ip=%s`, privateKey, publicKey, pskLine, endpoint, allowedIP)
|
|
```
|
|
|
|
**10c. Update function signature** and callers:
|
|
- Change `ConvertToUAPI` signature to accept `presharedKey string`
|
|
- In `main.go` where it's called, pass `cfg.PresharedKey`
|
|
|
|
**Must NOT do**:
|
|
- Do NOT touch `shared/crypto/encryptor.go` (known debt, keep duplicated)
|
|
- Do NOT change the provisioning function flow (decrypt, parse, apply — stay the same)
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES (with Tasks 7, 8, 9)
|
|
- **Parallel Group**: Wave 3
|
|
- **Blocks**: F1-F4
|
|
- **Blocked By**: Task 1 (conceptual dependency — agent reads Device model changes but doesn't block on compile)
|
|
|
|
**References**:
|
|
- `apps/device-agent/internal/client/provisioning.go:28-34` — WireGuardConfig struct (add PresharedKey)
|
|
- `apps/device-agent/internal/tunnel/wireguard.go:83-97` — ConvertToUAPI function
|
|
- `apps/device-agent/main.go:38` — caller of ConvertToUAPI
|
|
- `apps/server-core/api/provisioning.go:138-145` — ConfigPayload struct (already has PresharedKey, confirmed)
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] go build ./... passes (device-agent)
|
|
- [ ] WireGuardConfig has PresharedKey field
|
|
- [ ] ConvertToUAPI conditionally emits preshared_key when non-empty
|
|
- [ ] ConvertToUAPI omits preshared_key when empty
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: WireGuardConfig has PresharedKey field
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "PresharedKey\|preshared_key" apps/device-agent/internal/client/provisioning.go
|
|
Expected Result: Shows PresharedKey string field with json tag
|
|
Evidence: .sisyphus/evidence/task-10-struct-field.txt
|
|
|
|
Scenario: ConvertToUAPI accepts presharedKey param
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -n "func ConvertToUAPI" apps/device-agent/internal/tunnel/wireguard.go
|
|
Expected Result: Shows function signature with presharedKey string param
|
|
Evidence: .sisyphus/evidence/task-10-uapi-sig.txt
|
|
|
|
Scenario: ConvertToUAPI conditionally emits preshared_key
|
|
Tool: Bash (grep)
|
|
Steps:
|
|
1. grep -B1 -A3 "if presharedKey" apps/device-agent/internal/tunnel/wireguard.go
|
|
Expected Result: Shows conditional pskLine building
|
|
Evidence: .sisyphus/evidence/task-10-uapi-conditional.txt
|
|
|
|
Scenario: Build passes
|
|
Tool: Bash
|
|
Steps:
|
|
1. cd apps/device-agent && go build ./...
|
|
Expected Result: Exit 0
|
|
Evidence: .sisyphus/evidence/task-10-build.txt
|
|
```
|
|
|
|
**Commit**: YES (separate, agent is independent submodule)
|
|
- Message: `feat(agent): add PresharedKey support to WireGuard config and UAPI`
|
|
|
|
---
|
|
|
|
## Final Verification Wave
|
|
|
|
- [x] F1. **Plan Compliance Audit** — `oracle`
|
|
Read the plan end-to-end. For each Must Have: verify implementation exists. For each Must NOT Have: search codebase for forbidden patterns (refactored Axios, crypto/encryptor.go changes, etc.). Check evidence files exist in `.sisyphus/evidence/`. Compare deliverables against plan.
|
|
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
|
|
|
|
- [x] F2. **Code Quality + Build Tests** — `unspecified-high`
|
|
Run `go build ./...` on ALL 3 submodules (server-core, dashboard-ui, device-agent). Run `npm run build` on dashboard-ui. Check for: unused imports, commented-out code, AI slop (excessive comments, over-abstraction).
|
|
Output: `Build [PASS/FAIL] | Lint [PASS/FAIL] | Files [N clean/N issues] | VERDICT`
|
|
|
|
- [x] F3. **Real Manual QA** — `unspecified-high`
|
|
Execute these end-to-end scenarios:
|
|
1. **Share link fix**: Call POST /api/v1/devices/:id/share → get token → curl GET /share/TOKEN → verify 200 with config_text
|
|
2. **PSK creation**: POST /api/v1/peers with disable_preshared_key=false → response has PresharedKey. POST with true → no PresharedKey
|
|
3. **PSK config**: GET /devices/:id/config for both PSK states → verify correct format
|
|
4. **PSK toggle**: PUT /devices/:id with disable_preshared_key=true → GET device → verify field changed
|
|
5. **Server default**: Update server setting → create peer on that server → verify inheritance
|
|
6. **Share config PSK**: Create share for device with PSK disabled → verify share config omits PSK
|
|
7. **Agent build**: go build ./... in device-agent → verify WireGuardConfig has PresharedKey field
|
|
Save evidence to `.sisyphus/evidence/final-qa/`.
|
|
Output: `Scenarios [N/N pass] | Integration [N/N] | VERDICT`
|
|
|
|
- [x] F4. **Scope Fidelity Check** — `deep`
|
|
Read the actual diffs for all changed files. Verify 1:1 with spec. No missing, no scope creep. Check Must NOT do compliance. Detect cross-task contamination.
|
|
Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | VERDICT`
|
|
|
|
---
|
|
|
|
## Commit Strategy
|
|
|
|
- **Task 2** (standalone): `fix(ui): use fetch() for share endpoint to fix /api/v1 prefix bug`
|
|
- **Task 1 + 3-6**: `feat: conditional PresharedKey across model, creation, config, share, provisioning, device update`
|
|
- **Tasks 7-9**: `feat(ui): add PresharedKey disable toggles to AddPeerModal, DeviceDetail, Servers`
|
|
- **Task 10** (standalone agent): `feat(agent): add PresharedKey support to WireGuard config and UAPI`
|
|
- **Parent repo**: `feat: submodule refs for share link fix + PresharedKey disable feature`
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
### Verification Commands
|
|
```bash
|
|
cd apps/server-core && go build ./...
|
|
cd apps/dashboard-ui && npm run build
|
|
cd apps/device-agent && go build ./...
|
|
curl -v http://localhost:8080/share/{VALID_TOKEN}
|
|
curl -v http://localhost:8080/api/v1/peers -X POST -d '{"name":"test","wg_server_id":"...","disable_preshared_key":true}'
|
|
```
|
|
|
|
### Final Checklist
|
|
- [ ] All "Must Have" present
|
|
- [ ] All "Must NOT Have" absent
|
|
- [ ] All builds pass
|
|
- [ ] Share link: valid token returns 200 with config
|
|
- [ ] Share link: expired token returns 404 with error
|
|
- [ ] PSK disabled: no PresharedKey in any generated config
|
|
- [ ] PSK enabled (default): PresharedKey present in all generated configs
|
|
- [ ] Agent: builds with PresharedKey field in WireGuardConfig
|
|
- [ ] Agent: ConvertToUAPI conditionally emits preshared_key
|
|
|
|
|
|
|