All checks were successful
ci/woodpecker/push/push Pipeline was successful
## Added - CoT (Cursor on Target) server on port 8089 enabling ATAK/iTAK device connectivity - Support for TAK stream protocol and traditional XML CoT messages - TLS/SSL support with automatic fallback to plain TCP - Username/password authentication for CoT connections - Real-time device position tracking with TTL-based expiration (90s default) - API endpoints: `/api/cot/config`, `/api/cot/server-package`, `/api/cot/truststore`, `/api/me/cot-password` - TAK Server section in Settings with QR code for iTAK setup - ATAK password management in Account page for OIDC users - CoT device markers on map showing real-time positions - Comprehensive documentation in `docs/` directory - Environment variables: `COT_PORT`, `COT_TTL_MS`, `COT_REQUIRE_AUTH`, `COT_SSL_CERT`, `COT_SSL_KEY`, `COT_DEBUG` - Dependencies: `fast-xml-parser`, `jszip`, `qrcode` ## Changed - Authentication system supports CoT password management for OIDC users - Database schema includes `cot_password_hash` field - Test suite refactored to follow functional design principles ## Removed - Consolidated utility modules: `authConfig.js`, `authSkipPaths.js`, `bootstrap.js`, `poiConstants.js`, `session.js` ## Security - XML entity expansion protection in CoT parser - Enhanced input validation and SQL injection prevention - Authentication timeout to prevent hanging connections ## Breaking Changes - Port 8089 must be exposed for CoT server. Update firewall rules and Docker/Kubernetes configurations. ## Migration Notes - OIDC users must set ATAK password via Account settings before connecting - Docker: expose port 8089 (`-p 8089:8089`) - Kubernetes: update Helm values to expose port 8089 Co-authored-by: Madison Grubb <madison@elastiflow.com> Reviewed-on: #6
83 lines
2.6 KiB
JavaScript
83 lines
2.6 KiB
JavaScript
import { readFileSync, readdirSync } from 'node:fs'
|
|
import { resolve, dirname, relative } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { describe, it, expect } from 'vitest'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const projectRoot = resolve(__dirname, '../..')
|
|
const serverDir = resolve(projectRoot, 'server')
|
|
|
|
/** Collects all .js file paths under dir (recursive). */
|
|
function listJsFiles(dir, base = dir) {
|
|
const entries = readdirSync(dir, { withFileTypes: true })
|
|
const files = []
|
|
for (const e of entries) {
|
|
const full = resolve(dir, e.name)
|
|
if (e.isDirectory()) {
|
|
files.push(...listJsFiles(full, base))
|
|
}
|
|
else if (e.isFile() && e.name.endsWith('.js')) {
|
|
files.push(relative(base, full))
|
|
}
|
|
}
|
|
return files
|
|
}
|
|
|
|
/** Extracts relative import/require paths from file content (no node_modules). */
|
|
function getRelativeImports(content) {
|
|
const paths = []
|
|
const fromRegex = /from\s+['"]([^'"]+)['"]/g
|
|
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
const extractMatches = (regex, text) => {
|
|
const matches = []
|
|
const execRegex = (r) => {
|
|
const match = r.exec(text)
|
|
if (match) {
|
|
matches.push(match[1])
|
|
return execRegex(r)
|
|
}
|
|
return matches
|
|
}
|
|
return execRegex(regex)
|
|
}
|
|
for (const re of [fromRegex, requireRegex]) {
|
|
const matches = extractMatches(re, content)
|
|
matches.forEach((p) => {
|
|
if (p.startsWith('.')) paths.push(p)
|
|
})
|
|
}
|
|
return paths
|
|
}
|
|
|
|
describe('server imports', () => {
|
|
it('only import from within server/ (no shared/ or other outside paths)', () => {
|
|
const violations = []
|
|
const files = listJsFiles(serverDir).map(f => resolve(serverDir, f))
|
|
|
|
for (const filePath of files) {
|
|
const content = readFileSync(filePath, 'utf-8')
|
|
const fileDir = dirname(filePath)
|
|
for (const importPath of getRelativeImports(content)) {
|
|
const resolved = resolve(fileDir, importPath)
|
|
const relToServer = relative(serverDir, resolved)
|
|
if (relToServer.startsWith('..') || relToServer.startsWith('/')) {
|
|
violations.push({
|
|
file: relative(projectRoot, filePath),
|
|
import: importPath,
|
|
resolved: relative(projectRoot, resolved),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(
|
|
violations,
|
|
violations.length
|
|
? `Server files must not import from outside server/. Violations:\n${violations
|
|
.map(v => ` ${v.file} → ${v.import} (resolves to ${v.resolved})`)
|
|
.join('\n')}`
|
|
: undefined,
|
|
).toHaveLength(0)
|
|
})
|
|
})
|