fix(hasura-auth-js): transition to the signedOut state when the token is invalid or expired (#2835)
### **User description** fixes https://github.com/nhost/nhost/issues/2817 ___ ### **PR Type** Bug fix, Tests, Enhancement ___ ### **Description** - Added error handling logic to transition to the `signedOut` state when the token is invalid or expired. - Updated the authentication machine to handle 401 errors by signing out the user. - Enhanced test cases to verify the new behavior of signing out on unauthorized errors. - Updated Hasura page teardown logic to ensure the first matching element is clicked. - Added `micromatch` to the audit-ci allowlist for dependency management. ___ ### **Changes walkthrough** 📝 <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Bug fix</strong></td><td><table> <tr> <td> <details> <summary><strong>machine.ts</strong><dd><code>Add error handling for unauthorized token refresh</code> </dd></summary> <hr> packages/hasura-auth-js/src/machines/authentication/machine.ts <li>Added error handling logic to transition to <code>signedOut</code> state on <br>unauthorized error.<br> <li> Introduced a new condition <code>isUnauthorizedError</code> to check for 401 <br>status.<br> <li> Reordered imports for better organization.<br> </details> </td> <td><a href="https://github.com/nhost/nhost/pull/2835/files#diff-a8fdfee087ad5a72ea0a64667e2a0c7f25baa84eaaf73ebfee3f5a5a1b7584d1">+10/-3</a> </td> </tr> </table></td></tr><tr><td><strong>Tests</strong></td><td><table> <tr> <td> <details> <summary><strong>refreshToken.test.ts</strong><dd><code>Update token refresh test for unauthorized error handling</code></dd></summary> <hr> packages/hasura-auth-js/tests/refreshToken.test.ts <li>Updated test to expect sign out on unauthorized error during token <br>refresh.<br> <li> Adjusted test logic to match new authentication state transitions.<br> </details> </td> <td><a href="https://github.com/nhost/nhost/pull/2835/files#diff-271b5a8899ade50e4876f5a50f06da16954125f50d16f28219598cff4e39344b">+3/-7</a> </td> </tr> </table></td></tr><tr><td><strong>Enhancement</strong></td><td><table> <tr> <td> <details> <summary><strong>global-teardown.ts</strong><dd><code>Update Hasura locator to click first matching element</code> </dd></summary> <hr> dashboard/global-teardown.ts <li>Updated locator to click the first matching element for Hasura page <br>teardown.<br> </details> </td> <td><a href="https://github.com/nhost/nhost/pull/2835/files#diff-1ee3d64258c498cdfa30665ec61605ab817622c7dae2a09bd4b6b23606c13e9f">+1/-1</a> </td> </tr> <tr> <td> <details> <summary><strong>machine.typegen.ts</strong><dd><code>Update type definitions for unauthorized error handling</code> </dd></summary> <hr> packages/hasura-auth-js/src/machines/authentication/machine.typegen.ts - Added `isUnauthorizedError` to type definitions. </details> </td> <td><a href="https://github.com/nhost/nhost/pull/2835/files#diff-b0050ab06a8f00d3ae5decd65565adb1bdae3b4b6d19d4f67b9013ffb14e18ee">+2/-0</a> </td> </tr> </table></td></tr><tr><td><strong>Documentation</strong></td><td><table> <tr> <td> <details> <summary><strong>silent-lies-smoke.md</strong><dd><code>Document bug fix for invalid token handling</code> </dd></summary> <hr> .changeset/silent-lies-smoke.md <li>Documented the bug fix for transitioning to <code>signedOut</code> state on invalid <br>token.<br> </details> </td> <td><a href="https://github.com/nhost/nhost/pull/2835/files#diff-f8d41906481f17db7208e2c154075e8679f222536c7958000e6f50f1f019aa01">+5/-0</a> </td> </tr> </table></td></tr><tr><td><strong>Configuration changes</strong></td><td><table> <tr> <td> <details> <summary><strong>audit-ci.jsonc</strong><dd><code>Update audit-ci allowlist with micromatch</code> </dd></summary> <hr> audit-ci.jsonc - Added `micromatch` to the audit-ci allowlist. </details> </td> <td><a href="https://github.com/nhost/nhost/pull/2835/files#diff-4ede69da2a1704e53e08b8d647a315c202f037cc9277f16c94176d9622d261c6">+1/-1</a> </td> </tr> </table></td></tr></tr></tbody></table> ___ > 💡 **PR-Agent usage**: >Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions
This commit is contained in:
committed by
GitHub
parent
40c0d7b914
commit
caa8bd75ec
5
.changeset/silent-lies-smoke.md
Normal file
5
.changeset/silent-lies-smoke.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@nhost/hasura-auth-js': patch
|
||||
---
|
||||
|
||||
fix: add error handling logic to transition to the signedOut state when the token is invalid or expired
|
||||
@@ -2,5 +2,5 @@
|
||||
// $schema provides code completion hints to IDEs.
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": ["vue-template-compiler"]
|
||||
"allowlist": ["vue-template-compiler", "micromatch"]
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ async function globalTeardown() {
|
||||
await adminSecretInput.press('Enter');
|
||||
|
||||
// note: getByRole doesn't work here
|
||||
await hasuraPage.locator('a', { hasText: /data/i }).click();
|
||||
await hasuraPage.locator('a', { hasText: /data/i }).nth(0).click();
|
||||
await hasuraPage.locator('[data-test="sql-link"]').click();
|
||||
|
||||
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
PublicKeyCredentialRequestOptionsJSON,
|
||||
RegistrationCredentialJSON
|
||||
} from '@simplewebauthn/typescript-types'
|
||||
import { InterpreterFrom, assign, createMachine, send } from 'xstate'
|
||||
import { assign, createMachine, InterpreterFrom, send } from 'xstate'
|
||||
import {
|
||||
NHOST_JWT_EXPIRES_AT_KEY,
|
||||
NHOST_REFRESH_TOKEN_ID_KEY,
|
||||
@@ -341,7 +341,13 @@ export const createAuthMachine = ({
|
||||
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||
target: 'pending'
|
||||
},
|
||||
onError: [{ actions: 'saveRefreshAttempt', target: 'pending' }]
|
||||
onError: [
|
||||
{
|
||||
cond: 'isUnauthorizedError',
|
||||
target: '#nhost.authentication.signedOut'
|
||||
},
|
||||
{ actions: 'saveRefreshAttempt', target: 'pending' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,7 +761,8 @@ export const createAuthMachine = ({
|
||||
|
||||
// * Event guards
|
||||
hasSession: (_, e) => !!e.data?.session,
|
||||
hasMfaTicket: (_, e) => !!e.data?.mfa
|
||||
hasMfaTicket: (_, e) => !!e.data?.mfa,
|
||||
isUnauthorizedError: (_, { data: { error } }: any) => error.status === 401
|
||||
},
|
||||
|
||||
services: {
|
||||
|
||||
@@ -213,6 +213,7 @@ export interface Typegen0 {
|
||||
| 'error.platform.authenticateWithPAT'
|
||||
| 'error.platform.authenticateWithToken'
|
||||
| 'error.platform.importRefreshToken'
|
||||
| 'error.platform.refreshToken'
|
||||
| 'error.platform.signInMfaTotp'
|
||||
reportTokenChanged:
|
||||
| 'SESSION_UPDATE'
|
||||
@@ -305,6 +306,7 @@ export interface Typegen0 {
|
||||
isAutoRefreshDisabled: ''
|
||||
isRefreshTokenPAT: ''
|
||||
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
||||
isUnauthorizedError: 'error.platform.refreshToken'
|
||||
noToken: ''
|
||||
refreshTimerShouldRefresh: ''
|
||||
shouldRetryImportToken: 'error.platform.importRefreshToken'
|
||||
|
||||
@@ -87,21 +87,17 @@ describe(`Time based token refresh`, () => {
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
test(`token refresh should fail if the signed-in user's refresh token was invalid`, async () => {
|
||||
test(`token refresh should fail and sign out the user when the server returns an unauthorized error`, async () => {
|
||||
server.use(authTokenUnauthorizedHandler)
|
||||
|
||||
// Fast forwarding to initial expiration date
|
||||
vi.setSystemTime(initialExpiration)
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state) =>
|
||||
const state = await waitFor(authServiceWithInitialSession, (state) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const state = await waitFor(authServiceWithInitialSession, (state) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
expect(state.context.refreshTimer.attempts).toBeGreaterThan(0)
|
||||
expect(state.matches({ authentication: 'signedOut' }))
|
||||
})
|
||||
|
||||
test(`access token should always be refreshed when reaching the expiration margin`, async () => {
|
||||
|
||||
Reference in New Issue
Block a user