Skip to content

Commit a347bff

Browse files
committed
Applied auth patch
1 parent d93e915 commit a347bff

14 files changed

+573
-44
lines changed

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@
7777
},
7878
"contributes": {
7979
"commands": [
80+
{
81+
"command": "atlascode.authenticateWithBitbucketToken",
82+
"title": "Atlasian: Authenticate with Bitbucket Token"
83+
},
8084
{
8185
"command": "atlascode.jira.todoIssue",
8286
"title": "TO DO"
@@ -853,7 +857,7 @@
853857
"properties": {
854858
"atlascode.outputLevel": {
855859
"type": "string",
856-
"default": "silent",
860+
"default": "debug",
857861
"enum": [
858862
"silent",
859863
"errors",
@@ -1324,6 +1328,7 @@
13241328
"@material-ui/styles": "^4.11.5",
13251329
"@segment/analytics-node": "^2.1.3",
13261330
"@vscode/webview-ui-toolkit": "^1.4.0",
1331+
"async-mutex": "^0.5.0",
13271332
"awesome-debounce-promise": "^2.1.0",
13281333
"axios": "1.9.0",
13291334
"axios-curlirize": "^2.0.0",

src/atlclients/authInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export interface AuthInfo {
6262

6363
export interface OAuthInfo extends AuthInfo {
6464
access: string;
65-
refresh: string;
65+
refresh?: string;
6666
expirationDate?: number;
6767
iat?: number;
6868
recievedAt: number;

src/atlclients/authStore.test.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -223,21 +223,6 @@ describe('CredentialManager', () => {
223223
site: mockJiraSite,
224224
});
225225
});
226-
227-
it("should not save to secret storage if auth info hasn't changed", async () => {
228-
// Setup existing auth info
229-
const memStore = (credentialManager as any)._memStore;
230-
const jiraStore = memStore.get(ProductJira.key);
231-
jiraStore.set(mockJiraSite.credentialId, mockAuthInfo);
232-
233-
// Mock getAuthInfo to return the existing info
234-
jest.spyOn(credentialManager, 'getAuthInfo').mockResolvedValue(mockAuthInfo);
235-
236-
await credentialManager.saveAuthInfo(mockJiraSite, mockAuthInfo);
237-
238-
expect(Container.context.secrets.store).not.toHaveBeenCalled();
239-
expect(mockFireEvent).not.toHaveBeenCalled();
240-
});
241226
});
242227

243228
describe('refreshAccessToken', () => {

src/atlclients/authStore.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Mutex } from 'async-mutex';
12
import crypto from 'crypto';
23
import PQueue from 'p-queue';
34
import { Disposable, Event, EventEmitter, version, window } from 'vscode';
@@ -34,10 +35,15 @@ enum Priority {
3435
Write,
3536
}
3637

38+
function getKeyForSecrets(productKey: string, credentialId: string): string {
39+
return `${productKey}-${credentialId}`;
40+
}
41+
3742
export class CredentialManager implements Disposable {
3843
private _memStore: Map<string, Map<string, AuthInfo>> = new Map<string, Map<string, AuthInfo>>();
3944
private _queue = new PQueue({ concurrency: 1 });
4045
private _refresher = new OAuthRefesher();
46+
private mutex = new Mutex();
4147

4248
constructor(private _analyticsClient: AnalyticsClient) {
4349
this._memStore.set(ProductJira.key, new Map<string, AuthInfo>());
@@ -59,21 +65,40 @@ export class CredentialManager implements Disposable {
5965
* it's available, otherwise will return the value in the secretstorage.
6066
*/
6167
public async getAuthInfo(site: DetailedSiteInfo, allowCache = true): Promise<AuthInfo | undefined> {
62-
return this.getAuthInfoForProductAndCredentialId(site, allowCache);
68+
return this.mutex.runExclusive(() => {
69+
return this.getAuthInfoForProductAndCredentialId(site, allowCache);
70+
});
6371
}
6472

6573
/**
6674
* Saves the auth info to both the in-memory store and the secretstorage.
6775
*/
6876
public async saveAuthInfo(site: DetailedSiteInfo, info: AuthInfo): Promise<void> {
69-
Logger.debug(`Saving auth info for site: ${site.baseApiUrl} credentialID: ${site.credentialId}`);
77+
return this.mutex.runExclusive(() => {
78+
return this.saveAuthInfoForProductAndCredentialId(site, info);
79+
});
80+
}
81+
82+
/**
83+
* Removes an auth item from both the in-memory store and the secretstorage.
84+
*/
85+
public async removeAuthInfo(site: DetailedSiteInfo): Promise<boolean> {
86+
return this.mutex.runExclusive(() => {
87+
return this.removeAuthInfoForProductAndCredentialId(site);
88+
});
89+
}
90+
91+
/**
92+
* Saves the auth info to both the in-memory store and the secretstorage.
93+
*/
94+
public async saveAuthInfoForProductAndCredentialId(site: DetailedSiteInfo, info: AuthInfo): Promise<void> {
7095
let productAuths = this._memStore.get(site.product.key);
7196

7297
if (!productAuths) {
7398
productAuths = new Map<string, AuthInfo>();
7499
}
75100

76-
const existingInfo = await this.getAuthInfo(site, false);
101+
const existingInfo = await this.getAuthInfoForProductAndCredentialId(site, false);
77102

78103
this._memStore.set(site.product.key, productAuths.set(site.credentialId, info));
79104

@@ -100,6 +125,29 @@ export class CredentialManager implements Disposable {
100125
}
101126
}
102127

128+
/**
129+
* Saves the auth info to both the in-memory store and the secretstorage for Bitbucket API key login
130+
*/
131+
public async saveAuthInfoBBToken(site: DetailedSiteInfo, info: AuthInfo, id: number): Promise<void> {
132+
Logger.debug(
133+
`[id=${id}] Saving auth info for site: ${site.baseApiUrl} credentialID: ${site.credentialId} using BB token`,
134+
);
135+
const productAuths = this._memStore.get(site.product.key);
136+
Logger.debug(`[id=${id}] productAuths: ${productAuths}`);
137+
if (!productAuths) {
138+
this._memStore.set(site.product.key, new Map<string, AuthInfo>().set(site.credentialId, info));
139+
Logger.debug(`[id=${id}] productAuths is empty so setting it to the new auth info in memstore`);
140+
} else {
141+
productAuths.set(site.credentialId, info);
142+
this._memStore.set(site.product.key, productAuths);
143+
Logger.debug(
144+
`[id=${id}] productAuths is not empty so setting it to productAuth: ${productAuths}in memstore`,
145+
);
146+
}
147+
Logger.debug(`[id=${id}] Calling saveAuthInfo for site: ${site.baseApiUrl} credentialID: ${site.credentialId}`);
148+
await this.saveAuthInfo(site, info);
149+
}
150+
103151
private async getAuthInfoForProductAndCredentialId(
104152
site: DetailedSiteInfo,
105153
allowCache: boolean,
@@ -202,7 +250,10 @@ export class CredentialManager implements Disposable {
202250
await this._queue.add(
203251
async () => {
204252
try {
205-
await Container.context.secrets.store(`${productKey}-${credentialId}`, JSON.stringify(info));
253+
await Container.context.secrets.store(
254+
getKeyForSecrets(productKey, credentialId),
255+
JSON.stringify(info),
256+
);
206257
} catch (e) {
207258
Logger.error(e, `Error writing to secretstorage`);
208259
}
@@ -217,7 +268,7 @@ export class CredentialManager implements Disposable {
217268
let info: string | undefined = undefined;
218269
await this._queue.add(
219270
async () => {
220-
info = await Container.context.secrets.get(`${productKey}-${credentialId}`);
271+
info = await Container.context.secrets.get(getKeyForSecrets(productKey, credentialId));
221272
},
222273
{ priority: Priority.Read },
223274
);
@@ -227,9 +278,9 @@ export class CredentialManager implements Disposable {
227278
let wasKeyDeleted = false;
228279
await this._queue.add(
229280
async () => {
230-
const storedInfo = await Container.context.secrets.get(`${productKey}-${credentialId}`);
281+
const storedInfo = await Container.context.secrets.get(getKeyForSecrets(productKey, credentialId));
231282
if (storedInfo) {
232-
await Container.context.secrets.delete(`${productKey}-${credentialId}`);
283+
await Container.context.secrets.delete(getKeyForSecrets(productKey, credentialId));
233284
wasKeyDeleted = true;
234285
}
235286
},
@@ -244,7 +295,7 @@ export class CredentialManager implements Disposable {
244295
if (keychain) {
245296
wasKeyDeleted = await keychain.deletePassword(
246297
keychainServiceNameV3,
247-
`${productKey}-${credentialId}`,
298+
getKeyForSecrets(productKey, credentialId),
248299
);
249300
}
250301
},
@@ -256,9 +307,7 @@ export class CredentialManager implements Disposable {
256307
private async getAuthInfoFromSecretStorage(
257308
productKey: string,
258309
credentialId: string,
259-
serviceName?: string,
260310
): Promise<AuthInfo | undefined> {
261-
Logger.debug(`Retrieving secretstorage info for product: ${productKey} credentialID: ${credentialId}`);
262311
let authInfo: string | undefined = undefined;
263312
authInfo = await this.getSiteInformationFromSecretStorage(productKey, credentialId);
264313
if (!authInfo) {
@@ -288,7 +337,7 @@ export class CredentialManager implements Disposable {
288337
await this._queue.add(
289338
async () => {
290339
if (keychain) {
291-
authInfo = await keychain.getPassword(svcName, `${productKey}-${credentialId}`);
340+
authInfo = await keychain.getPassword(svcName, getKeyForSecrets(productKey, credentialId));
292341
}
293342
},
294343
{ priority: Priority.Read },
@@ -320,7 +369,7 @@ export class CredentialManager implements Disposable {
320369

321370
const provider: OAuthProvider | undefined = oauthProviderForSite(site);
322371
const newTokens = undefined;
323-
if (provider && credentials) {
372+
if (provider && credentials && credentials.refresh) {
324373
const tokenResponse = await this._refresher.getNewTokens(provider, credentials.refresh);
325374
if (tokenResponse.tokens) {
326375
const newTokens = tokenResponse.tokens;
@@ -344,7 +393,7 @@ export class CredentialManager implements Disposable {
344393
/**
345394
* Removes an auth item from both the in-memory store and the secretstorage.
346395
*/
347-
public async removeAuthInfo(site: DetailedSiteInfo): Promise<boolean> {
396+
public async removeAuthInfoForProductAndCredentialId(site: DetailedSiteInfo): Promise<boolean> {
348397
const productAuths = this._memStore.get(site.product.key);
349398
let wasKeyDeleted = false;
350399
let wasMemDeleted = false;

0 commit comments

Comments
 (0)