freeschema 1.0.9 → 1.0.10

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 +88 -95
  2. package/package.json +1 -1
package/bin/freeschema.js CHANGED
@@ -614,19 +614,6 @@ async function cmdStart() {
614
614
  console.log(' CLIENT_SECRET is not configured.');
615
615
  console.log(' Setting up admin credentials…');
616
616
  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
617
  await cmdSetupCredentials();
631
618
  }
632
619
  }
@@ -772,116 +759,122 @@ function cmdDbRestore() {
772
759
 
773
760
  // ── setup-credentials ─────────────────────────────────────────────────────────
774
761
 
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')}`;
762
+ function getWicoContainer() {
763
+ const out = runComposeOutput(['ps', '--format', 'json']);
764
+ if (!out) return null;
765
+ const containers = out.split('\n')
766
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
767
+ .filter(Boolean);
768
+ const wico = containers.find(c => c.Service === 'wico-server' || (c.Name || '').includes('wico-server'));
769
+ return wico ? (wico.Name || wico.ID) : null;
782
770
  }
783
771
 
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();
772
+ function waitForWicoServer(container, maxAttempts = 60) {
773
+ process.stdout.write(' Waiting for wico-server');
774
+ for (let i = 0; i < maxAttempts; i++) {
775
+ const r = spawnSync('docker', [
776
+ 'exec', container, 'curl', '-sf', 'http://localhost:7000/health'
777
+ ], { stdio: 'pipe' });
778
+ if (r.status === 0) { process.stdout.write(' ready\n'); return true; }
779
+ process.stdout.write('.');
780
+ sleep(3000);
781
+ }
782
+ process.stdout.write(' timed out\n');
783
+ return false;
795
784
  }
796
785
 
797
786
  async function cmdSetupCredentials() {
798
787
  const envPath = path.join(process.cwd(), '.env');
799
788
  if (!fs.existsSync(envPath)) die('.env not found. Run `freeschema init` first.');
800
789
 
801
- const env = readEnv();
802
- const dbName = env.DB_DATABASE || env.MYSQL_DATABASE || 'freeschema_db';
790
+ console.log('\nSetting up CLIENT_ID and CLIENT_SECRET via API\n');
803
791
 
804
- console.log('\nSetting up CLIENT_ID and CLIENT_SECRET');
792
+ // Find wico-server container
793
+ const container = getWicoContainer();
794
+ if (!container) die('wico-server is not running.\nStart with `freeschema start`.');
805
795
 
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.');
796
+ // Wait for wico-server to be healthy
797
+ if (!waitForWicoServer(container)) {
798
+ die('wico-server did not become ready.\nCheck logs: freeschema logs wico-server');
799
+ }
809
800
 
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');
801
+ // Prompt for admin credentials
802
+ const email = await prompt('Admin email');
803
+ const password = await promptSecret('Admin password');
804
+ if (!email || !password) die('Email and password are required.');
815
805
 
816
- // Step 2: insert hash directly into the database
817
- process.stdout.write('[2/3] Writing to database… ');
818
- const container = getMysqlContainer();
806
+ // Step 1: Login
807
+ process.stdout.write('\n[1/3] Logging in… ');
808
+ const loginBody = JSON.stringify({ email, password, application: 'boomconsole' });
809
+ const loginResult = spawnSync('docker', [
810
+ 'exec', container, 'curl', '-sf',
811
+ '-X', 'POST', 'http://localhost:7000/api/auth/login',
812
+ '-H', 'Content-Type: application/json',
813
+ '-d', loginBody
814
+ ], { stdio: ['inherit', 'pipe', 'pipe'] });
819
815
 
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
- `;
816
+ if (loginResult.status !== 0) {
817
+ const errText = (loginResult.stderr || '').toString().trim() || 'curl returned non-zero';
818
+ die(`Login request failed: ${errText}`);
819
+ }
863
820
 
821
+ let loginRes;
864
822
  try {
865
- runSql(container, dbName, sql);
866
- } catch (e) {
867
- die(`Database insert failed: ${e.message}`);
823
+ loginRes = JSON.parse((loginResult.stdout || '').toString().trim());
824
+ } catch {
825
+ die('Invalid JSON in login response — is wico-server healthy?');
826
+ }
827
+
828
+ // Response shape: { status: 200, data: { token, userConcept, ... } }
829
+ const token = loginRes?.data?.token;
830
+ const entityId = loginRes?.data?.userConcept;
831
+ if (!token) {
832
+ die(`Login failed: ${JSON.stringify(loginRes)}`);
833
+ }
834
+ console.log(`done (entity ${entityId})`);
835
+
836
+ // Step 2: Generate client-secret
837
+ process.stdout.write('[2/3] Generating client-secret… ');
838
+ const secretResult = spawnSync('docker', [
839
+ 'exec', container, 'curl', '-sf',
840
+ '-X', 'POST', 'http://localhost:7000/api/client-secret',
841
+ '-H', `Authorization: Bearer ${token}`,
842
+ '-H', 'Content-Type: application/json'
843
+ ], { stdio: ['inherit', 'pipe', 'pipe'] });
844
+
845
+ if (secretResult.status !== 0) {
846
+ const errText = (secretResult.stderr || '').toString().trim() || 'curl returned non-zero';
847
+ die(`/api/client-secret request failed: ${errText}`);
848
+ }
849
+
850
+ let secretRes;
851
+ try {
852
+ secretRes = JSON.parse((secretResult.stdout || '').toString().trim());
853
+ } catch {
854
+ die('Invalid JSON in client-secret response');
855
+ }
856
+
857
+ // Response shape: { success: true, message: "<plainSecretString>" }
858
+ const secret = secretRes?.message;
859
+ if (!secret || typeof secret !== 'string' || !secretRes?.success) {
860
+ die(`Unexpected client-secret response: ${JSON.stringify(secretRes)}`);
868
861
  }
869
862
  console.log('done');
870
863
 
871
- // Step 3: update .env
864
+ // Step 3: Update .env
872
865
  process.stdout.write('[3/3] Updating .env… ');
873
866
  setEnvVar(envPath, 'CLIENT_ID', String(entityId));
874
- setEnvVar(envPath, 'CLIENT_SECRET', plainSecret);
867
+ setEnvVar(envPath, 'CLIENT_SECRET', secret);
875
868
  console.log('done');
876
869
 
877
870
  console.log(`
878
871
  ─────────────────────────────────────────
879
872
  Credentials saved to .env:
880
873
  CLIENT_ID = ${entityId}
881
- CLIENT_SECRET = ${plainSecret}
874
+ CLIENT_SECRET = (saved — restart to apply)
882
875
  ─────────────────────────────────────────
883
876
 
884
- Restart the node-server to apply:
877
+ Restart node-server and node-cache-server to pick up the new secret:
885
878
  freeschema restart node-server
886
879
  `);
887
880
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freeschema",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "FreeSchema — self-hosted deployment CLI",
5
5
  "keywords": [
6
6
  "freeschema",