-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinux.ts
More file actions
161 lines (152 loc) · 4.75 KB
/
linux.ts
File metadata and controls
161 lines (152 loc) · 4.75 KB
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
157
158
159
160
161
/**
* @file Linux Secret Service backend via `secret-tool`. `secret-tool` is the
* user-facing CLI for libsecret, which talks to any running Secret Service
* provider (gnome-keyring, kwallet5, KeePassXC's Secret Service integration,
* etc.). Most desktop Linux installs have one; headless / containerized hosts
* do not. Storage shape: each item has a `service=<svc> user=<account>`
* attribute pair plus the password body. Reads + writes use those two
* attributes as the lookup key. The label (`--label`) is what shows up in the
* user's keyring UI; we set it to the same string passed in by the caller. No
* backing-file fallback. If `secret-tool` isn't on PATH or no Secret Service
* provider is running, reads return `undefined` and writes throw with an
* actionable hint — callers fall back to env variables for that session.
*/
import { spawn, spawnSync } from 'node:child_process'
const SECRET_TOOL_BIN = 'secret-tool'
export async function deleteLinux(
service: string,
account: string,
): Promise<'removed' | 'absent'> {
return new Promise(resolve => {
const child = spawn(
SECRET_TOOL_BIN,
['clear', 'service', service, 'user', account],
{ stdio: 'ignore' },
)
child.on('error', () => resolve('absent'))
child.on('close', status => resolve(status === 0 ? 'removed' : 'absent'))
})
}
export function deleteLinuxSync(
service: string,
account: string,
): 'removed' | 'absent' {
const r = spawnSync(
SECRET_TOOL_BIN,
['clear', 'service', service, 'user', account],
{ stdio: 'ignore' },
)
return r.status === 0 ? 'removed' : 'absent'
}
export function isLinuxBackendAvailable(): boolean {
const r = spawnSync(SECRET_TOOL_BIN, ['--version'], { stdio: 'ignore' })
return r.status === 0
}
export async function readLinux(
service: string,
account: string,
): Promise<string | undefined> {
return new Promise(resolve => {
const child = spawn(
SECRET_TOOL_BIN,
['lookup', 'service', service, 'user', account],
{ stdio: ['ignore', 'pipe', 'pipe'] },
)
let stdout = ''
child.stdout.setEncoding('utf8')
child.stdout.on('data', chunk => {
stdout += chunk
})
child.on('error', () => resolve(undefined))
child.on('close', status => {
if (status !== 0) {
resolve(undefined)
return
}
const out = stdout.trim()
resolve(out || undefined)
})
})
}
export function readLinuxSync(
service: string,
account: string,
): string | undefined {
const r = spawnSync(
SECRET_TOOL_BIN,
['lookup', 'service', service, 'user', account],
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
)
if (r.status !== 0) {
return undefined
}
const out = r.stdout.trim()
return out || undefined
}
export async function writeLinux(
service: string,
account: string,
value: string,
label: string,
): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(
SECRET_TOOL_BIN,
['store', `--label=${label}`, 'service', service, 'user', account],
{ stdio: ['pipe', 'pipe', 'pipe'] },
)
let stderr = ''
child.stderr.setEncoding('utf8')
child.stderr.on('data', chunk => {
stderr += chunk
})
child.on('error', err =>
reject(
new Error(
`secret-tool store failed: ${err.message}. ` +
'Install libsecret-tools (apt install libsecret-tools / dnf install libsecret) ' +
'or ensure a Secret Service provider (gnome-keyring, kwallet) is running.',
),
),
)
child.on('close', status => {
if (status === 0) {
resolve()
return
}
reject(
new Error(
`secret-tool store failed (status=${status}, user=${account}): ${stderr.trim()}. ` +
'Install libsecret-tools (apt install libsecret-tools / dnf install libsecret) ' +
'or ensure a Secret Service provider (gnome-keyring, kwallet) is running.',
),
)
})
// `secret-tool store` reads the password from stdin so the value
// never appears in `ps(1)` / `/proc/<pid>/cmdline`.
child.stdin.end(value)
})
}
export function writeLinuxSync(
service: string,
account: string,
value: string,
label: string,
): void {
const r = spawnSync(
SECRET_TOOL_BIN,
['store', `--label=${label}`, 'service', service, 'user', account],
{
encoding: 'utf8',
input: value,
stdio: ['pipe', 'pipe', 'pipe'],
},
)
if (r.status !== 0) {
throw new Error(
`secret-tool store failed (status=${r.status}, user=${account}): ${r.stderr.trim()}. ` +
'Install libsecret-tools (apt install libsecret-tools / dnf install libsecret) ' +
'or ensure a Secret Service provider (gnome-keyring, kwallet) is running.',
)
}
}