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>
89 lines
2.0 KiB
TypeScript
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()
|
|
},
|
|
},
|
|
})
|