@@ -8,6 +8,7 @@ import { Emitter } from 'vs/base/common/event';
8
8
import { Disposable } from 'vs/base/common/lifecycle' ;
9
9
import { ILogService } from 'vs/platform/log/common/log' ;
10
10
import { isWindows } from 'vs/base/common/platform' ;
11
+ import { retry } from 'vs/base/common/async' ;
11
12
12
13
interface ChunkedPassword {
13
14
content : string ;
@@ -46,6 +47,7 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
46
47
//#endregion
47
48
48
49
async getPassword ( service : string , account : string ) : Promise < string | null > {
50
+ this . logService . trace ( 'Getting password from keytar:' , service , account ) ;
49
51
let keytar : KeytarModule ;
50
52
try {
51
53
keytar = await this . withKeytar ( ) ;
@@ -54,33 +56,37 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
54
56
return null ;
55
57
}
56
58
57
- const password = await keytar . getPassword ( service , account ) ;
58
- if ( password ) {
59
- try {
60
- let { content, hasNextChunk } : ChunkedPassword = JSON . parse ( password ) ;
61
- if ( ! content || ! hasNextChunk ) {
62
- return password ;
63
- }
64
-
65
- let index = 1 ;
66
- while ( hasNextChunk ) {
67
- const nextChunk = await keytar . getPassword ( service , `${ account } -${ index } ` ) ;
68
- const result : ChunkedPassword = JSON . parse ( nextChunk ! ) ;
69
- content += result . content ;
70
- hasNextChunk = result . hasNextChunk ;
71
- index ++ ;
72
- }
73
-
74
- return content ;
75
- } catch {
59
+ const password = await retry ( ( ) => keytar . getPassword ( service , account ) , 50 , 3 ) ;
60
+ if ( ! password ) {
61
+ this . logService . trace ( 'Did not get a password from keytar for account:' , account ) ;
62
+ return password ;
63
+ }
64
+ try {
65
+ let { content, hasNextChunk } : ChunkedPassword = JSON . parse ( password ) ;
66
+ if ( ! content || ! hasNextChunk ) {
67
+ this . logService . trace ( 'Got password from keytar for account:' , account ) ;
76
68
return password ;
77
69
}
78
- }
79
70
80
- return password ;
71
+ let index = 1 ;
72
+ while ( hasNextChunk ) {
73
+ const nextChunk = await retry ( ( ) => keytar . getPassword ( service , `${ account } -${ index } ` ) , 50 , 3 ) ;
74
+ const result : ChunkedPassword = JSON . parse ( nextChunk ! ) ;
75
+ content += result . content ;
76
+ hasNextChunk = result . hasNextChunk ;
77
+ index ++ ;
78
+ }
79
+
80
+ this . logService . trace ( `Got ${ index } -chunked password from keytar for account:` , account ) ;
81
+ return content ;
82
+ } catch ( e ) {
83
+ this . logService . error ( e ) ;
84
+ return password ;
85
+ }
81
86
}
82
87
83
88
async setPassword ( service : string , account : string , password : string ) : Promise < void > {
89
+ this . logService . trace ( 'Setting password using keytar:' , service , account ) ;
84
90
let keytar : KeytarModule ;
85
91
try {
86
92
keytar = await this . withKeytar ( ) ;
@@ -89,28 +95,6 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
89
95
throw e ;
90
96
}
91
97
92
- const MAX_SET_ATTEMPTS = 3 ;
93
-
94
- // Sometimes Keytar has a problem talking to the keychain on the OS. To be more resilient, we retry a few times.
95
- const setPasswordWithRetry = async ( service : string , account : string , password : string ) => {
96
- let attempts = 0 ;
97
- let error : any ;
98
- while ( attempts < MAX_SET_ATTEMPTS ) {
99
- try {
100
- await keytar . setPassword ( service , account , password ) ;
101
- return ;
102
- } catch ( e ) {
103
- error = e ;
104
- this . logService . warn ( 'Error attempting to set a password: ' , e ?. message ?? e ) ;
105
- attempts ++ ;
106
- await new Promise ( resolve => setTimeout ( resolve , 200 ) ) ;
107
- }
108
- }
109
-
110
- // throw last error
111
- throw error ;
112
- } ;
113
-
114
98
if ( isWindows && password . length > BaseCredentialsMainService . MAX_PASSWORD_LENGTH ) {
115
99
let index = 0 ;
116
100
let chunk = 0 ;
@@ -124,19 +108,21 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
124
108
content : passwordChunk ,
125
109
hasNextChunk : hasNextChunk
126
110
} ;
127
-
128
- await setPasswordWithRetry ( service , chunk ? `${ account } -${ chunk } ` : account , JSON . stringify ( content ) ) ;
111
+ await retry ( ( ) => keytar . setPassword ( service , chunk ? `${ account } -${ chunk } ` : account , JSON . stringify ( content ) ) , 50 , 3 ) ;
129
112
chunk ++ ;
130
113
}
131
114
115
+ this . logService . trace ( `Got${ chunk ? ` ${ chunk } -chunked` : '' } password from keytar for account:` , account ) ;
132
116
} else {
133
- await setPasswordWithRetry ( service , account , password ) ;
117
+ await retry ( ( ) => keytar . setPassword ( service , account , password ) , 50 , 3 ) ;
118
+ this . logService . trace ( 'Got password from keytar for account:' , account ) ;
134
119
}
135
120
136
121
this . _onDidChangePassword . fire ( { service, account } ) ;
137
122
}
138
123
139
124
async deletePassword ( service : string , account : string ) : Promise < boolean > {
125
+ this . logService . trace ( 'Deleting password using keytar:' , service , account ) ;
140
126
let keytar : KeytarModule ;
141
127
try {
142
128
keytar = await this . withKeytar ( ) ;
@@ -147,14 +133,30 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
147
133
148
134
const password = await keytar . getPassword ( service , account ) ;
149
135
if ( ! password ) {
136
+ this . logService . trace ( 'Did not get a password to delete from keytar for account:' , account ) ;
150
137
return false ;
151
138
}
152
- const didDelete = await keytar . deletePassword ( service , account ) ;
139
+
140
+ let content : string | undefined ;
141
+ let hasNextChunk : boolean | undefined ;
142
+ try {
143
+ const possibleChunk = JSON . parse ( password ) ;
144
+ content = possibleChunk . content ;
145
+ hasNextChunk = possibleChunk . hasNextChunk ;
146
+ } catch {
147
+ // When the password is saved the entire JSON payload is encrypted then stored, thus the result from getPassword might not be valid JSON
148
+ // https://github.com/microsoft/vscode/blob/c22cb87311b5eb1a3bf5600d18733f7485355dc0/src/vs/workbench/api/browser/mainThreadSecretState.ts#L83
149
+ // However in the chunked case we JSONify each chunk after encryption so for the chunked case we do expect valid JSON here
150
+ // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L128
151
+ // Empty catch here just as in getPassword because we expect to handle both JSON cases and non JSON cases here it's not an error case to fail to parse
152
+ // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L76
153
+ }
154
+
155
+ let index = 0 ;
153
156
try {
154
- let { content, hasNextChunk } : ChunkedPassword = JSON . parse ( password ) ;
155
157
if ( content && hasNextChunk ) {
156
158
// need to delete additional chunks
157
- let index = 1 ;
159
+ index ++ ;
158
160
while ( hasNextChunk ) {
159
161
const accountWithIndex = `${ account } -${ index } ` ;
160
162
const nextChunk = await keytar . getPassword ( service , accountWithIndex ) ;
@@ -165,20 +167,19 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
165
167
index ++ ;
166
168
}
167
169
}
168
- } catch {
169
- // When the password is saved the entire JSON payload is encrypted then stored, thus the result from getPassword might not be valid JSON
170
- // https://github.com/microsoft/vscode/blob/c22cb87311b5eb1a3bf5600d18733f7485355dc0/src/vs/workbench/api/browser/mainThreadSecretState.ts#L83
171
- // However in the chunked case we JSONify each chunk after encryption so for the chunked case we do expect valid JSON here
172
- // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L128
173
- // Empty catch here just as in getPassword because we expect to handle both JSON cases and non JSON cases here it's not an error case to fail to parse
174
- // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L76
170
+ } catch ( e ) {
171
+ this . logService . error ( e ) ;
175
172
}
176
173
177
- if ( didDelete ) {
174
+ // Delete the first account to determine deletion success
175
+ if ( await keytar . deletePassword ( service , account ) ) {
178
176
this . _onDidChangePassword . fire ( { service, account } ) ;
177
+ this . logService . trace ( `Deleted${ index ? ` ${ index } -chunked` : '' } password from keytar for account:` , account ) ;
178
+ return true ;
179
179
}
180
180
181
- return didDelete ;
181
+ this . logService . trace ( `Keytar failed to delete${ index ? ` ${ index } -chunked` : '' } password for account:` , account ) ;
182
+ return false ;
182
183
}
183
184
184
185
async findPassword ( service : string ) : Promise < string | null > {
@@ -190,7 +191,7 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
190
191
return null ;
191
192
}
192
193
193
- return keytar . findPassword ( service ) ;
194
+ return await keytar . findPassword ( service ) ;
194
195
}
195
196
196
197
async findCredentials ( service : string ) : Promise < Array < { account : string ; password : string } > > {
@@ -202,7 +203,7 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
202
203
return [ ] ;
203
204
}
204
205
205
- return keytar . findCredentials ( service ) ;
206
+ return await keytar . findCredentials ( service ) ;
206
207
}
207
208
208
209
public clear ( ) : Promise < void > {
0 commit comments