Config Structure Changes and Gateway Integration ## Configuration Structure Changes - Restructured .taskmasterconfig to use 'account' section for user settings - Moved userId, userEmail, mode, telemetryEnabled from global to account section - API keys remain isolated in .env file (not accessible to AI) - Enhanced getUserId() to always return value, never null (sets default '1234567890') ## Gateway Integration Enhancements - Updated registerUserWithGateway() to accept both email and userId parameters - Enhanced /auth/init endpoint integration for existing user validation - API key updates automatically written to .env during registration process - Improved user identification and validation flow ## Code Updates for New Structure - Fixed config-manager.js getter functions for account section access - Updated user-management.js to use config.account.userId/mode - Modified telemetry-submission.js to read from account section - Added getTelemetryEnabled() function with proper account section access - Enhanced telemetry configuration reading with new structure ## Comprehensive Test Updates - Updated integration tests (init-config.test.js) for new config structure - Fixed unit tests (config-manager.test.js) with updated default config - Updated telemetry tests (telemetry-submission.test.js) for account structure - Added missing getTelemetryEnabled mock to ai-services-unified.test.js - Fixed all test expectations to use config.account.* instead of config.global.* - Removed references to deprecated config.subscription object ## Configuration Access Consistency - Standardized configuration access patterns across entire codebase - Clean separation: user settings in account, API keys in .env, models/global in respective sections - All tests passing with new configuration structure - Maintained backward compatibility during transition Changes support enhanced telemetry system with proper user management and gateway integration while maintaining security through API key isolation.
316 lines
8.4 KiB
JavaScript
316 lines
8.4 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import { log, findProjectRoot } from "./utils.js";
|
|
import { getConfig, writeConfig } from "./config-manager.js";
|
|
|
|
/**
|
|
* Registers or finds a user via the gateway's /auth/init endpoint
|
|
* @param {string|null} email - Optional user's email address (only needed for billing)
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {Promise<{success: boolean, userId: string, token: string, isNewUser: boolean, error?: string}>}
|
|
*/
|
|
async function registerUserWithGateway(email = null, explicitRoot = null) {
|
|
try {
|
|
const gatewayUrl =
|
|
process.env.TASKMASTER_GATEWAY_URL || "http://localhost:4444";
|
|
|
|
// Email is optional - only send if provided
|
|
const requestBody = email ? { email } : {};
|
|
|
|
const response = await fetch(`${gatewayUrl}/auth/init`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(requestBody),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
token: "",
|
|
isNewUser: false,
|
|
error: `Gateway registration failed: ${response.status} ${errorText}`,
|
|
};
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.data) {
|
|
return {
|
|
success: true,
|
|
userId: result.data.userId,
|
|
token: result.data.token,
|
|
isNewUser: result.data.isNewUser,
|
|
};
|
|
} else {
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
token: "",
|
|
isNewUser: false,
|
|
error: "Invalid response format from gateway",
|
|
};
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
token: "",
|
|
isNewUser: false,
|
|
error: `Network error: ${error.message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the user configuration with gateway registration results
|
|
* @param {string} userId - User ID from gateway
|
|
* @param {string} token - API token from gateway
|
|
* @param {string} mode - User mode ('byok' or 'hosted')
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {boolean} Success status
|
|
*/
|
|
function updateUserConfig(userId, token, mode, explicitRoot = null) {
|
|
try {
|
|
const config = getConfig(explicitRoot);
|
|
|
|
// Ensure account section exists
|
|
if (!config.account) {
|
|
config.account = {};
|
|
}
|
|
|
|
// Update user configuration in account section
|
|
config.account.userId = userId;
|
|
config.account.mode = mode; // 'byok' or 'hosted'
|
|
|
|
// Write API token to .env file (not config)
|
|
if (token) {
|
|
writeApiKeyToEnv(token, explicitRoot);
|
|
}
|
|
|
|
// Save updated config
|
|
const success = writeConfig(config, explicitRoot);
|
|
if (success) {
|
|
log("info", `User configuration updated: userId=${userId}, mode=${mode}`);
|
|
} else {
|
|
log("error", "Failed to write updated user configuration");
|
|
}
|
|
|
|
return success;
|
|
} catch (error) {
|
|
log("error", `Error updating user config: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the API token to the .env file
|
|
* @param {string} token - API token to write
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
*/
|
|
function writeApiKeyToEnv(token, explicitRoot = null) {
|
|
try {
|
|
// Determine project root
|
|
let rootPath = explicitRoot;
|
|
if (!rootPath) {
|
|
rootPath = findProjectRoot();
|
|
if (!rootPath) {
|
|
log("warn", "Could not determine project root for .env file");
|
|
return;
|
|
}
|
|
}
|
|
|
|
const envPath = path.join(rootPath, ".env");
|
|
let envContent = "";
|
|
|
|
// Read existing .env content if file exists
|
|
if (fs.existsSync(envPath)) {
|
|
envContent = fs.readFileSync(envPath, "utf8");
|
|
}
|
|
|
|
// Check if TASKMASTER_API_KEY already exists
|
|
const lines = envContent.split("\n");
|
|
let keyExists = false;
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if (lines[i].startsWith("TASKMASTER_API_KEY=")) {
|
|
lines[i] = `TASKMASTER_API_KEY=${token}`;
|
|
keyExists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add key if it doesn't exist
|
|
if (!keyExists) {
|
|
if (envContent && !envContent.endsWith("\n")) {
|
|
envContent += "\n";
|
|
}
|
|
envContent += `TASKMASTER_API_KEY=${token}\n`;
|
|
} else {
|
|
envContent = lines.join("\n");
|
|
}
|
|
|
|
// Write updated content
|
|
fs.writeFileSync(envPath, envContent);
|
|
log("info", "API key written to .env file");
|
|
} catch (error) {
|
|
log("error", `Failed to write API key to .env: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current user mode from configuration
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {string} User mode ('byok', 'hosted', or 'unknown')
|
|
*/
|
|
function getUserMode(explicitRoot = null) {
|
|
try {
|
|
const config = getConfig(explicitRoot);
|
|
return config?.account?.mode || "unknown";
|
|
} catch (error) {
|
|
log("error", `Error getting user mode: ${error.message}`);
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if user is in hosted mode
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {boolean} True if user is in hosted mode
|
|
*/
|
|
function isHostedMode(explicitRoot = null) {
|
|
return getUserMode(explicitRoot) === "hosted";
|
|
}
|
|
|
|
/**
|
|
* Checks if user is in BYOK mode
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {boolean} True if user is in BYOK mode
|
|
*/
|
|
function isByokMode(explicitRoot = null) {
|
|
return getUserMode(explicitRoot) === "byok";
|
|
}
|
|
|
|
/**
|
|
* Complete user setup: register with gateway and configure TaskMaster
|
|
* @param {string|null} email - Optional user's email (only needed for billing)
|
|
* @param {string} mode - User's mode: 'byok' or 'hosted'
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {Promise<{success: boolean, userId: string, mode: string, error?: string}>}
|
|
*/
|
|
async function setupUser(email = null, mode = "hosted", explicitRoot = null) {
|
|
try {
|
|
// Step 1: Register with gateway (email optional)
|
|
const registrationResult = await registerUserWithGateway(
|
|
email,
|
|
explicitRoot
|
|
);
|
|
|
|
if (!registrationResult.success) {
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
mode: "",
|
|
error: registrationResult.error,
|
|
};
|
|
}
|
|
|
|
// Step 2: Update config with userId and mode
|
|
const configResult = await updateUserConfig(
|
|
registrationResult.userId,
|
|
mode,
|
|
explicitRoot
|
|
);
|
|
|
|
if (!configResult) {
|
|
return {
|
|
success: false,
|
|
userId: registrationResult.userId,
|
|
mode: "",
|
|
error: "Failed to update user configuration",
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
userId: registrationResult.userId,
|
|
mode: mode,
|
|
message: email
|
|
? `User setup complete with email ${email}`
|
|
: "User setup complete (email will be collected during billing setup)",
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
mode: "",
|
|
error: `Setup failed: ${error.message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize TaskMaster user (typically called during init)
|
|
* Gets userId from gateway without requiring email upfront
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {Promise<{success: boolean, userId: string, error?: string}>}
|
|
*/
|
|
async function initializeUser(explicitRoot = null) {
|
|
try {
|
|
// Register with gateway without email
|
|
const result = await registerUserWithGateway(null, explicitRoot);
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
error: result.error,
|
|
};
|
|
}
|
|
|
|
// Update config with userId, token, and default hosted mode
|
|
const configResult = updateUserConfig(
|
|
result.userId,
|
|
result.token, // Include the token parameter
|
|
"hosted", // Default to hosted mode until user chooses plan
|
|
explicitRoot
|
|
);
|
|
|
|
if (!configResult) {
|
|
return {
|
|
success: false,
|
|
userId: result.userId,
|
|
error: "Failed to update user configuration",
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
userId: result.userId,
|
|
message: result.isNewUser
|
|
? "New user registered with gateway"
|
|
: "Existing user found in gateway",
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
userId: "",
|
|
error: `Initialization failed: ${error.message}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
export {
|
|
registerUserWithGateway,
|
|
updateUserConfig,
|
|
writeApiKeyToEnv,
|
|
getUserMode,
|
|
isHostedMode,
|
|
isByokMode,
|
|
setupUser,
|
|
initializeUser,
|
|
};
|