147 lines
4.0 KiB
JavaScript
147 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
const fs = require('fs');
|
|
const https = require('https');
|
|
|
|
const CONFIG_PATH = process.env.HOME + '/.config/vaultwarden/config.json';
|
|
let config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
|
|
const VAULT_URL = config.url;
|
|
const CLIENT_ID = config.client_id;
|
|
const CLIENT_SECRET = config.client_secret;
|
|
let token = null;
|
|
let tokenExpiry = null;
|
|
const deviceId = 'openclaw-cli-' + require('crypto').randomUUID();
|
|
|
|
async function getToken() {
|
|
if (token && tokenExpiry && Date.now() < tokenExpiry - 300000) {
|
|
return token;
|
|
}
|
|
|
|
const params = new URLSearchParams({
|
|
grant_type: 'client_credentials',
|
|
scope: 'api',
|
|
client_id: CLIENT_ID,
|
|
client_secret: CLIENT_SECRET,
|
|
device_identifier: deviceId,
|
|
device_type: '14',
|
|
device_name: 'OpenClaw CLI'
|
|
});
|
|
|
|
const response = await new Promise((resolve, reject) => {
|
|
const url = new URL(VAULT_URL + '/identity/connect/token');
|
|
const options = {
|
|
hostname: url.hostname, port: url.port || 443,
|
|
path: url.pathname + url.search, method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
timeout: 30000
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', chunk => body += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const json = JSON.parse(body);
|
|
resolve(json);
|
|
} catch (e) { reject(e); }
|
|
});
|
|
});
|
|
req.on('error', reject);
|
|
req.write(params.toString());
|
|
req.end();
|
|
});
|
|
|
|
if (!response.access_token) {
|
|
throw new Error('认证失败');
|
|
}
|
|
|
|
token = response.access_token;
|
|
tokenExpiry = Date.now() + ((response.expires_in || 7200) - 300) * 1000;
|
|
return token;
|
|
}
|
|
|
|
async function getItem(id) {
|
|
const t = await getToken();
|
|
return new Promise((resolve, reject) => {
|
|
const url = new URL(VAULT_URL + `/api/ciphers/${id}`);
|
|
const options = {
|
|
hostname: url.hostname, port: url.port || 443,
|
|
path: url.pathname + url.search, method: 'GET',
|
|
headers: { 'Authorization': `Bearer ${t}` },
|
|
timeout: 30000
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', chunk => body += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const json = JSON.parse(body);
|
|
resolve(json);
|
|
} catch (e) { reject(e); }
|
|
});
|
|
});
|
|
req.on('error', reject);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function getAllItems() {
|
|
const t = await getToken();
|
|
const response = await new Promise((resolve, reject) => {
|
|
const url = new URL(VAULT_URL + '/api/ciphers');
|
|
const options = {
|
|
hostname: url.hostname, port: url.port || 443,
|
|
path: url.pathname + url.search, method: 'GET',
|
|
headers: { 'Authorization': `Bearer ${t}` },
|
|
timeout: 30000
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', chunk => body += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const json = JSON.parse(body);
|
|
resolve(json);
|
|
} catch (e) { reject(e); }
|
|
});
|
|
});
|
|
req.on('error', reject);
|
|
req.end();
|
|
});
|
|
|
|
return response.data || [];
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
const items = await getAllItems();
|
|
console.log(`\n📋 密码列表 (${items.length})\n`);
|
|
console.log('=' .repeat(70));
|
|
|
|
for (const item of items) {
|
|
const detail = await getItem(item.id);
|
|
console.log(`\n🔐 ${detail.name}`);
|
|
console.log('-'.repeat(50));
|
|
console.log(` ID: ${detail.id}`);
|
|
if (detail.login?.username) console.log(` 用户名: ${detail.login.username}`);
|
|
if (detail.login?.password) console.log(` 密码: ${detail.login.password}`);
|
|
if (detail.login?.uris?.length > 0) {
|
|
detail.login.uris.forEach(uri => {
|
|
console.log(` URL: ${uri.uri}`);
|
|
});
|
|
}
|
|
if (detail.notes) console.log(` 备注: ${detail.notes}`);
|
|
}
|
|
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(`\n✅ 共 ${items.length} 个密码\n`);
|
|
} catch (error) {
|
|
console.error('\n❌ 错误:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|