Skip to content

Commit d3b8b10

Browse files
mkenchugondesinhaviraj
authored andcommitted
Merged in mk/use-authtoken-from-user-machine (pull request #1)
SHIFT-519: Mk/use authtoken from user machine Approved-by: Megha Agarwal
2 parents d09597d + 493c3cd commit d3b8b10

File tree

8 files changed

+145
-4
lines changed

8 files changed

+145
-4
lines changed

src/atlclients/authInfo.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export interface AuthInfo {
7878

7979
export interface OAuthInfo extends AuthInfo {
8080
access: string;
81-
refresh: string;
81+
refresh?: string;
8282
expirationDate?: number;
8383
iat?: number;
8484
recievedAt: number;

src/atlclients/authStore.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export class CredentialManager implements Disposable {
331331

332332
const provider: OAuthProvider | undefined = oauthProviderForSite(site);
333333
const newTokens = undefined;
334-
if (provider && credentials) {
334+
if (provider && credentials && credentials.refresh) {
335335
const tokenResponse = await this._refresher.getNewTokens(provider, credentials.refresh);
336336
if (tokenResponse.tokens) {
337337
const newTokens = tokenResponse.tokens;

src/atlclients/loginManager.ts

+125-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@ import { Logger } from '../logger';
2727
import { OAuthDancer } from './oauthDancer';
2828
import { SiteManager } from '../siteManager';
2929
import { Container } from '../container';
30+
import { strategyForProvider } from './strategy';
31+
import { BitbucketResponseHandler } from './responseHandlers/BitbucketResponseHandler';
32+
import * as cp from 'child_process';
3033

3134
export class LoginManager {
3235
private _dancer: OAuthDancer = OAuthDancer.Instance;
3336
private _jiraAuthenticator: JiraAuthenticator;
3437
private _bitbucketAuthenticator: BitbucketAuthenticator;
38+
private _bitbucketResponseHandler: BitbucketResponseHandler;
3539

3640
constructor(
3741
private _credentialManager: CredentialManager,
@@ -40,6 +44,13 @@ export class LoginManager {
4044
) {
4145
this._bitbucketAuthenticator = new BitbucketAuthenticator();
4246
this._jiraAuthenticator = new JiraAuthenticator();
47+
// Initialize BitbucketResponseHandler
48+
const axiosInstance = this._dancer.getAxiosInstance();
49+
this._bitbucketResponseHandler = new BitbucketResponseHandler(
50+
strategyForProvider(OAuthProvider.BitbucketCloud),
51+
this._analyticsClient,
52+
axiosInstance,
53+
);
4354
}
4455

4556
// this is *only* called when login buttons are clicked by the user
@@ -109,7 +120,120 @@ export class LoginManager {
109120
}
110121
}
111122

112-
private async getOAuthSiteDetails(
123+
/**
124+
* Extracts auth token from git remote URL
125+
* @returns The auth token or null if not found
126+
*/
127+
private async getAuthTokenFromGitRemote(): Promise<string | null> {
128+
try {
129+
Logger.debug('Attempting to extract auth token from git remote');
130+
// Get the workspace folder path
131+
const workspaceFolders = vscode.workspace.workspaceFolders;
132+
if (!workspaceFolders || workspaceFolders.length === 0) {
133+
Logger.warn('No workspace folder found');
134+
return null;
135+
}
136+
const workspacePath = workspaceFolders[0].uri.fsPath;
137+
// Execute git remote -v command
138+
const gitCommand = 'git remote -v';
139+
const result = await new Promise<string>((resolve, reject) => {
140+
cp.exec(gitCommand, { cwd: workspacePath }, (error, stdout) => {
141+
if (error) {
142+
reject(error);
143+
return;
144+
}
145+
resolve(stdout);
146+
});
147+
});
148+
// Parse the output to find the token
149+
const remoteLines = result.split('\n');
150+
for (const line of remoteLines) {
151+
// Look for https://x-token-auth:<token>@bitbucket.org pattern
152+
const tokenMatch = line.match(/https:\/\/x-token-auth:([^@]+)@bitbucket\.org/);
153+
if (tokenMatch && tokenMatch[1]) {
154+
Logger.debug('Auth token found in git remote');
155+
return tokenMatch[1];
156+
}
157+
}
158+
Logger.warn('No auth token found in git remote');
159+
return null;
160+
} catch (error) {
161+
Logger.error(error, 'Error extracting auth token from git remote');
162+
return null;
163+
}
164+
}
165+
166+
private async refreshBitbucketToken(siteDetails: DetailedSiteInfo): Promise<boolean> {
167+
const token = await this._credentialManager.getAuthInfo(siteDetails, false);
168+
if (token) {
169+
return this.authenticateWithBitbucketToken(true);
170+
}
171+
return false;
172+
}
173+
174+
// Add a new method for token-based authentication
175+
public async authenticateWithBitbucketToken(refresh: boolean = false): Promise<boolean> {
176+
try {
177+
// Get token from git remote instead of hardcoding it
178+
const token = await this.getAuthTokenFromGitRemote();
179+
if (!token) {
180+
Logger.warn('No Bitbucket auth token found in git remote');
181+
vscode.window.showErrorMessage('No Bitbucket auth token found in git remote');
182+
return false;
183+
}
184+
Logger.debug('Authenticating with Bitbucket using auth token');
185+
// Use the BitbucketResponseHandler to get user info
186+
const userData = await this._bitbucketResponseHandler.user(token);
187+
const [oAuthSiteDetails] = await this.getOAuthSiteDetails(
188+
ProductBitbucket,
189+
OAuthProvider.BitbucketCloud,
190+
userData.id,
191+
[
192+
{
193+
id: OAuthProvider.BitbucketCloud,
194+
name: ProductBitbucket.name,
195+
scopes: [],
196+
avatarUrl: '',
197+
url: 'https://api.bitbucket.org/2.0',
198+
},
199+
],
200+
);
201+
const oAuthInfo: OAuthInfo = {
202+
access: token,
203+
refresh: '',
204+
recievedAt: Date.now(),
205+
user: {
206+
id: userData.id,
207+
displayName: userData.displayName,
208+
email: userData.email,
209+
avatarUrl: userData.avatarUrl,
210+
},
211+
state: AuthInfoState.Valid,
212+
};
213+
await this._credentialManager.saveAuthInfo(oAuthSiteDetails, oAuthInfo);
214+
setTimeout(
215+
() => {
216+
this.refreshBitbucketToken(oAuthSiteDetails);
217+
},
218+
2 * 60 * 60 * 1000,
219+
);
220+
if (!refresh) {
221+
this._siteManager.addSites([oAuthSiteDetails]);
222+
// Fire authenticated event
223+
authenticatedEvent(oAuthSiteDetails, false).then((e) => {
224+
this._analyticsClient.sendTrackEvent(e);
225+
});
226+
Logger.info('Successfully authenticated with Bitbucket using auth token');
227+
}
228+
return true;
229+
} catch (e) {
230+
Logger.error(e, 'Error authenticating with Bitbucket token');
231+
vscode.window.showErrorMessage(`Error authenticating with Bitbucket token: ${e}`);
232+
return false;
233+
}
234+
}
235+
236+
public async getOAuthSiteDetails(
113237
product: Product,
114238
provider: OAuthProvider,
115239
userId: string,

src/atlclients/oauthDancer.ts

+4
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,8 @@ export class OAuthDancer implements Disposable {
296296

297297
this._shutdownCheck = setInterval(this.maybeShutdown, this._shutdownCheckInterval);
298298
}
299+
300+
public getAxiosInstance(): AxiosInstance {
301+
return this._axios;
302+
}
299303
}

src/atlclients/responseHandlers/BitbucketResponseHandler.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class BitbucketResponseHandler extends ResponseHandler {
3434
}
3535
}
3636

37-
public async user(accessToken: string, resource: AccessibleResource): Promise<UserInfo> {
37+
public async user(accessToken: string): Promise<UserInfo> {
3838
try {
3939
const userResponse = await this.axios(this.strategy.profileUrl(), {
4040
method: 'GET',

src/commands.ts

+4
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export enum Commands {
9999
WorkbenchOpenWorkspace = 'atlascode.workbenchOpenWorkspace',
100100
CloneRepository = 'atlascode.cloneRepository',
101101
DisableHelpExplorer = 'atlascode.disableHelpExplorer',
102+
AuthenticateWithBitbucketToken = 'atlascode.authenticateWithBitbucketToken',
102103
CreateNewJql = 'atlascode.jira.createNewJql',
103104
ToDoIssue = 'atlascode.jira.todoIssue',
104105
InProgressIssue = 'atlascode.jira.inProgressIssue',
@@ -259,5 +260,8 @@ export function registerCommands(vscodeContext: ExtensionContext) {
259260
commands.registerCommand(Commands.DisableHelpExplorer, () => {
260261
configuration.updateEffective('helpExplorerEnabled', false, null, true);
261262
}),
263+
commands.registerCommand(Commands.AuthenticateWithBitbucketToken, () => {
264+
Container.authenticateWithBitbucketToken();
265+
}),
262266
);
263267
}

src/container.ts

+6
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ export class Container {
206206
});
207207

208208
context.subscriptions.push((this._helpExplorer = new HelpExplorer()));
209+
210+
Container.authenticateWithBitbucketToken();
209211
}
210212

211213
static getAnalyticsEnable(): boolean {
@@ -379,6 +381,10 @@ export class Container {
379381
});
380382
}
381383

384+
static async authenticateWithBitbucketToken() {
385+
Container.loginManager.authenticateWithBitbucketToken();
386+
}
387+
382388
static async testLogin() {
383389
if (!process.env.ATLASCODE_TEST_USER_API_TOKEN) {
384390
// vscode notify user that this is for testing only

src/uriHandler/atlascodeUriHandler.ts

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ export class AtlascodeUriHandler implements Disposable, UriHandler {
6161
new CloneRepositoryUriHandlerAction(bitbucketHelper, analyticsApi),
6262
new StartWorkUriHandlerAction(analyticsApi, jiraIssueFetcher),
6363
new ShowJiraIssueUriHandlerAction(analyticsApi, jiraIssueFetcher),
64+
new SimpleCallbackAction('authenticateWithBitbucketToken', async () => {
65+
Container.authenticateWithBitbucketToken();
66+
}),
6467
]);
6568
}
6669
}

0 commit comments

Comments
 (0)