1
+ import { readFile } from 'fs/promises' ;
1
2
import * as vscode from 'vscode' ;
2
3
3
4
import { authenticatedEvent , editedEvent } from '../analytics' ;
4
5
import { AnalyticsClient } from '../analytics-node-client/src/client.min.js' ;
6
+ import { HardcodedSite , ValidHardcodedSite } from '../config/model' ;
5
7
import { Container } from '../container' ;
6
8
import { getAgent , getAxiosInstance } from '../jira/jira-client/providers' ;
7
9
import { Logger } from '../logger' ;
8
10
import { SiteManager } from '../siteManager' ;
11
+ import { substitute } from '../util/variable-substitution' ;
9
12
import {
10
13
AccessibleResource ,
11
14
AuthInfo ,
12
15
AuthInfoState ,
13
16
BasicAuthInfo ,
14
17
DetailedSiteInfo ,
18
+ HardCodedAuthInfo ,
15
19
isBasicAuthInfo ,
16
20
isOAuthInfo ,
17
21
isPATAuthInfo ,
@@ -27,6 +31,7 @@ import {
27
31
} from './authInfo' ;
28
32
import { CredentialManager } from './authStore' ;
29
33
import { BitbucketAuthenticator } from './bitbucketAuthenticator' ;
34
+ import { getUserForBBToken } from './getUserForBBToken' ;
30
35
import { JiraAuthentictor as JiraAuthenticator } from './jiraAuthenticator' ;
31
36
import { OAuthDancer } from './oauthDancer' ;
32
37
import { basicAuthEncode } from './strategyCrypto' ;
@@ -113,6 +118,142 @@ export class LoginManager {
113
118
}
114
119
}
115
120
121
+ // Look for https://x-token-auth:<token>@bitbucket.org pattern
122
+ private extractTokenFromGitRemoteRegex ( line : string ) : string | null {
123
+ const tokenMatch = line . match ( / h t t p s : \/ \/ x - t o k e n - a u t h : ( [ ^ @ ] + ) @ b i t b u c k e t \. o r g / ) ;
124
+ if ( tokenMatch && tokenMatch [ 1 ] ) {
125
+ Logger . debug ( 'Auth token found in git remote' ) ;
126
+ return tokenMatch [ 1 ] ;
127
+ }
128
+ return null ;
129
+ }
130
+
131
+ /**
132
+ * Extracts auth token from git remote URL
133
+ * @returns The auth token or null if not found
134
+ */
135
+ private async getAuthTokenFromCredentialsPath (
136
+ credentialsPath : string ,
137
+ credentialsFormat : HardcodedSite [ 'credentialsFormat' ] ,
138
+ ) : Promise < string | null > {
139
+ try {
140
+ const resolvedPath = substitute ( credentialsPath ) ;
141
+ const credentialsContents = ( await readFile ( resolvedPath , 'utf-8' ) ) . trim ( ) ;
142
+
143
+ let token : string | null = null ;
144
+ switch ( credentialsFormat ) {
145
+ case 'git-remote' :
146
+ token = this . extractTokenFromGitRemoteRegex ( credentialsContents ) ;
147
+ break ;
148
+ case 'self' :
149
+ token = credentialsContents ;
150
+ break ;
151
+ }
152
+
153
+ if ( token ) {
154
+ Logger . debug ( `Auth token for initial site found` ) ;
155
+ } else {
156
+ Logger . warn ( `No auth token found for initial site` ) ;
157
+ }
158
+ return token ;
159
+ } catch ( error ) {
160
+ Logger . error ( error , `Error extracting auth token for initial site` ) ;
161
+ return null ;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * This function is used to authenticate and add a hardcoded site.
167
+ *
168
+ * The flow is quite constant: for a given setting, simply read a token the given credentials path
169
+ * and update the auth info and site based on the VS Code settings.
170
+ *
171
+ * The only branching happens if we provide existing auth info too. In that case, the authentication fails
172
+ * if the existing auth info is the same as the fetched auth info. This flow is used while refreshing to
173
+ * know if the token got refreshed or not.
174
+ */
175
+ public async authenticateHardcodedSite (
176
+ hardcodedSite : ValidHardcodedSite ,
177
+ existingAuthInfo ?: AuthInfo ,
178
+ ) : Promise < boolean > {
179
+ const { product, host, credentialsPath, credentialsFormat, authHeader } = hardcodedSite ;
180
+
181
+ let siteProduct : Product | null = null ;
182
+ switch ( product ) {
183
+ case 'bitbucket' :
184
+ siteProduct = ProductBitbucket ;
185
+ break ;
186
+ default :
187
+ Logger . warn ( `Invalid product for initial site` ) ;
188
+ return false ;
189
+ }
190
+
191
+ const site : SiteInfo = {
192
+ host,
193
+ product : siteProduct ,
194
+ } ;
195
+
196
+ try {
197
+ const token = await this . getAuthTokenFromCredentialsPath ( credentialsPath , credentialsFormat ) ;
198
+
199
+ if ( ! token ) {
200
+ Logger . warn ( 'No hardcoded Bitbucket auth token found' ) ;
201
+ vscode . window . showErrorMessage ( 'No hardcoded Bitbucket auth token found' ) ;
202
+ return false ;
203
+ }
204
+ Logger . debug ( 'Authenticating with Bitbucket using auth token' ) ;
205
+
206
+ if ( existingAuthInfo && existingAuthInfo . type === 'hardcoded' && existingAuthInfo . token === token ) {
207
+ Logger . debug ( `Same token found, skipping authentication` ) ;
208
+ return false ;
209
+ }
210
+ // The part of the code where the hardcoded site is assumed to be Bitbucket Cloud.
211
+ // This function can be extended to support other sites as needed.
212
+ const userData = await getUserForBBToken ( LoginManager . authHeaderMaker ( hardcodedSite . authHeader , token ) ) ;
213
+
214
+ const hardcodedAuthInfo : HardCodedAuthInfo = {
215
+ type : 'hardcoded' ,
216
+ token,
217
+ authHeader,
218
+ user : {
219
+ id : userData . id ,
220
+ displayName : userData . displayName ,
221
+ email : userData . email ,
222
+ avatarUrl : userData . avatarUrl ,
223
+ } ,
224
+ state : AuthInfoState . Valid ,
225
+ } ;
226
+
227
+ const detailedSiteInfo : DetailedSiteInfo = {
228
+ ...site ,
229
+ id : site . host ,
230
+ name : site . host ,
231
+ userId : userData . id ,
232
+ credentialId : CredentialManager . generateCredentialId ( site . product . key , userData . id ) ,
233
+ avatarUrl : userData . avatarUrl ,
234
+ baseLinkUrl : site . host ,
235
+ baseApiUrl : site . host ,
236
+ isCloud : hardcodedSite . isCloud ?? true ,
237
+ hasResolutionField : hardcodedSite . hasResolutionField ?? true ,
238
+ } ;
239
+
240
+ await this . _credentialManager . saveAuthInfo ( detailedSiteInfo , hardcodedAuthInfo ) ;
241
+
242
+ this . _siteManager . addOrUpdateSite ( detailedSiteInfo ) ;
243
+ // Fire authenticated event
244
+ authenticatedEvent ( detailedSiteInfo , false ) . then ( ( e ) => {
245
+ this . _analyticsClient . sendTrackEvent ( e ) ;
246
+ } ) ;
247
+ Logger . info ( `Successfully authenticated with Bitbucket using auth token` ) ;
248
+
249
+ return true ;
250
+ } catch ( e ) {
251
+ Logger . error ( e , 'Error authenticating with Bitbucket token' ) ;
252
+ vscode . window . showErrorMessage ( `Error authenticating with Bitbucket token: ${ e } ` ) ;
253
+ return false ;
254
+ }
255
+ }
256
+
116
257
private async getOAuthSiteDetails (
117
258
product : Product ,
118
259
provider : OAuthProvider ,
@@ -167,6 +308,8 @@ export class LoginManager {
167
308
return LoginManager . authHeaderMaker ( 'basic' , basicAuthEncode ( credentials . username , credentials . password ) ) ;
168
309
} else if ( isPATAuthInfo ( credentials ) ) {
169
310
return LoginManager . authHeaderMaker ( 'bearer' , credentials . token ) ;
311
+ } else if ( credentials . type === 'hardcoded' ) {
312
+ return LoginManager . authHeaderMaker ( credentials . authHeader , credentials . token ) ;
170
313
} else {
171
314
return '' ;
172
315
}
0 commit comments