@joystick.js/db-canary 0.0.0-canary.2213 → 0.0.0-canary.2217
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/dist/server/index.js +1 -1
- package/dist/server/lib/api_key_manager.js +4 -4
- package/dist/server/lib/development_mode.js +7 -0
- package/dist/server/lib/http_server.js +7 -7
- package/package.json +4 -4
- package/src/server/index.js +14 -1
- package/src/server/lib/api_key_manager.js +71 -5
- package/src/server/lib/development_mode.js +60 -0
- package/src/server/lib/http_server.js +31 -8
- package/{data → test_data_dev_1758048078945_ow5lwnkbj}/data.mdb +0 -0
- package/{test_data → test_data_dev_1758048078945_ow5lwnkbj}/lock.mdb +0 -0
- package/test_data_dev_1758048695693_jhp72uh09/data.mdb +0 -0
- package/{data → test_data_dev_1758048695693_jhp72uh09}/lock.mdb +0 -0
- package/tests/server/lib/api_key_manager_development.test.js +163 -0
- package/tests/server/lib/development_mode.test.js +106 -0
- package/API_KEY +0 -1
- package/logs/.013e15b54597d05db4b4b53ecc37b10c92a72927-audit.json +0 -20
- package/logs/.02de550a67ea0f5961faa2dfd458a4d06f59ebd1-audit.json +0 -20
- package/logs/.03494ba24eb3c72214b4068a77d54b8993bee651-audit.json +0 -20
- package/logs/.06309ec60b339be1259a7993dd09c732f8907fbc-audit.json +0 -20
- package/logs/.0663a04dcfa17285661e5e1b8cfa51f41523b210-audit.json +0 -20
- package/logs/.0f06e6c4c9b824622729e13927587479e5060391-audit.json +0 -20
- package/logs/.16ccf58682ecb22b3e3ec63f0da1b7fe9be56528-audit.json +0 -20
- package/logs/.1fa1a5d02f496474b1ab473524c65c984146a9ad-audit.json +0 -20
- package/logs/.2223c0ae3bea6f0d62c62b1d319cc8634856abb7-audit.json +0 -20
- package/logs/.23dc79ffda3e083665e6f5993f59397adcbf4a46-audit.json +0 -20
- package/logs/.28104f49b03906b189eefd1cd462cb46c3c0af22-audit.json +0 -20
- package/logs/.29cdbf13808abe6a0ce70ee2f2efdd680ce3fd8e-audit.json +0 -20
- package/logs/.2a9889afd071f77f41f5170d08703a0afca866b7-audit.json +0 -20
- package/logs/.2acec3d1940a2bbed487528b703ee5948959a599-audit.json +0 -20
- package/logs/.2fb60ff326338c02bfedbcd0e936444e4a216750-audit.json +0 -20
- package/logs/.318fc7a19530d76a345f030f7cad00dda15300e7-audit.json +0 -20
- package/logs/.3cf27043e19085f908cedc7701e6d013463208ee-audit.json +0 -25
- package/logs/.3d90d785415817fc443402843b7c95f8371adc9b-audit.json +0 -20
- package/logs/.4074bca620375f72966fc52dfd439577727671e5-audit.json +0 -20
- package/logs/.40eecf018417ea80a70ea8ec7a3cc9406bc6334b-audit.json +0 -20
- package/logs/.50e974f1ef7c365fca6a1251b2e2c2252914cb5e-audit.json +0 -20
- package/logs/.52cb7d9e4223cf26ba36006ac26b949a97c7923c-audit.json +0 -20
- package/logs/.54befcdb84c15aad980705a31bcc9f555c3577ab-audit.json +0 -20
- package/logs/.57dfb70e22eddb84db2e3c0ceeefac5c0b9baffa-audit.json +0 -20
- package/logs/.5f0b24705a1eaad4eca4968f2d86f91b3f9be683-audit.json +0 -20
- package/logs/.61ba98fdda7db58576b382fee07904e5db1169d6-audit.json +0 -20
- package/logs/.6235017727ef6b199d569a99d6aa8c8e80a1b475-audit.json +0 -20
- package/logs/.63db16193699219489d218a1ddea5dde3750cae4-audit.json +0 -20
- package/logs/.64fb67dfe14149c9eef728d79bf30a54da809c60-audit.json +0 -20
- package/logs/.669137453368987c1f311b5345342527afb54e50-audit.json +0 -20
- package/logs/.7a71f8c89ea28ae266d356aeff6306e876a30fbb-audit.json +0 -20
- package/logs/.7afbaa90fe9dc3a7d682676f9bb79f9a1b1fd9a6-audit.json +0 -20
- package/logs/.7ca29e322cd05327035de850099e7610864f2347-audit.json +0 -20
- package/logs/.83335ab3347e449dae03455a110aaf7f120d4802-audit.json +0 -20
- package/logs/.8c2487b5fd445d2c8e5c483c80b9fa99bbf1ca58-audit.json +0 -20
- package/logs/.8c8b9dc386922c9f3b4c13251af7052aac1d24c0-audit.json +0 -20
- package/logs/.8d6155d94640c4863301ae0fee5e4e7372a21446-audit.json +0 -20
- package/logs/.944a3119a243deea7c8270d5d9e582bb1d0eaa10-audit.json +0 -20
- package/logs/.9816a845c30fb2909f3b26a23eeb3538ebcad5db-audit.json +0 -20
- package/logs/.9dc08784e38b865488177c26d4af5934555e0323-audit.json +0 -20
- package/logs/.9dd27d2e0e454ac0a37600206d1cac5493b0d7ee-audit.json +0 -20
- package/logs/.a3d486feeac7654c59b547de96600e8849a06d4f-audit.json +0 -20
- package/logs/.a5b811f4def22250f86cc18870d7c4573625df22-audit.json +0 -20
- package/logs/.a61648eb5f830e0b6f508ac35e4f8f629d2ad4c7-audit.json +0 -20
- package/logs/.a89016d507045771b4b5a65656944a9c0f1e528b-audit.json +0 -20
- package/logs/.a99bee160a1c590be959af46bacc02724803f691-audit.json +0 -20
- package/logs/.ada7906d6243fd7da802f03d86c4ae5dd9df6236-audit.json +0 -20
- package/logs/.b518339ee942143b6af983af167f5bbb6983b4de-audit.json +0 -20
- package/logs/.b51b124b166d53c9519017856ea610d61d65fabe-audit.json +0 -20
- package/logs/.b7a6aee19f58e55633d5e4a3709041c47dfff975-audit.json +0 -20
- package/logs/.bd7a8a6ba9c55d557a4867ab53f02e3ec2e1553d-audit.json +0 -20
- package/logs/.c1435dafe453b169d6392b25065f3cf4ab6fbb21-audit.json +0 -20
- package/logs/.c17e1ce043109f77dc2f0e2aa290a9d1ed842c03-audit.json +0 -20
- package/logs/.ca62637ce9540e5a38a2fbedb2115febb6ad308a-audit.json +0 -20
- package/logs/.ccee67b9c176967f8977071409a41f5cb5cd6ad4-audit.json +0 -20
- package/logs/.db24043417ea79a6f14cd947476399e53930b48d-audit.json +0 -20
- package/logs/.e0f12acccb57829f5f33712bb2e2607ecd808147-audit.json +0 -20
- package/logs/.e9b6cc33d0bbd2e644c4e2bf44d177f850016557-audit.json +0 -20
- package/logs/.f15291d434808e3bdca7963ccd2e73893be027e6-audit.json +0 -20
- package/logs/.f4bdf9e21ef84f8a3fae3ffb32bbc39275991351-audit.json +0 -20
- package/logs/.fbac3aefac1e81b4230df5aa50667cb90d51024f-audit.json +0 -20
- package/logs/.fcfd495c0a9169db243f4a4f21878ee02b76413c-audit.json +0 -20
- package/logs/admin-2025-09-12.log +0 -580
- package/logs/admin-2025-09-15.log +0 -283
- package/logs/admin-error-2025-09-12.log +0 -22
- package/logs/admin-error-2025-09-15.log +0 -10
- package/logs/api_key_manager-2025-09-12.log +0 -658
- package/logs/api_key_manager-2025-09-15.log +0 -295
- 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 +0 -4432
- package/logs/auth_manager-2025-09-15.log +0 -2000
- package/logs/auth_manager-error-2025-09-12.log +0 -11
- package/logs/auth_manager-error-2025-09-15.log +0 -5
- package/logs/auto_index_manager-2025-09-12.log +0 -84
- package/logs/auto_index_manager-2025-09-15.log +0 -45
- package/logs/auto_index_manager-error-2025-09-12.log +0 -6
- package/logs/auto_index_manager-error-2025-09-15.log +0 -0
- package/logs/backup_manager-2025-09-12.log +0 -198
- package/logs/backup_manager-2025-09-15.log +0 -90
- package/logs/backup_manager-error-2025-09-12.log +0 -198
- package/logs/backup_manager-error-2025-09-15.log +0 -90
- package/logs/bulk_write-2025-09-12.log +0 -66
- package/logs/bulk_write-2025-09-15.log +0 -38
- 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 +0 -2412
- package/logs/connection_manager-2025-09-15.log +0 -1132
- 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 +0 -302
- package/logs/create_index-2025-09-15.log +0 -158
- package/logs/create_index-error-2025-09-12.log +0 -30
- package/logs/create_index-error-2025-09-15.log +0 -13
- package/logs/delete_one-2025-09-12.log +0 -73
- package/logs/delete_one-2025-09-15.log +0 -43
- 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 +0 -4954
- package/logs/disk_utils-2025-09-15.log +0 -2446
- 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 +0 -41
- package/logs/drop_index-2025-09-15.log +0 -23
- package/logs/drop_index-error-2025-09-12.log +0 -11
- package/logs/drop_index-error-2025-09-15.log +0 -5
- package/logs/find-2025-09-12.log +0 -1050
- package/logs/find-2025-09-15.log +0 -592
- package/logs/find-error-2025-09-12.log +0 -1
- package/logs/find-error-2025-09-15.log +0 -0
- package/logs/find_one-2025-09-12.log +0 -425
- package/logs/find_one-2025-09-15.log +0 -264
- package/logs/find_one-error-2025-09-12.log +0 -5
- package/logs/find_one-error-2025-09-15.log +0 -0
- package/logs/get_indexes-2025-09-12.log +0 -84
- package/logs/get_indexes-2025-09-15.log +0 -56
- package/logs/get_indexes-error-2025-09-12.log +0 -6
- package/logs/get_indexes-error-2025-09-15.log +0 -0
- package/logs/http_server-2025-09-12.log +0 -2772
- package/logs/http_server-2025-09-15.log +0 -1276
- package/logs/http_server-error-2025-09-12.log +0 -212
- package/logs/http_server-error-2025-09-15.log +0 -44
- package/logs/index_manager-2025-09-12.log +0 -5031
- package/logs/index_manager-2025-09-15.log +0 -2909
- package/logs/index_manager-error-2025-09-12.log +0 -80
- package/logs/index_manager-error-2025-09-15.log +0 -38
- package/logs/insert_one-2025-09-12.log +0 -2181
- package/logs/insert_one-2025-09-15.log +0 -1293
- 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 +0 -1882
- package/logs/master-2025-09-15.log +0 -910
- package/logs/master-error-2025-09-12.log +0 -80
- package/logs/master-error-2025-09-15.log +0 -0
- package/logs/operation_dispatcher-2025-09-12.log +0 -751
- package/logs/operation_dispatcher-2025-09-15.log +0 -359
- package/logs/operation_dispatcher-error-2025-09-12.log +0 -33
- package/logs/operation_dispatcher-error-2025-09-15.log +0 -11
- package/logs/performance_monitor-2025-09-12.log +0 -14889
- package/logs/performance_monitor-2025-09-15.log +0 -6803
- 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 +0 -5310
- package/logs/query_engine-2025-09-15.log +0 -2639
- 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 +0 -462
- package/logs/recovery_manager-2025-09-15.log +0 -210
- package/logs/recovery_manager-error-2025-09-12.log +0 -22
- package/logs/recovery_manager-error-2025-09-15.log +0 -10
- package/logs/replication-2025-09-12.log +0 -1923
- package/logs/replication-2025-09-15.log +0 -917
- package/logs/replication-error-2025-09-12.log +0 -33
- package/logs/replication-error-2025-09-15.log +0 -15
- package/logs/server-2025-09-12.log +0 -2601
- package/logs/server-2025-09-15.log +0 -1191
- 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 +0 -22
- package/logs/tcp_protocol-2025-09-15.log +0 -10
- package/logs/tcp_protocol-error-2025-09-12.log +0 -22
- package/logs/tcp_protocol-error-2025-09-15.log +0 -10
- 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 +0 -173
- package/logs/update_one-2025-09-15.log +0 -118
- 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 +0 -1457
- package/logs/worker-2025-09-15.log +0 -695
- 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 +0 -1956
- package/logs/write_forwarder-2025-09-15.log +0 -932
- package/logs/write_forwarder-error-2025-09-12.log +0 -66
- package/logs/write_forwarder-error-2025-09-15.log +0 -30
- package/logs/write_queue-2025-09-12.log +0 -612
- package/logs/write_queue-2025-09-15.log +0 -301
- package/logs/write_queue-error-2025-09-12.log +0 -184
- package/logs/write_queue-error-2025-09-15.log +0 -83
- package/prompts/01-core-infrastructure.md +0 -56
- package/prompts/02-secondary-indexing.md +0 -65
- package/prompts/03-write-serialization.md +0 -63
- package/prompts/04-enhanced-authentication.md +0 -75
- package/prompts/05-comprehensive-admin-operations.md +0 -75
- package/prompts/06-backup-and-restore-system.md +0 -106
- package/prompts/07-production-safety-features.md +0 -107
- package/prompts/08-tcp-client-library.md +0 -121
- package/prompts/09-api-method-chaining.md +0 -134
- package/prompts/10-automatic-index-creation.md +0 -223
- package/prompts/11-operation-naming-consistency.md +0 -268
- package/prompts/12-tcp-replication-system.md +0 -333
- package/prompts/13-master-read-write-operations.md +0 -57
- package/prompts/14-index-upsert-operations.md +0 -68
- package/prompts/15-client-api-return-types.md +0 -81
- package/prompts/16-server-setup-ui.md +0 -97
- package/prompts/17-emergency-password-change.md +0 -108
- package/prompts/18-joystick-framework-integration.md +0 -116
- package/prompts/19-api-key-authentication-system.md +0 -137
- package/prompts/20-configurable-server-port.md +0 -105
- package/prompts/21-multi-database-support.md +0 -161
- package/prompts/22-build-script-integration.md +0 -129
- package/prompts/23-cli-integration.md +0 -268
- package/prompts/FULL_TEXT_SEARCH.md +0 -293
- package/prompts/PROMPTS.md +0 -158
- package/prompts/README.md +0 -221
- package/prompts/TYPESCRIPT_GENERATION.md +0 -179
- package/test_data/data.mdb +0 -0
package/dist/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import k from"net";import{decode as C}from"msgpackr";import E from"./lib/op_types.js";import
|
|
1
|
+
import k from"net";import{decode as C}from"msgpackr";import E from"./lib/op_types.js";import S from"./lib/safe_json_parse.js";import{load_settings as y,get_settings as g,get_port_configuration as v}from"./lib/load_settings.js";import{send_error as f}from"./lib/send_response.js";import{start_cluster as q}from"./cluster/index.js";import x from"./lib/logger.js";import{initialize_database as N,cleanup_database as A}from"./lib/query_engine.js";import{create_message_parser as B,encode_message as _}from"./lib/tcp_protocol.js";import{create_connection_manager as D}from"./lib/connection_manager.js";import{shutdown_write_queue as $}from"./lib/write_queue.js";import{setup_authentication as F,verify_password as J,get_client_ip as K,is_rate_limited as P,initialize_auth_manager as j,reset_auth_state as G}from"./lib/auth_manager.js";import{initialize_api_key_manager as M}from"./lib/api_key_manager.js";import{is_development_mode as W,display_development_startup_message as H,warn_undefined_node_env as U}from"./lib/development_mode.js";import{restore_backup as V,start_backup_schedule as Y,stop_backup_schedule as L}from"./lib/backup_manager.js";import{initialize_replication_manager as Q,shutdown_replication_manager as X}from"./lib/replication_manager.js";import{initialize_write_forwarder as Z,shutdown_write_forwarder as ee}from"./lib/write_forwarder.js";import{handle_database_operation as re,handle_admin_operation as te,handle_ping_operation as oe}from"./lib/operation_dispatcher.js";import{start_http_server as ne,stop_http_server as se}from"./lib/http_server.js";import{create_recovery_token as ae,initialize_recovery_manager as T,reset_recovery_state as ie}from"./lib/recovery_manager.js";const d=new Set;let c=null;const ce=async(t,r={})=>{if(!r?.password){const s=_({ok:0,error:"Authentication operation requires password to be set in data."});t.write(s),t.end();return}try{const o=K(t);if(P(o)){const n=_({ok:0,error:"Too many failed attempts. Please try again later."});t.write(n),t.end();return}if(!await J(r.password,o)){const n=_({ok:0,error:"Authentication failed"});t.write(n),t.end();return}d.add(t.id);const e=_({ok:1,version:"1.0.0",message:"Authentication successful"});t.write(e)}catch(o){const s={ok:0,error:`Authentication error: ${o.message}`},a=_(s);t.write(a),t.end()}},_e=async(t,r={})=>{try{const s={ok:1,password:F(),message:"Authentication setup completed successfully. Save this password - it will not be shown again."},a=_(s);t.write(a)}catch(o){const s={ok:0,error:`Setup error: ${o.message}`},a=_(s);t.write(a)}},pe=(t="")=>{if(!t)throw new Error("Must pass an op type for operation.");return E.includes(t)},Be=t=>{try{if(typeof t=="string")return S(t);if(Buffer.isBuffer(t)){const r=C(t);return typeof r=="string"?S(r):r}else return t}catch{return null}},b=t=>d.has(t.id),De=async()=>{const{create_context_logger:t}=x("server"),r=t();let o=null;try{y(),o=g()}catch{}if(o?.restore_from)try{r.info("Startup restore requested",{backup_filename:o.restore_from});const e=await V(o.restore_from);r.info("Startup restore completed",{backup_filename:o.restore_from,duration_ms:e.duration_ms});const i={...o};delete i.restore_from,process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(i),y(),o=g(),r.info("Removed restore_from from settings after successful restore")}catch(e){r.error("Startup restore failed",{backup_filename:o.restore_from,error:e.message}),r.info("Continuing with fresh database after restore failure")}N(),j(),await M(),T();try{Q(),r.info("Replication manager initialized")}catch(e){r.warn("Failed to initialize replication manager",{error:e.message})}try{Z(),r.info("Write forwarder initialized")}catch(e){r.warn("Failed to initialize write forwarder",{error:e.message})}if(o?.s3)try{Y(),r.info("Backup scheduling started")}catch(e){r.warn("Failed to start backup scheduling",{error:e.message})}c=D({max_connections:1e3,idle_timeout:600*1e3,request_timeout:5*1e3});let s=null;try{const{http_port:e}=v();s=await ne(e),s&&r.info("HTTP server started",{http_port:e})}catch(e){r.warn("Failed to start HTTP server",{error:e.message})}if(W()){const{tcp_port:e,http_port:i}=v();H(e,i)}else U();const a=k.createServer((e={})=>{if(!c.add_connection(e))return;const i=B();e.on("data",async n=>{c.update_activity(e.id);try{const h=i.parse_messages(n);for(const O of h){const u=O,l=u?.op||null;if(!l){f(e,{message:"Missing operation type"});continue}if(!pe(l)){f(e,{message:"Invalid operation type"});continue}const z=c.create_request_timeout(e.id,l);try{switch(l){case"authentication":await ce(e,u?.data||{});break;case"setup":await _e(e,u?.data||{});break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":case"find_one":case"find":case"create_index":case"drop_index":case"get_indexes":await re(e,l,u?.data||{},b,n.length,c,d);break;case"ping":oe(e);break;case"admin":await te(e,u?.data||{},b,c,d);break;case"reload":if(!b(e)){f(e,{message:"Authentication required"});break}try{let p=null;try{p=g()}catch{}let m=null;try{await y(),m=g()}catch{m={port:1983,authentication:{}}}const w={ok:1,status:"success",message:"Configuration reloaded successfully",changes:{port_changed:p?p.port!==m.port:!1,authentication_changed:p?p.authentication?.password_hash!==m.authentication?.password_hash:!1},timestamp:new Date().toISOString()},I=_(w);e.write(I)}catch(p){const m={ok:0,error:`Reload operation failed: ${p.message}`},w=_(m);e.write(w)}break;default:f(e,{message:`Operation ${l} not implemented`})}}finally{clearTimeout(z)}}}catch(h){r.error("Message parsing failed",{client_id:e.id,error:h.message}),f(e,{message:"Invalid message format"}),e.end()}}),e.on("end",()=>{r.info("Client disconnected",{socket_id:e.id}),d.delete(e.id),c.remove_connection(e.id)}),e.on("error",n=>{r.error("Socket error",{socket_id:e.id,error:n.message}),d.delete(e.id),c.remove_connection(e.id)})});return a.cleanup=async()=>{try{await se(),L(),await X(),await ee(),c&&c.shutdown(),d.clear(),await $(),await new Promise(e=>setTimeout(e,100)),await A(),G(),ie()}catch{}},a};if(import.meta.url===`file://${process.argv[1]}`){const{create_context_logger:t}=x("main"),r=t();if(process.argv.includes("--generate-recovery-token"))try{T();const n=ae();console.log("Emergency Recovery Token Generated"),console.log(`Visit: ${n.url}`),console.log("Token expires in 10 minutes"),r.info("Recovery token generated via CLI",{expires_at:new Date(n.expires_at).toISOString()}),process.exit(0)}catch(n){console.error("Failed to generate recovery token:",n.message),r.error("Recovery token generation failed",{error:n.message}),process.exit(1)}const{tcp_port:o,http_port:s}=v(),a={worker_count:process.env.WORKER_COUNT?parseInt(process.env.WORKER_COUNT):void 0,port:o,environment:process.env.NODE_ENV||"development"},{has_settings:e}=await import("./lib/load_settings.js"),i=e();r.info("Starting JoystickDB server...",{workers:a.worker_count||"auto",tcp_port:o,http_port:s,environment:a.environment,has_settings:i,port_source:i?"JOYSTICK_DB_SETTINGS":"default"}),q(a)}export{ce as authentication,pe as check_op_type,De as create_server,Be as parse_data,_e as setup};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{readFileSync as x,writeFileSync as T,existsSync as v,unlinkSync as F}from"fs";import N from"crypto";import y from"bcrypt";import D from"./logger.js";import{get_database as _,build_collection_key as m}from"./query_engine.js";import{is_development_mode as g}from"./development_mode.js";const{create_context_logger:J}=D("api_key_manager"),a=J(),f="./API_KEY",l="_users",S=12;let d=null,u=!1;const R=()=>{const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";let r="";const o=N.randomBytes(32);for(let s=0;s<32;s++)r+=e[o[s]%e.length];return r},w=()=>{if(g())return d="development-mode-bypass",a.info("Development mode: API key generation bypassed"),d;if(d)return d;if(v(f))try{return d=x(f,"utf8").trim(),a.info("API key loaded from file"),d}catch(r){throw a.error("Failed to read API key file",{error:r.message}),new Error(`Failed to read API key file: ${r.message}`)}const e=R();try{return T(f,e,{mode:384}),d=e,a.info("New API key generated and saved",{file_path:f}),U(e),e}catch(r){throw a.error("Failed to save API key file",{error:r.message}),new Error(`Failed to save API key file: ${r.message}`)}},U=e=>{const r=process.env.JOYSTICK_DB_PORT||"1983",o=process.env.JOYSTICK_DB_HTTP_PORT||"1984";console.log(`
|
|
2
2
|
JoystickDB Setup
|
|
3
3
|
`),console.log(`To finish setting up your database and enable operations for authenticated users, you will need to create an admin user via the database's admin API using the API key displayed below.
|
|
4
4
|
`),console.log("=== STORE THIS KEY SECURELY -- IT WILL NOT BE DISPLAYED AGAIN ==="),console.log(e),console.log(`===
|
|
5
5
|
`),console.log(`To create a user, send a POST request to the server:
|
|
6
|
-
`),console.log(`fetch('http://localhost:${
|
|
6
|
+
`),console.log(`fetch('http://localhost:${o}/api/users', {`),console.log(" method: 'POST',"),console.log(" headers: {"),console.log(" 'Content-Type': 'application/json',"),console.log(` 'x-joystick-db-api-key': '${e}'`),console.log(" },"),console.log(" body: JSON.stringify({"),console.log(" username: 'admin',"),console.log(" password: 'your_secure_password',"),console.log(" role: 'read_write'"),console.log(" })"),console.log(`});
|
|
7
7
|
`),console.log("==!=="),console.log("Store this key securely. It will not be displayed again."),console.log(`==!==
|
|
8
|
-
`),console.log(`API key saved to: ${
|
|
9
|
-
`)}
|
|
8
|
+
`),console.log(`API key saved to: ${f}
|
|
9
|
+
`)},$=e=>{if(g())return!0;if(!e)return!1;const r=w();return e===r},L=e=>!e||typeof e!="string"?!1:/^[a-zA-Z0-9]{3,50}$/.test(e),k=(e,r=!1)=>!e||typeof e!="string"?{valid:!1,error:"Password is required"}:g()&&r?{valid:!0}:e.length<8?{valid:!1,error:"Password must be at least 8 characters long"}:e.length>128?{valid:!1,error:"Password must be no more than 128 characters long"}:{valid:!0},I=e=>["read","write","read_write"].includes(e),b=async(e,r=!1)=>{const{username:o,password:s,role:t}=e;if(!L(o))throw new Error("Username must be alphanumeric and 3-50 characters long");const n=k(s,r);if(!n.valid)throw new Error(n.error);if(!I(t))throw new Error("Role must be one of: read, write, read_write");const p=_(),i=await y.hash(s,S),c={username:o,password_hash:i,role:t,created_at:new Date().toISOString(),updated_at:new Date().toISOString()},h=m(l,o);let E=null;return await p.transaction(()=>{if(p.get(h))throw new Error("Username already exists");p.put(h,JSON.stringify(c)),E=c}),t==="read_write"&&(u=!0,a.info("Admin user created, API key requirement relaxed for authenticated operations")),a.info("User created successfully",{username:o,role:t}),{username:c.username,role:c.role,created_at:c.created_at}},Y=()=>{const e=_(),r=[];for(const{key:o,value:s}of e.getRange({start:`${l}:`,end:`${l}:\xFF`})){const t=JSON.parse(s),n={username:t.username,role:t.role,created_at:t.created_at,updated_at:t.updated_at};r.push(n)}return r},P=e=>{const r=_(),o=m(l,e),s=r.get(o);if(!s)return null;const t=JSON.parse(s);return{username:t.username,role:t.role,created_at:t.created_at,updated_at:t.updated_at}},C=async(e,r)=>{const o=_(),s=m(l,e);let t=null;if(r.password){const c=k(r.password);if(!c.valid)throw new Error(c.error);t=await y.hash(r.password,S)}if(r.role&&!I(r.role))throw new Error("Role must be one of: read, write, read_write");const n=o.get(s);if(!n)throw new Error("User not found");const i={...JSON.parse(n)};return r.role&&(i.role=r.role),t&&(i.password_hash=t),i.updated_at=new Date().toISOString(),o.putSync(s,JSON.stringify(i)),a.info("User updated successfully",{username:e,updates:Object.keys(r)}),{username:i.username,role:i.role,created_at:i.created_at,updated_at:i.updated_at}},B=e=>{const r=_(),o=m(l,e);if(!r.get(o))throw new Error("User not found");const t=r.removeSync(o);return a.info("User deleted successfully",{username:e}),!0},K=async(e,r)=>{const o=_(),s=m(l,e),t=o.get(s);if(!t)return null;const n=JSON.parse(t);return await y.compare(r,n.password_hash)?{username:n.username,role:n.role,created_at:n.created_at,updated_at:n.updated_at}:null},O=()=>{if(u)return!0;const e=_();for(const{value:r}of e.getRange({start:`${l}:`,end:`${l}:\xFF`}))if(JSON.parse(r).role==="read_write")return u=!0,!0;return!1},A=async()=>{if(!g())return null;const e=P("admin");if(e)return a.info("Development admin user already exists, skipping creation"),e;try{const r=await b({username:"admin",password:"password",role:"read_write"},!0);return a.info("Development admin user created automatically",{username:"admin"}),r}catch(r){throw a.error("Failed to create development admin user",{error:r.message}),r}},q=async()=>{if(w(),u=O(),u&&a.info("Admin user detected, API key requirement relaxed for authenticated operations"),g()&&!u)try{await A(),u=!0}catch(e){a.error("Failed to initialize development admin user",{error:e.message})}},j=()=>{if(d=null,u=!1,v(f))try{F(f)}catch{}};export{O as check_admin_user_exists,A as create_development_admin_user,b as create_user,B as delete_user,Y as get_all_users,P as get_user,q as initialize_api_key_manager,w as load_or_generate_api_key,j as reset_api_key_state,C as update_user,$ as validate_api_key,K as verify_user_password};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import s from"./logger.js";const{create_context_logger:t}=s("development_mode"),n=t(),l=()=>process.env.NODE_ENV==="development",r=(e,o)=>{console.log(`
|
|
2
|
+
JoystickDB Development Mode
|
|
3
|
+
`),console.log("Development environment detected (NODE_ENV=development)."),console.log(`Security features have been bypassed for local development.
|
|
4
|
+
`),console.log("Default admin user created:"),console.log(" Username: admin"),console.log(` Password: password
|
|
5
|
+
`),console.log("WARNING: This configuration is NOT suitable for production use."),console.log(`Set NODE_ENV to 'production' or another value for secure operation.
|
|
6
|
+
`),console.log(`TCP Server: localhost:${e}`),console.log(`HTTP Server: localhost:${o}
|
|
7
|
+
`),n.info("Development mode activated",{tcp_port:e,http_port:o,default_admin_created:!0,security_bypassed:!0})},c=()=>{process.env.NODE_ENV||(n.warn("NODE_ENV is not set, defaulting to secure mode"),console.log("WARNING: NODE_ENV is not set. Defaulting to secure mode."))};export{r as display_development_startup_message,l as is_development_mode,c as warn_undefined_node_env};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import _ from"http";import{URL as x}from"url";import k from"crypto";import T from"./logger.js";import{setup_authentication as P,get_auth_stats as A}from"./auth_manager.js";import{is_token_valid as C,record_failed_recovery_attempt as S,change_password as I}from"./recovery_manager.js";import{validate_api_key as D,create_user as E,get_all_users as H,update_user as O,delete_user as R}from"./api_key_manager.js";import{is_development_mode as l}from"./development_mode.js";const{create_context_logger:B}=T("http_server"),a=B();let u=null,c=null,m=!1,h=new Map;const U=60*1e3,J=10,Y=()=>k.randomUUID(),g=()=>!A().configured,$=t=>{const e=Date.now(),r=(h.get(t)||[]).filter(n=>e-n<U);return h.set(t,r),r.length>=J},j=t=>{const e=Date.now(),o=h.get(t)||[];o.push(e),h.set(t,o)},N=(t,e=null)=>`<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>JoystickDB Setup</title>
|
|
@@ -65,7 +65,7 @@ import v from"http";import{URL as x}from"url";import _ from"crypto";import k fro
|
|
|
65
65
|
</ul>
|
|
66
66
|
</div>
|
|
67
67
|
</body>
|
|
68
|
-
</html>`,
|
|
68
|
+
</html>`,W=t=>`<!DOCTYPE html>
|
|
69
69
|
<html>
|
|
70
70
|
<head>
|
|
71
71
|
<title>JoystickDB Setup Complete</title>
|
|
@@ -159,7 +159,7 @@ await client.ping();
|
|
|
159
159
|
<p><strong>Your JoystickDB server is ready to use!</strong></p>
|
|
160
160
|
</div>
|
|
161
161
|
</body>
|
|
162
|
-
</html>`,
|
|
162
|
+
</html>`,p=t=>`<!DOCTYPE html>
|
|
163
163
|
<html>
|
|
164
164
|
<head>
|
|
165
165
|
<title>JoystickDB Setup Error</title>
|
|
@@ -202,7 +202,7 @@ await client.ping();
|
|
|
202
202
|
<p><a href="javascript:history.back()">\u2190 Go Back</a></p>
|
|
203
203
|
</div>
|
|
204
204
|
</body>
|
|
205
|
-
</html>`,
|
|
205
|
+
</html>`,y=(t,e=null)=>`<!DOCTYPE html>
|
|
206
206
|
<html>
|
|
207
207
|
<head>
|
|
208
208
|
<title>JoystickDB Emergency Password Recovery</title>
|
|
@@ -325,7 +325,7 @@ await client.ping();
|
|
|
325
325
|
</ul>
|
|
326
326
|
</div>
|
|
327
327
|
</body>
|
|
328
|
-
</html>`,
|
|
328
|
+
</html>`,M=t=>`<!DOCTYPE html>
|
|
329
329
|
<html>
|
|
330
330
|
<head>
|
|
331
331
|
<title>JoystickDB Password Changed</title>
|
|
@@ -395,11 +395,11 @@ await client.ping();
|
|
|
395
395
|
<p><strong>Your JoystickDB server is ready with the new password!</strong></p>
|
|
396
396
|
</div>
|
|
397
397
|
</body>
|
|
398
|
-
</html>`,
|
|
398
|
+
</html>`,G=t=>new Promise((e,o)=>{let r="";t.on("data",n=>{r+=n.toString()}),t.on("end",()=>{try{const n=new URLSearchParams(r),s={};for(const[d,f]of n)s[d]=f;e(s)}catch(n){o(n)}}),t.on("error",n=>{o(n)})}),v=t=>new Promise((e,o)=>{let r="";t.on("data",n=>{r+=n.toString()}),t.on("end",()=>{try{const n=JSON.parse(r);e(n)}catch(n){o(n)}}),t.on("error",n=>{o(n)})}),w=t=>{if(l())return!0;const e=t.headers["x-joystick-db-api-key"];return D(e)},i=(t,e,o)=>{t.writeHead(e,{"Content-Type":"application/json"}),t.end(JSON.stringify(o))},z=async(t,e)=>{const o=t.socket.remoteAddress||"127.0.0.1";if(!w(t)){const r=l()?"API key validation failed (this should not happen in development mode)":"Database setup incomplete. A valid API key must be passed until an admin user has been created.";a.warn("Invalid API key for user creation",{client_ip:o,development_mode:l()}),i(e,403,{error:r});return}try{const r=await v(t),n=await E(r);a.info("User created via API",{username:n.username,client_ip:o}),i(e,201,{ok:1,user:n})}catch(r){a.error("User creation failed via API",{client_ip:o,error:r.message}),i(e,400,{error:r.message})}},F=async(t,e)=>{const o=t.socket.remoteAddress||"127.0.0.1";if(!w(t)){const r=l()?"API key validation failed (this should not happen in development mode)":"Database setup incomplete. A valid API key must be passed until an admin user has been created.";a.warn("Invalid API key for get users",{client_ip:o,development_mode:l()}),i(e,403,{error:r});return}try{const r=H();a.info("Users retrieved via API",{count:r.length,client_ip:o}),i(e,200,{ok:1,users:r})}catch(r){a.error("Get users failed via API",{client_ip:o,error:r.message}),i(e,500,{error:r.message})}},K=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(!w(t)){const n=l()?"API key validation failed (this should not happen in development mode)":"Database setup incomplete. A valid API key must be passed until an admin user has been created.";a.warn("Invalid API key for user update",{client_ip:r,username:o,development_mode:l()}),i(e,403,{error:n});return}try{const n=await v(t),s=await O(o,n);a.info("User updated via API",{username:o,client_ip:r}),i(e,200,{ok:1,user:s})}catch(n){a.error("User update failed via API",{client_ip:r,username:o,error:n.message});const s=n.message==="User not found"?404:400;i(e,s,{error:n.message})}},L=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(!w(t)){const n=l()?"API key validation failed (this should not happen in development mode)":"Database setup incomplete. A valid API key must be passed until an admin user has been created.";a.warn("Invalid API key for user deletion",{client_ip:r,username:o,development_mode:l()}),i(e,403,{error:n});return}try{R(o),a.info("User deleted via API",{username:o,client_ip:r}),i(e,200,{ok:1,message:"User deleted successfully"})}catch(n){a.error("User deletion failed via API",{client_ip:r,username:o,error:n.message});const s=n.message==="User not found"?404:400;i(e,s,{error:n.message})}},V=async(t,e,o)=>{if(t.method==="POST"&&o.length===0){await z(t,e);return}if(t.method==="GET"&&o.length===0){await F(t,e);return}if(t.method==="PUT"&&o.length===1){const r=o[0];await K(t,e,r);return}if(t.method==="DELETE"&&o.length===1){const r=o[0];await L(t,e,r);return}i(e,405,{error:"Method not allowed"})},X=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if($(r)){e.writeHead(429,{"Content-Type":"text/html"}),e.end(p("Too many setup attempts. Please try again later."));return}if(j(r),!g()){e.writeHead(400,{"Content-Type":"text/html"}),e.end(p("Setup has already been completed."));return}if(!c||o!==c){a.warn("Invalid setup token attempt",{client_ip:r,provided_token:o}),e.writeHead(403,{"Content-Type":"text/html"}),e.end(p("Invalid or missing setup token."));return}if(t.method==="GET"){e.writeHead(200,{"Content-Type":"text/html"}),e.end(N(c));return}if(t.method==="POST"){try{const n=P();m=!0,c=null,a.info("Setup completed successfully via HTTP interface",{client_ip:r}),e.writeHead(200,{"Content-Type":"text/html"}),e.end(W(n))}catch(n){a.error("Setup failed via HTTP interface",{client_ip:r,error:n.message}),e.writeHead(500,{"Content-Type":"text/html"}),e.end(p(n.message))}return}e.writeHead(405,{"Content-Type":"text/html"}),e.end(p("Method not allowed."))},Q=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(a.info("Recovery request received",{client_ip:r,method:t.method}),!o){e.writeHead(400,{"Content-Type":"text/html"}),e.end(p("Recovery token is required."));return}const n=C(o);if(!n.valid){S(r);let s="Invalid recovery token.";n.reason==="expired"?s="Recovery token has expired. Generate a new token using --generate-recovery-token.":n.reason==="locked"?s="Recovery is locked due to too many failed attempts. Please try again later.":n.reason==="no_token"&&(s="No active recovery token found. Generate a new token using --generate-recovery-token."),a.warn("Invalid recovery token attempt",{client_ip:r,reason:n.reason,provided_token:o}),e.writeHead(403,{"Content-Type":"text/html"}),e.end(p(s));return}if(t.method==="GET"){e.writeHead(200,{"Content-Type":"text/html"}),e.end(y(o));return}if(t.method==="POST"){try{const s=await G(t),{password:d,confirm_password:f}=s;if(!d||!f){e.writeHead(400,{"Content-Type":"text/html"}),e.end(y(o,"Both password fields are required."));return}if(d!==f){e.writeHead(400,{"Content-Type":"text/html"}),e.end(y(o,"Passwords do not match."));return}const b=await I(d,r,()=>{a.info("Password change completed, existing connections should be terminated")});a.info("Emergency password change completed via HTTP interface",{client_ip:r,timestamp:b.timestamp}),e.writeHead(200,{"Content-Type":"text/html"}),e.end(M(b.timestamp))}catch(s){a.error("Emergency password change failed via HTTP interface",{client_ip:r,error:s.message}),e.writeHead(400,{"Content-Type":"text/html"}),e.end(y(o,s.message))}return}e.writeHead(405,{"Content-Type":"text/html"}),e.end(p("Method not allowed."))},Z=(t=1984)=>{const e=_.createServer(async(r,n)=>{try{const s=new x(r.url,`http://localhost:${t}`);if(s.pathname==="/setup"){const d=s.searchParams.get("token");await X(r,n,d);return}if(s.pathname==="/recovery"){const d=s.searchParams.get("token");await Q(r,n,d);return}if(s.pathname.startsWith("/api/users")){const d=s.pathname.split("/").slice(3);await V(r,n,d);return}n.writeHead(404,{"Content-Type":"text/html"}),n.end(`<!DOCTYPE html>
|
|
399
399
|
<html>
|
|
400
400
|
<head><title>404 Not Found</title></head>
|
|
401
401
|
<body>
|
|
402
402
|
<h1>404 Not Found</h1>
|
|
403
403
|
<p>The requested resource was not found on this server.</p>
|
|
404
404
|
</body>
|
|
405
|
-
</html>`)}catch(s){a.error("HTTP request error",{error:s.message,url:r.url}),n.writeHead(500,{"Content-Type":"text/html"}),n.end(
|
|
405
|
+
</html>`)}catch(s){a.error("HTTP request error",{error:s.message,url:r.url}),n.writeHead(500,{"Content-Type":"text/html"}),n.end(p("Internal server error."))}}),o=new Set;return e.on("connection",r=>{o.add(r),r.on("close",()=>{o.delete(r)})}),e._connections=o,e.on("error",r=>{a.error("HTTP server error",{error:r.message})}),e},q=(t=1984)=>{const e=g();e&&(c=Y(),m=!1);const o=Z(t);return new Promise((r,n)=>{o.once("error",s=>{e&&(c=null,m=!1),a.error("Failed to start HTTP server",{port:t,error:s.message}),n(s)}),o.listen(t,()=>{u=o,e?(a.info("JoystickDB Setup Required"),a.info(`Visit: http://localhost:${t}/setup?token=${c}`)):a.info("HTTP server started for recovery operations",{port:t}),r(o)})})},ee=()=>new Promise(t=>{if(!u){t();return}const e=u,o=e._connections||new Set;u=null,c=null,m=!1,h.clear(),o.forEach(r=>{try{r.destroy()}catch{}}),e.close(r=>{r?a.warn("HTTP server close error",{error:r.message}):a.info("HTTP server stopped"),setTimeout(()=>{t()},250)}),setTimeout(()=>{a.warn("HTTP server forced shutdown after timeout"),t()},2e3)}),te=()=>({setup_required:g(),setup_token:c,setup_completed:m,http_server_running:!!u});export{te as get_setup_info,g as is_setup_required,q as start_http_server,ee as stop_http_server};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joystick.js/db-canary",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.0-canary.
|
|
5
|
-
"canary_version": "0.0.0-canary.
|
|
4
|
+
"version": "0.0.0-canary.2217",
|
|
5
|
+
"canary_version": "0.0.0-canary.2216",
|
|
6
6
|
"description": "JoystickDB - A minimalist database server for the Joystick framework",
|
|
7
|
-
"main": "./dist/index.js",
|
|
7
|
+
"main": "./dist/server/index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "node ./.build/index.js",
|
|
10
10
|
"release": "node increment_version.js && npm run build && npm publish",
|
|
@@ -45,4 +45,4 @@
|
|
|
45
45
|
"winston": "^3.17.0",
|
|
46
46
|
"winston-daily-rotate-file": "^5.0.0"
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|
package/src/server/index.js
CHANGED
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
verify_user_password,
|
|
32
32
|
check_admin_user_exists
|
|
33
33
|
} from './lib/api_key_manager.js';
|
|
34
|
+
import {
|
|
35
|
+
is_development_mode,
|
|
36
|
+
display_development_startup_message,
|
|
37
|
+
warn_undefined_node_env
|
|
38
|
+
} from './lib/development_mode.js';
|
|
34
39
|
import {
|
|
35
40
|
restore_backup,
|
|
36
41
|
start_backup_schedule,
|
|
@@ -252,7 +257,7 @@ export const create_server = async () => {
|
|
|
252
257
|
|
|
253
258
|
initialize_database();
|
|
254
259
|
initialize_auth_manager();
|
|
255
|
-
initialize_api_key_manager();
|
|
260
|
+
await initialize_api_key_manager();
|
|
256
261
|
initialize_recovery_manager();
|
|
257
262
|
|
|
258
263
|
// NOTE: Initialize replication manager for primary nodes.
|
|
@@ -299,6 +304,14 @@ export const create_server = async () => {
|
|
|
299
304
|
log.warn('Failed to start HTTP server', { error: http_error.message });
|
|
300
305
|
}
|
|
301
306
|
|
|
307
|
+
// NOTE: Display development mode startup message if in development.
|
|
308
|
+
if (is_development_mode()) {
|
|
309
|
+
const { tcp_port, http_port } = get_port_configuration();
|
|
310
|
+
display_development_startup_message(tcp_port, http_port);
|
|
311
|
+
} else {
|
|
312
|
+
warn_undefined_node_env();
|
|
313
|
+
}
|
|
314
|
+
|
|
302
315
|
const server = net.createServer((socket = {}) => {
|
|
303
316
|
if (!connection_manager.add_connection(socket)) {
|
|
304
317
|
return;
|
|
@@ -10,6 +10,7 @@ import crypto from 'crypto';
|
|
|
10
10
|
import bcrypt from 'bcrypt';
|
|
11
11
|
import create_logger from './logger.js';
|
|
12
12
|
import { get_database, build_collection_key, generate_document_id } from './query_engine.js';
|
|
13
|
+
import { is_development_mode } from './development_mode.js';
|
|
13
14
|
|
|
14
15
|
const { create_context_logger } = create_logger('api_key_manager');
|
|
15
16
|
const log = create_context_logger();
|
|
@@ -47,9 +48,17 @@ const generate_api_key = () => {
|
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Loads API key from file or generates new one if it doesn't exist.
|
|
51
|
+
* In development mode, skips API key generation entirely.
|
|
50
52
|
* @returns {string} API key from file or newly generated key
|
|
51
53
|
*/
|
|
52
54
|
const load_or_generate_api_key = () => {
|
|
55
|
+
// NOTE: Skip API key generation in development mode.
|
|
56
|
+
if (is_development_mode()) {
|
|
57
|
+
cached_api_key = 'development-mode-bypass';
|
|
58
|
+
log.info('Development mode: API key generation bypassed');
|
|
59
|
+
return cached_api_key;
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
if (cached_api_key) {
|
|
54
63
|
return cached_api_key;
|
|
55
64
|
}
|
|
@@ -118,10 +127,16 @@ const display_startup_message = (api_key) => {
|
|
|
118
127
|
|
|
119
128
|
/**
|
|
120
129
|
* Validates an API key against the stored key.
|
|
130
|
+
* In development mode, always returns true to bypass API key validation.
|
|
121
131
|
* @param {string} provided_key - API key to validate
|
|
122
132
|
* @returns {boolean} True if API key is valid
|
|
123
133
|
*/
|
|
124
134
|
const validate_api_key = (provided_key) => {
|
|
135
|
+
// NOTE: Bypass API key validation in development mode.
|
|
136
|
+
if (is_development_mode()) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
125
140
|
if (!provided_key) {
|
|
126
141
|
return false;
|
|
127
142
|
}
|
|
@@ -145,16 +160,23 @@ const validate_username = (username) => {
|
|
|
145
160
|
|
|
146
161
|
/**
|
|
147
162
|
* Validates password complexity requirements.
|
|
163
|
+
* In development mode, allows simple passwords for default admin user.
|
|
148
164
|
* @param {string} password - Password to validate
|
|
165
|
+
* @param {boolean} [is_development_default=false] - Whether this is the development default user
|
|
149
166
|
* @returns {Object} Validation result
|
|
150
167
|
* @returns {boolean} returns.valid - Whether password is valid
|
|
151
168
|
* @returns {string} returns.error - Error message if invalid
|
|
152
169
|
*/
|
|
153
|
-
const validate_password = (password) => {
|
|
170
|
+
const validate_password = (password, is_development_default = false) => {
|
|
154
171
|
if (!password || typeof password !== 'string') {
|
|
155
172
|
return { valid: false, error: 'Password is required' };
|
|
156
173
|
}
|
|
157
174
|
|
|
175
|
+
// NOTE: Skip complexity requirements for development default user.
|
|
176
|
+
if (is_development_mode() && is_development_default) {
|
|
177
|
+
return { valid: true };
|
|
178
|
+
}
|
|
179
|
+
|
|
158
180
|
if (password.length < 8) {
|
|
159
181
|
return { valid: false, error: 'Password must be at least 8 characters long' };
|
|
160
182
|
}
|
|
@@ -182,10 +204,11 @@ const validate_role = (role) => {
|
|
|
182
204
|
* @param {string} user_data.username - Username
|
|
183
205
|
* @param {string} user_data.password - Plain text password
|
|
184
206
|
* @param {string} user_data.role - User role
|
|
207
|
+
* @param {boolean} [is_development_default=false] - Whether this is the development default user
|
|
185
208
|
* @returns {Promise<Object>} Created user data
|
|
186
209
|
* @throws {Error} When validation fails or user already exists
|
|
187
210
|
*/
|
|
188
|
-
const create_user = async (user_data) => {
|
|
211
|
+
const create_user = async (user_data, is_development_default = false) => {
|
|
189
212
|
const { username, password, role } = user_data;
|
|
190
213
|
|
|
191
214
|
// Validate input
|
|
@@ -193,7 +216,7 @@ const create_user = async (user_data) => {
|
|
|
193
216
|
throw new Error('Username must be alphanumeric and 3-50 characters long');
|
|
194
217
|
}
|
|
195
218
|
|
|
196
|
-
const password_validation = validate_password(password);
|
|
219
|
+
const password_validation = validate_password(password, is_development_default);
|
|
197
220
|
if (!password_validation.valid) {
|
|
198
221
|
throw new Error(password_validation.error);
|
|
199
222
|
}
|
|
@@ -429,16 +452,58 @@ const check_admin_user_exists = () => {
|
|
|
429
452
|
return false;
|
|
430
453
|
};
|
|
431
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Creates the default admin user for development mode.
|
|
457
|
+
* @returns {Promise<Object|null>} Created user data or null if already exists
|
|
458
|
+
*/
|
|
459
|
+
const create_development_admin_user = async () => {
|
|
460
|
+
if (!is_development_mode()) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Check if admin user already exists
|
|
465
|
+
const existing_admin = get_user('admin');
|
|
466
|
+
if (existing_admin) {
|
|
467
|
+
log.info('Development admin user already exists, skipping creation');
|
|
468
|
+
return existing_admin;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const admin_user = await create_user({
|
|
473
|
+
username: 'admin',
|
|
474
|
+
password: 'password',
|
|
475
|
+
role: 'read_write'
|
|
476
|
+
}, true);
|
|
477
|
+
|
|
478
|
+
log.info('Development admin user created automatically', { username: 'admin' });
|
|
479
|
+
return admin_user;
|
|
480
|
+
} catch (error) {
|
|
481
|
+
log.error('Failed to create development admin user', { error: error.message });
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
432
486
|
/**
|
|
433
487
|
* Initializes the API key manager.
|
|
488
|
+
* In development mode, creates default admin user automatically.
|
|
434
489
|
*/
|
|
435
|
-
const initialize_api_key_manager = () => {
|
|
490
|
+
const initialize_api_key_manager = async () => {
|
|
436
491
|
load_or_generate_api_key();
|
|
437
492
|
admin_user_exists = check_admin_user_exists();
|
|
438
493
|
|
|
439
494
|
if (admin_user_exists) {
|
|
440
495
|
log.info('Admin user detected, API key requirement relaxed for authenticated operations');
|
|
441
496
|
}
|
|
497
|
+
|
|
498
|
+
// NOTE: Create development admin user if in development mode.
|
|
499
|
+
if (is_development_mode() && !admin_user_exists) {
|
|
500
|
+
try {
|
|
501
|
+
await create_development_admin_user();
|
|
502
|
+
admin_user_exists = true;
|
|
503
|
+
} catch (error) {
|
|
504
|
+
log.error('Failed to initialize development admin user', { error: error.message });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
442
507
|
};
|
|
443
508
|
|
|
444
509
|
/**
|
|
@@ -469,5 +534,6 @@ export {
|
|
|
469
534
|
verify_user_password,
|
|
470
535
|
check_admin_user_exists,
|
|
471
536
|
initialize_api_key_manager,
|
|
472
|
-
reset_api_key_state
|
|
537
|
+
reset_api_key_state,
|
|
538
|
+
create_development_admin_user
|
|
473
539
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Development environment detection and setup utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to detect development mode, bypass security requirements,
|
|
5
|
+
* and automatically create default admin users for local development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import create_logger from './logger.js';
|
|
9
|
+
|
|
10
|
+
const { create_context_logger } = create_logger('development_mode');
|
|
11
|
+
const log = create_context_logger();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the server is running in development mode.
|
|
15
|
+
* @returns {boolean} True if NODE_ENV is set to 'development'
|
|
16
|
+
*/
|
|
17
|
+
const is_development_mode = () => {
|
|
18
|
+
return process.env.NODE_ENV === 'development';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Displays development mode startup message with security warnings.
|
|
23
|
+
* @param {number} tcp_port - TCP server port
|
|
24
|
+
* @param {number} http_port - HTTP server port
|
|
25
|
+
*/
|
|
26
|
+
const display_development_startup_message = (tcp_port, http_port) => {
|
|
27
|
+
console.log('\nJoystickDB Development Mode\n');
|
|
28
|
+
console.log('Development environment detected (NODE_ENV=development).');
|
|
29
|
+
console.log('Security features have been bypassed for local development.\n');
|
|
30
|
+
console.log('Default admin user created:');
|
|
31
|
+
console.log(' Username: admin');
|
|
32
|
+
console.log(' Password: password\n');
|
|
33
|
+
console.log('WARNING: This configuration is NOT suitable for production use.');
|
|
34
|
+
console.log('Set NODE_ENV to \'production\' or another value for secure operation.\n');
|
|
35
|
+
console.log(`TCP Server: localhost:${tcp_port}`);
|
|
36
|
+
console.log(`HTTP Server: localhost:${http_port}\n`);
|
|
37
|
+
|
|
38
|
+
log.info('Development mode activated', {
|
|
39
|
+
tcp_port,
|
|
40
|
+
http_port,
|
|
41
|
+
default_admin_created: true,
|
|
42
|
+
security_bypassed: true
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Logs warning if NODE_ENV is undefined or empty.
|
|
48
|
+
*/
|
|
49
|
+
const warn_undefined_node_env = () => {
|
|
50
|
+
if (!process.env.NODE_ENV) {
|
|
51
|
+
log.warn('NODE_ENV is not set, defaulting to secure mode');
|
|
52
|
+
console.log('WARNING: NODE_ENV is not set. Defaulting to secure mode.');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
is_development_mode,
|
|
58
|
+
display_development_startup_message,
|
|
59
|
+
warn_undefined_node_env
|
|
60
|
+
};
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
delete_user,
|
|
25
25
|
check_admin_user_exists
|
|
26
26
|
} from './api_key_manager.js';
|
|
27
|
+
import { is_development_mode } from './development_mode.js';
|
|
27
28
|
|
|
28
29
|
const { create_context_logger } = create_logger('http_server');
|
|
29
30
|
const log = create_context_logger();
|
|
@@ -613,10 +614,16 @@ const parse_json_data = (req) => {
|
|
|
613
614
|
|
|
614
615
|
/**
|
|
615
616
|
* Validates API key from request headers.
|
|
617
|
+
* In development mode, always returns true to bypass API key validation.
|
|
616
618
|
* @param {http.IncomingMessage} req - HTTP request object
|
|
617
619
|
* @returns {boolean} True if API key is valid
|
|
618
620
|
*/
|
|
619
621
|
const validate_request_api_key = (req) => {
|
|
622
|
+
// NOTE: Bypass API key validation in development mode.
|
|
623
|
+
if (is_development_mode()) {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
|
|
620
627
|
const api_key = req.headers['x-joystick-db-api-key'];
|
|
621
628
|
return validate_api_key(api_key);
|
|
622
629
|
};
|
|
@@ -642,9 +649,13 @@ const handle_create_user = async (req, res) => {
|
|
|
642
649
|
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
643
650
|
|
|
644
651
|
if (!validate_request_api_key(req)) {
|
|
645
|
-
|
|
652
|
+
const error_message = is_development_mode()
|
|
653
|
+
? 'API key validation failed (this should not happen in development mode)'
|
|
654
|
+
: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.';
|
|
655
|
+
|
|
656
|
+
log.warn('Invalid API key for user creation', { client_ip, development_mode: is_development_mode() });
|
|
646
657
|
send_json_response(res, 403, {
|
|
647
|
-
error:
|
|
658
|
+
error: error_message
|
|
648
659
|
});
|
|
649
660
|
return;
|
|
650
661
|
}
|
|
@@ -677,9 +688,13 @@ const handle_get_users = async (req, res) => {
|
|
|
677
688
|
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
678
689
|
|
|
679
690
|
if (!validate_request_api_key(req)) {
|
|
680
|
-
|
|
691
|
+
const error_message = is_development_mode()
|
|
692
|
+
? 'API key validation failed (this should not happen in development mode)'
|
|
693
|
+
: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.';
|
|
694
|
+
|
|
695
|
+
log.warn('Invalid API key for get users', { client_ip, development_mode: is_development_mode() });
|
|
681
696
|
send_json_response(res, 403, {
|
|
682
|
-
error:
|
|
697
|
+
error: error_message
|
|
683
698
|
});
|
|
684
699
|
return;
|
|
685
700
|
}
|
|
@@ -712,9 +727,13 @@ const handle_update_user = async (req, res, username) => {
|
|
|
712
727
|
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
713
728
|
|
|
714
729
|
if (!validate_request_api_key(req)) {
|
|
715
|
-
|
|
730
|
+
const error_message = is_development_mode()
|
|
731
|
+
? 'API key validation failed (this should not happen in development mode)'
|
|
732
|
+
: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.';
|
|
733
|
+
|
|
734
|
+
log.warn('Invalid API key for user update', { client_ip, username, development_mode: is_development_mode() });
|
|
716
735
|
send_json_response(res, 403, {
|
|
717
|
-
error:
|
|
736
|
+
error: error_message
|
|
718
737
|
});
|
|
719
738
|
return;
|
|
720
739
|
}
|
|
@@ -750,9 +769,13 @@ const handle_delete_user = async (req, res, username) => {
|
|
|
750
769
|
const client_ip = req.socket.remoteAddress || '127.0.0.1';
|
|
751
770
|
|
|
752
771
|
if (!validate_request_api_key(req)) {
|
|
753
|
-
|
|
772
|
+
const error_message = is_development_mode()
|
|
773
|
+
? 'API key validation failed (this should not happen in development mode)'
|
|
774
|
+
: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.';
|
|
775
|
+
|
|
776
|
+
log.warn('Invalid API key for user deletion', { client_ip, username, development_mode: is_development_mode() });
|
|
754
777
|
send_json_response(res, 403, {
|
|
755
|
-
error:
|
|
778
|
+
error: error_message
|
|
756
779
|
});
|
|
757
780
|
return;
|
|
758
781
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|