keyvault-cli
Version:
Secure API key management CLI tool
256 lines (255 loc) • 9.82 kB
JavaScript
import fs from 'fs';
import path from 'path';
import os from 'os';
import chalk from 'chalk';
import inquirer from 'inquirer';
export class CLIAuth {
static async login() {
console.log(chalk.blue('🔐 KeyVault Login'));
const apiUrl = 'https://1pass.vercel.app';
const { email, password } = await inquirer.prompt([
{
type: 'input',
name: 'email',
message: 'Email:',
validate: (input) => input.includes('@') || 'Please enter a valid email'
},
{
type: 'password',
name: 'password',
message: 'Password:',
mask: '*'
}
]);
try {
// API'ye login isteği gönder
const fetch = (await import('node-fetch')).default;
const response = await fetch(`${apiUrl}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error('Invalid credentials');
}
const data = await response.json();
// Token'ı güvenli şekilde sakla
await this.saveConfig({
apiUrl,
token: data.token,
email
});
console.log(chalk.green('✅ Successfully logged in!'));
console.log(chalk.gray(`Logged in as: ${email}`));
}
catch (error) {
console.log(chalk.red('❌ Login failed:'), error instanceof Error ? error.message : 'Unknown error');
process.exit(1);
}
}
static async logout() {
try {
await fs.promises.unlink(this.configPath);
console.log(chalk.green('✅ Successfully logged out!'));
}
catch (error) {
console.log(chalk.yellow('⚠️ No active session found'));
}
}
static async getConfig() {
try {
const configData = await fs.promises.readFile(this.configPath, 'utf8');
return JSON.parse(configData);
}
catch (error) {
return null;
}
}
static async isLoggedIn() {
const config = await this.getConfig();
return !!(config?.token);
}
static async requireAuth() {
const config = await this.getConfig();
if (!config?.token) {
console.log(chalk.red('❌ Not logged in. Please run "keyvault login" first.'));
process.exit(1);
}
return config;
}
static async saveConfig(config) {
const configDir = path.dirname(this.configPath);
// Config klasörünü oluştur
await fs.promises.mkdir(configDir, { recursive: true });
// Config'i kaydet
await fs.promises.writeFile(this.configPath, JSON.stringify(config, null, 2));
// Dosya izinlerini sıkılaştır (sadece owner okuyabilir)
await fs.promises.chmod(this.configPath, 0o600);
}
static async savePersonalKeys(keys) {
const config = await this.getConfig();
if (!config) {
throw new Error('No config found. Please login first.');
}
config.personalKeys = keys;
await this.saveConfig(config);
}
static async saveTeamKeys(teamName, keys) {
const config = await this.getConfig();
if (!config) {
throw new Error('No config found. Please login first.');
}
if (!config.teamKeys) {
config.teamKeys = {};
}
config.teamKeys[teamName] = keys;
await this.saveConfig(config);
}
static async getPersonalKeys() {
const config = await this.getConfig();
return config?.personalKeys || null;
}
static async getTeamKeys(teamName) {
const config = await this.getConfig();
if (!config?.teamKeys)
return null;
if (teamName) {
return config.teamKeys[teamName] || null;
}
return config.teamKeys;
}
static async whoami() {
const config = await this.getConfig();
if (!config?.token) {
console.log(chalk.yellow('Not logged in'));
return;
}
console.log(chalk.blue('Current session:'));
console.log(` Email: ${config.email}`);
console.log(` API URL: ${config.apiUrl}`);
if (config.activeTeamId) {
try {
// Fetch team name from API
const fetch = (await import('node-fetch')).default;
const response = await fetch(`${config.apiUrl}/api/teams/${config.activeTeamId}`, {
headers: {
'Authorization': `Bearer ${config.token}`
}
});
if (response.ok) {
const data = await response.json();
const team = data.team || data;
console.log(` Active Team: ${team.name} - ${config.activeTeamId}`);
}
else {
console.log(` Active Team: ${config.activeTeamId} (name unavailable)`);
}
}
catch (error) {
console.log(` Active Team: ${config.activeTeamId} (name unavailable)`);
}
}
else {
console.log(chalk.gray(' No active team selected'));
}
}
static async forgotPassword() {
console.log(chalk.blue('🔐 KeyVault Password Reset'));
const apiUrl = 'https://1pass.vercel.app';
const { email } = await inquirer.prompt([
{
type: 'input',
name: 'email',
message: 'Your email address:',
validate: (input) => input.includes('@') || 'Please enter a valid email'
}
]);
try {
const fetch = (await import('node-fetch')).default;
const response = await fetch(`${apiUrl}/api/auth/forgot-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Request failed');
}
const data = await response.json();
console.log(chalk.green('✅ Password reset email sent!'));
console.log(chalk.gray('Check your email for reset instructions.'));
// In development mode, show additional info
if (data.resetToken) {
console.log(chalk.yellow('\n🔧 Development Mode:'));
console.log(chalk.gray('Reset token:', data.resetToken));
if (data.info) {
console.log(chalk.gray(data.info));
}
// Ask if user wants to reset password immediately
const { useToken } = await inquirer.prompt([
{
type: 'confirm',
name: 'useToken',
message: 'Do you want to reset your password now? (dev mode)',
default: false
}
]);
if (useToken) {
await this.resetPassword(apiUrl, data.resetToken);
}
}
}
catch (error) {
console.log(chalk.red('❌ Request failed:'), error instanceof Error ? error.message : 'Unknown error');
}
}
static async resetPassword(apiUrl, token) {
console.log(chalk.blue('🔑 KeyVault Password Reset'));
let resetToken = token;
if (!resetToken) {
const { inputToken } = await inquirer.prompt([
{
type: 'input',
name: 'inputToken',
message: 'Enter your reset token:',
validate: (input) => input.length > 0 || 'Reset token is required'
}
]);
resetToken = inputToken;
}
const { newPassword, confirmPassword } = await inquirer.prompt([
{
type: 'password',
name: 'newPassword',
message: 'New password:',
mask: '*',
validate: (input) => input.length >= 6 || 'Password must be at least 6 characters'
},
{
type: 'password',
name: 'confirmPassword',
message: 'Confirm new password:',
mask: '*',
validate: (input, answers) => input === answers.newPassword || 'Passwords do not match'
}
]);
try {
const fetch = (await import('node-fetch')).default;
const response = await fetch(`${apiUrl}/api/auth/reset-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: resetToken, newPassword })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Request failed');
}
console.log(chalk.green('✅ Password reset successfully!'));
console.log(chalk.gray('You can now login with your new password.'));
}
catch (error) {
console.log(chalk.red('❌ Password reset failed:'), error instanceof Error ? error.message : 'Unknown error');
}
}
}
CLIAuth.configPath = path.join(os.homedir(), '.keyvault', 'config.json');