freeschema 1.0.8 → 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.
package/bin/freeschema.js CHANGED
@@ -122,6 +122,47 @@ function generateMosquittoPassword(username, password) {
122
122
  return `${username}:$7$${iterations}$${salt.toString('base64')}$${hash.toString('base64')}\n`;
123
123
  }
124
124
 
125
+ function waitForAccessControl(container) {
126
+ process.stdout.write(' Waiting for access-control-server');
127
+ for (let i = 0; i < 30; i++) {
128
+ const r = spawnSync('docker', [
129
+ 'exec', container, 'curl', '-sf', 'http://localhost:7000/health'
130
+ ], { stdio: 'pipe' });
131
+ if (r.status === 0) { process.stdout.write(' ready\n'); return true; }
132
+ process.stdout.write('.');
133
+ sleep(2000);
134
+ }
135
+ process.stdout.write(' timed out\n');
136
+ return false;
137
+ }
138
+
139
+ function setupSuperAdmin() {
140
+ const env = readEnv();
141
+ const adminId = parseInt(env.SUPER_ADMIN_ID || '100000016', 10);
142
+ const out = runComposeOutput(['ps', '--format', 'json']);
143
+ const containers = out.split('\n')
144
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
145
+ .filter(Boolean);
146
+ const ac = containers.find(c => c.Service === 'access-control-server');
147
+ if (!ac) return;
148
+ const container = ac.Name || ac.ID;
149
+
150
+ if (!waitForAccessControl(container)) return;
151
+
152
+ const result = spawnSync('docker', [
153
+ 'exec', container, 'curl', '-sf',
154
+ '-X', 'POST', 'http://localhost:7000/api/access/super-admin/concept',
155
+ '-H', 'Content-Type: application/json',
156
+ '-d', JSON.stringify({ ConceptId: adminId })
157
+ ], { stdio: ['inherit', 'pipe', 'pipe'] });
158
+
159
+ if (result.status === 0) {
160
+ console.log(` Super admin concept ${adminId} ✓`);
161
+ } else {
162
+ console.log(` Super admin already set or skipped`);
163
+ }
164
+ }
165
+
125
166
  function ensureMosquittoConfig(cwd) {
126
167
  const dir = (sub) => path.join(cwd, 'mosquitto', sub);
127
168
  ['config', 'data', 'log'].forEach(d => {
@@ -556,6 +597,9 @@ async function cmdStart() {
556
597
  console.log(' Seed applied ✓');
557
598
  }
558
599
 
600
+ console.log('\nFinalizing setup…');
601
+ setupSuperAdmin();
602
+
559
603
  console.log('\nFreeSchema is running.');
560
604
  console.log(' Status: freeschema status');
561
605
  console.log(' Logs: freeschema logs');
@@ -570,19 +614,6 @@ async function cmdStart() {
570
614
  console.log(' CLIENT_SECRET is not configured.');
571
615
  console.log(' Setting up admin credentials…');
572
616
  console.log('─────────────────────────────────────────');
573
-
574
- // Wait for MySQL to be ready before inserting credentials
575
- try {
576
- const container = getMysqlContainer();
577
- const env = readEnv();
578
- const dbUser = env.DB_USER || env.MYSQL_USER || 'freeschema';
579
- const dbPass = env.DB_PASSWORD || env.MYSQL_PASSWORD || 'Freeschema@123';
580
- waitForMySQL(container, dbUser, dbPass);
581
- } catch (e) {
582
- console.log(' (skipping credentials setup — MySQL not running)');
583
- return;
584
- }
585
-
586
617
  await cmdSetupCredentials();
587
618
  }
588
619
  }
@@ -728,116 +759,122 @@ function cmdDbRestore() {
728
759
 
729
760
  // ── setup-credentials ─────────────────────────────────────────────────────────
730
761
 
731
- function hashClientSecret(plainSecret) {
732
- // Matches C# ClientSecretHasher: PBKDF2-HMAC-SHA256, 100000 iterations, 16-byte salt, 32-byte output
733
- // Stored format: "{iterations}.{base64_salt}.{base64_hash}"
734
- const iterations = 100_000;
735
- const salt = crypto.randomBytes(16);
736
- const hash = crypto.pbkdf2Sync(plainSecret, salt, iterations, 32, 'sha256');
737
- 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;
738
770
  }
739
771
 
740
- function runSql(container, dbName, sql) {
741
- const env = readEnv();
742
- const dbPass = env.MYSQL_ROOT_PASSWORD || ('Admin@' + (env.MYSQL_PASSWORD || 'Freeschema@123'));
743
- const result = spawnSync(
744
- 'docker',
745
- ['exec', '-i', container, 'mysql', '-uroot', `-p${dbPass}`, dbName],
746
- { input: sql, stdio: ['pipe', 'pipe', 'pipe'] }
747
- );
748
- if (result.error) throw new Error(`docker exec failed: ${result.error.message}`);
749
- if (result.status !== 0) throw new Error((result.stderr || '').toString().trim());
750
- 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;
751
784
  }
752
785
 
753
786
  async function cmdSetupCredentials() {
754
787
  const envPath = path.join(process.cwd(), '.env');
755
788
  if (!fs.existsSync(envPath)) die('.env not found. Run `freeschema init` first.');
756
789
 
757
- const env = readEnv();
758
- const dbName = env.DB_DATABASE || env.MYSQL_DATABASE || 'freeschema_db';
790
+ console.log('\nSetting up CLIENT_ID and CLIENT_SECRET via API\n');
759
791
 
760
- 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`.');
761
795
 
762
- // Prompt for entity concept ID
763
- const entityId = await prompt('Admin entity concept ID', env.CLIENT_ID || '100000016');
764
- 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
+ }
765
800
 
766
- // Step 1: generate secret + hash it locally — no API call needed
767
- process.stdout.write('[1/3] Generating secret… ');
768
- const plainSecret = crypto.randomBytes(32).toString('hex'); // 64-char hex
769
- const hashedSecret = hashClientSecret(plainSecret);
770
- 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.');
805
+
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'] });
815
+
816
+ if (loginResult.status !== 0) {
817
+ const errText = (loginResult.stderr || '').toString().trim() || 'curl returned non-zero';
818
+ die(`Login request failed: ${errText}`);
819
+ }
771
820
 
772
- // Step 2: insert hash directly into the database
773
- process.stdout.write('[2/3] Writing to database… ');
774
- const container = getMysqlContainer();
821
+ let loginRes;
822
+ try {
823
+ loginRes = JSON.parse((loginResult.stdout || '').toString().trim());
824
+ } catch {
825
+ die('Invalid JSON in login response — is wico-server healthy?');
826
+ }
775
827
 
776
- const sql = `
777
- SET @entity_id = ${parseInt(entityId, 10)};
778
-
779
- -- Look up the type concept for 'the_client_secret'
780
- SET @type_id = (
781
- SELECT id FROM the_concepts
782
- WHERE character_value = 'the_client_secret'
783
- ORDER BY id ASC LIMIT 1
784
- );
785
-
786
- -- Insert the secret hash as a new concept
787
- INSERT INTO the_concepts (
788
- user_id, category_id, category_user_id, type_id, type_user_id,
789
- character_value, security_id, security_user_id, access_id, access_user_id,
790
- session_information_id, session_information_user_id
791
- ) VALUES (
792
- 999, @type_id, 999, @type_id, 999,
793
- '${hashedSecret}', 4, 999, 4, 999, 999, 999
794
- );
795
-
796
- SET @secret_concept_id = LAST_INSERT_ID();
797
-
798
- -- Look up the connection type for 'the_entity_s_client_secret'
799
- SET @conn_type_id = (
800
- SELECT id FROM the_concepts
801
- WHERE character_value = 'the_entity_s_client_secret'
802
- ORDER BY id ASC LIMIT 1
803
- );
804
-
805
- -- Link entity → secret concept
806
- INSERT INTO the_connections (
807
- user_id, of_the_concepts_id, of_the_concepts_user_id,
808
- type_id, type_user_id, order_id, order_user_id,
809
- to_the_concepts_id, to_the_concepts_user_id,
810
- security_id, security_user_id, access_id, access_user_id,
811
- session_information_id, session_information_user_id
812
- ) VALUES (
813
- 999, @entity_id, 999,
814
- @conn_type_id, 999, 999, 999,
815
- @secret_concept_id, 999,
816
- 4, 999, 4, 999, 999, 999
817
- );
818
- `;
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
+ }
819
849
 
850
+ let secretRes;
820
851
  try {
821
- runSql(container, dbName, sql);
822
- } catch (e) {
823
- die(`Database insert failed: ${e.message}`);
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)}`);
824
861
  }
825
862
  console.log('done');
826
863
 
827
- // Step 3: update .env
864
+ // Step 3: Update .env
828
865
  process.stdout.write('[3/3] Updating .env… ');
829
866
  setEnvVar(envPath, 'CLIENT_ID', String(entityId));
830
- setEnvVar(envPath, 'CLIENT_SECRET', plainSecret);
867
+ setEnvVar(envPath, 'CLIENT_SECRET', secret);
831
868
  console.log('done');
832
869
 
833
870
  console.log(`
834
871
  ─────────────────────────────────────────
835
872
  Credentials saved to .env:
836
873
  CLIENT_ID = ${entityId}
837
- CLIENT_SECRET = ${plainSecret}
874
+ CLIENT_SECRET = (saved — restart to apply)
838
875
  ─────────────────────────────────────────
839
876
 
840
- Restart the node-server to apply:
877
+ Restart node-server and node-cache-server to pick up the new secret:
841
878
  freeschema restart node-server
842
879
  `);
843
880
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freeschema",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "FreeSchema — self-hosted deployment CLI",
5
5
  "keywords": [
6
6
  "freeschema",
@@ -121,3 +121,8 @@ DISABLE_SWAGGER=true
121
121
  # ====================================
122
122
  CLIENT_ID=
123
123
  CLIENT_SECRET=
124
+
125
+ # ====================================
126
+ # SUPER ADMIN SETUP
127
+ # ====================================
128
+ SUPER_ADMIN_ID=100000016
@@ -188,6 +188,12 @@ services:
188
188
  condition: service_started
189
189
  networks:
190
190
  - freeschema-network
191
+ healthcheck:
192
+ test: ["CMD-SHELL", "curl -sI http://localhost:5000/health || exit 1"]
193
+ interval: 1m30s
194
+ timeout: 30s
195
+ retries: 5
196
+ start_period: 30s
191
197
 
192
198
  log-server:
193
199
  image: mentorayush/log-server:latest
@@ -229,6 +235,12 @@ services:
229
235
  - node-server
230
236
  networks:
231
237
  - freeschema-network
238
+ healthcheck:
239
+ test: ["CMD-SHELL", "wget -q --spider http://localhost/ || exit 1"]
240
+ interval: 30s
241
+ timeout: 10s
242
+ retries: 3
243
+ start_period: 30s
232
244
 
233
245
  wico-app:
234
246
  image: mentorayush/wico-app:latest
@@ -243,6 +255,12 @@ services:
243
255
  - node-server
244
256
  networks:
245
257
  - freeschema-network
258
+ healthcheck:
259
+ test: ["CMD-SHELL", "wget -q --spider http://localhost/ || exit 1"]
260
+ interval: 30s
261
+ timeout: 10s
262
+ retries: 3
263
+ start_period: 30s
246
264
 
247
265
  nginx-server:
248
266
  image: nginx:alpine
@@ -255,13 +273,20 @@ services:
255
273
  volumes:
256
274
  - ./nginx/nginx.conf.template:/etc/nginx/templates/default.conf.template:ro
257
275
  depends_on:
258
- - wico-app
259
- - vccs-app
260
- - node-server
261
- - node-cache-server
262
- - log-server
263
- - wico-server
264
- - access-control-server
276
+ access-control-server:
277
+ condition: service_healthy
278
+ wico-server:
279
+ condition: service_healthy
280
+ node-server:
281
+ condition: service_healthy
282
+ node-cache-server:
283
+ condition: service_healthy
284
+ log-server:
285
+ condition: service_healthy
286
+ vccs-app:
287
+ condition: service_healthy
288
+ wico-app:
289
+ condition: service_healthy
265
290
  networks:
266
291
  - freeschema-network
267
292