@joystick.js/db-canary 0.0.0-canary.2209
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build/getFilesToBuild.js +26 -0
- package/.build/getPlatformSafeFilePath.js +6 -0
- package/.build/getPlatformSafePath.js +6 -0
- package/.build/index.js +88 -0
- package/.build/isWindows.js +3 -0
- package/API_KEY +1 -0
- package/README.md +1821 -0
- package/data/data.mdb +0 -0
- package/data/lock.mdb +0 -0
- package/dist/client/database.js +1 -0
- package/dist/client/index.js +1 -0
- package/dist/server/cluster/index.js +1 -0
- package/dist/server/cluster/master.js +20 -0
- package/dist/server/cluster/worker.js +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/lib/api_key_manager.js +9 -0
- package/dist/server/lib/auth_manager.js +1 -0
- package/dist/server/lib/auto_index_manager.js +1 -0
- package/dist/server/lib/backup_manager.js +1 -0
- package/dist/server/lib/connection_manager.js +1 -0
- package/dist/server/lib/disk_utils.js +2 -0
- package/dist/server/lib/http_server.js +405 -0
- package/dist/server/lib/index_manager.js +1 -0
- package/dist/server/lib/load_settings.js +1 -0
- package/dist/server/lib/logger.js +1 -0
- package/dist/server/lib/op_types.js +1 -0
- package/dist/server/lib/operation_dispatcher.js +1 -0
- package/dist/server/lib/operations/admin.js +1 -0
- package/dist/server/lib/operations/bulk_write.js +1 -0
- package/dist/server/lib/operations/create_index.js +1 -0
- package/dist/server/lib/operations/delete_one.js +1 -0
- package/dist/server/lib/operations/drop_index.js +1 -0
- package/dist/server/lib/operations/find.js +1 -0
- package/dist/server/lib/operations/find_one.js +1 -0
- package/dist/server/lib/operations/get_indexes.js +1 -0
- package/dist/server/lib/operations/insert_one.js +1 -0
- package/dist/server/lib/operations/update_one.js +1 -0
- package/dist/server/lib/performance_monitor.js +1 -0
- package/dist/server/lib/query_engine.js +1 -0
- package/dist/server/lib/recovery_manager.js +1 -0
- package/dist/server/lib/replication_manager.js +1 -0
- package/dist/server/lib/safe_json_parse.js +1 -0
- package/dist/server/lib/send_response.js +1 -0
- package/dist/server/lib/tcp_protocol.js +1 -0
- package/dist/server/lib/write_forwarder.js +1 -0
- package/dist/server/lib/write_queue.js +1 -0
- package/increment_version.js +3 -0
- package/logs/.013e15b54597d05db4b4b53ecc37b10c92a72927-audit.json +20 -0
- package/logs/.02de550a67ea0f5961faa2dfd458a4d06f59ebd1-audit.json +20 -0
- package/logs/.03494ba24eb3c72214b4068a77d54b8993bee651-audit.json +20 -0
- package/logs/.06309ec60b339be1259a7993dd09c732f8907fbc-audit.json +20 -0
- package/logs/.0663a04dcfa17285661e5e1b8cfa51f41523b210-audit.json +20 -0
- package/logs/.0f06e6c4c9b824622729e13927587479e5060391-audit.json +20 -0
- package/logs/.16ccf58682ecb22b3e3ec63f0da1b7fe9be56528-audit.json +20 -0
- package/logs/.1fa1a5d02f496474b1ab473524c65c984146a9ad-audit.json +20 -0
- package/logs/.2223c0ae3bea6f0d62c62b1d319cc8634856abb7-audit.json +20 -0
- package/logs/.23dc79ffda3e083665e6f5993f59397adcbf4a46-audit.json +20 -0
- package/logs/.28104f49b03906b189eefd1cd462cb46c3c0af22-audit.json +20 -0
- package/logs/.29cdbf13808abe6a0ce70ee2f2efdd680ce3fd8e-audit.json +20 -0
- package/logs/.2a9889afd071f77f41f5170d08703a0afca866b7-audit.json +20 -0
- package/logs/.2acec3d1940a2bbed487528b703ee5948959a599-audit.json +20 -0
- package/logs/.2fb60ff326338c02bfedbcd0e936444e4a216750-audit.json +20 -0
- package/logs/.318fc7a19530d76a345f030f7cad00dda15300e7-audit.json +20 -0
- package/logs/.3cf27043e19085f908cedc7701e6d013463208ee-audit.json +25 -0
- package/logs/.3d90d785415817fc443402843b7c95f8371adc9b-audit.json +20 -0
- package/logs/.4074bca620375f72966fc52dfd439577727671e5-audit.json +20 -0
- package/logs/.40eecf018417ea80a70ea8ec7a3cc9406bc6334b-audit.json +20 -0
- package/logs/.50e974f1ef7c365fca6a1251b2e2c2252914cb5e-audit.json +20 -0
- package/logs/.52cb7d9e4223cf26ba36006ac26b949a97c7923c-audit.json +20 -0
- package/logs/.54befcdb84c15aad980705a31bcc9f555c3577ab-audit.json +20 -0
- package/logs/.57dfb70e22eddb84db2e3c0ceeefac5c0b9baffa-audit.json +20 -0
- package/logs/.5f0b24705a1eaad4eca4968f2d86f91b3f9be683-audit.json +20 -0
- package/logs/.61ba98fdda7db58576b382fee07904e5db1169d6-audit.json +20 -0
- package/logs/.6235017727ef6b199d569a99d6aa8c8e80a1b475-audit.json +20 -0
- package/logs/.63db16193699219489d218a1ddea5dde3750cae4-audit.json +20 -0
- package/logs/.64fb67dfe14149c9eef728d79bf30a54da809c60-audit.json +20 -0
- package/logs/.669137453368987c1f311b5345342527afb54e50-audit.json +20 -0
- package/logs/.7a71f8c89ea28ae266d356aeff6306e876a30fbb-audit.json +20 -0
- package/logs/.7afbaa90fe9dc3a7d682676f9bb79f9a1b1fd9a6-audit.json +20 -0
- package/logs/.7ca29e322cd05327035de850099e7610864f2347-audit.json +20 -0
- package/logs/.83335ab3347e449dae03455a110aaf7f120d4802-audit.json +20 -0
- package/logs/.8c2487b5fd445d2c8e5c483c80b9fa99bbf1ca58-audit.json +20 -0
- package/logs/.8c8b9dc386922c9f3b4c13251af7052aac1d24c0-audit.json +20 -0
- package/logs/.8d6155d94640c4863301ae0fee5e4e7372a21446-audit.json +20 -0
- package/logs/.944a3119a243deea7c8270d5d9e582bb1d0eaa10-audit.json +20 -0
- package/logs/.9816a845c30fb2909f3b26a23eeb3538ebcad5db-audit.json +20 -0
- package/logs/.9dc08784e38b865488177c26d4af5934555e0323-audit.json +20 -0
- package/logs/.9dd27d2e0e454ac0a37600206d1cac5493b0d7ee-audit.json +20 -0
- package/logs/.a3d486feeac7654c59b547de96600e8849a06d4f-audit.json +20 -0
- package/logs/.a5b811f4def22250f86cc18870d7c4573625df22-audit.json +20 -0
- package/logs/.a61648eb5f830e0b6f508ac35e4f8f629d2ad4c7-audit.json +20 -0
- package/logs/.a89016d507045771b4b5a65656944a9c0f1e528b-audit.json +20 -0
- package/logs/.a99bee160a1c590be959af46bacc02724803f691-audit.json +20 -0
- package/logs/.ada7906d6243fd7da802f03d86c4ae5dd9df6236-audit.json +20 -0
- package/logs/.b518339ee942143b6af983af167f5bbb6983b4de-audit.json +20 -0
- package/logs/.b51b124b166d53c9519017856ea610d61d65fabe-audit.json +20 -0
- package/logs/.b7a6aee19f58e55633d5e4a3709041c47dfff975-audit.json +20 -0
- package/logs/.bd7a8a6ba9c55d557a4867ab53f02e3ec2e1553d-audit.json +20 -0
- package/logs/.c1435dafe453b169d6392b25065f3cf4ab6fbb21-audit.json +20 -0
- package/logs/.c17e1ce043109f77dc2f0e2aa290a9d1ed842c03-audit.json +20 -0
- package/logs/.ca62637ce9540e5a38a2fbedb2115febb6ad308a-audit.json +15 -0
- package/logs/.ccee67b9c176967f8977071409a41f5cb5cd6ad4-audit.json +20 -0
- package/logs/.db24043417ea79a6f14cd947476399e53930b48d-audit.json +20 -0
- package/logs/.e0f12acccb57829f5f33712bb2e2607ecd808147-audit.json +20 -0
- package/logs/.e9b6cc33d0bbd2e644c4e2bf44d177f850016557-audit.json +20 -0
- package/logs/.f15291d434808e3bdca7963ccd2e73893be027e6-audit.json +20 -0
- package/logs/.f4bdf9e21ef84f8a3fae3ffb32bbc39275991351-audit.json +15 -0
- package/logs/.fbac3aefac1e81b4230df5aa50667cb90d51024f-audit.json +20 -0
- package/logs/.fcfd495c0a9169db243f4a4f21878ee02b76413c-audit.json +20 -0
- package/logs/admin-2025-09-12.log +580 -0
- package/logs/admin-2025-09-15.log +283 -0
- package/logs/admin-error-2025-09-12.log +22 -0
- package/logs/admin-error-2025-09-15.log +10 -0
- package/logs/api_key_manager-2025-09-12.log +658 -0
- package/logs/api_key_manager-2025-09-15.log +295 -0
- package/logs/api_key_manager-error-2025-09-12.log +0 -0
- package/logs/api_key_manager-error-2025-09-15.log +0 -0
- package/logs/auth_manager-2025-09-12.log +4432 -0
- package/logs/auth_manager-2025-09-15.log +2000 -0
- package/logs/auth_manager-error-2025-09-12.log +11 -0
- package/logs/auth_manager-error-2025-09-15.log +5 -0
- package/logs/auto_index_manager-2025-09-12.log +84 -0
- package/logs/auto_index_manager-2025-09-15.log +45 -0
- package/logs/auto_index_manager-error-2025-09-12.log +6 -0
- package/logs/auto_index_manager-error-2025-09-15.log +0 -0
- package/logs/backup_manager-2025-09-12.log +198 -0
- package/logs/backup_manager-2025-09-15.log +90 -0
- package/logs/backup_manager-error-2025-09-12.log +198 -0
- package/logs/backup_manager-error-2025-09-15.log +90 -0
- package/logs/bulk_write-2025-09-12.log +66 -0
- package/logs/bulk_write-2025-09-15.log +38 -0
- package/logs/bulk_write-error-2025-09-12.log +0 -0
- package/logs/bulk_write-error-2025-09-15.log +0 -0
- package/logs/connection_manager-2025-09-12.log +2412 -0
- package/logs/connection_manager-2025-09-15.log +1132 -0
- package/logs/connection_manager-error-2025-09-12.log +0 -0
- package/logs/connection_manager-error-2025-09-15.log +0 -0
- package/logs/create_index-2025-09-12.log +302 -0
- package/logs/create_index-2025-09-15.log +158 -0
- package/logs/create_index-error-2025-09-12.log +30 -0
- package/logs/create_index-error-2025-09-15.log +13 -0
- package/logs/delete_one-2025-09-12.log +73 -0
- package/logs/delete_one-2025-09-15.log +43 -0
- package/logs/delete_one-error-2025-09-12.log +0 -0
- package/logs/delete_one-error-2025-09-15.log +0 -0
- package/logs/disk_utils-2025-09-12.log +4954 -0
- package/logs/disk_utils-2025-09-15.log +2446 -0
- package/logs/disk_utils-error-2025-09-12.log +0 -0
- package/logs/disk_utils-error-2025-09-15.log +0 -0
- package/logs/drop_index-2025-09-12.log +41 -0
- package/logs/drop_index-2025-09-15.log +23 -0
- package/logs/drop_index-error-2025-09-12.log +11 -0
- package/logs/drop_index-error-2025-09-15.log +5 -0
- package/logs/find-2025-09-12.log +1050 -0
- package/logs/find-2025-09-15.log +592 -0
- package/logs/find-error-2025-09-12.log +1 -0
- package/logs/find-error-2025-09-15.log +0 -0
- package/logs/find_one-2025-09-12.log +425 -0
- package/logs/find_one-2025-09-15.log +264 -0
- package/logs/find_one-error-2025-09-12.log +5 -0
- package/logs/find_one-error-2025-09-15.log +0 -0
- package/logs/get_indexes-2025-09-12.log +84 -0
- package/logs/get_indexes-2025-09-15.log +56 -0
- package/logs/get_indexes-error-2025-09-12.log +6 -0
- package/logs/get_indexes-error-2025-09-15.log +0 -0
- package/logs/http_server-2025-09-12.log +2772 -0
- package/logs/http_server-2025-09-15.log +1276 -0
- package/logs/http_server-error-2025-09-12.log +212 -0
- package/logs/http_server-error-2025-09-15.log +44 -0
- package/logs/index_manager-2025-09-12.log +5031 -0
- package/logs/index_manager-2025-09-15.log +2909 -0
- package/logs/index_manager-error-2025-09-12.log +80 -0
- package/logs/index_manager-error-2025-09-15.log +38 -0
- package/logs/insert_one-2025-09-12.log +2181 -0
- package/logs/insert_one-2025-09-15.log +1293 -0
- package/logs/insert_one-error-2025-09-12.log +0 -0
- package/logs/insert_one-error-2025-09-15.log +0 -0
- package/logs/master-2025-09-12.log +1882 -0
- package/logs/master-2025-09-15.log +910 -0
- package/logs/master-error-2025-09-12.log +80 -0
- package/logs/master-error-2025-09-15.log +0 -0
- package/logs/operation_dispatcher-2025-09-12.log +751 -0
- package/logs/operation_dispatcher-2025-09-15.log +359 -0
- package/logs/operation_dispatcher-error-2025-09-12.log +33 -0
- package/logs/operation_dispatcher-error-2025-09-15.log +11 -0
- package/logs/performance_monitor-2025-09-12.log +14889 -0
- package/logs/performance_monitor-2025-09-15.log +6803 -0
- package/logs/performance_monitor-error-2025-09-12.log +0 -0
- package/logs/performance_monitor-error-2025-09-15.log +0 -0
- package/logs/query_engine-2025-09-12.log +5310 -0
- package/logs/query_engine-2025-09-15.log +2639 -0
- package/logs/query_engine-error-2025-09-12.log +0 -0
- package/logs/query_engine-error-2025-09-15.log +0 -0
- package/logs/recovery_manager-2025-09-12.log +462 -0
- package/logs/recovery_manager-2025-09-15.log +210 -0
- package/logs/recovery_manager-error-2025-09-12.log +22 -0
- package/logs/recovery_manager-error-2025-09-15.log +10 -0
- package/logs/replication-2025-09-12.log +1923 -0
- package/logs/replication-2025-09-15.log +917 -0
- package/logs/replication-error-2025-09-12.log +33 -0
- package/logs/replication-error-2025-09-15.log +15 -0
- package/logs/server-2025-09-12.log +2601 -0
- package/logs/server-2025-09-15.log +1191 -0
- package/logs/server-error-2025-09-12.log +0 -0
- package/logs/server-error-2025-09-15.log +0 -0
- package/logs/tcp_protocol-2025-09-12.log +22 -0
- package/logs/tcp_protocol-2025-09-15.log +10 -0
- package/logs/tcp_protocol-error-2025-09-12.log +22 -0
- package/logs/tcp_protocol-error-2025-09-15.log +10 -0
- package/logs/test-2025-09-12.log +0 -0
- package/logs/test-2025-09-15.log +0 -0
- package/logs/test-error-2025-09-12.log +0 -0
- package/logs/test-error-2025-09-15.log +0 -0
- package/logs/update_one-2025-09-12.log +173 -0
- package/logs/update_one-2025-09-15.log +118 -0
- package/logs/update_one-error-2025-09-12.log +0 -0
- package/logs/update_one-error-2025-09-15.log +0 -0
- package/logs/worker-2025-09-12.log +1457 -0
- package/logs/worker-2025-09-15.log +695 -0
- package/logs/worker-error-2025-09-12.log +0 -0
- package/logs/worker-error-2025-09-15.log +0 -0
- package/logs/write_forwarder-2025-09-12.log +1956 -0
- package/logs/write_forwarder-2025-09-15.log +932 -0
- package/logs/write_forwarder-error-2025-09-12.log +66 -0
- package/logs/write_forwarder-error-2025-09-15.log +30 -0
- package/logs/write_queue-2025-09-12.log +612 -0
- package/logs/write_queue-2025-09-15.log +301 -0
- package/logs/write_queue-error-2025-09-12.log +184 -0
- package/logs/write_queue-error-2025-09-15.log +83 -0
- package/package.json +48 -0
- package/prompts/01-core-infrastructure.md +56 -0
- package/prompts/02-secondary-indexing.md +65 -0
- package/prompts/03-write-serialization.md +63 -0
- package/prompts/04-enhanced-authentication.md +75 -0
- package/prompts/05-comprehensive-admin-operations.md +75 -0
- package/prompts/06-backup-and-restore-system.md +106 -0
- package/prompts/07-production-safety-features.md +107 -0
- package/prompts/08-tcp-client-library.md +121 -0
- package/prompts/09-api-method-chaining.md +134 -0
- package/prompts/10-automatic-index-creation.md +223 -0
- package/prompts/11-operation-naming-consistency.md +268 -0
- package/prompts/12-tcp-replication-system.md +333 -0
- package/prompts/13-master-read-write-operations.md +57 -0
- package/prompts/14-index-upsert-operations.md +68 -0
- package/prompts/15-client-api-return-types.md +81 -0
- package/prompts/16-server-setup-ui.md +97 -0
- package/prompts/17-emergency-password-change.md +108 -0
- package/prompts/18-joystick-framework-integration.md +116 -0
- package/prompts/19-api-key-authentication-system.md +137 -0
- package/prompts/20-configurable-server-port.md +105 -0
- package/prompts/21-multi-database-support.md +161 -0
- package/prompts/FULL_TEXT_SEARCH.md +293 -0
- package/prompts/PROMPTS.md +158 -0
- package/prompts/README.md +221 -0
- package/prompts/TYPESCRIPT_GENERATION.md +179 -0
- package/src/client/database.js +166 -0
- package/src/client/index.js +752 -0
- package/src/server/cluster/index.js +53 -0
- package/src/server/cluster/master.js +774 -0
- package/src/server/cluster/worker.js +537 -0
- package/src/server/index.js +540 -0
- package/src/server/lib/api_key_manager.js +473 -0
- package/src/server/lib/auth_manager.js +375 -0
- package/src/server/lib/auto_index_manager.js +681 -0
- package/src/server/lib/backup_manager.js +650 -0
- package/src/server/lib/connection_manager.js +218 -0
- package/src/server/lib/disk_utils.js +118 -0
- package/src/server/lib/http_server.js +1165 -0
- package/src/server/lib/index_manager.js +756 -0
- package/src/server/lib/load_settings.js +143 -0
- package/src/server/lib/logger.js +135 -0
- package/src/server/lib/op_types.js +29 -0
- package/src/server/lib/operation_dispatcher.js +268 -0
- package/src/server/lib/operations/admin.js +808 -0
- package/src/server/lib/operations/bulk_write.js +367 -0
- package/src/server/lib/operations/create_index.js +68 -0
- package/src/server/lib/operations/delete_one.js +114 -0
- package/src/server/lib/operations/drop_index.js +58 -0
- package/src/server/lib/operations/find.js +340 -0
- package/src/server/lib/operations/find_one.js +319 -0
- package/src/server/lib/operations/get_indexes.js +52 -0
- package/src/server/lib/operations/insert_one.js +113 -0
- package/src/server/lib/operations/update_one.js +225 -0
- package/src/server/lib/performance_monitor.js +313 -0
- package/src/server/lib/query_engine.js +243 -0
- package/src/server/lib/recovery_manager.js +388 -0
- package/src/server/lib/replication_manager.js +727 -0
- package/src/server/lib/safe_json_parse.js +21 -0
- package/src/server/lib/send_response.js +47 -0
- package/src/server/lib/tcp_protocol.js +130 -0
- package/src/server/lib/write_forwarder.js +636 -0
- package/src/server/lib/write_queue.js +335 -0
- package/test_data/data.mdb +0 -0
- package/test_data/lock.mdb +0 -0
- package/tests/client/index.test.js +1232 -0
- package/tests/server/cluster/cluster.test.js +248 -0
- package/tests/server/cluster/master_read_write_operations.test.js +577 -0
- package/tests/server/index.test.js +651 -0
- package/tests/server/integration/authentication_integration.test.js +294 -0
- package/tests/server/integration/auto_indexing_integration.test.js +268 -0
- package/tests/server/integration/backup_integration.test.js +513 -0
- package/tests/server/integration/indexing_integration.test.js +126 -0
- package/tests/server/integration/production_safety_integration.test.js +358 -0
- package/tests/server/integration/replication_integration.test.js +227 -0
- package/tests/server/integration/write_serialization_integration.test.js +246 -0
- package/tests/server/lib/api_key_manager.test.js +516 -0
- package/tests/server/lib/auth_manager.test.js +317 -0
- package/tests/server/lib/auto_index_manager.test.js +275 -0
- package/tests/server/lib/backup_manager.test.js +238 -0
- package/tests/server/lib/connection_manager.test.js +221 -0
- package/tests/server/lib/disk_utils.test.js +63 -0
- package/tests/server/lib/http_server.test.js +389 -0
- package/tests/server/lib/index_manager.test.js +301 -0
- package/tests/server/lib/load_settings.test.js +107 -0
- package/tests/server/lib/load_settings_port_config.test.js +243 -0
- package/tests/server/lib/logger.test.js +282 -0
- package/tests/server/lib/operations/admin.test.js +638 -0
- package/tests/server/lib/operations/bulk_write.test.js +128 -0
- package/tests/server/lib/operations/create_index.test.js +138 -0
- package/tests/server/lib/operations/delete_one.test.js +52 -0
- package/tests/server/lib/operations/drop_index.test.js +72 -0
- package/tests/server/lib/operations/find.test.js +93 -0
- package/tests/server/lib/operations/find_one.test.js +91 -0
- package/tests/server/lib/operations/get_indexes.test.js +87 -0
- package/tests/server/lib/operations/insert_one.test.js +42 -0
- package/tests/server/lib/operations/update_one.test.js +89 -0
- package/tests/server/lib/performance_monitor.test.js +185 -0
- package/tests/server/lib/query_engine.test.js +46 -0
- package/tests/server/lib/recovery_manager.test.js +414 -0
- package/tests/server/lib/replication_manager.test.js +202 -0
- package/tests/server/lib/safe_json_parse.test.js +45 -0
- package/tests/server/lib/send_response.test.js +155 -0
- package/tests/server/lib/tcp_protocol.test.js +169 -0
- package/tests/server/lib/write_forwarder.test.js +258 -0
- package/tests/server/lib/write_queue.test.js +255 -0
- package/tsconfig.json +30 -0
- package/types/client/index.d.ts +447 -0
- package/types/server/cluster/index.d.ts +28 -0
- package/types/server/cluster/master.d.ts +115 -0
- package/types/server/cluster/worker.d.ts +1 -0
- package/types/server/lib/auth_manager.d.ts +13 -0
- package/types/server/lib/backup_manager.d.ts +43 -0
- package/types/server/lib/connection_manager.d.ts +15 -0
- package/types/server/lib/disk_utils.d.ts +3 -0
- package/types/server/lib/index_manager.d.ts +24 -0
- package/types/server/lib/load_settings.d.ts +4 -0
- package/types/server/lib/logger.d.ts +44 -0
- package/types/server/lib/op_types.d.ts +6 -0
- package/types/server/lib/performance_monitor.d.ts +68 -0
- package/types/server/lib/query_engine.d.ts +10 -0
- package/types/server/lib/safe_json_parse.d.ts +7 -0
- package/types/server/lib/send_response.d.ts +3 -0
- package/types/server/lib/tcp_protocol.d.ts +12 -0
- package/types/server/lib/write_queue.d.ts +2 -0
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HTTP server for JoystickDB setup and emergency recovery operations.
|
|
3
|
+
* Provides web-based interfaces for initial database setup with authentication configuration
|
|
4
|
+
* and emergency password recovery. Includes rate limiting, token validation, and comprehensive
|
|
5
|
+
* HTML interfaces for secure database administration without requiring TCP client access.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
import create_logger from './logger.js';
|
|
12
|
+
import { setup_authentication, get_auth_stats } from './auth_manager.js';
|
|
13
|
+
import {
|
|
14
|
+
is_token_valid,
|
|
15
|
+
record_failed_recovery_attempt,
|
|
16
|
+
change_password,
|
|
17
|
+
} from './recovery_manager.js';
|
|
18
|
+
import {
|
|
19
|
+
validate_api_key,
|
|
20
|
+
create_user,
|
|
21
|
+
get_all_users,
|
|
22
|
+
get_user,
|
|
23
|
+
update_user,
|
|
24
|
+
delete_user,
|
|
25
|
+
check_admin_user_exists
|
|
26
|
+
} from './api_key_manager.js';
|
|
27
|
+
|
|
28
|
+
const { create_context_logger } = create_logger('http_server');
|
|
29
|
+
const log = create_context_logger();
|
|
30
|
+
|
|
31
|
+
/** @type {http.Server|null} HTTP server instance for setup and recovery operations */
|
|
32
|
+
let http_server = null;
|
|
33
|
+
|
|
34
|
+
/** @type {string|null} Secure token for setup authentication */
|
|
35
|
+
let setup_token = null;
|
|
36
|
+
|
|
37
|
+
/** @type {boolean} Flag indicating if initial setup has been completed */
|
|
38
|
+
let setup_completed = false;
|
|
39
|
+
|
|
40
|
+
/** @type {Map<string, Array<number>>} Rate limiting tracking by IP address */
|
|
41
|
+
let rate_limit_attempts = new Map();
|
|
42
|
+
|
|
43
|
+
/** @type {number} Rate limiting window in milliseconds (1 minute) */
|
|
44
|
+
const RATE_LIMIT_WINDOW = 60 * 1000;
|
|
45
|
+
|
|
46
|
+
/** @type {number} Maximum setup attempts per IP within rate limit window */
|
|
47
|
+
const MAX_SETUP_ATTEMPTS = 10;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generates a secure random UUID token for setup authentication.
|
|
51
|
+
* Uses Node.js crypto.randomUUID() for cryptographically secure token generation.
|
|
52
|
+
* @returns {string} Secure UUID token for setup authentication
|
|
53
|
+
*/
|
|
54
|
+
const generate_setup_token = () => {
|
|
55
|
+
return crypto.randomUUID();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines if initial database setup is required.
|
|
60
|
+
* Checks authentication configuration status to determine if setup interface should be available.
|
|
61
|
+
* @returns {boolean} True if setup is required, false if already configured
|
|
62
|
+
*/
|
|
63
|
+
const is_setup_required = () => {
|
|
64
|
+
const auth_stats = get_auth_stats();
|
|
65
|
+
return !auth_stats.configured;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Checks if an IP address is currently rate limited for setup attempts.
|
|
70
|
+
* Filters out expired attempts and updates the tracking map with recent attempts only.
|
|
71
|
+
* @param {string} ip - IP address to check for rate limiting
|
|
72
|
+
* @returns {boolean} True if IP is rate limited, false otherwise
|
|
73
|
+
*/
|
|
74
|
+
const is_rate_limited = (ip) => {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const attempts = rate_limit_attempts.get(ip) || [];
|
|
77
|
+
|
|
78
|
+
const recent_attempts = attempts.filter(timestamp => now - timestamp < RATE_LIMIT_WINDOW);
|
|
79
|
+
rate_limit_attempts.set(ip, recent_attempts);
|
|
80
|
+
|
|
81
|
+
return recent_attempts.length >= MAX_SETUP_ATTEMPTS;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Records a setup attempt for rate limiting purposes.
|
|
86
|
+
* Adds current timestamp to the IP's attempt history for rate limiting calculations.
|
|
87
|
+
* @param {string} ip - IP address making the setup attempt
|
|
88
|
+
* @returns {void}
|
|
89
|
+
*/
|
|
90
|
+
const record_setup_attempt = (ip) => {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const attempts = rate_limit_attempts.get(ip) || [];
|
|
93
|
+
attempts.push(now);
|
|
94
|
+
rate_limit_attempts.set(ip, attempts);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates HTML content for the database setup interface.
|
|
99
|
+
* Generates a complete HTML page with setup form, instructions, and error display.
|
|
100
|
+
* @param {string} token - Setup authentication token to include in form
|
|
101
|
+
* @param {string|null} [error=null] - Error message to display, if any
|
|
102
|
+
* @returns {string} Complete HTML page content for setup interface
|
|
103
|
+
*/
|
|
104
|
+
const create_setup_html = (token, error = null) => {
|
|
105
|
+
const error_html = error ? `<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffe6e6;">${error}</div>` : '';
|
|
106
|
+
|
|
107
|
+
return `<!DOCTYPE html>
|
|
108
|
+
<html>
|
|
109
|
+
<head>
|
|
110
|
+
<title>JoystickDB Setup</title>
|
|
111
|
+
<meta charset="utf-8">
|
|
112
|
+
<style>
|
|
113
|
+
body {
|
|
114
|
+
font-family: Arial, sans-serif;
|
|
115
|
+
max-width: 600px;
|
|
116
|
+
margin: 50px auto;
|
|
117
|
+
padding: 20px;
|
|
118
|
+
line-height: 1.6;
|
|
119
|
+
}
|
|
120
|
+
.container {
|
|
121
|
+
border: 1px solid #ddd;
|
|
122
|
+
padding: 30px;
|
|
123
|
+
border-radius: 5px;
|
|
124
|
+
background: #f9f9f9;
|
|
125
|
+
}
|
|
126
|
+
.button {
|
|
127
|
+
background: #007bff;
|
|
128
|
+
color: white;
|
|
129
|
+
padding: 10px 20px;
|
|
130
|
+
border: none;
|
|
131
|
+
border-radius: 3px;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-size: 16px;
|
|
134
|
+
}
|
|
135
|
+
.button:hover {
|
|
136
|
+
background: #0056b3;
|
|
137
|
+
}
|
|
138
|
+
.code {
|
|
139
|
+
background: #f4f4f4;
|
|
140
|
+
padding: 2px 6px;
|
|
141
|
+
border-radius: 3px;
|
|
142
|
+
font-family: monospace;
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
145
|
+
</head>
|
|
146
|
+
<body>
|
|
147
|
+
<div class="container">
|
|
148
|
+
<h1>JoystickDB Setup</h1>
|
|
149
|
+
${error_html}
|
|
150
|
+
<p>Your JoystickDB server needs to be configured with authentication before it can be used.</p>
|
|
151
|
+
<p>Click the button below to generate a secure password and complete the setup process.</p>
|
|
152
|
+
|
|
153
|
+
<form method="POST" action="/setup?token=${token}">
|
|
154
|
+
<button type="submit" class="button">Setup JoystickDB</button>
|
|
155
|
+
</form>
|
|
156
|
+
|
|
157
|
+
<hr style="margin: 30px 0;">
|
|
158
|
+
|
|
159
|
+
<h3>What happens during setup?</h3>
|
|
160
|
+
<ul>
|
|
161
|
+
<li>A secure random password will be generated</li>
|
|
162
|
+
<li>Authentication configuration will be saved to <span class="code">settings.db.json</span></li>
|
|
163
|
+
<li>You'll receive the password to use with your client connections</li>
|
|
164
|
+
</ul>
|
|
165
|
+
|
|
166
|
+
<h3>After setup:</h3>
|
|
167
|
+
<ul>
|
|
168
|
+
<li>Set the environment variable: <span class="code">JOYSTICKDB_PASSWORD=<your_password></span></li>
|
|
169
|
+
<li>Connect your client to TCP port 1983</li>
|
|
170
|
+
<li>This setup interface will no longer be accessible</li>
|
|
171
|
+
</ul>
|
|
172
|
+
</div>
|
|
173
|
+
</body>
|
|
174
|
+
</html>`;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates HTML content for successful setup completion.
|
|
179
|
+
* Displays the generated password and provides instructions for client connection.
|
|
180
|
+
* @param {string} password - Generated password to display to user
|
|
181
|
+
* @returns {string} Complete HTML page content for setup success
|
|
182
|
+
*/
|
|
183
|
+
const create_success_html = (password) => {
|
|
184
|
+
return `<!DOCTYPE html>
|
|
185
|
+
<html>
|
|
186
|
+
<head>
|
|
187
|
+
<title>JoystickDB Setup Complete</title>
|
|
188
|
+
<meta charset="utf-8">
|
|
189
|
+
<style>
|
|
190
|
+
body {
|
|
191
|
+
font-family: Arial, sans-serif;
|
|
192
|
+
max-width: 600px;
|
|
193
|
+
margin: 50px auto;
|
|
194
|
+
padding: 20px;
|
|
195
|
+
line-height: 1.6;
|
|
196
|
+
}
|
|
197
|
+
.container {
|
|
198
|
+
border: 1px solid #28a745;
|
|
199
|
+
padding: 30px;
|
|
200
|
+
border-radius: 5px;
|
|
201
|
+
background: #d4edda;
|
|
202
|
+
}
|
|
203
|
+
.password {
|
|
204
|
+
background: #f8f9fa;
|
|
205
|
+
padding: 15px;
|
|
206
|
+
border: 1px solid #dee2e6;
|
|
207
|
+
border-radius: 5px;
|
|
208
|
+
font-family: monospace;
|
|
209
|
+
font-size: 18px;
|
|
210
|
+
font-weight: bold;
|
|
211
|
+
text-align: center;
|
|
212
|
+
margin: 20px 0;
|
|
213
|
+
word-break: break-all;
|
|
214
|
+
}
|
|
215
|
+
.code {
|
|
216
|
+
background: #f4f4f4;
|
|
217
|
+
padding: 2px 6px;
|
|
218
|
+
border-radius: 3px;
|
|
219
|
+
font-family: monospace;
|
|
220
|
+
}
|
|
221
|
+
.warning {
|
|
222
|
+
background: #fff3cd;
|
|
223
|
+
border: 1px solid #ffeaa7;
|
|
224
|
+
padding: 15px;
|
|
225
|
+
border-radius: 5px;
|
|
226
|
+
margin: 20px 0;
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
229
|
+
</head>
|
|
230
|
+
<body>
|
|
231
|
+
<div class="container">
|
|
232
|
+
<h1>✅ Setup Completed Successfully!</h1>
|
|
233
|
+
|
|
234
|
+
<div class="warning">
|
|
235
|
+
<strong>⚠️ Important:</strong> Save this password immediately. It will not be shown again.
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<h3>Generated Password:</h3>
|
|
239
|
+
<div class="password">${password}</div>
|
|
240
|
+
|
|
241
|
+
<h3>Next Steps:</h3>
|
|
242
|
+
<ol>
|
|
243
|
+
<li>
|
|
244
|
+
<strong>Set environment variable:</strong><br>
|
|
245
|
+
<span class="code">JOYSTICKDB_PASSWORD=${password}</span>
|
|
246
|
+
</li>
|
|
247
|
+
<li>
|
|
248
|
+
<strong>Restart your application</strong> to use the new password
|
|
249
|
+
</li>
|
|
250
|
+
<li>
|
|
251
|
+
<strong>Connect using the TCP client on port 1983</strong>
|
|
252
|
+
</li>
|
|
253
|
+
</ol>
|
|
254
|
+
|
|
255
|
+
<h3>Client Connection Example:</h3>
|
|
256
|
+
<pre style="background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto;">
|
|
257
|
+
import joystickdb from '@joystickdb/client';
|
|
258
|
+
|
|
259
|
+
const client = joystickdb.client({
|
|
260
|
+
host: 'localhost',
|
|
261
|
+
port: 1983,
|
|
262
|
+
password: '${password}'
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
await client.ping();
|
|
266
|
+
</pre>
|
|
267
|
+
|
|
268
|
+
<h3>Configuration Details:</h3>
|
|
269
|
+
<ul>
|
|
270
|
+
<li>Configuration saved to: <span class="code">settings.db.json</span></li>
|
|
271
|
+
<li>TCP server running on port: <span class="code">1983</span></li>
|
|
272
|
+
<li>This setup interface is now disabled</li>
|
|
273
|
+
</ul>
|
|
274
|
+
|
|
275
|
+
<p><strong>Your JoystickDB server is ready to use!</strong></p>
|
|
276
|
+
</div>
|
|
277
|
+
</body>
|
|
278
|
+
</html>`;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creates HTML content for error display.
|
|
283
|
+
* Generates an error page with troubleshooting information and navigation options.
|
|
284
|
+
* @param {string} error_message - Error message to display to user
|
|
285
|
+
* @returns {string} Complete HTML page content for error display
|
|
286
|
+
*/
|
|
287
|
+
const create_error_html = (error_message) => {
|
|
288
|
+
return `<!DOCTYPE html>
|
|
289
|
+
<html>
|
|
290
|
+
<head>
|
|
291
|
+
<title>JoystickDB Setup Error</title>
|
|
292
|
+
<meta charset="utf-8">
|
|
293
|
+
<style>
|
|
294
|
+
body {
|
|
295
|
+
font-family: Arial, sans-serif;
|
|
296
|
+
max-width: 600px;
|
|
297
|
+
margin: 50px auto;
|
|
298
|
+
padding: 20px;
|
|
299
|
+
line-height: 1.6;
|
|
300
|
+
}
|
|
301
|
+
.container {
|
|
302
|
+
border: 1px solid #dc3545;
|
|
303
|
+
padding: 30px;
|
|
304
|
+
border-radius: 5px;
|
|
305
|
+
background: #f8d7da;
|
|
306
|
+
}
|
|
307
|
+
.code {
|
|
308
|
+
background: #f4f4f4;
|
|
309
|
+
padding: 2px 6px;
|
|
310
|
+
border-radius: 3px;
|
|
311
|
+
font-family: monospace;
|
|
312
|
+
}
|
|
313
|
+
</style>
|
|
314
|
+
</head>
|
|
315
|
+
<body>
|
|
316
|
+
<div class="container">
|
|
317
|
+
<h1>❌ Setup Error</h1>
|
|
318
|
+
<p><strong>Error:</strong> ${error_message}</p>
|
|
319
|
+
|
|
320
|
+
<h3>Troubleshooting:</h3>
|
|
321
|
+
<ul>
|
|
322
|
+
<li>If authentication is already configured, delete the <span class="code">authentication</span> section from <span class="code">settings.db.json</span></li>
|
|
323
|
+
<li>Ensure the server has write permissions to the current directory</li>
|
|
324
|
+
<li>Check server logs for additional error details</li>
|
|
325
|
+
<li>Restart the server and try again</li>
|
|
326
|
+
</ul>
|
|
327
|
+
|
|
328
|
+
<p><a href="javascript:history.back()">← Go Back</a></p>
|
|
329
|
+
</div>
|
|
330
|
+
</body>
|
|
331
|
+
</html>`;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Creates HTML content for emergency password recovery interface.
|
|
336
|
+
* Generates a password change form with validation, warnings, and instructions.
|
|
337
|
+
* @param {string} token - Recovery authentication token to include in form
|
|
338
|
+
* @param {string|null} [error=null] - Error message to display, if any
|
|
339
|
+
* @returns {string} Complete HTML page content for password recovery
|
|
340
|
+
*/
|
|
341
|
+
const create_recovery_html = (token, error = null) => {
|
|
342
|
+
const error_html = error ? `<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffe6e6;">${error}</div>` : '';
|
|
343
|
+
|
|
344
|
+
return `<!DOCTYPE html>
|
|
345
|
+
<html>
|
|
346
|
+
<head>
|
|
347
|
+
<title>JoystickDB Emergency Password Recovery</title>
|
|
348
|
+
<meta charset="utf-8">
|
|
349
|
+
<style>
|
|
350
|
+
body {
|
|
351
|
+
font-family: Arial, sans-serif;
|
|
352
|
+
max-width: 600px;
|
|
353
|
+
margin: 50px auto;
|
|
354
|
+
padding: 20px;
|
|
355
|
+
line-height: 1.6;
|
|
356
|
+
}
|
|
357
|
+
.container {
|
|
358
|
+
border: 1px solid #ffc107;
|
|
359
|
+
padding: 30px;
|
|
360
|
+
border-radius: 5px;
|
|
361
|
+
background: #fff3cd;
|
|
362
|
+
}
|
|
363
|
+
.button {
|
|
364
|
+
background: #dc3545;
|
|
365
|
+
color: white;
|
|
366
|
+
padding: 10px 20px;
|
|
367
|
+
border: none;
|
|
368
|
+
border-radius: 3px;
|
|
369
|
+
cursor: pointer;
|
|
370
|
+
font-size: 16px;
|
|
371
|
+
}
|
|
372
|
+
.button:hover {
|
|
373
|
+
background: #c82333;
|
|
374
|
+
}
|
|
375
|
+
.form-group {
|
|
376
|
+
margin-bottom: 20px;
|
|
377
|
+
}
|
|
378
|
+
.form-group label {
|
|
379
|
+
display: block;
|
|
380
|
+
margin-bottom: 5px;
|
|
381
|
+
font-weight: bold;
|
|
382
|
+
}
|
|
383
|
+
.form-group input {
|
|
384
|
+
width: 100%;
|
|
385
|
+
padding: 10px;
|
|
386
|
+
border: 1px solid #ddd;
|
|
387
|
+
border-radius: 3px;
|
|
388
|
+
font-size: 16px;
|
|
389
|
+
box-sizing: border-box;
|
|
390
|
+
}
|
|
391
|
+
.warning {
|
|
392
|
+
background: #f8d7da;
|
|
393
|
+
border: 1px solid #f5c6cb;
|
|
394
|
+
padding: 15px;
|
|
395
|
+
border-radius: 5px;
|
|
396
|
+
margin: 20px 0;
|
|
397
|
+
}
|
|
398
|
+
.code {
|
|
399
|
+
background: #f4f4f4;
|
|
400
|
+
padding: 2px 6px;
|
|
401
|
+
border-radius: 3px;
|
|
402
|
+
font-family: monospace;
|
|
403
|
+
}
|
|
404
|
+
</style>
|
|
405
|
+
<script>
|
|
406
|
+
function validateForm() {
|
|
407
|
+
const password = document.getElementById('password').value;
|
|
408
|
+
const confirmPassword = document.getElementById('confirm_password').value;
|
|
409
|
+
|
|
410
|
+
if (password.length < 12) {
|
|
411
|
+
alert('Password must be at least 12 characters long');
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (password !== confirmPassword) {
|
|
416
|
+
alert('Passwords do not match');
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return confirm('Are you sure you want to change the database password? This will disconnect all existing clients.');
|
|
421
|
+
}
|
|
422
|
+
</script>
|
|
423
|
+
</head>
|
|
424
|
+
<body>
|
|
425
|
+
<div class="container">
|
|
426
|
+
<h1>🚨 Emergency Password Recovery</h1>
|
|
427
|
+
${error_html}
|
|
428
|
+
|
|
429
|
+
<div class="warning">
|
|
430
|
+
<strong>⚠️ Warning:</strong> This will change your database password and disconnect all existing clients.
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<p>Enter a new password for your JoystickDB server. The password must be at least 12 characters long.</p>
|
|
434
|
+
|
|
435
|
+
<form method="POST" action="/recovery?token=${token}" onsubmit="return validateForm()">
|
|
436
|
+
<div class="form-group">
|
|
437
|
+
<label for="password">New Password:</label>
|
|
438
|
+
<input type="password" id="password" name="password" required minlength="12" placeholder="Enter new password (minimum 12 characters)">
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div class="form-group">
|
|
442
|
+
<label for="confirm_password">Confirm Password:</label>
|
|
443
|
+
<input type="password" id="confirm_password" name="confirm_password" required minlength="12" placeholder="Confirm new password">
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<button type="submit" class="button">Change Password</button>
|
|
447
|
+
</form>
|
|
448
|
+
|
|
449
|
+
<hr style="margin: 30px 0;">
|
|
450
|
+
|
|
451
|
+
<h3>What happens when you change the password?</h3>
|
|
452
|
+
<ul>
|
|
453
|
+
<li>The new password will be hashed and stored securely</li>
|
|
454
|
+
<li>All existing client connections will be terminated</li>
|
|
455
|
+
<li>Failed authentication attempts will be reset</li>
|
|
456
|
+
<li>This recovery token will be invalidated</li>
|
|
457
|
+
</ul>
|
|
458
|
+
|
|
459
|
+
<h3>After password change:</h3>
|
|
460
|
+
<ul>
|
|
461
|
+
<li>Update your environment variable: <span class="code">JOYSTICKDB_PASSWORD=<new_password></span></li>
|
|
462
|
+
<li>Restart or redeploy your applications</li>
|
|
463
|
+
<li>Monitor logs for any unauthorized access attempts</li>
|
|
464
|
+
</ul>
|
|
465
|
+
</div>
|
|
466
|
+
</body>
|
|
467
|
+
</html>`;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Creates HTML content for successful password recovery completion.
|
|
472
|
+
* Displays confirmation and provides instructions for updating client applications.
|
|
473
|
+
* @param {string} timestamp - ISO timestamp of password change completion
|
|
474
|
+
* @returns {string} Complete HTML page content for recovery success
|
|
475
|
+
*/
|
|
476
|
+
const create_recovery_success_html = (timestamp) => {
|
|
477
|
+
return `<!DOCTYPE html>
|
|
478
|
+
<html>
|
|
479
|
+
<head>
|
|
480
|
+
<title>JoystickDB Password Changed</title>
|
|
481
|
+
<meta charset="utf-8">
|
|
482
|
+
<style>
|
|
483
|
+
body {
|
|
484
|
+
font-family: Arial, sans-serif;
|
|
485
|
+
max-width: 600px;
|
|
486
|
+
margin: 50px auto;
|
|
487
|
+
padding: 20px;
|
|
488
|
+
line-height: 1.6;
|
|
489
|
+
}
|
|
490
|
+
.container {
|
|
491
|
+
border: 1px solid #28a745;
|
|
492
|
+
padding: 30px;
|
|
493
|
+
border-radius: 5px;
|
|
494
|
+
background: #d4edda;
|
|
495
|
+
}
|
|
496
|
+
.code {
|
|
497
|
+
background: #f4f4f4;
|
|
498
|
+
padding: 2px 6px;
|
|
499
|
+
border-radius: 3px;
|
|
500
|
+
font-family: monospace;
|
|
501
|
+
}
|
|
502
|
+
.warning {
|
|
503
|
+
background: #fff3cd;
|
|
504
|
+
border: 1px solid #ffeaa7;
|
|
505
|
+
padding: 15px;
|
|
506
|
+
border-radius: 5px;
|
|
507
|
+
margin: 20px 0;
|
|
508
|
+
}
|
|
509
|
+
</style>
|
|
510
|
+
</head>
|
|
511
|
+
<body>
|
|
512
|
+
<div class="container">
|
|
513
|
+
<h1>✅ Password Changed Successfully!</h1>
|
|
514
|
+
|
|
515
|
+
<p>New password is now active.</p>
|
|
516
|
+
<p>All existing connections have been terminated.</p>
|
|
517
|
+
|
|
518
|
+
<div class="warning">
|
|
519
|
+
<strong>⚠️ Important:</strong> Update your applications immediately to use the new password.
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<h3>Next steps:</h3>
|
|
523
|
+
<ol>
|
|
524
|
+
<li>
|
|
525
|
+
<strong>Update your app's environment settings</strong> so the client can connect using your new password:<br>
|
|
526
|
+
<span class="code">JOYSTICKDB_PASSWORD=<your_new_password></span>
|
|
527
|
+
</li>
|
|
528
|
+
<li>
|
|
529
|
+
<strong>Restart or redeploy your applications</strong> to use new password
|
|
530
|
+
</li>
|
|
531
|
+
<li>
|
|
532
|
+
<strong>Monitor logs</strong> for any unauthorized access attempts
|
|
533
|
+
</li>
|
|
534
|
+
</ol>
|
|
535
|
+
|
|
536
|
+
<h3>Recovery Details:</h3>
|
|
537
|
+
<ul>
|
|
538
|
+
<li>Recovery completed at: <span class="code">${timestamp}</span></li>
|
|
539
|
+
<li>All failed authentication attempts have been reset</li>
|
|
540
|
+
<li>Recovery token has been invalidated</li>
|
|
541
|
+
<li>TCP server continues running on port 1983</li>
|
|
542
|
+
</ul>
|
|
543
|
+
|
|
544
|
+
<p><strong>Your JoystickDB server is ready with the new password!</strong></p>
|
|
545
|
+
</div>
|
|
546
|
+
</body>
|
|
547
|
+
</html>`;
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Parses URL-encoded form data from HTTP request body.
|
|
552
|
+
* Streams request body data and parses it into a key-value object using URLSearchParams.
|
|
553
|
+
* @param {http.IncomingMessage} req - HTTP request object with form data
|
|
554
|
+
* @returns {Promise<Object>} Parsed form data as key-value pairs
|
|
555
|
+
* @throws {Error} When form data parsing fails
|
|
556
|
+
*/
|
|
557
|
+
const parse_form_data = (req) => {
|
|
558
|
+
return new Promise((resolve, reject) => {
|
|
559
|
+
let body = '';
|
|
560
|
+
|
|
561
|
+
req.on('data', (chunk) => {
|
|
562
|
+
body += chunk.toString();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
req.on('end', () => {
|
|
566
|
+
try {
|
|
567
|
+
const params = new URLSearchParams(body);
|
|
568
|
+
const form_data = {};
|
|
569
|
+
for (const [key, value] of params) {
|
|
570
|
+
form_data[key] = value;
|
|
571
|
+
}
|
|
572
|
+
resolve(form_data);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
reject(error);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
req.on('error', (error) => {
|
|
579
|
+
reject(error);
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Parses JSON data from HTTP request body.
|
|
586
|
+
* Streams request body data and parses it as JSON.
|
|
587
|
+
* @param {http.IncomingMessage} req - HTTP request object with JSON data
|
|
588
|
+
* @returns {Promise<Object>} Parsed JSON data
|
|
589
|
+
* @throws {Error} When JSON parsing fails
|
|
590
|
+
*/
|
|
591
|
+
const parse_json_data = (req) => {
|
|
592
|
+
return new Promise((resolve, reject) => {
|
|
593
|
+
let body = '';
|
|
594
|
+
|
|
595
|
+
req.on('data', (chunk) => {
|
|
596
|
+
body += chunk.toString();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
req.on('end', () => {
|
|
600
|
+
try {
|
|
601
|
+
const json_data = JSON.parse(body);
|
|
602
|
+
resolve(json_data);
|
|
603
|
+
} catch (error) {
|
|
604
|
+
reject(error);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
req.on('error', (error) => {
|
|
609
|
+
reject(error);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Validates API key from request headers.
|
|
616
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
617
|
+
* @returns {boolean} True if API key is valid
|
|
618
|
+
*/
|
|
619
|
+
const validate_request_api_key = (req) => {
|
|
620
|
+
const api_key = req.headers['x-joystick-db-api-key'];
|
|
621
|
+
return validate_api_key(api_key);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Sends JSON response with appropriate headers.
|
|
626
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
627
|
+
* @param {number} status_code - HTTP status code
|
|
628
|
+
* @param {Object} data - Data to send as JSON
|
|
629
|
+
*/
|
|
630
|
+
const send_json_response = (res, status_code, data) => {
|
|
631
|
+
res.writeHead(status_code, { 'Content-Type': 'application/json' });
|
|
632
|
+
res.end(JSON.stringify(data));
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Handles user creation API endpoint.
|
|
637
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
638
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
639
|
+
* @returns {Promise<void>}
|
|
640
|
+
*/
|
|
641
|
+
const handle_create_user = async (req, res) => {
|
|
642
|
+
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
643
|
+
|
|
644
|
+
if (!validate_request_api_key(req)) {
|
|
645
|
+
log.warn('Invalid API key for user creation', { client_ip });
|
|
646
|
+
send_json_response(res, 403, {
|
|
647
|
+
error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
|
|
648
|
+
});
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
const user_data = await parse_json_data(req);
|
|
654
|
+
const created_user = await create_user(user_data);
|
|
655
|
+
|
|
656
|
+
log.info('User created via API', { username: created_user.username, client_ip });
|
|
657
|
+
|
|
658
|
+
send_json_response(res, 201, {
|
|
659
|
+
ok: 1,
|
|
660
|
+
user: created_user
|
|
661
|
+
});
|
|
662
|
+
} catch (error) {
|
|
663
|
+
log.error('User creation failed via API', { client_ip, error: error.message });
|
|
664
|
+
send_json_response(res, 400, {
|
|
665
|
+
error: error.message
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Handles get all users API endpoint.
|
|
672
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
673
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
674
|
+
* @returns {Promise<void>}
|
|
675
|
+
*/
|
|
676
|
+
const handle_get_users = async (req, res) => {
|
|
677
|
+
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
678
|
+
|
|
679
|
+
if (!validate_request_api_key(req)) {
|
|
680
|
+
log.warn('Invalid API key for get users', { client_ip });
|
|
681
|
+
send_json_response(res, 403, {
|
|
682
|
+
error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
|
|
683
|
+
});
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
const users = get_all_users();
|
|
689
|
+
|
|
690
|
+
log.info('Users retrieved via API', { count: users.length, client_ip });
|
|
691
|
+
|
|
692
|
+
send_json_response(res, 200, {
|
|
693
|
+
ok: 1,
|
|
694
|
+
users: users
|
|
695
|
+
});
|
|
696
|
+
} catch (error) {
|
|
697
|
+
log.error('Get users failed via API', { client_ip, error: error.message });
|
|
698
|
+
send_json_response(res, 500, {
|
|
699
|
+
error: error.message
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Handles update user API endpoint.
|
|
706
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
707
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
708
|
+
* @param {string} username - Username to update
|
|
709
|
+
* @returns {Promise<void>}
|
|
710
|
+
*/
|
|
711
|
+
const handle_update_user = async (req, res, username) => {
|
|
712
|
+
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
713
|
+
|
|
714
|
+
if (!validate_request_api_key(req)) {
|
|
715
|
+
log.warn('Invalid API key for user update', { client_ip, username });
|
|
716
|
+
send_json_response(res, 403, {
|
|
717
|
+
error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
|
|
718
|
+
});
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
const updates = await parse_json_data(req);
|
|
724
|
+
const updated_user = await update_user(username, updates);
|
|
725
|
+
|
|
726
|
+
log.info('User updated via API', { username, client_ip });
|
|
727
|
+
|
|
728
|
+
send_json_response(res, 200, {
|
|
729
|
+
ok: 1,
|
|
730
|
+
user: updated_user
|
|
731
|
+
});
|
|
732
|
+
} catch (error) {
|
|
733
|
+
log.error('User update failed via API', { client_ip, username, error: error.message });
|
|
734
|
+
|
|
735
|
+
const status_code = error.message === 'User not found' ? 404 : 400;
|
|
736
|
+
send_json_response(res, status_code, {
|
|
737
|
+
error: error.message
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Handles delete user API endpoint.
|
|
744
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
745
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
746
|
+
* @param {string} username - Username to delete
|
|
747
|
+
* @returns {Promise<void>}
|
|
748
|
+
*/
|
|
749
|
+
const handle_delete_user = async (req, res, username) => {
|
|
750
|
+
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
751
|
+
|
|
752
|
+
if (!validate_request_api_key(req)) {
|
|
753
|
+
log.warn('Invalid API key for user deletion', { client_ip, username });
|
|
754
|
+
send_json_response(res, 403, {
|
|
755
|
+
error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
|
|
756
|
+
});
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
delete_user(username);
|
|
762
|
+
|
|
763
|
+
log.info('User deleted via API', { username, client_ip });
|
|
764
|
+
|
|
765
|
+
send_json_response(res, 200, {
|
|
766
|
+
ok: 1,
|
|
767
|
+
message: 'User deleted successfully'
|
|
768
|
+
});
|
|
769
|
+
} catch (error) {
|
|
770
|
+
log.error('User deletion failed via API', { client_ip, username, error: error.message });
|
|
771
|
+
|
|
772
|
+
const status_code = error.message === 'User not found' ? 404 : 400;
|
|
773
|
+
send_json_response(res, status_code, {
|
|
774
|
+
error: error.message
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Handles user management API requests.
|
|
781
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
782
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
783
|
+
* @param {string} path_parts - URL path parts after /api/users
|
|
784
|
+
* @returns {Promise<void>}
|
|
785
|
+
*/
|
|
786
|
+
const handle_users_api = async (req, res, path_parts) => {
|
|
787
|
+
if (req.method === 'POST' && path_parts.length === 0) {
|
|
788
|
+
await handle_create_user(req, res);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (req.method === 'GET' && path_parts.length === 0) {
|
|
793
|
+
await handle_get_users(req, res);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (req.method === 'PUT' && path_parts.length === 1) {
|
|
798
|
+
const username = path_parts[0];
|
|
799
|
+
await handle_update_user(req, res, username);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (req.method === 'DELETE' && path_parts.length === 1) {
|
|
804
|
+
const username = path_parts[0];
|
|
805
|
+
await handle_delete_user(req, res, username);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
send_json_response(res, 405, {
|
|
810
|
+
error: 'Method not allowed'
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Handles HTTP requests for database setup operations.
|
|
816
|
+
* Processes both GET (display form) and POST (execute setup) requests with rate limiting,
|
|
817
|
+
* token validation, and setup completion. Generates secure passwords and saves configuration.
|
|
818
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
819
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
820
|
+
* @param {string} token_param - Setup token from URL parameters
|
|
821
|
+
* @returns {Promise<void>}
|
|
822
|
+
*/
|
|
823
|
+
const handle_setup_request = async (req, res, token_param) => {
|
|
824
|
+
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
825
|
+
|
|
826
|
+
if (is_rate_limited(client_ip)) {
|
|
827
|
+
res.writeHead(429, { 'Content-Type': 'text/html' });
|
|
828
|
+
res.end(create_error_html('Too many setup attempts. Please try again later.'));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
record_setup_attempt(client_ip);
|
|
833
|
+
|
|
834
|
+
if (!is_setup_required()) {
|
|
835
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
836
|
+
res.end(create_error_html('Setup has already been completed.'));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (!setup_token || token_param !== setup_token) {
|
|
841
|
+
log.warn('Invalid setup token attempt', { client_ip, provided_token: token_param });
|
|
842
|
+
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
843
|
+
res.end(create_error_html('Invalid or missing setup token.'));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (req.method === 'GET') {
|
|
848
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
849
|
+
res.end(create_setup_html(setup_token));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (req.method === 'POST') {
|
|
854
|
+
try {
|
|
855
|
+
const password = setup_authentication();
|
|
856
|
+
setup_completed = true;
|
|
857
|
+
setup_token = null;
|
|
858
|
+
|
|
859
|
+
log.info('Setup completed successfully via HTTP interface', { client_ip });
|
|
860
|
+
|
|
861
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
862
|
+
res.end(create_success_html(password));
|
|
863
|
+
} catch (error) {
|
|
864
|
+
log.error('Setup failed via HTTP interface', { client_ip, error: error.message });
|
|
865
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
866
|
+
res.end(create_error_html(error.message));
|
|
867
|
+
}
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
res.writeHead(405, { 'Content-Type': 'text/html' });
|
|
872
|
+
res.end(create_error_html('Method not allowed.'));
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Handles HTTP requests for emergency password recovery operations.
|
|
877
|
+
* Processes both GET (display form) and POST (execute recovery) requests with token validation,
|
|
878
|
+
* password validation, and connection termination. Updates authentication configuration.
|
|
879
|
+
* @param {http.IncomingMessage} req - HTTP request object
|
|
880
|
+
* @param {http.ServerResponse} res - HTTP response object
|
|
881
|
+
* @param {string} token_param - Recovery token from URL parameters
|
|
882
|
+
* @returns {Promise<void>}
|
|
883
|
+
*/
|
|
884
|
+
const handle_recovery_request = async (req, res, token_param) => {
|
|
885
|
+
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
886
|
+
|
|
887
|
+
log.info('Recovery request received', { client_ip, method: req.method });
|
|
888
|
+
|
|
889
|
+
if (!token_param) {
|
|
890
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
891
|
+
res.end(create_error_html('Recovery token is required.'));
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const token_validation = is_token_valid(token_param);
|
|
896
|
+
|
|
897
|
+
if (!token_validation.valid) {
|
|
898
|
+
record_failed_recovery_attempt(client_ip);
|
|
899
|
+
|
|
900
|
+
let error_message = 'Invalid recovery token.';
|
|
901
|
+
if (token_validation.reason === 'expired') {
|
|
902
|
+
error_message = 'Recovery token has expired. Generate a new token using --generate-recovery-token.';
|
|
903
|
+
} else if (token_validation.reason === 'locked') {
|
|
904
|
+
error_message = 'Recovery is locked due to too many failed attempts. Please try again later.';
|
|
905
|
+
} else if (token_validation.reason === 'no_token') {
|
|
906
|
+
error_message = 'No active recovery token found. Generate a new token using --generate-recovery-token.';
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
log.warn('Invalid recovery token attempt', {
|
|
910
|
+
client_ip,
|
|
911
|
+
reason: token_validation.reason,
|
|
912
|
+
provided_token: token_param
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
916
|
+
res.end(create_error_html(error_message));
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (req.method === 'GET') {
|
|
921
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
922
|
+
res.end(create_recovery_html(token_param));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (req.method === 'POST') {
|
|
927
|
+
try {
|
|
928
|
+
const form_data = await parse_form_data(req);
|
|
929
|
+
const { password, confirm_password } = form_data;
|
|
930
|
+
|
|
931
|
+
if (!password || !confirm_password) {
|
|
932
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
933
|
+
res.end(create_recovery_html(token_param, 'Both password fields are required.'));
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (password !== confirm_password) {
|
|
938
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
939
|
+
res.end(create_recovery_html(token_param, 'Passwords do not match.'));
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// NOTE: Create connection terminator function to disconnect all clients.
|
|
944
|
+
const connection_terminator = () => {
|
|
945
|
+
// NOTE: This will be called by the recovery manager to terminate connections.
|
|
946
|
+
// For now, we'll just log that connections should be terminated.
|
|
947
|
+
// The actual termination will be handled by the server when it detects the password change.
|
|
948
|
+
log.info('Password change completed, existing connections should be terminated');
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
const result = await change_password(password, client_ip, connection_terminator);
|
|
952
|
+
|
|
953
|
+
log.info('Emergency password change completed via HTTP interface', {
|
|
954
|
+
client_ip,
|
|
955
|
+
timestamp: result.timestamp
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
959
|
+
res.end(create_recovery_success_html(result.timestamp));
|
|
960
|
+
} catch (error) {
|
|
961
|
+
log.error('Emergency password change failed via HTTP interface', {
|
|
962
|
+
client_ip,
|
|
963
|
+
error: error.message
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
967
|
+
res.end(create_recovery_html(token_param, error.message));
|
|
968
|
+
}
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
res.writeHead(405, { 'Content-Type': 'text/html' });
|
|
973
|
+
res.end(create_error_html('Method not allowed.'));
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Creates HTTP server instance for setup and recovery operations.
|
|
978
|
+
* Sets up request routing, error handling, and connection tracking for proper cleanup.
|
|
979
|
+
* Handles /setup and /recovery endpoints with appropriate authentication and validation.
|
|
980
|
+
* @param {number} [port=1984] - Port number for HTTP server
|
|
981
|
+
* @returns {http.Server} Configured HTTP server instance
|
|
982
|
+
*/
|
|
983
|
+
const create_http_server = (port = 1984) => {
|
|
984
|
+
// NOTE: Create a new server instance but don't assign to global yet.
|
|
985
|
+
const server = http.createServer(async (req, res) => {
|
|
986
|
+
try {
|
|
987
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
988
|
+
|
|
989
|
+
if (url.pathname === '/setup') {
|
|
990
|
+
const token_param = url.searchParams.get('token');
|
|
991
|
+
await handle_setup_request(req, res, token_param);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (url.pathname === '/recovery') {
|
|
996
|
+
const token_param = url.searchParams.get('token');
|
|
997
|
+
await handle_recovery_request(req, res, token_param);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (url.pathname.startsWith('/api/users')) {
|
|
1002
|
+
const path_parts = url.pathname.split('/').slice(3); // Remove '', 'api', 'users'
|
|
1003
|
+
await handle_users_api(req, res, path_parts);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
1008
|
+
res.end(`<!DOCTYPE html>
|
|
1009
|
+
<html>
|
|
1010
|
+
<head><title>404 Not Found</title></head>
|
|
1011
|
+
<body>
|
|
1012
|
+
<h1>404 Not Found</h1>
|
|
1013
|
+
<p>The requested resource was not found on this server.</p>
|
|
1014
|
+
</body>
|
|
1015
|
+
</html>`);
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
log.error('HTTP request error', { error: error.message, url: req.url });
|
|
1018
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
1019
|
+
res.end(create_error_html('Internal server error.'));
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// NOTE: Track connections for proper cleanup.
|
|
1024
|
+
const connections = new Set();
|
|
1025
|
+
|
|
1026
|
+
server.on('connection', (connection) => {
|
|
1027
|
+
connections.add(connection);
|
|
1028
|
+
connection.on('close', () => {
|
|
1029
|
+
connections.delete(connection);
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// NOTE: Store connections reference for cleanup.
|
|
1034
|
+
server._connections = connections;
|
|
1035
|
+
|
|
1036
|
+
server.on('error', (error) => {
|
|
1037
|
+
log.error('HTTP server error', { error: error.message });
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
return server;
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Starts the HTTP server for setup and recovery operations.
|
|
1045
|
+
* Generates setup token if needed, creates server instance, and handles startup errors.
|
|
1046
|
+
* Logs appropriate messages for setup requirements and server status.
|
|
1047
|
+
* @param {number} [port=1984] - Port number to start HTTP server on
|
|
1048
|
+
* @returns {Promise<http.Server>} Promise resolving to started HTTP server
|
|
1049
|
+
* @throws {Error} When server fails to start or port is unavailable
|
|
1050
|
+
*/
|
|
1051
|
+
const start_http_server = (port = 1984) => {
|
|
1052
|
+
const setup_required = is_setup_required();
|
|
1053
|
+
|
|
1054
|
+
if (setup_required) {
|
|
1055
|
+
setup_token = generate_setup_token();
|
|
1056
|
+
setup_completed = false;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const server = create_http_server(port);
|
|
1060
|
+
|
|
1061
|
+
return new Promise((resolve, reject) => {
|
|
1062
|
+
// NOTE: Set up error handler before calling listen.
|
|
1063
|
+
server.once('error', (error) => {
|
|
1064
|
+
// NOTE: Clean up on startup failure.
|
|
1065
|
+
if (setup_required) {
|
|
1066
|
+
setup_token = null;
|
|
1067
|
+
setup_completed = false;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
log.error('Failed to start HTTP server', { port, error: error.message });
|
|
1071
|
+
reject(error);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
server.listen(port, () => {
|
|
1075
|
+
// NOTE: Only assign to global variable after successful startup.
|
|
1076
|
+
http_server = server;
|
|
1077
|
+
|
|
1078
|
+
if (setup_required) {
|
|
1079
|
+
log.info('JoystickDB Setup Required');
|
|
1080
|
+
log.info(`Visit: http://localhost:${port}/setup?token=${setup_token}`);
|
|
1081
|
+
} else {
|
|
1082
|
+
log.info('HTTP server started for recovery operations', { port });
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
resolve(server);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Stops the HTTP server and cleans up all resources.
|
|
1092
|
+
* Terminates all active connections, clears rate limiting data, and resets setup state.
|
|
1093
|
+
* Provides graceful shutdown with timeout fallback for forced termination.
|
|
1094
|
+
* @returns {Promise<void>} Promise resolving when server is fully stopped
|
|
1095
|
+
*/
|
|
1096
|
+
const stop_http_server = () => {
|
|
1097
|
+
return new Promise((resolve) => {
|
|
1098
|
+
if (!http_server) {
|
|
1099
|
+
resolve();
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const server_to_close = http_server;
|
|
1104
|
+
const connections = server_to_close._connections || new Set();
|
|
1105
|
+
|
|
1106
|
+
http_server = null;
|
|
1107
|
+
setup_token = null;
|
|
1108
|
+
setup_completed = false;
|
|
1109
|
+
rate_limit_attempts.clear();
|
|
1110
|
+
|
|
1111
|
+
// NOTE: Force close all existing connections.
|
|
1112
|
+
connections.forEach(connection => {
|
|
1113
|
+
try {
|
|
1114
|
+
connection.destroy();
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
// NOTE: Ignore errors when destroying connections.
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
server_to_close.close((error) => {
|
|
1121
|
+
if (error) {
|
|
1122
|
+
log.warn('HTTP server close error', { error: error.message });
|
|
1123
|
+
} else {
|
|
1124
|
+
log.info('HTTP server stopped');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// NOTE: Add delay to ensure port is fully released.
|
|
1128
|
+
setTimeout(() => {
|
|
1129
|
+
resolve();
|
|
1130
|
+
}, 250);
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
// NOTE: Force resolve if server doesn't close gracefully within 2 seconds.
|
|
1134
|
+
setTimeout(() => {
|
|
1135
|
+
log.warn('HTTP server forced shutdown after timeout');
|
|
1136
|
+
resolve();
|
|
1137
|
+
}, 2000);
|
|
1138
|
+
});
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Gets current setup and server status information.
|
|
1143
|
+
* Returns comprehensive status including setup requirements, token availability,
|
|
1144
|
+
* completion status, and HTTP server running state.
|
|
1145
|
+
* @returns {Object} Setup status information
|
|
1146
|
+
* @returns {boolean} returns.setup_required - Whether initial setup is needed
|
|
1147
|
+
* @returns {string|null} returns.setup_token - Current setup token if available
|
|
1148
|
+
* @returns {boolean} returns.setup_completed - Whether setup has been completed
|
|
1149
|
+
* @returns {boolean} returns.http_server_running - Whether HTTP server is active
|
|
1150
|
+
*/
|
|
1151
|
+
const get_setup_info = () => {
|
|
1152
|
+
return {
|
|
1153
|
+
setup_required: is_setup_required(),
|
|
1154
|
+
setup_token: setup_token,
|
|
1155
|
+
setup_completed: setup_completed,
|
|
1156
|
+
http_server_running: !!http_server
|
|
1157
|
+
};
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
export {
|
|
1161
|
+
start_http_server,
|
|
1162
|
+
stop_http_server,
|
|
1163
|
+
get_setup_info,
|
|
1164
|
+
is_setup_required
|
|
1165
|
+
};
|