Files
claude-task-master/scripts/modules/user-management.js

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 global section exists
if (!config.global) {
config.global = {};
}
// Update user configuration
config.global.userId = userId;
config.global.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?.global?.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,
};