- Fix getUserId() to use placeholder that triggers auth/init if the auth/init endpoint is down for whatever reason - Add silent auth/init attempt in AI services - Improve hosted mode error handling - Remove fake userId/email generation from init.js
373 lines
10 KiB
JavaScript
373 lines
10 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 - User authentication token from gateway (stored in .env)
|
|
* @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 user authentication 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 user authentication token to the .env file
|
|
* This token is used as Bearer auth for gateway API calls
|
|
* @param {string} token - Authentication 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", "User authentication token written to .env file");
|
|
} catch (error) {
|
|
log("error", `Failed to write user token 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}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current user authentication token from .env file
|
|
* This is the Bearer token used for gateway API calls
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {string|null} User authentication token or null if not found
|
|
*/
|
|
function getUserToken(explicitRoot = null) {
|
|
try {
|
|
// Determine project root
|
|
let rootPath = explicitRoot;
|
|
if (!rootPath) {
|
|
rootPath = findProjectRoot();
|
|
if (!rootPath) {
|
|
log("error", "Could not determine project root for .env file");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const envPath = path.join(rootPath, ".env");
|
|
if (!fs.existsSync(envPath)) {
|
|
return null;
|
|
}
|
|
|
|
const envContent = fs.readFileSync(envPath, "utf8");
|
|
const lines = envContent.split("\n");
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith("TASKMASTER_API_KEY=")) {
|
|
return line.substring("TASKMASTER_API_KEY=".length).trim();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
log("error", `Error getting user token from .env: ${error.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current user email from configuration
|
|
* @param {string|null} explicitRoot - Optional explicit project root path
|
|
* @returns {string|null} User email or null if not found
|
|
*/
|
|
function getUserEmail(explicitRoot = null) {
|
|
try {
|
|
const config = getConfig(explicitRoot);
|
|
return config?.global?.email || null;
|
|
} catch (error) {
|
|
log("error", `Error getting user email: ${error.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export {
|
|
registerUserWithGateway,
|
|
updateUserConfig,
|
|
writeApiKeyToEnv,
|
|
getUserMode,
|
|
isHostedMode,
|
|
isByokMode,
|
|
setupUser,
|
|
initializeUser,
|
|
getUserToken,
|
|
getUserEmail,
|
|
};
|