freeschema 1.0.9 → 1.0.11

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.
Files changed (2) hide show
  1. package/bin/freeschema.js +100 -96
  2. package/package.json +1 -1
package/bin/freeschema.js CHANGED
@@ -122,9 +122,18 @@ function generateMosquittoPassword(username, password) {
122
122
  return `${username}:$7$${iterations}$${salt.toString('base64')}$${hash.toString('base64')}\n`;
123
123
  }
124
124
 
125
+ function isContainerHealthy(containerName) {
126
+ const r = spawnSync('docker', [
127
+ 'inspect', '--format', '{{.State.Health.Status}}', containerName
128
+ ], { stdio: 'pipe' });
129
+ return (r.stdout || '').toString().trim() === 'healthy';
130
+ }
131
+
125
132
  function waitForAccessControl(container) {
133
+ if (isContainerHealthy(container)) return true; // already healthy — no wait needed
126
134
  process.stdout.write(' Waiting for access-control-server');
127
- for (let i = 0; i < 30; i++) {
135
+ for (let i = 0; i < 60; i++) { // 60 × 2s = 120s
136
+ if (isContainerHealthy(container)) { process.stdout.write(' ready\n'); return true; }
128
137
  const r = spawnSync('docker', [
129
138
  'exec', container, 'curl', '-sf', 'http://localhost:7000/health'
130
139
  ], { stdio: 'pipe' });
@@ -614,19 +623,6 @@ async function cmdStart() {
614
623
  console.log(' CLIENT_SECRET is not configured.');
615
624
  console.log(' Setting up admin credentials…');
616
625
  console.log('─────────────────────────────────────────');
617
-
618
- // Wait for MySQL to be ready before inserting credentials
619
- try {
620
- const container = getMysqlContainer();
621
- const env = readEnv();
622
- const dbUser = env.DB_USER || env.MYSQL_USER || 'freeschema';
623
- const dbPass = env.DB_PASSWORD || env.MYSQL_PASSWORD || 'Freeschema@123';
624
- waitForMySQL(container, dbUser, dbPass);
625
- } catch (e) {
626
- console.log(' (skipping credentials setup — MySQL not running)');
627
- return;
628
- }
629
-
630
626
  await cmdSetupCredentials();
631
627
  }
632
628
  }
@@ -772,116 +768,124 @@ function cmdDbRestore() {
772
768
 
773
769
  // ── setup-credentials ─────────────────────────────────────────────────────────
774
770
 
775
- function hashClientSecret(plainSecret) {
776
- // Matches C# ClientSecretHasher: PBKDF2-HMAC-SHA256, 100000 iterations, 16-byte salt, 32-byte output
777
- // Stored format: "{iterations}.{base64_salt}.{base64_hash}"
778
- const iterations = 100_000;
779
- const salt = crypto.randomBytes(16);
780
- const hash = crypto.pbkdf2Sync(plainSecret, salt, iterations, 32, 'sha256');
781
- return `${iterations}.${salt.toString('base64')}.${hash.toString('base64')}`;
771
+ function getWicoContainer() {
772
+ const out = runComposeOutput(['ps', '--format', 'json']);
773
+ if (!out) return null;
774
+ const containers = out.split('\n')
775
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
776
+ .filter(Boolean);
777
+ const wico = containers.find(c => c.Service === 'wico-server' || (c.Name || '').includes('wico-server'));
778
+ return wico ? (wico.Name || wico.ID) : null;
782
779
  }
783
780
 
784
- function runSql(container, dbName, sql) {
785
- const env = readEnv();
786
- const dbPass = env.MYSQL_ROOT_PASSWORD || ('Admin@' + (env.MYSQL_PASSWORD || 'Freeschema@123'));
787
- const result = spawnSync(
788
- 'docker',
789
- ['exec', '-i', container, 'mysql', '-uroot', `-p${dbPass}`, dbName],
790
- { input: sql, stdio: ['pipe', 'pipe', 'pipe'] }
791
- );
792
- if (result.error) throw new Error(`docker exec failed: ${result.error.message}`);
793
- if (result.status !== 0) throw new Error((result.stderr || '').toString().trim());
794
- return (result.stdout || '').toString().trim();
781
+ function waitForWicoServer(container, maxAttempts = 60) {
782
+ if (isContainerHealthy(container)) return true; // already healthy — no wait needed
783
+ process.stdout.write(' Waiting for wico-server');
784
+ for (let i = 0; i < maxAttempts; i++) {
785
+ if (isContainerHealthy(container)) { process.stdout.write(' ready\n'); return true; }
786
+ const r = spawnSync('docker', [
787
+ 'exec', container, 'curl', '-sf', 'http://localhost:7000/health'
788
+ ], { stdio: 'pipe' });
789
+ if (r.status === 0) { process.stdout.write(' ready\n'); return true; }
790
+ process.stdout.write('.');
791
+ sleep(3000);
792
+ }
793
+ process.stdout.write(' timed out\n');
794
+ return false;
795
795
  }
796
796
 
797
797
  async function cmdSetupCredentials() {
798
798
  const envPath = path.join(process.cwd(), '.env');
799
799
  if (!fs.existsSync(envPath)) die('.env not found. Run `freeschema init` first.');
800
800
 
801
- const env = readEnv();
802
- const dbName = env.DB_DATABASE || env.MYSQL_DATABASE || 'freeschema_db';
801
+ console.log('\nSetting up CLIENT_ID and CLIENT_SECRET via API\n');
803
802
 
804
- console.log('\nSetting up CLIENT_ID and CLIENT_SECRET');
803
+ // Find wico-server container
804
+ const container = getWicoContainer();
805
+ if (!container) die('wico-server is not running.\nStart with `freeschema start`.');
805
806
 
806
- // Prompt for entity concept ID
807
- const entityId = await prompt('Admin entity concept ID', env.CLIENT_ID || '100000016');
808
- if (!entityId) die('Entity ID is required.');
807
+ // Wait for wico-server to be healthy
808
+ if (!waitForWicoServer(container)) {
809
+ die('wico-server did not become ready.\nCheck logs: freeschema logs wico-server');
810
+ }
809
811
 
810
- // Step 1: generate secret + hash it locally — no API call needed
811
- process.stdout.write('[1/3] Generating secret… ');
812
- const plainSecret = crypto.randomBytes(32).toString('hex'); // 64-char hex
813
- const hashedSecret = hashClientSecret(plainSecret);
814
- console.log('done');
812
+ // Prompt for admin credentials
813
+ const email = await prompt('Admin email');
814
+ const password = await promptSecret('Admin password');
815
+ if (!email || !password) die('Email and password are required.');
815
816
 
816
- // Step 2: insert hash directly into the database
817
- process.stdout.write('[2/3] Writing to database… ');
818
- const container = getMysqlContainer();
817
+ // Step 1: Login
818
+ process.stdout.write('\n[1/3] Logging in… ');
819
+ const loginBody = JSON.stringify({ email, password, application: 'boomconsole' });
820
+ const loginResult = spawnSync('docker', [
821
+ 'exec', container, 'curl', '-sf',
822
+ '-X', 'POST', 'http://localhost:7000/api/auth/login',
823
+ '-H', 'Content-Type: application/json',
824
+ '-d', loginBody
825
+ ], { stdio: ['inherit', 'pipe', 'pipe'] });
826
+
827
+ if (loginResult.status !== 0) {
828
+ const errText = (loginResult.stderr || '').toString().trim() || 'curl returned non-zero';
829
+ die(`Login request failed: ${errText}`);
830
+ }
831
+
832
+ let loginRes;
833
+ try {
834
+ loginRes = JSON.parse((loginResult.stdout || '').toString().trim());
835
+ } catch {
836
+ die('Invalid JSON in login response — is wico-server healthy?');
837
+ }
838
+
839
+ // Response shape: { status: 200, data: { token, userConcept, ... } }
840
+ const token = loginRes?.data?.token;
841
+ const entityId = loginRes?.data?.userConcept;
842
+ if (!token) {
843
+ die(`Login failed: ${JSON.stringify(loginRes)}`);
844
+ }
845
+ console.log(`done (entity ${entityId})`);
846
+
847
+ // Step 2: Generate client-secret
848
+ process.stdout.write('[2/3] Generating client-secret… ');
849
+ const secretResult = spawnSync('docker', [
850
+ 'exec', container, 'curl', '-sf',
851
+ '-X', 'POST', 'http://localhost:7000/api/client-secret',
852
+ '-H', `Authorization: Bearer ${token}`,
853
+ '-H', 'Content-Type: application/json'
854
+ ], { stdio: ['inherit', 'pipe', 'pipe'] });
819
855
 
820
- const sql = `
821
- SET @entity_id = ${parseInt(entityId, 10)};
822
-
823
- -- Look up the type concept for 'the_client_secret'
824
- SET @type_id = (
825
- SELECT id FROM the_concepts
826
- WHERE character_value = 'the_client_secret'
827
- ORDER BY id ASC LIMIT 1
828
- );
829
-
830
- -- Insert the secret hash as a new concept
831
- INSERT INTO the_concepts (
832
- user_id, category_id, category_user_id, type_id, type_user_id,
833
- character_value, security_id, security_user_id, access_id, access_user_id,
834
- session_information_id, session_information_user_id
835
- ) VALUES (
836
- 999, @type_id, 999, @type_id, 999,
837
- '${hashedSecret}', 4, 999, 4, 999, 999, 999
838
- );
839
-
840
- SET @secret_concept_id = LAST_INSERT_ID();
841
-
842
- -- Look up the connection type for 'the_entity_s_client_secret'
843
- SET @conn_type_id = (
844
- SELECT id FROM the_concepts
845
- WHERE character_value = 'the_entity_s_client_secret'
846
- ORDER BY id ASC LIMIT 1
847
- );
848
-
849
- -- Link entity → secret concept
850
- INSERT INTO the_connections (
851
- user_id, of_the_concepts_id, of_the_concepts_user_id,
852
- type_id, type_user_id, order_id, order_user_id,
853
- to_the_concepts_id, to_the_concepts_user_id,
854
- security_id, security_user_id, access_id, access_user_id,
855
- session_information_id, session_information_user_id
856
- ) VALUES (
857
- 999, @entity_id, 999,
858
- @conn_type_id, 999, 999, 999,
859
- @secret_concept_id, 999,
860
- 4, 999, 4, 999, 999, 999
861
- );
862
- `;
856
+ if (secretResult.status !== 0) {
857
+ const errText = (secretResult.stderr || '').toString().trim() || 'curl returned non-zero';
858
+ die(`/api/client-secret request failed: ${errText}`);
859
+ }
863
860
 
861
+ let secretRes;
864
862
  try {
865
- runSql(container, dbName, sql);
866
- } catch (e) {
867
- die(`Database insert failed: ${e.message}`);
863
+ secretRes = JSON.parse((secretResult.stdout || '').toString().trim());
864
+ } catch {
865
+ die('Invalid JSON in client-secret response');
866
+ }
867
+
868
+ // Response shape: { success: true, message: "<plainSecretString>" }
869
+ const secret = secretRes?.message;
870
+ if (!secret || typeof secret !== 'string' || !secretRes?.success) {
871
+ die(`Unexpected client-secret response: ${JSON.stringify(secretRes)}`);
868
872
  }
869
873
  console.log('done');
870
874
 
871
- // Step 3: update .env
875
+ // Step 3: Update .env
872
876
  process.stdout.write('[3/3] Updating .env… ');
873
877
  setEnvVar(envPath, 'CLIENT_ID', String(entityId));
874
- setEnvVar(envPath, 'CLIENT_SECRET', plainSecret);
878
+ setEnvVar(envPath, 'CLIENT_SECRET', secret);
875
879
  console.log('done');
876
880
 
877
881
  console.log(`
878
882
  ─────────────────────────────────────────
879
883
  Credentials saved to .env:
880
884
  CLIENT_ID = ${entityId}
881
- CLIENT_SECRET = ${plainSecret}
885
+ CLIENT_SECRET = (saved — restart to apply)
882
886
  ─────────────────────────────────────────
883
887
 
884
- Restart the node-server to apply:
888
+ Restart node-server and node-cache-server to pick up the new secret:
885
889
  freeschema restart node-server
886
890
  `);
887
891
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freeschema",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "FreeSchema — self-hosted deployment CLI",
5
5
  "keywords": [
6
6
  "freeschema",