Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 336 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,324 @@
}
}

// Command to handle login
// Command to handle registration
async function registerCommand(options) {
const host = options.host || 'https://api.fleetbase.io';
// Ensure host has protocol, add https:// if missing
const apiHost = host.startsWith('http://') || host.startsWith('https://') ? host : `https://${host}`;
const registrationApi = `${apiHost}/~registry/v1/developer-account/register`;

try {
// Collect registration information
const answers = await prompt([
{
type: 'input',
name: 'username',
message: 'Username:',
initial: options.username,
skip: !!options.username,
validate: (value) => {
if (!value || value.length < 3) {
return 'Username must be at least 3 characters';
}
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
return 'Username can only contain letters, numbers, hyphens, and underscores';
}
return true;
}
},
{
type: 'input',
name: 'email',
message: 'Email:',
initial: options.email,
skip: !!options.email,
validate: (value) => {
if (!value || !value.includes('@')) {
return 'Please enter a valid email address';
}
return true;
}
},
{
type: 'password',
name: 'password',
message: 'Password:',
skip: !!options.password,
validate: (value) => {
if (!value || value.length < 8) {
return 'Password must be at least 8 characters';
}
return true;
}
},
{
type: 'input',
name: 'name',
message: 'Full Name (optional):',
initial: options.name
}
]);

const registrationData = {
username: options.username || answers.username,
email: options.email || answers.email,
password: options.password || answers.password,
name: options.name || answers.name || undefined
};

console.log('\nRegistering account...');
console.log(`API Endpoint: ${registrationApi}`);
console.log(`Request Data:`, JSON.stringify(registrationData, null, 2));

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This logs sensitive data returned by
an access to password
as clear text.

Copilot Autofix

AI about 11 hours ago

In general, to fix clear‑text logging of sensitive information, you should either (1) avoid logging the sensitive fields entirely, or (2) mask/redact them before logging. For passwords, the best practice is to never log them, even in masked form if avoidable.

Here, the minimal, behavior‑preserving fix is to keep the helpful logging of request parameters but omit or redact the password field when printing. We can do this by creating a shallow copy of registrationData specifically for logging, removing or masking the password property, and then passing that safe object to JSON.stringify. This keeps the registration logic and API call unchanged; only the logging statement is updated.

Concretely, in index.js around line 790–792, we will replace:

console.log('\nRegistering account...');
console.log(`API Endpoint: ${registrationApi}`);
console.log(`Request Data:`, JSON.stringify(registrationData, null, 2));

with code that constructs a registrationDataForLog object (using object spread) and sets password to a placeholder (or deletes it) before logging. No new imports are needed; we can use plain JavaScript. The rest of the file remains as is.

Suggested changeset 1
index.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/index.js b/index.js
--- a/index.js
+++ b/index.js
@@ -789,7 +789,8 @@
 
         console.log('\nRegistering account...');
         console.log(`API Endpoint: ${registrationApi}`);
-        console.log(`Request Data:`, JSON.stringify(registrationData, null, 2));
+        const registrationDataForLog = { ...registrationData, password: '[REDACTED]' };
+        console.log(`Request Data:`, JSON.stringify(registrationDataForLog, null, 2));
 
         // Make API call to register
         const response = await axios.post(registrationApi, registrationData);
EOF
@@ -789,7 +789,8 @@

console.log('\nRegistering account...');
console.log(`API Endpoint: ${registrationApi}`);
console.log(`Request Data:`, JSON.stringify(registrationData, null, 2));
const registrationDataForLog = { ...registrationData, password: '[REDACTED]' };
console.log(`Request Data:`, JSON.stringify(registrationDataForLog, null, 2));

// Make API call to register
const response = await axios.post(registrationApi, registrationData);
Copilot is powered by AI and may make mistakes. Always verify output.

// Make API call to register
const response = await axios.post(registrationApi, registrationData);

if (response.data.status === 'success') {
console.log('\n✓ Account created successfully!');
console.log('✓ Please check your email to verify your account.');
const loginCmd = options.host ? `flb login -u ${registrationData.username} --host ${options.host}` : `flb login -u ${registrationData.username}`;
console.log(`\n✓ Once verified, you can login with: ${loginCmd}`);
} else {
console.error('Registration failed:', response.data.message || 'Unknown error');
process.exit(1);
}
} catch (error) {
console.error('\n[DEBUG] Error caught:', error.message);
if (error.code) console.error('[DEBUG] Error code:', error.code);
if (error.request && !error.response) {
console.error('[DEBUG] No response received from server');
console.error('[DEBUG] Request was made to:', registrationApi);
}
if (error.response && error.response.data) {
const errorData = error.response.data;
if (errorData.errors) {
console.error('\nRegistration failed with the following errors:');
Object.keys(errorData.errors).forEach(field => {
const fieldErrors = errorData.errors[field];
// Handle both array and string error formats
if (Array.isArray(fieldErrors)) {
fieldErrors.forEach(message => {
console.error(` - ${field}: ${message}`);
});
} else {
console.error(` - ${field}: ${fieldErrors}`);
}
});
} else {
console.error('Registration failed:', errorData.message || 'Unknown error');
}
} else {
console.error('Registration failed:', error.message);
}
process.exit(1);
}
}

