freeschema 1.0.7 → 1.0.8
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 +93 -94
- package/package.json +1 -1
package/bin/freeschema.js
CHANGED
|
@@ -6,7 +6,6 @@ const { spawnSync } = require('child_process');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const https = require('https');
|
|
9
|
-
const http = require('http');
|
|
10
9
|
const crypto = require('crypto');
|
|
11
10
|
const readline = require('readline');
|
|
12
11
|
|
|
@@ -569,8 +568,21 @@ async function cmdStart() {
|
|
|
569
568
|
if (isPlaceholder) {
|
|
570
569
|
console.log('\n─────────────────────────────────────────');
|
|
571
570
|
console.log(' CLIENT_SECRET is not configured.');
|
|
572
|
-
console.log(' Setting up admin credentials
|
|
571
|
+
console.log(' Setting up admin credentials…');
|
|
573
572
|
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
|
+
|
|
574
586
|
await cmdSetupCredentials();
|
|
575
587
|
}
|
|
576
588
|
}
|
|
@@ -716,126 +728,113 @@ function cmdDbRestore() {
|
|
|
716
728
|
|
|
717
729
|
// ── setup-credentials ─────────────────────────────────────────────────────────
|
|
718
730
|
|
|
719
|
-
function
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
path: parsed.pathname,
|
|
727
|
-
method: 'POST',
|
|
728
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
729
|
-
};
|
|
730
|
-
const lib = parsed.protocol === 'https:' ? https : http;
|
|
731
|
-
const req = lib.request(opts, res => {
|
|
732
|
-
let data = '';
|
|
733
|
-
res.on('data', chunk => { data += chunk; });
|
|
734
|
-
res.on('end', () => {
|
|
735
|
-
try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
|
|
736
|
-
catch { resolve({ status: res.statusCode, body: data }); }
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
req.on('error', reject);
|
|
740
|
-
req.write(payload);
|
|
741
|
-
req.end();
|
|
742
|
-
});
|
|
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')}`;
|
|
743
738
|
}
|
|
744
739
|
|
|
745
|
-
function
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const req = lib.request(opts, res => {
|
|
757
|
-
let data = '';
|
|
758
|
-
res.on('data', chunk => { data += chunk; });
|
|
759
|
-
res.on('end', () => {
|
|
760
|
-
try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
|
|
761
|
-
catch { resolve({ status: res.statusCode, body: data }); }
|
|
762
|
-
});
|
|
763
|
-
});
|
|
764
|
-
req.on('error', reject);
|
|
765
|
-
req.end();
|
|
766
|
-
});
|
|
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();
|
|
767
751
|
}
|
|
768
752
|
|
|
769
753
|
async function cmdSetupCredentials() {
|
|
770
754
|
const envPath = path.join(process.cwd(), '.env');
|
|
771
755
|
if (!fs.existsSync(envPath)) die('.env not found. Run `freeschema init` first.');
|
|
772
756
|
|
|
773
|
-
const env
|
|
774
|
-
const
|
|
775
|
-
const baseUrl = `http://localhost:${wicoPort}/api`;
|
|
757
|
+
const env = readEnv();
|
|
758
|
+
const dbName = env.DB_DATABASE || env.MYSQL_DATABASE || 'freeschema_db';
|
|
776
759
|
|
|
777
|
-
console.log(
|
|
778
|
-
console.log(`Using WICO server at ${baseUrl}\n`);
|
|
760
|
+
console.log('\nSetting up CLIENT_ID and CLIENT_SECRET');
|
|
779
761
|
|
|
780
|
-
//
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
if (!email || !password) die('Email and password are required.');
|
|
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.');
|
|
784
765
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
die(`Could not reach WICO server at ${baseUrl}/auth/login\nIs FreeSchema running? Try: freeschema start`);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
if (loginRes.status !== 200 || !loginRes.body) {
|
|
794
|
-
die(`Login failed (HTTP ${loginRes.status}): ${JSON.stringify(loginRes.body)}`);
|
|
795
|
-
}
|
|
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');
|
|
796
771
|
|
|
797
|
-
|
|
798
|
-
|
|
772
|
+
// Step 2: insert hash directly into the database
|
|
773
|
+
process.stdout.write('[2/3] Writing to database… ');
|
|
774
|
+
const container = getMysqlContainer();
|
|
799
775
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
+
`;
|
|
810
819
|
|
|
811
|
-
// Step 2: generate client-secret
|
|
812
|
-
process.stdout.write('[2/3] Generating client-secret… ');
|
|
813
|
-
let secretRes;
|
|
814
820
|
try {
|
|
815
|
-
|
|
821
|
+
runSql(container, dbName, sql);
|
|
816
822
|
} catch (e) {
|
|
817
|
-
die(`
|
|
823
|
+
die(`Database insert failed: ${e.message}`);
|
|
818
824
|
}
|
|
819
|
-
|
|
820
|
-
if (secretRes.status !== 200 || !secretRes.body) {
|
|
821
|
-
die(`client-secret failed (HTTP ${secretRes.status}): ${JSON.stringify(secretRes.body)}`);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
const secret = secretRes.body.message || secretRes.body.data || secretRes.body;
|
|
825
|
-
if (!secret || typeof secret !== 'string') die(`Empty secret in response: ${JSON.stringify(secretRes.body)}`);
|
|
826
825
|
console.log('done');
|
|
827
826
|
|
|
828
827
|
// Step 3: update .env
|
|
829
828
|
process.stdout.write('[3/3] Updating .env… ');
|
|
830
829
|
setEnvVar(envPath, 'CLIENT_ID', String(entityId));
|
|
831
|
-
setEnvVar(envPath, 'CLIENT_SECRET',
|
|
830
|
+
setEnvVar(envPath, 'CLIENT_SECRET', plainSecret);
|
|
832
831
|
console.log('done');
|
|
833
832
|
|
|
834
833
|
console.log(`
|
|
835
834
|
─────────────────────────────────────────
|
|
836
835
|
Credentials saved to .env:
|
|
837
836
|
CLIENT_ID = ${entityId}
|
|
838
|
-
CLIENT_SECRET = ${
|
|
837
|
+
CLIENT_SECRET = ${plainSecret}
|
|
839
838
|
─────────────────────────────────────────
|
|
840
839
|
|
|
841
840
|
Restart the node-server to apply:
|