mirror of
https://github.com/sasjs/adapter.git
synced 2026-04-21 13:11:31 +00:00
fix: handle session inactivity expiry
This commit is contained in:
@@ -375,7 +375,7 @@ export class AuthManager {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public async logOut() {
|
public async logOut() {
|
||||||
this.requestClient.clearCsrfTokens()
|
this.requestClient.resetInMemoryAuthState()
|
||||||
|
|
||||||
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
|
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export class RequestClient implements HttpClient {
|
|||||||
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
||||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||||
protected httpClient!: AxiosInstance
|
protected httpClient!: AxiosInstance
|
||||||
|
private isRecoveringFromNetworkError = false
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected baseUrl: string,
|
protected baseUrl: string,
|
||||||
@@ -77,6 +78,16 @@ export class RequestClient implements HttpClient {
|
|||||||
localStorage.setItem('refreshToken', '')
|
localStorage.setItem('refreshToken', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resetInMemoryAuthState() {
|
||||||
|
this.clearCsrfTokens()
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
this.clearLocalStorageTokens()
|
||||||
|
}
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.cookie = 'XSRF-TOKEN=; Max-Age=0; Path=/;'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getBaseUrl() {
|
public getBaseUrl() {
|
||||||
return this.httpClient.defaults.baseURL || ''
|
return this.httpClient.defaults.baseURL || ''
|
||||||
}
|
}
|
||||||
@@ -687,6 +698,24 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|||||||
throw new CertificateError(e.message)
|
throw new CertificateError(e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
e.isAxiosError &&
|
||||||
|
!response &&
|
||||||
|
e.code === 'ERR_NETWORK' &&
|
||||||
|
!this.isRecoveringFromNetworkError
|
||||||
|
) {
|
||||||
|
// Opaque ERR_NETWORK usually means the server rejected stale credentials.
|
||||||
|
// Wipe in-memory auth state so the retry either succeeds
|
||||||
|
// or surfaces a clean LoginRequiredError.
|
||||||
|
this.resetInMemoryAuthState()
|
||||||
|
this.isRecoveringFromNetworkError = true
|
||||||
|
try {
|
||||||
|
return await callback()
|
||||||
|
} finally {
|
||||||
|
this.isRecoveringFromNetworkError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (e.message) throw e
|
if (e.message) throw e
|
||||||
else throw prefixMessage(e, 'Error while handling error. ')
|
else throw prefixMessage(e, 'Error while handling error. ')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ export class Sas9RequestClient extends RequestClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resetInMemoryAuthState() {
|
||||||
|
super.resetInMemoryAuthState()
|
||||||
|
if (this.httpClient.defaults.jar) {
|
||||||
|
;(this.httpClient.defaults.jar as tough.CookieJar).removeAllCookiesSync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async login(username: string, password: string, jobsPath: string) {
|
public async login(username: string, password: string, jobsPath: string) {
|
||||||
const codeInjectorPath = `/User Folders/${username}/My Folder/sasjs/runner`
|
const codeInjectorPath = `/User Folders/${username}/My Folder/sasjs/runner`
|
||||||
if (this.httpClient.defaults.jar) {
|
if (this.httpClient.defaults.jar) {
|
||||||
|
|||||||
@@ -589,6 +589,42 @@ ${resHeaders[0]}: ${resHeaders[1]}${
|
|||||||
requestClient['handleError'](error, () => {}, false)
|
requestClient['handleError'](error, () => {}, false)
|
||||||
).resolves.toEqual(undefined)
|
).resolves.toEqual(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should clear CSRF and retry once on opaque ERR_NETWORK', async () => {
|
||||||
|
const networkError = {
|
||||||
|
isAxiosError: true,
|
||||||
|
code: 'ERR_NETWORK',
|
||||||
|
message: 'Network Error'
|
||||||
|
}
|
||||||
|
requestClient['csrfToken'] = { headerName: 'h', value: 'v' }
|
||||||
|
const callback = jest.fn().mockResolvedValue('ok')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
requestClient['handleError'](networkError, callback)
|
||||||
|
).resolves.toEqual('ok')
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1)
|
||||||
|
expect(requestClient['csrfToken']).toEqual({ headerName: '', value: '' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not loop if retry also fails with ERR_NETWORK', async () => {
|
||||||
|
const networkError = {
|
||||||
|
isAxiosError: true,
|
||||||
|
code: 'ERR_NETWORK',
|
||||||
|
message: 'Network Error'
|
||||||
|
}
|
||||||
|
const innerHandle = jest.fn(() =>
|
||||||
|
requestClient['handleError'](networkError, () =>
|
||||||
|
Promise.reject(networkError)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
requestClient['handleError'](networkError, innerHandle)
|
||||||
|
).rejects.toEqual(networkError)
|
||||||
|
|
||||||
|
expect(innerHandle).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user