// Command to install Fleetbase via Docker
async function installFleetbaseCommand(options) {
const crypto = require('crypto');
const os = require('os');

console.log('\n🚀 Fleetbase Installation\n');

try {
// Collect installation parameters
const answers = await prompt([
{
type: 'input',
name: 'host',
message: 'Enter host or IP address to bind to:',
initial: options.host || 'localhost',
validate: (value) => {
if (!value) {
return 'Host is required';
}
return true;
}
},
{
type: 'select',
name: 'environment',
message: 'Choose environment:',
initial: options.environment === 'production' ? 1 : 0,
choices: [
{ title: 'Development', value: 'development' },
{ title: 'Production', value: 'production' }
]
},
{
type: 'input',
name: 'directory',
message: 'Installation directory:',
initial: options.directory || process.cwd(),
validate: (value) => {
if (!value) {
return 'Directory is required';
}
return true;
}
}
]);

const host = options.host || answers.host;
const environment = options.environment || answers.environment;
const directory = options.directory || answers.directory;

// Determine configuration based on environment
const useHttps = environment === 'production';
const appDebug = environment === 'development';
const scSecure = environment === 'production';
const schemeApi = useHttps ? 'https' : 'http';
const schemeConsole = useHttps ? 'https' : 'http';

console.log(`\n📋 Configuration:`);
console.log(` Host: ${host}`);
console.log(` Environment: ${environment}`);
console.log(` Directory: ${directory}`);
console.log(` HTTPS: ${useHttps}`);

// Check if directory exists and has Fleetbase files
const dockerComposePath = path.join(directory, 'docker-compose.yml');
const needsClone = !await fs.pathExists(dockerComposePath);

if (needsClone) {
console.log('\n⏳ Fleetbase repository not found, cloning...');

// Ensure parent directory exists
await fs.ensureDir(directory);

// Clone the repository
const { execSync } = require('child_process');
try {
execSync('git clone https://github.com/fleetbase/fleetbase.git .', {
cwd: directory,
stdio: 'inherit'
});
console.log('✔ Repository cloned successfully');
} catch (error) {
console.error('\n✖ Failed to clone repository:', error.message);
console.log('\nℹ️ You can manually clone with:');
console.log(' git clone https://github.com/fleetbase/fleetbase.git');
process.exit(1);
}
}

// Generate APP_KEY
console.log('\n⏳ Generating APP_KEY...');
const appKey = 'base64:' + crypto.randomBytes(32).toString('base64');
console.log('✔ APP_KEY generated');

// Create docker-compose.override.yml
console.log('⏳ Creating docker-compose.override.yml...');
const overrideContent = `services:
application:
environment:
APP_KEY: "${appKey}"
CONSOLE_HOST: "${schemeConsole}://${host}:4200"
ENVIRONMENT: "${environment}"
APP_DEBUG: "${appDebug}"
`;
const overridePath = path.join(directory, 'docker-compose.override.yml');
await fs.writeFile(overridePath, overrideContent);
console.log('✔ docker-compose.override.yml created');

// Create console/fleetbase.config.json (for development runtime config)
console.log('⏳ Creating console/fleetbase.config.json...');
const configDir = path.join(directory, 'console');
await fs.ensureDir(configDir);
const configContent = {
API_HOST: `${schemeApi}://${host}:8000`,
SOCKETCLUSTER_HOST: host,
SOCKETCLUSTER_PORT: '38000',
SOCKETCLUSTER_SECURE: scSecure
};
const configPath = path.join(configDir, 'fleetbase.config.json');
await fs.writeJson(configPath, configContent, { spaces: 2 });
console.log('✔ console/fleetbase.config.json created');

// Update console environment files (.env.development and .env.production)
console.log('⏳ Updating console environment files...');
const environmentsDir = path.join(configDir, 'environments');

// Update .env.development
const envDevelopmentContent = `API_HOST=http://${host}:8000
API_NAMESPACE=int/v1
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=${host}
SOCKETCLUSTER_SECURE=false
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://router.project-osrm.org
`;
const envDevelopmentPath = path.join(environmentsDir, '.env.development');
await fs.writeFile(envDevelopmentPath, envDevelopmentContent);

// Update .env.production
const envProductionContent = `API_HOST=https://${host}:8000
API_NAMESPACE=int/v1
API_SECURE=true
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=${host}
SOCKETCLUSTER_SECURE=true
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://router.project-osrm.org
`;
const envProductionPath = path.join(environmentsDir, '.env.production');
await fs.writeFile(envProductionPath, envProductionContent);

console.log('✔ Console environment files updated');

// Start Docker containers
console.log('\n⏳ Starting Fleetbase containers...');
console.log(' This may take a few minutes on first run...\n');

exec('docker compose up -d', { cwd: directory, maxBuffer: maxBuffer }, async (error, stdout, stderr) => {
if (error) {
console.error(`\n✖ Error starting containers: ${error.message}`);
if (stderr) console.error(stderr);
process.exit(1);
}

console.log(stdout);
console.log('✔ Containers started');

// Wait for database
console.log('\n⏳ Waiting for database to be ready...');
await new Promise(resolve => setTimeout(resolve, 15000)); // Wait 15 seconds
console.log('✔ Database should be ready');

// Run deploy script
console.log('\n⏳ Running deployment script...');
exec('docker compose exec -T application bash -c "./deploy.sh"', { cwd: directory, maxBuffer: maxBuffer }, (deployError, deployStdout, deployStderr) => {
if (deployError) {
console.error(`\n✖ Error during deployment: ${deployError.message}`);
if (deployStderr) console.error(deployStderr);
console.log('\nℹ️ You may need to run the deployment manually:');
console.log(' docker compose exec application bash -c "./deploy.sh"');
} else {
console.log(deployStdout);
console.log('✔ Deployment complete');
}

// Restart containers to ensure all changes are applied
exec('docker compose up -d', { cwd: directory }, () => {
console.log('\n🏁 Fleetbase is up!');
console.log(` API → ${schemeApi}://${host}:8000`);
console.log(` Console → ${schemeConsole}://${host}:4200\n`);
console.log('ℹ️ Default credentials:');
console.log(' Email: admin@fleetbase.io');
console.log(' Password: password\n');
});
});
});
} catch (error) {
console.error('\n✖ Installation failed:', error.message);
process.exit(1);
}
}



