tinyagent
Version:
Connect your local shell to any device - access your dev environment from anywhere
165 lines ⢠6.99 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthClient = void 0;
const chalk_1 = __importDefault(require("chalk"));
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const open_1 = __importDefault(require("open"));
class AuthClient {
authUrl;
configPath;
token;
constructor(authUrl = 'https://auth.tinyagent.app') {
this.authUrl = process.env.AUTH_URL || authUrl;
this.configPath = path_1.default.join(os_1.default.homedir(), '.tinyagent', 'auth.json');
this.loadToken();
}
loadToken() {
try {
if (fs_1.default.existsSync(this.configPath)) {
const data = fs_1.default.readFileSync(this.configPath, 'utf-8');
this.token = JSON.parse(data);
}
}
catch (error) {
// Ignore errors, token will be undefined
}
}
saveToken(token) {
try {
const dir = path_1.default.dirname(this.configPath);
if (!fs_1.default.existsSync(dir)) {
fs_1.default.mkdirSync(dir, { recursive: true });
}
fs_1.default.writeFileSync(this.configPath, JSON.stringify(token, null, 2));
this.token = token;
}
catch (error) {
console.error(chalk_1.default.yellow('Warning: Could not save auth token'));
}
}
isAuthenticated() {
if (!this.token)
return false;
if (this.token.expiresAt && this.token.expiresAt < Date.now()) {
return false;
}
return true;
}
getToken() {
return this.isAuthenticated() ? this.token?.accessToken : undefined;
}
async authenticate() {
console.log(chalk_1.default.cyan('\nš Authentication required'));
// Generate PKCE challenge
const codeVerifier = crypto_1.default.randomBytes(32).toString('base64url');
const codeChallenge = crypto_1.default
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
try {
// Request device code
const deviceResponse = await fetch(`${this.authUrl}/api/auth/device`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ codeChallenge }),
});
if (!deviceResponse.ok) {
throw new Error('Failed to get device code');
}
const deviceData = await deviceResponse.json();
console.log(chalk_1.default.white('\nTo authenticate, visit:'));
console.log(chalk_1.default.green.bold(deviceData.verificationUrl));
console.log(chalk_1.default.white('\nVerification code: ') + chalk_1.default.yellow.bold(deviceData.userCode));
// Open browser
try {
await (0, open_1.default)(deviceData.verificationUrl);
console.log(chalk_1.default.gray('(Browser should open automatically)'));
}
catch {
console.log(chalk_1.default.gray('(Please open the URL manually in your browser)'));
}
console.log(chalk_1.default.gray('\nWaiting for authentication...'));
// Poll for authentication
const startTime = Date.now();
const expiresIn = deviceData.expiresIn * 1000; // Convert to ms
const interval = deviceData.interval * 1000; // Convert to ms
while (Date.now() - startTime < expiresIn) {
await new Promise(resolve => setTimeout(resolve, interval));
const pollResponse = await fetch(`${this.authUrl}/api/auth/cli?device_code=${deviceData.deviceCode}`);
if (!pollResponse.ok)
continue;
const pollData = await pollResponse.json();
if (pollData.status === 'authenticated') {
// Exchange for tokens
const tokenResponse = await fetch(`${this.authUrl}/api/auth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grantType: 'authorization_code',
code: pollData.accessToken, // Using token as code for simplicity
codeVerifier,
}),
});
if (!tokenResponse.ok) {
throw new Error('Failed to exchange code for tokens');
}
const tokenData = await tokenResponse.json();
// Save token
this.saveToken({
accessToken: tokenData.accessToken,
refreshToken: tokenData.refreshToken,
user: tokenData.user,
expiresAt: Date.now() + (tokenData.expiresIn * 1000),
});
console.log(chalk_1.default.green('\nā Authentication successful!'));
if (tokenData.user?.email) {
console.log(chalk_1.default.gray(`Logged in as: ${tokenData.user.email}`));
}
return true;
}
}
console.log(chalk_1.default.red('\nā Authentication timed out'));
return false;
}
catch (error) {
console.error(chalk_1.default.red('\nā Authentication failed:'), error);
return false;
}
}
logout() {
try {
if (fs_1.default.existsSync(this.configPath)) {
fs_1.default.unlinkSync(this.configPath);
}
this.token = undefined;
console.log(chalk_1.default.green('ā Logged out successfully'));
}
catch (error) {
console.error(chalk_1.default.red('ā Logout failed:'), error);
}
}
whoami() {
if (!this.isAuthenticated()) {
console.log(chalk_1.default.yellow('Not authenticated. Run "tinyagent login" to authenticate.'));
return;
}
if (this.token?.user) {
console.log(chalk_1.default.cyan('Current user:'));
console.log(` ID: ${this.token.user.id}`);
if (this.token.user.email) {
console.log(` Email: ${this.token.user.email}`);
}
if (this.token.user.name) {
console.log(` Name: ${this.token.user.name}`);
}
}
}
}
exports.AuthClient = AuthClient;
//# sourceMappingURL=auth-client.js.map