Skip to content

Commit 699019f

Browse files
authored
Merge pull request #250 from leancodepl/fix/login-manager-concurrent-token-fix
fix: fix usage of stale token in login manager
2 parents 56993c8 + d96b544 commit 699019f

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

packages/login-manager/__tests__/test.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,50 @@ describe("LoginManager", () => {
102102
expect(globalThis.fetch).toHaveBeenCalledTimes(1)
103103
})
104104

105+
it("should not refresh again if token was already refreshed by another tab", async () => {
106+
const manager1 = new SyncLoginManager(storage, "https://api.example.com", undefined, "client1", "openid")
107+
const manager2 = new SyncLoginManager(storage, "https://api.example.com", undefined, "client1", "openid")
108+
109+
await manager1.tryRefreshToken()
110+
expect(globalThis.fetch).toHaveBeenCalledTimes(1)
111+
112+
const result = await manager2.tryRefreshToken()
113+
expect(result).toBe(true)
114+
expect(globalThis.fetch).toHaveBeenCalledTimes(1)
115+
})
116+
117+
it("should re-read token from storage inside lock to avoid using stale refresh token", async () => {
118+
let fetchCallCount = 0
119+
globalThis.fetch = vi.fn(() => {
120+
fetchCallCount++
121+
if (fetchCallCount === 1) {
122+
return Promise.resolve({
123+
ok: true,
124+
status: 200,
125+
json: () =>
126+
Promise.resolve({
127+
access_token: "new_access_token",
128+
refresh_token: "new_refresh_token",
129+
expires_in: 3600,
130+
}),
131+
} as Response)
132+
}
133+
return Promise.resolve({
134+
ok: false,
135+
status: 400,
136+
} as Response)
137+
})
138+
139+
const manager1 = new SyncLoginManager(storage, "https://api.example.com", undefined, "client1", "openid")
140+
const manager2 = new SyncLoginManager(storage, "https://api.example.com", undefined, "client1", "openid")
141+
142+
await manager1.tryRefreshToken()
143+
144+
const result = await manager2.tryRefreshToken()
145+
expect(result).toBe(true)
146+
expect(globalThis.fetch).toHaveBeenCalledTimes(1)
147+
})
148+
105149
it("should handle failed refresh", async () => {
106150
globalThis.fetch = vi.fn(() =>
107151
Promise.resolve({

packages/login-manager/src/lib/baseLoginManager.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ export abstract class BaseLoginManager<TStorage extends TokenStorage> {
114114
return await this.waitForLockRelease()
115115
}
116116

117+
if (token === null) {
118+
return false
119+
}
120+
121+
if (token.expirationDate >= new Date()) {
122+
return true
123+
}
124+
117125
try {
118126
const result = await this.acquireToken(this.buildRefreshRequest(token))
119127
return result.type === "success"
@@ -124,7 +132,7 @@ export abstract class BaseLoginManager<TStorage extends TokenStorage> {
124132
}
125133

126134
private async waitForLockRelease(): Promise<boolean> {
127-
return await navigator.locks.request(refreshLockKey, async () => {
135+
return await navigator.locks.request(refreshLockKey, { mode: "shared" }, async () => {
128136
const currentToken = await this.storage.getToken()
129137
return currentToken !== null
130138
})

tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@
101101
},
102102
{
103103
"path": "./packages/logger"
104+
},
105+
{
106+
"path": "./packages/nx-plugins"
104107
}
105108
]
106109
}

0 commit comments

Comments
 (0)