Security hardening: go-live review fixes

- TV tokens upgraded from 4 to 6 digits; Regen Token button in Admin
- Nginx rate limiting on TV dashboard and WebSocket endpoints
- Login lockout after 5 failed attempts (15 min); clears on admin password reset
- HSTS header added; CSP unsafe-inline removed from script-src; CORS restricted to explicit methods/headers
- Dependency CVE fixes: PyJWT 2.12.0, aiomysql 0.3.0, cryptography 46.0.5, python-multipart 0.0.22
- datetime.utcnow() replaced with datetime.now(timezone.utc) throughout
- SQL identifier whitelist for startup migration queries
- README updated: security notes section, lockout docs, token regen, NPM proxy guidance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 00:00:14 -07:00
parent be86cae7fa
commit 3022bc328b
11 changed files with 228 additions and 30 deletions

View File

@@ -32,9 +32,16 @@ export const useChildrenStore = defineStore('children', () => {
children.value = children.value.filter((c) => c.id !== id)
}
async function regenerateToken(id) {
const res = await api.post(`/api/children/${id}/regenerate-token`)
const idx = children.value.findIndex((c) => c.id === id)
if (idx !== -1) children.value[idx] = res.data
return res.data
}
function setActiveChild(child) {
activeChild.value = child
}
return { children, activeChild, fetchChildren, createChild, updateChild, deleteChild, setActiveChild }
return { children, activeChild, fetchChildren, createChild, updateChild, deleteChild, regenerateToken, setActiveChild }
})

View File

@@ -48,11 +48,13 @@
<div class="item-color" :style="{ background: child.color }"></div>
<span class="item-name">{{ child.name }}</span>
<span class="item-meta">{{ child.is_active ? 'Active' : 'Inactive' }}</span>
<span class="item-meta token-meta" title="TV token">📺 {{ child.tv_token }}</span>
<div class="item-actions">
<button class="btn-sm" @click="startEditChild(child)">Edit</button>
<button class="btn-sm" @click="toggleChild(child)">
{{ child.is_active ? 'Deactivate' : 'Activate' }}
</button>
<button class="btn-sm" @click="regenToken(child)" title="Generate a new TV token (old URL will stop working)">Regen Token</button>
<button class="btn-sm btn-danger" @click="deleteChild(child.id)">Delete</button>
</div>
</template>
@@ -536,6 +538,12 @@ async function deleteChild(id) {
}
}
async function regenToken(child) {
if (confirm(`Regenerate TV token for ${child.name}? The old TV URL will stop working immediately.`)) {
await childrenStore.regenerateToken(child.id)
}
}
// Subjects
const showSubjectForm = ref(false)
const newSubject = ref({ name: '', icon: '📚', color: '#10B981' })