Skip to content

Commit 63c17bd

Browse files
committed
safe auth
1 parent 828d66a commit 63c17bd

File tree

3 files changed

+89
-10
lines changed

3 files changed

+89
-10
lines changed

package-lock.json

Lines changed: 27 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"devDependencies": {
2525
"@types/jest": "^29.5.12",
2626
"@types/node-fetch": "^2.5.12",
27+
"@types/rwlock": "^5.0.6",
2728
"@typescript-eslint/eslint-plugin": "^5.4.0",
2829
"@typescript-eslint/parser": "^5.4.0",
2930
"allure-commandline": "^2.29.0",
@@ -46,6 +47,7 @@
4647
"abort-controller": "^3.0.0",
4748
"agentkeepalive": "^4.5.0",
4849
"json-bigint": "^1.0.0",
49-
"node-fetch": "^2.6.6"
50+
"node-fetch": "^2.6.6",
51+
"rwlock": "^5.0.0"
5052
}
5153
}

src/auth/index.ts

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
UsernamePasswordAuth
88
} from "../types";
99
import { TokenKey, inMemoryCache, noneCache } from "../common/tokenCache";
10+
import ReadWriteLock from "rwlock";
1011

1112
type Login = {
1213
access_token: string;
@@ -22,11 +23,16 @@ export class Authenticator {
2223
options: ConnectionOptions;
2324

2425
accessToken?: string;
26+
private rwlock = new ReadWriteLock();
2527

2628
constructor(context: Context, options: ConnectionOptions) {
27-
context.httpClient.authenticator = this;
2829
this.context = context;
2930
this.options = options;
31+
if (context.httpClient.authenticator) {
32+
return context.httpClient.authenticator;
33+
} else {
34+
context.httpClient.authenticator = this;
35+
}
3036
}
3137

3238
private getCacheKey(): TokenKey | undefined {
@@ -157,21 +163,66 @@ export class Authenticator {
157163
);
158164
}
159165

160-
async authenticate() {
161-
const options = this.options.auth || this.options;
162-
const cachedToken = this.getCachedToken();
166+
async authenticate(): Promise<void> {
167+
// Try to get token from cache using read lock
168+
const cachedToken = await this.tryGetCachedToken();
163169
if (cachedToken) {
164170
this.accessToken = cachedToken;
165171
return;
166172
}
167173

174+
// No cached token, acquire write lock and authenticate
175+
await this.acquireWriteLockAndAuthenticate();
176+
}
177+
178+
private async tryGetCachedToken(): Promise<string | undefined> {
179+
return new Promise((resolve, reject) => {
180+
this.rwlock.readLock(releaseReadLock => {
181+
try {
182+
const cachedToken = this.getCachedToken();
183+
releaseReadLock();
184+
resolve(cachedToken);
185+
} catch (error) {
186+
releaseReadLock();
187+
reject(error);
188+
}
189+
});
190+
});
191+
}
192+
193+
private async acquireWriteLockAndAuthenticate(): Promise<void> {
194+
return new Promise((resolve, reject) => {
195+
this.rwlock.writeLock(async releaseWriteLock => {
196+
try {
197+
// Double-check cache in case another thread authenticated while waiting
198+
const cachedToken = this.getCachedToken();
199+
if (cachedToken) {
200+
this.accessToken = cachedToken;
201+
releaseWriteLock();
202+
return resolve();
203+
}
204+
205+
await this.performAuthentication();
206+
207+
releaseWriteLock();
208+
resolve();
209+
} catch (error) {
210+
releaseWriteLock();
211+
reject(error);
212+
}
213+
});
214+
});
215+
}
216+
217+
private async performAuthentication(): Promise<void> {
218+
const options = this.options.auth || this.options;
219+
168220
if (this.isUsernamePassword()) {
169-
await this.authenticateUsernamePassword(options as UsernamePasswordAuth);
170-
return;
221+
return this.authenticateUsernamePassword(options as UsernamePasswordAuth);
171222
}
223+
172224
if (this.isServiceAccount()) {
173-
await this.authenticateServiceAccount(options as ServiceAccountAuth);
174-
return;
225+
return this.authenticateServiceAccount(options as ServiceAccountAuth);
175226
}
176227

177228
throw new Error("Please provide valid auth credentials");

0 commit comments

Comments
 (0)