function loginCommand (options) {
const npmLogin = require('npm-cli-login');
const username = options.username;
Expand Down Expand Up @@ -875,6 +1192,24 @@
.option('--pre-release [identifier]', 'Add pre-release identifier')
.action(versionBump);

program
.command('register')
.description('Register a new Registry Developer Account')
.option('-u, --username <username>', 'Username for the registry')
.option('-e, --email <email>', 'Email address')
.option('-p, --password <password>', 'Password')
.option('-n, --name <name>', 'Your full name (optional)')
.option('-h, --host <host>', 'API host with protocol (default: https://api.fleetbase.io)')
.action(registerCommand);

program
.command('install-fleetbase')
.description('Install Fleetbase using Docker')
.option('--host <host>', 'Host or IP address to bind to (default: localhost)')
.option('--environment <environment>', 'Environment: development or production (default: development)')
.option('--directory <directory>', 'Installation directory (default: current directory)')
.action(installFleetbaseCommand);

program
.command('login')
.description('Log in to the Fleetbase registry')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fleetbase/cli",
"version": "0.0.3",
"version": "0.0.4",
"description": "CLI tool for managing Fleetbase Extensions",
"repository": "https://github.com/fleetbase/fleetbase",
"license": "AGPL-3.0-or-later",
Expand Down