-
Notifications
You must be signed in to change notification settings - Fork 9
/
index.js
156 lines (140 loc) · 4.73 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
const { existsSync, writeFileSync, readFileSync } = require('fs')
const clipboardy = require('clipboardy')
const { CommandError } = require('./util/error')
const dmenuRun = require('./executable-wrappers/dmenu')
const bwRun = require('./executable-wrappers/bitwarden-cli')
const obfuscate = require('./util/obfuscate/object')
const packageJson = require('../package.json')
class BitwardenDmenu {
constructor(args) {
Object.assign(this, args)
}
}
const isLoggedIn = async () => {
try {
bwRun('login', '--check')
} catch (e) {
if (e instanceof CommandError && e.stderr === 'You are not logged in.') {
return false
} else {
throw e
}
}
return true
}
const login = async ({ dmenuArgs, dmenuPswdArgs }) => {
const email = await dmenuRun(
'-p',
'You are logged out. Please provide your email:',
...dmenuArgs
)('\n')
const password = await dmenuRun(...dmenuPswdArgs)('\n')
const session = bwRun('login', email, password, '--raw')
return session
}
/**
* get a session token, either from existing sessionFile or by `bw unlock [password]`
*/
const getSessionVar = async ({ dmenuPswdArgs, saveSession, sessionFile }) => {
if (saveSession) {
console.debug(`checking for session file at ${sessionFile}`)
const sessionFileExists = existsSync(sessionFile)
if (sessionFileExists) {
const session = readFileSync(sessionFile)
.toString()
.replace(/\n$/, '')
console.debug('read existing session file.')
return session
} else {
console.debug('no session file found.')
// prompt for password in dmenu
const password = await dmenuRun(...dmenuPswdArgs)('\n')
if (!password.length) throw new Error('no password given!')
const session = bwRun('unlock', password, '--raw')
writeFileSync(sessionFile, session)
console.debug('saved new session file.')
return session
}
} else {
// Why doesn't dmenuRun('...', dmenuPswdArgs)('\n') work here?
const password = await dmenuRun(...dmenuPswdArgs)('\n')
if (!password.length) throw new Error('no password given!')
const session = bwRun('unlock', password, '--raw')
return session
}
}
/**
* sync the password accounts with the remote server
* if --sync-vault-after < time since the last sync
*/
const syncIfNecessary = ({ oldestAllowedVaultSync }, session) => {
const last = bwRun('sync', '--last', `--session=${session}`)
const timeSinceSync = (new Date().getTime() - new Date(last).getTime()) / 1000
if (timeSinceSync > oldestAllowedVaultSync) {
console.debug('syncing vault...')
bwRun('sync', `--session=${session}`)
console.debug(`sync complete, last sync was ${last}`)
}
}
/**
* get the list all password accounts in the vault
*/
const getAccounts = ({ bwListArgs }, session) => {
const listStr = bwRun('list', 'items', bwListArgs, `--session=${session}`)
const list = JSON.parse(listStr)
return list
}
/**
* choose one account with dmenu
*/
const chooseAccount = async ({ dmenuArgs }, list) => {
const LOGIN_TYPE = 1
const loginList = list.filter(a => a.type === LOGIN_TYPE)
const accountNames = loginList.map(a => `${a.name}: ${a.login.username}`)
const selected = await dmenuRun(...dmenuArgs)(accountNames.join('\n'))
const index = accountNames.indexOf(selected)
// accountNames indexes match loginList indexes
const selectedAccount = loginList[index]
if (!selectedAccount) throw new Error('no account selected!')
console.debug('selected account:\n', obfuscate(selectedAccount))
return selectedAccount
}
/**
* choose one field with dmenu
*/
const chooseField = async ({ dmenuArgs }, selectedAccount) => {
const copyable = {
password: selectedAccount.login.password,
username: selectedAccount.login.username,
notes: selectedAccount.notes,
...(selectedAccount.fields || []).reduce(
(acc, f) => ({
...acc,
['custom.' + f.name]: f.value
}),
{}
)
}
const field = await dmenuRun(...dmenuArgs)(Object.keys(copyable).join('\n'))
console.debug(`selected field '${field}'`)
const valueToCopy = copyable[field]
return valueToCopy
}
module.exports = async args => {
console.debug(`bitwarden-dmenu v${packageJson.version}`)
const session = (await isLoggedIn()) ? await getSessionVar(args) : await login(args)
// bw sync if necessary
syncIfNecessary(args, session)
// bw list
const list = getAccounts(args, session)
// choose account in dmenu
const selectedAccount = await chooseAccount(args, list)
if (args.stdout) {
console.log(`${selectedAccount.login.username}\n${selectedAccount.login.password}`)
} else {
// choose field to copy in dmenu
const valueToCopy = await chooseField(args, selectedAccount)
// copy to clipboard
clipboardy.writeSync(valueToCopy)
}
}