@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,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core database engine using LMDB for persistent storage with automatic map size management.
|
|
3
|
+
*
|
|
4
|
+
* Provides the foundational database layer for JoystickDB, handling LMDB initialization,
|
|
5
|
+
* document key generation, collection management, and automatic database growth.
|
|
6
|
+
* Integrates with index and auto-index managers for comprehensive data management.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as lmdb from 'lmdb';
|
|
10
|
+
import { rmSync, existsSync } from 'fs';
|
|
11
|
+
import create_logger from './logger.js';
|
|
12
|
+
import { calculate_map_size, get_disk_size, should_grow_map_size } from './disk_utils.js';
|
|
13
|
+
import { initialize_index_database, cleanup_index_database } from './index_manager.js';
|
|
14
|
+
import { initialize_auto_index_database, cleanup_auto_index_database } from './auto_index_manager.js';
|
|
15
|
+
|
|
16
|
+
const { create_context_logger } = create_logger('query_engine');
|
|
17
|
+
|
|
18
|
+
/** @type {lmdb.Database|null} Main LMDB database instance */
|
|
19
|
+
let db_instance = null;
|
|
20
|
+
|
|
21
|
+
/** @type {number|null} Current database map size in bytes */
|
|
22
|
+
let current_map_size = null;
|
|
23
|
+
|
|
24
|
+
/** @type {string|null} Path to database directory */
|
|
25
|
+
let database_path = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initializes the LMDB database with automatic map size calculation and compression.
|
|
29
|
+
* Sets up the main database instance along with index and auto-index databases.
|
|
30
|
+
* @param {string} [db_path='./data'] - Path to database directory
|
|
31
|
+
* @returns {lmdb.Database} Initialized database instance
|
|
32
|
+
*/
|
|
33
|
+
const initialize_database = (db_path = './data') => {
|
|
34
|
+
const log = create_context_logger();
|
|
35
|
+
|
|
36
|
+
if (!db_instance) {
|
|
37
|
+
database_path = db_path;
|
|
38
|
+
|
|
39
|
+
calculate_map_size(database_path).then((map_size) => {
|
|
40
|
+
current_map_size = map_size;
|
|
41
|
+
|
|
42
|
+
if (db_instance && db_instance.resize) {
|
|
43
|
+
db_instance.resize(current_map_size);
|
|
44
|
+
|
|
45
|
+
log.info('Database map_size updated', {
|
|
46
|
+
path: database_path,
|
|
47
|
+
map_size: current_map_size,
|
|
48
|
+
map_size_gb: Math.round(current_map_size / (1024 * 1024 * 1024) * 100) / 100
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}).catch((error) => {
|
|
52
|
+
log.warn('Failed to calculate map_size, using default', {
|
|
53
|
+
database_path,
|
|
54
|
+
error: error.message
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const default_map_size = 1024 * 1024 * 1024 * 10;
|
|
59
|
+
current_map_size = default_map_size;
|
|
60
|
+
|
|
61
|
+
db_instance = lmdb.open({
|
|
62
|
+
path: database_path,
|
|
63
|
+
compression: true,
|
|
64
|
+
useVersions: false,
|
|
65
|
+
encoding: 'msgpack',
|
|
66
|
+
mapSize: current_map_size
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
log.info('Database initialized', {
|
|
70
|
+
path: database_path,
|
|
71
|
+
map_size: current_map_size,
|
|
72
|
+
map_size_gb: Math.round(current_map_size / (1024 * 1024 * 1024) * 100) / 100
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
initialize_index_database();
|
|
76
|
+
initialize_auto_index_database();
|
|
77
|
+
}
|
|
78
|
+
return db_instance;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Checks database usage and automatically grows map size if needed.
|
|
83
|
+
* Monitors database statistics and disk space to determine optimal growth.
|
|
84
|
+
* @returns {Promise<void>}
|
|
85
|
+
*/
|
|
86
|
+
const check_and_grow_map_size = async () => {
|
|
87
|
+
if (!db_instance || !database_path) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const log = create_context_logger();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const stats = db_instance.getStats ? db_instance.getStats() : {};
|
|
95
|
+
const used_size = stats.ms_psize * stats.ms_leaf_pages || 0;
|
|
96
|
+
|
|
97
|
+
if (used_size === 0) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const disk_size = await get_disk_size(database_path);
|
|
102
|
+
const new_map_size = should_grow_map_size(current_map_size, used_size, disk_size);
|
|
103
|
+
|
|
104
|
+
if (new_map_size) {
|
|
105
|
+
log.info('Growing map_size', {
|
|
106
|
+
current_map_size,
|
|
107
|
+
new_map_size,
|
|
108
|
+
used_size,
|
|
109
|
+
usage_percentage: Math.round((used_size / current_map_size) * 100)
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
db_instance.resize(new_map_size);
|
|
113
|
+
current_map_size = new_map_size;
|
|
114
|
+
|
|
115
|
+
log.info('Map size grown successfully', {
|
|
116
|
+
new_map_size,
|
|
117
|
+
new_map_size_gb: Math.round(new_map_size / (1024 * 1024 * 1024) * 100) / 100
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
log.error('Failed to check/grow map_size', { error: error.message });
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Gets the initialized database instance.
|
|
127
|
+
* @returns {lmdb.Database} Database instance
|
|
128
|
+
* @throws {Error} When database is not initialized
|
|
129
|
+
*/
|
|
130
|
+
const get_database = () => {
|
|
131
|
+
if (!db_instance) {
|
|
132
|
+
throw new Error('Database not initialized. Call initialize_database first.');
|
|
133
|
+
}
|
|
134
|
+
return db_instance;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generates a unique document ID using timestamp and random string.
|
|
139
|
+
* @returns {string} Unique document identifier
|
|
140
|
+
*/
|
|
141
|
+
const generate_document_id = () => {
|
|
142
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Builds a database key for a document in a collection.
|
|
147
|
+
* @param {string} database_name - Name of the database
|
|
148
|
+
* @param {string} collection_name - Name of the collection
|
|
149
|
+
* @param {string} document_id - Document identifier
|
|
150
|
+
* @returns {string} Database key in format "database:collection:document_id"
|
|
151
|
+
*/
|
|
152
|
+
const build_collection_key = (database_name, collection_name, document_id) => {
|
|
153
|
+
return `${database_name}:${collection_name}:${document_id}`;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Parses a database key to extract database name, collection name and document ID.
|
|
158
|
+
* @param {string} key - Database key in format "database:collection:document_id"
|
|
159
|
+
* @returns {Object} Parsed key components
|
|
160
|
+
* @returns {string} returns.database - Database name
|
|
161
|
+
* @returns {string} returns.collection - Collection name
|
|
162
|
+
* @returns {string} returns.document_id - Document identifier
|
|
163
|
+
*/
|
|
164
|
+
const parse_collection_key = (key) => {
|
|
165
|
+
const parts = key.split(':');
|
|
166
|
+
return {
|
|
167
|
+
database: parts[0],
|
|
168
|
+
collection: parts[1],
|
|
169
|
+
document_id: parts.slice(2).join(':')
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Cleanly shuts down the database and all associated resources.
|
|
175
|
+
* Closes index databases, auto-index databases, and the main database instance.
|
|
176
|
+
* @param {boolean} [remove_directory=false] - Whether to remove the database directory from disk
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
const cleanup_database = async (remove_directory = false) => {
|
|
180
|
+
const log = create_context_logger();
|
|
181
|
+
const path_to_remove = database_path;
|
|
182
|
+
|
|
183
|
+
if (db_instance) {
|
|
184
|
+
try {
|
|
185
|
+
// Wait a bit to ensure all operations are complete
|
|
186
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
187
|
+
|
|
188
|
+
// Cleanup databases first
|
|
189
|
+
cleanup_auto_index_database();
|
|
190
|
+
cleanup_index_database();
|
|
191
|
+
|
|
192
|
+
// Close the main database
|
|
193
|
+
await db_instance.close();
|
|
194
|
+
log.info('Database closed successfully');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
log.warn('Error closing database', { error: error.message });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Reset all state
|
|
200
|
+
db_instance = null;
|
|
201
|
+
current_map_size = null;
|
|
202
|
+
database_path = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Remove directory if requested and it's a test directory
|
|
206
|
+
if (remove_directory && path_to_remove && is_test_database_path(path_to_remove)) {
|
|
207
|
+
try {
|
|
208
|
+
if (existsSync(path_to_remove)) {
|
|
209
|
+
rmSync(path_to_remove, { recursive: true, force: true });
|
|
210
|
+
log.info('Test database directory removed', { path: path_to_remove });
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
log.warn('Failed to remove test database directory', {
|
|
214
|
+
path: path_to_remove,
|
|
215
|
+
error: error.message
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Determines if a database path is a test database that can be safely removed.
|
|
223
|
+
* @param {string} path - Database path to check
|
|
224
|
+
* @returns {boolean} True if it's a test database path
|
|
225
|
+
*/
|
|
226
|
+
const is_test_database_path = (path) => {
|
|
227
|
+
return path && (
|
|
228
|
+
path.includes('test_data') ||
|
|
229
|
+
path.startsWith('./test_') ||
|
|
230
|
+
path.startsWith('test_')
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export {
|
|
235
|
+
initialize_database,
|
|
236
|
+
get_database,
|
|
237
|
+
generate_document_id,
|
|
238
|
+
build_collection_key,
|
|
239
|
+
parse_collection_key,
|
|
240
|
+
check_and_grow_map_size,
|
|
241
|
+
cleanup_database,
|
|
242
|
+
is_test_database_path
|
|
243
|
+
};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Emergency password recovery system for JoystickDB authentication.
|
|
3
|
+
* Provides secure token-based password recovery with rate limiting, expiration handling,
|
|
4
|
+
* and lockout protection. Enables emergency password changes when normal authentication
|
|
5
|
+
* is compromised or unavailable, with comprehensive security measures and audit logging.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
import bcrypt from 'bcrypt';
|
|
11
|
+
import create_logger from './logger.js';
|
|
12
|
+
|
|
13
|
+
const { create_context_logger } = create_logger('recovery_manager');
|
|
14
|
+
const log = create_context_logger();
|
|
15
|
+
|
|
16
|
+
/** @type {string} Path to recovery token storage file */
|
|
17
|
+
const RECOVERY_TOKEN_FILE = './recovery_token.json';
|
|
18
|
+
|
|
19
|
+
import { load_settings as load_joystick_settings, get_settings as get_joystick_settings, has_settings } from './load_settings.js';
|
|
20
|
+
|
|
21
|
+
/** @type {number} Recovery token expiration time in minutes */
|
|
22
|
+
const TOKEN_EXPIRY_MINUTES = 10;
|
|
23
|
+
|
|
24
|
+
/** @type {number} Bcrypt salt rounds for password hashing */
|
|
25
|
+
const SALT_ROUNDS = 12;
|
|
26
|
+
|
|
27
|
+
/** @type {number} Maximum failed recovery attempts before lockout */
|
|
28
|
+
const MAX_RECOVERY_ATTEMPTS = 3;
|
|
29
|
+
|
|
30
|
+
/** @type {Object} Current recovery state tracking */
|
|
31
|
+
let recovery_state = {
|
|
32
|
+
token: null,
|
|
33
|
+
expires_at: null,
|
|
34
|
+
failed_attempts: 0,
|
|
35
|
+
locked_until: null
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates a cryptographically secure recovery token.
|
|
40
|
+
* Uses Node.js crypto.randomUUID() for secure token generation.
|
|
41
|
+
* @returns {string} Secure UUID token for recovery authentication
|
|
42
|
+
*/
|
|
43
|
+
const generate_recovery_token = () => {
|
|
44
|
+
return crypto.randomUUID();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new recovery token with expiration and saves it to file.
|
|
49
|
+
* Generates a secure token, sets expiration time, and stores recovery state
|
|
50
|
+
* both in memory and persistent storage with restricted file permissions.
|
|
51
|
+
* @returns {Object} Recovery token information
|
|
52
|
+
* @returns {string} returns.token - Generated recovery token
|
|
53
|
+
* @returns {number} returns.expires_at - Token expiration timestamp
|
|
54
|
+
* @returns {string} returns.url - Complete recovery URL with token
|
|
55
|
+
* @throws {Error} When token file cannot be saved
|
|
56
|
+
*/
|
|
57
|
+
const create_recovery_token = () => {
|
|
58
|
+
const token = generate_recovery_token();
|
|
59
|
+
const expires_at = Date.now() + (TOKEN_EXPIRY_MINUTES * 60 * 1000);
|
|
60
|
+
|
|
61
|
+
recovery_state = {
|
|
62
|
+
token,
|
|
63
|
+
expires_at,
|
|
64
|
+
failed_attempts: 0,
|
|
65
|
+
locked_until: null
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// NOTE: Store token temporarily in memory and file.
|
|
69
|
+
try {
|
|
70
|
+
writeFileSync(RECOVERY_TOKEN_FILE, JSON.stringify(recovery_state, null, 2), { mode: 0o600 });
|
|
71
|
+
log.info('Recovery token generated', { expires_at: new Date(expires_at).toISOString() });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
log.error('Failed to save recovery token', { error: error.message });
|
|
74
|
+
throw new Error(`Failed to save recovery token: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
token,
|
|
79
|
+
expires_at,
|
|
80
|
+
url: `http://localhost:1984/recovery?token=${token}`
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Loads recovery state from persistent storage file.
|
|
86
|
+
* Reads recovery token file and parses state, falling back to defaults
|
|
87
|
+
* if file doesn't exist or parsing fails. Handles corrupted state gracefully.
|
|
88
|
+
* @returns {void}
|
|
89
|
+
*/
|
|
90
|
+
const load_recovery_state = () => {
|
|
91
|
+
try {
|
|
92
|
+
if (!existsSync(RECOVERY_TOKEN_FILE)) {
|
|
93
|
+
recovery_state = {
|
|
94
|
+
token: null,
|
|
95
|
+
expires_at: null,
|
|
96
|
+
failed_attempts: 0,
|
|
97
|
+
locked_until: null
|
|
98
|
+
};
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const raw_data = readFileSync(RECOVERY_TOKEN_FILE, 'utf8');
|
|
103
|
+
const parsed_data = JSON.parse(raw_data);
|
|
104
|
+
|
|
105
|
+
recovery_state = {
|
|
106
|
+
token: parsed_data.token || null,
|
|
107
|
+
expires_at: parsed_data.expires_at || null,
|
|
108
|
+
failed_attempts: parsed_data.failed_attempts || 0,
|
|
109
|
+
locked_until: parsed_data.locked_until || null
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
log.info('Recovery state loaded');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
log.warn('Failed to load recovery state, using defaults', { error: error.message });
|
|
115
|
+
recovery_state = {
|
|
116
|
+
token: null,
|
|
117
|
+
expires_at: null,
|
|
118
|
+
failed_attempts: 0,
|
|
119
|
+
locked_until: null
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Saves current recovery state to persistent storage file.
|
|
126
|
+
* Writes recovery state to file with restricted permissions for security.
|
|
127
|
+
* Logs errors but doesn't throw to avoid disrupting recovery operations.
|
|
128
|
+
* @returns {void}
|
|
129
|
+
*/
|
|
130
|
+
const save_recovery_state = () => {
|
|
131
|
+
try {
|
|
132
|
+
writeFileSync(RECOVERY_TOKEN_FILE, JSON.stringify(recovery_state, null, 2), { mode: 0o600 });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
log.error('Failed to save recovery state', { error: error.message });
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Cleans up recovery token and resets state.
|
|
140
|
+
* Resets in-memory state and removes recovery token file from disk.
|
|
141
|
+
* Used after successful recovery or token expiration.
|
|
142
|
+
* @returns {void}
|
|
143
|
+
*/
|
|
144
|
+
const cleanup_recovery_token = () => {
|
|
145
|
+
recovery_state = {
|
|
146
|
+
token: null,
|
|
147
|
+
expires_at: null,
|
|
148
|
+
failed_attempts: 0,
|
|
149
|
+
locked_until: null
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (existsSync(RECOVERY_TOKEN_FILE)) {
|
|
154
|
+
unlinkSync(RECOVERY_TOKEN_FILE);
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
log.warn('Failed to cleanup recovery token file', { error: error.message });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Validates a provided recovery token against current state.
|
|
163
|
+
* Checks token existence, expiration, lockout status, and token match.
|
|
164
|
+
* Reloads state from file to ensure latest data and handles cleanup.
|
|
165
|
+
* @param {string} provided_token - Token to validate
|
|
166
|
+
* @returns {Object} Validation result
|
|
167
|
+
* @returns {boolean} returns.valid - Whether token is valid
|
|
168
|
+
* @returns {string} returns.reason - Reason for invalidity if applicable
|
|
169
|
+
*/
|
|
170
|
+
const is_token_valid = (provided_token) => {
|
|
171
|
+
// NOTE: Always reload state from file to get latest data.
|
|
172
|
+
load_recovery_state();
|
|
173
|
+
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
|
|
176
|
+
// NOTE: Check if recovery is locked due to too many failed attempts.
|
|
177
|
+
if (recovery_state.locked_until && now < recovery_state.locked_until) {
|
|
178
|
+
return { valid: false, reason: 'locked' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// NOTE: Check if token exists and hasn't expired.
|
|
182
|
+
if (!recovery_state.token || !recovery_state.expires_at) {
|
|
183
|
+
return { valid: false, reason: 'no_token' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (now > recovery_state.expires_at) {
|
|
187
|
+
cleanup_recovery_token();
|
|
188
|
+
return { valid: false, reason: 'expired' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (provided_token !== recovery_state.token) {
|
|
192
|
+
return { valid: false, reason: 'invalid' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { valid: true };
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Records a failed recovery attempt and applies lockout if needed.
|
|
200
|
+
* Increments failed attempt counter and locks recovery if maximum attempts
|
|
201
|
+
* are exceeded. Saves state to persistent storage and logs security events.
|
|
202
|
+
* @param {string} ip - IP address of failed attempt for audit logging
|
|
203
|
+
* @returns {void}
|
|
204
|
+
*/
|
|
205
|
+
const record_failed_recovery_attempt = (ip) => {
|
|
206
|
+
recovery_state.failed_attempts += 1;
|
|
207
|
+
|
|
208
|
+
log.warn('Failed recovery attempt', {
|
|
209
|
+
ip,
|
|
210
|
+
attempt_count: recovery_state.failed_attempts
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (recovery_state.failed_attempts >= MAX_RECOVERY_ATTEMPTS) {
|
|
214
|
+
const lock_duration = 30 * 60 * 1000; // 30 minutes
|
|
215
|
+
recovery_state.locked_until = Date.now() + lock_duration;
|
|
216
|
+
|
|
217
|
+
log.warn('Recovery locked due to too many failed attempts', {
|
|
218
|
+
ip,
|
|
219
|
+
locked_until: new Date(recovery_state.locked_until).toISOString()
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
save_recovery_state();
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Validates password strength requirements for recovery.
|
|
228
|
+
* Checks password existence, type, and minimum length requirements
|
|
229
|
+
* to ensure secure password policies are enforced.
|
|
230
|
+
* @param {string} password - Password to validate
|
|
231
|
+
* @returns {Object} Validation result
|
|
232
|
+
* @returns {boolean} returns.valid - Whether password meets requirements
|
|
233
|
+
* @returns {string} returns.message - Error message if validation fails
|
|
234
|
+
*/
|
|
235
|
+
const validate_password_strength = (password) => {
|
|
236
|
+
if (!password || typeof password !== 'string') {
|
|
237
|
+
return { valid: false, message: 'Password is required' };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (password.length < 12) {
|
|
241
|
+
return { valid: false, message: 'Password must be at least 12 characters long' };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { valid: true };
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Changes the database password through emergency recovery process.
|
|
249
|
+
* Validates password strength, hashes new password, updates settings file,
|
|
250
|
+
* terminates existing connections, and cleans up recovery state. Provides
|
|
251
|
+
* atomic updates with rollback protection and comprehensive audit logging.
|
|
252
|
+
* @param {string} new_password - New password to set
|
|
253
|
+
* @param {string} client_ip - IP address of recovery request for audit
|
|
254
|
+
* @param {Function|null} [connection_terminator=null] - Function to terminate active connections
|
|
255
|
+
* @returns {Promise<Object>} Password change result
|
|
256
|
+
* @returns {boolean} returns.success - Whether password change succeeded
|
|
257
|
+
* @returns {string} returns.timestamp - ISO timestamp of password change
|
|
258
|
+
* @returns {string} returns.message - Success message
|
|
259
|
+
* @throws {Error} When password validation fails or settings update fails
|
|
260
|
+
*/
|
|
261
|
+
const change_password = async (new_password, client_ip, connection_terminator = null) => {
|
|
262
|
+
const validation = validate_password_strength(new_password);
|
|
263
|
+
if (!validation.valid) {
|
|
264
|
+
throw new Error(validation.message);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// NOTE: Load current settings from environment variable.
|
|
269
|
+
if (!has_settings()) {
|
|
270
|
+
throw new Error('JOYSTICK_DB_SETTINGS environment variable not found');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const settings_data = load_joystick_settings();
|
|
274
|
+
|
|
275
|
+
if (!settings_data.authentication) {
|
|
276
|
+
throw new Error('Authentication not configured');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// NOTE: Hash new password.
|
|
280
|
+
const new_password_hash = await bcrypt.hash(new_password, SALT_ROUNDS);
|
|
281
|
+
const now = new Date().toISOString();
|
|
282
|
+
|
|
283
|
+
// NOTE: Update authentication section.
|
|
284
|
+
settings_data.authentication.password_hash = new_password_hash;
|
|
285
|
+
settings_data.authentication.last_updated = now;
|
|
286
|
+
settings_data.authentication.failed_attempts = {};
|
|
287
|
+
settings_data.authentication.rate_limits = {};
|
|
288
|
+
|
|
289
|
+
// NOTE: Update the environment variable with the new settings.
|
|
290
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(settings_data);
|
|
291
|
+
|
|
292
|
+
// NOTE: Terminate all existing connections if terminator function provided.
|
|
293
|
+
if (connection_terminator && typeof connection_terminator === 'function') {
|
|
294
|
+
try {
|
|
295
|
+
connection_terminator();
|
|
296
|
+
log.info('All existing connections terminated due to password change');
|
|
297
|
+
} catch (termination_error) {
|
|
298
|
+
log.warn('Failed to terminate some connections', { error: termination_error.message });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// NOTE: Cleanup recovery token after successful password change.
|
|
303
|
+
cleanup_recovery_token();
|
|
304
|
+
|
|
305
|
+
log.info('Emergency password change completed', {
|
|
306
|
+
client_ip,
|
|
307
|
+
timestamp: now
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
timestamp: now,
|
|
313
|
+
message: 'Password changed successfully'
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
log.error('Emergency password change failed', {
|
|
317
|
+
client_ip,
|
|
318
|
+
error: error.message
|
|
319
|
+
});
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Gets current recovery system status and state information.
|
|
326
|
+
* Returns comprehensive status including token validity, expiration,
|
|
327
|
+
* failed attempts, and lockout status for monitoring and debugging.
|
|
328
|
+
* @returns {Object} Recovery status information
|
|
329
|
+
* @returns {boolean} returns.token_active - Whether a valid token exists
|
|
330
|
+
* @returns {number|null} returns.expires_at - Token expiration timestamp
|
|
331
|
+
* @returns {number} returns.failed_attempts - Number of failed attempts
|
|
332
|
+
* @returns {number|null} returns.locked_until - Lockout expiration timestamp
|
|
333
|
+
* @returns {boolean} returns.is_locked - Whether recovery is currently locked
|
|
334
|
+
*/
|
|
335
|
+
const get_recovery_status = () => {
|
|
336
|
+
const now = Date.now();
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
token_active: !!(recovery_state.token && recovery_state.expires_at && now < recovery_state.expires_at),
|
|
340
|
+
expires_at: recovery_state.expires_at,
|
|
341
|
+
failed_attempts: recovery_state.failed_attempts,
|
|
342
|
+
locked_until: recovery_state.locked_until,
|
|
343
|
+
is_locked: !!(recovery_state.locked_until && now < recovery_state.locked_until)
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Initializes the recovery manager and cleans up expired state.
|
|
349
|
+
* Loads recovery state from file, cleans up expired tokens and locks,
|
|
350
|
+
* and ensures the recovery system is in a consistent state on startup.
|
|
351
|
+
* @returns {void}
|
|
352
|
+
*/
|
|
353
|
+
const initialize_recovery_manager = () => {
|
|
354
|
+
load_recovery_state();
|
|
355
|
+
|
|
356
|
+
// NOTE: Clean up expired tokens on startup.
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
if (recovery_state.expires_at && now > recovery_state.expires_at) {
|
|
359
|
+
cleanup_recovery_token();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// NOTE: Clean up expired locks.
|
|
363
|
+
if (recovery_state.locked_until && now > recovery_state.locked_until) {
|
|
364
|
+
recovery_state.locked_until = null;
|
|
365
|
+
recovery_state.failed_attempts = 0;
|
|
366
|
+
save_recovery_state();
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Resets all recovery state and cleans up tokens.
|
|
372
|
+
* Completely clears recovery state and removes token files.
|
|
373
|
+
* Used for administrative cleanup or system reset operations.
|
|
374
|
+
* @returns {void}
|
|
375
|
+
*/
|
|
376
|
+
const reset_recovery_state = () => {
|
|
377
|
+
cleanup_recovery_token();
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export {
|
|
381
|
+
create_recovery_token,
|
|
382
|
+
is_token_valid,
|
|
383
|
+
record_failed_recovery_attempt,
|
|
384
|
+
change_password,
|
|
385
|
+
get_recovery_status,
|
|
386
|
+
initialize_recovery_manager,
|
|
387
|
+
reset_recovery_state
|
|
388
|
+
};
|