"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.startDeviceFlow = startDeviceFlow;
exports.ensureAuthenticated = ensureAuthenticated;
const child_process_1 = require("child_process");
const credentials_1 = require("./credentials");
/**
* Open a URL in the default browser
*/
function openBrowser(url) {
return new Promise((resolve) => {
// Determine the command based on platform
let command;
switch (process.platform) {
case 'darwin':
command = `open "${url}"`;
break;
case 'win32':
command = `start "" "${url}"`;
break;
default:
// Linux and others
command = `xdg-open "${url}"`;
}
(0, child_process_1.exec)(command, (error) => {
if (error) {
// Don't fail if browser can't open - user can manually navigate
resolve();
}
else {
resolve();
}
});
});
}
/**
* Start the device authorization flow
* Returns a message describing the flow status
*/
async function startDeviceFlow() {
const apiUrl = (0, credentials_1.getApiUrl)();
// Step 1: Request device authorization
let authResponse;
try {
const response = await fetch(`${apiUrl}/api/auth/device/authorize`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientName: 'FastMode MCP',
}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
return `# Authentication Error
Failed to start device authorization: ${error.error || response.statusText}
Please check:
1. Your network connection
2. The API URL is correct (${apiUrl})
`;
}
const data = await response.json();
authResponse = data.data;
}
catch (error) {
return `# Network Error
Unable to connect to FastMode API.
**API URL:** ${apiUrl}
**Error:** ${error instanceof Error ? error.message : 'Unknown error'}
Please check your network connection and try again.
`;
}
// Step 2: Open browser for user authorization
try {
await openBrowser(authResponse.verification_uri_complete);
}
catch {
// Browser failed to open - user will need to navigate manually
}
// Step 3: Poll for authorization
const pollInterval = (authResponse.interval || 5) * 1000; // seconds to ms
const expiresAt = Date.now() + authResponse.expires_in * 1000;
// Log instructions to stderr for user visibility
console.error(`
# Device Authorization
A browser window should open automatically.
If it doesn't, please visit:
${authResponse.verification_uri}
And enter this code: ${authResponse.user_code}
**Don't have a Fast Mode account?**
Sign up at https://fastmode.ai first, then return here.
Waiting for authorization...
`);
// Start polling
const pollResult = await pollForToken(apiUrl, authResponse.device_code, pollInterval, expiresAt);
if (pollResult.success && pollResult.credentials) {
// Save credentials
(0, credentials_1.saveCredentials)(pollResult.credentials);
return `# Authentication Successful
Logged in as: **${pollResult.credentials.email}**${pollResult.credentials.name ? ` (${pollResult.credentials.name})` : ''}
Credentials saved to ~/.fastmode/credentials.json
You can now use FastMode MCP tools.
`;
}
else {
return `# Authentication Failed
${pollResult.error || 'Authorization timed out or was denied.'}
**Don't have an account?**
Sign up at https://fastmode.ai and then try again.
**To retry:** Call any authenticated tool like \`list_projects\` to start a new auth flow.
`;
}
}
/**
* Poll the token endpoint until authorization is complete or timeout
*/
async function pollForToken(apiUrl, deviceCode, interval, expiresAt) {
while (Date.now() < expiresAt) {
// Wait for the polling interval
await new Promise(resolve => setTimeout(resolve, interval));
try {
const response = await fetch(`${apiUrl}/api/auth/device/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
device_code: deviceCode,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
}),
});
const data = await response.json();
if (response.ok && data.success && data.data) {
// Authorization successful!
const tokenData = data.data;
const credentials = {
accessToken: tokenData.access_token,
refreshToken: tokenData.refresh_token,
expiresAt: new Date(Date.now() + tokenData.expires_in * 1000).toISOString(),
email: tokenData.email,
name: tokenData.name,
};
return { success: true, credentials };
}
// Check for specific error codes
if (data.error === 'authorization_pending') {
// User hasn't authorized yet - keep polling
continue;
}
if (data.error === 'slow_down') {
// Server is asking us to slow down
interval = Math.min(interval * 2, 30000); // Max 30 seconds
continue;
}
if (data.error === 'expired_token') {
return { success: false, error: 'The authorization request expired. Please try again.' };
}
if (data.error === 'access_denied') {
return { success: false, error: 'Authorization was denied. Please try again.' };
}
// Unknown error - keep polling
}
catch {
// Network error - keep trying
}
}
return { success: false, error: 'Authorization timed out. Please try again.' };
}
/**
* Check if device flow authentication is needed and perform it if so
* Returns the authentication result message
*/
async function ensureAuthenticated() {
// Import here to avoid circular dependency
const { getValidCredentials } = await Promise.resolve().then(() => __importStar(require('./credentials')));
const { waitForAuth } = await Promise.resolve().then(() => __importStar(require('./auth-state')));
// Wait for any startup auth that might be in progress
await waitForAuth();
const credentials = await getValidCredentials();
if (credentials) {
return {
authenticated: true,
message: `Authenticated as ${credentials.email}`,
};
}
// Need to authenticate
const result = await startDeviceFlow();
// Check if authentication was successful
const newCredentials = await getValidCredentials();
return {
authenticated: !!newCredentials,
message: result,
};
}