major: kestrel is now a tak server (#6)
All checks were successful
ci/woodpecker/push/push Pipeline was successful
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
This commit was merged in pull request #6.
This commit is contained in:
84
server/utils/logger.js
Normal file
84
server/utils/logger.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Structured logger with request context support.
|
||||
* Uses AsyncLocalStorage to provide request-scoped context that's automatically isolated per async context.
|
||||
*/
|
||||
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
|
||||
const asyncLocalStorage = new AsyncLocalStorage()
|
||||
|
||||
/**
|
||||
* Run a function with logger context. Context is automatically isolated per async execution.
|
||||
* @param {string} reqId - Request ID
|
||||
* @param {string|null} uId - User ID (optional)
|
||||
* @param {Function} fn - Function to run with context
|
||||
* @returns {Promise<any>} Result of the function
|
||||
*/
|
||||
export function runWithContext(reqId, uId, fn) {
|
||||
return asyncLocalStorage.run({ requestId: reqId, userId: uId }, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context for the current async context. Use runWithContext() instead for proper isolation.
|
||||
* @deprecated Use runWithContext() instead for proper async context isolation
|
||||
* @param {string} reqId - Request ID
|
||||
* @param {string|null} uId - User ID (optional)
|
||||
*/
|
||||
export function setContext(reqId, uId = null) {
|
||||
const store = asyncLocalStorage.getStore()
|
||||
if (store) {
|
||||
store.requestId = reqId
|
||||
store.userId = uId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear context for the current async context.
|
||||
* @deprecated Context is automatically cleared when async context ends. Use runWithContext() instead.
|
||||
*/
|
||||
export function clearContext() {
|
||||
const store = asyncLocalStorage.getStore()
|
||||
if (store) {
|
||||
store.requestId = null
|
||||
store.userId = null
|
||||
}
|
||||
}
|
||||
|
||||
function getContext() {
|
||||
return asyncLocalStorage.getStore() || { requestId: null, userId: null }
|
||||
}
|
||||
|
||||
function formatMessage(level, message, context = {}) {
|
||||
const { requestId, userId } = getContext()
|
||||
const timestamp = new Date().toISOString()
|
||||
const ctx = {
|
||||
timestamp,
|
||||
level,
|
||||
requestId,
|
||||
...(userId && { userId }),
|
||||
...context,
|
||||
}
|
||||
return `[${level.toUpperCase()}] ${JSON.stringify({ message, ...ctx })}`
|
||||
}
|
||||
|
||||
export function info(message, context = {}) {
|
||||
console.log(formatMessage('info', message, context))
|
||||
}
|
||||
|
||||
export function error(message, context = {}) {
|
||||
const ctx = { ...context }
|
||||
if (context.error && context.error.stack) {
|
||||
ctx.stack = context.error.stack
|
||||
}
|
||||
console.error(formatMessage('error', message, ctx))
|
||||
}
|
||||
|
||||
export function warn(message, context = {}) {
|
||||
console.warn(formatMessage('warn', message, context))
|
||||
}
|
||||
|
||||
export function debug(message, context = {}) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug(formatMessage('debug', message, context))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user