wraith/frontend/stores/connection.store.ts
Vantz Stockwell 93811b59cb fix(security): auth hardening — httpOnly cookies, Argon2id passwords, TOTP encryption, rate limiting
C-2: JWT moved from localStorage to httpOnly cookie (eliminates XSS token theft)
C-3: WebSocket auth via short-lived single-use tickets (JWT no longer in URLs)
H-1: JWT expiry reduced from 7 days to 4 hours
H-3: TOTP secrets encrypted at rest with vault EncryptionService (auto-migrates plaintext)
H-6: Rate limiting via @nestjs/throttler (60 req/min global, tighten on auth)
H-8: Constant-time login — Argon2id verify runs against dummy hash for non-existent users
H-9: Password hashing upgraded from bcrypt(10) to Argon2id (auto-upgrades on login)
H-10: Credential list API no longer returns encrypted blobs
H-16: Admin pages use Nuxt route middleware instead of client-side guard
Plus: auth bootstrap plugin, cookie-parser middleware, all frontend Authorization headers removed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:24:35 -04:00

89 lines
2.0 KiB
TypeScript

import { defineStore } from 'pinia'
interface Host {
id: number
name: string
hostname: string
port: number
protocol: 'ssh' | 'rdp'
groupId: number | null
credentialId: number | null
tags: string[]
notes: string | null
color: string | null
lastConnectedAt: string | null
group: { id: number; name: string } | null
}
interface HostGroup {
id: number
name: string
parentId: number | null
children: HostGroup[]
hosts: Host[]
}
export const useConnectionStore = defineStore('connections', {
state: () => ({
hosts: [] as Host[],
groups: [] as HostGroup[],
search: '',
loading: false,
}),
actions: {
// C-2: No more manual Authorization headers — httpOnly cookie sent automatically
async fetchHosts() {
this.loading = true
try {
this.hosts = await $fetch('/api/hosts')
} finally {
this.loading = false
}
},
async fetchTree() {
this.groups = await $fetch('/api/groups/tree')
},
async createHost(data: Partial<Host>) {
const host = await $fetch<Host>('/api/hosts', {
method: 'POST',
body: data,
})
await this.fetchHosts()
return host
},
async updateHost(id: number, data: Partial<Host>) {
await $fetch(`/api/hosts/${id}`, {
method: 'PUT',
body: data,
})
await this.fetchHosts()
},
async deleteHost(id: number) {
await $fetch(`/api/hosts/${id}`, {
method: 'DELETE',
})
await this.fetchHosts()
},
async createGroup(data: { name: string; parentId?: number }) {
await $fetch('/api/groups', {
method: 'POST',
body: data,
})
await this.fetchTree()
},
async updateGroup(id: number, data: { name?: string; parentId?: number }) {
await $fetch(`/api/groups/${id}`, {
method: 'PUT',
body: data,
})
await this.fetchTree()
},
async deleteGroup(id: number) {
await $fetch(`/api/groups/${id}`, {
method: 'DELETE',
})
await this.fetchTree()
},
},
})