Compare commits
7 Commits
@nhost/cor
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d1423158 | ||
|
|
e95881089b | ||
|
|
8726458df9 | ||
|
|
6c4233948d | ||
|
|
c16f630a7b | ||
|
|
4ecde10b99 | ||
|
|
0530bac1f1 |
@@ -1,5 +1,11 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 0.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@1.1.13
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.6",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/core
|
||||
|
||||
## 0.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6c423394: Improved authentication state machine logic
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/core",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.6",
|
||||
"description": "Nhost core client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -535,7 +535,7 @@ export const createAuthMachine = ({
|
||||
}
|
||||
}),
|
||||
saveMfaTicket: assign({
|
||||
mfa: (_, e: any) => e.data?.mfa ?? null
|
||||
mfa: (_, e: any) => e.data?.mfa
|
||||
}),
|
||||
|
||||
resetTimer: assign({
|
||||
@@ -637,7 +637,7 @@ export const createAuthMachine = ({
|
||||
if (refreshIntervalTime) {
|
||||
// * If a refreshIntervalTime has been passed on as an option, it will notify
|
||||
// * the token should be refershed when this interval is overdue
|
||||
const elapsed = Date.now() - (ctx.refreshTimer.startedAt?.getTime() || 0)
|
||||
const elapsed = Date.now() - ctx.refreshTimer.startedAt!.getTime()
|
||||
if (elapsed > refreshIntervalTime * 1_000) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import { AuthClient } from '../src/client'
|
||||
import { INVALID_EMAIL_ERROR } from '../src/errors'
|
||||
import { createAuthMachine, createChangeEmailMachine } from '../src/machines'
|
||||
import { Typegen0 } from '../src/machines/change-email.typegen'
|
||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
||||
import { BASE_URL } from './helpers/config'
|
||||
import { changeEmailInternalErrorHandler, changeEmailNetworkErrorHandler } from './helpers/handlers'
|
||||
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||
import server from './helpers/server'
|
||||
import CustomClientStorage from './helpers/storage'
|
||||
import { GeneralChangeEmailState } from './helpers/types'
|
||||
import fakeUser from './helpers/__mocks__/user'
|
||||
|
||||
type ChangeEmailState = GeneralChangeEmailState<Typegen0>
|
||||
|
||||
@@ -23,15 +22,10 @@ const authClient = new AuthClient({
|
||||
})
|
||||
|
||||
authClient.interpreter = interpret(
|
||||
createAuthMachine({ backendUrl: BASE_URL, clientUrl: 'http://localhost:3000' }).withContext({
|
||||
...INITIAL_MACHINE_CONTEXT,
|
||||
user: fakeUser,
|
||||
accessToken: {
|
||||
value: faker.datatype.string(40),
|
||||
expiresAt: faker.date.future()
|
||||
},
|
||||
refreshToken: { value: faker.datatype.uuid() }
|
||||
})
|
||||
createAuthMachine({
|
||||
backendUrl: BASE_URL,
|
||||
clientUrl: 'http://localhost:3000'
|
||||
}).withContext(contextWithUser)
|
||||
).start()
|
||||
|
||||
const changeEmailMachine = createChangeEmailMachine(authClient)
|
||||
|
||||
@@ -5,16 +5,15 @@ import { AuthClient } from '../src/client'
|
||||
import { INVALID_PASSWORD_ERROR } from '../src/errors'
|
||||
import { createAuthMachine, createChangePasswordMachine } from '../src/machines'
|
||||
import { Typegen0 } from '../src/machines/change-password.typegen'
|
||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
||||
import { BASE_URL } from './helpers/config'
|
||||
import {
|
||||
changePasswordInternalErrorHandler,
|
||||
changePasswordNetworkErrorHandler
|
||||
} from './helpers/handlers'
|
||||
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||
import server from './helpers/server'
|
||||
import CustomClientStorage from './helpers/storage'
|
||||
import { GeneralChangePasswordState } from './helpers/types'
|
||||
import fakeUser from './helpers/__mocks__/user'
|
||||
|
||||
type ChangePasswordState = GeneralChangePasswordState<Typegen0>
|
||||
|
||||
@@ -26,15 +25,10 @@ const authClient = new AuthClient({
|
||||
})
|
||||
|
||||
authClient.interpreter = interpret(
|
||||
createAuthMachine({ backendUrl: BASE_URL, clientUrl: 'http://localhost:3000' }).withContext({
|
||||
...INITIAL_MACHINE_CONTEXT,
|
||||
user: fakeUser,
|
||||
accessToken: {
|
||||
value: faker.datatype.string(40),
|
||||
expiresAt: faker.date.future()
|
||||
},
|
||||
refreshToken: { value: faker.datatype.uuid() }
|
||||
})
|
||||
createAuthMachine({
|
||||
backendUrl: BASE_URL,
|
||||
clientUrl: 'http://localhost:3000'
|
||||
}).withContext(contextWithUser)
|
||||
).start()
|
||||
|
||||
const changePasswordMachine = createChangePasswordMachine(authClient)
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import faker from '@faker-js/faker'
|
||||
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'
|
||||
import { interpret, Interpreter } from 'xstate'
|
||||
import { interpret } from 'xstate'
|
||||
import { waitFor } from 'xstate/lib/waitFor'
|
||||
import { AuthClient } from '../src/client'
|
||||
import {
|
||||
INVALID_MFA_CODE_ERROR,
|
||||
INVALID_MFA_TICKET_ERROR,
|
||||
INVALID_MFA_TYPE_ERROR
|
||||
} from '../src/errors'
|
||||
import { INVALID_MFA_CODE_ERROR, INVALID_MFA_TYPE_ERROR } from '../src/errors'
|
||||
import { createAuthMachine, createEnableMfaMachine } from '../src/machines'
|
||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
||||
import { Typegen0 } from '../src/machines/enable-mfa.typegen'
|
||||
import { BASE_URL } from './helpers/config'
|
||||
import {
|
||||
@@ -20,10 +15,10 @@ import {
|
||||
generateMfaTotpNetworkErrorHandler,
|
||||
generateMfaTotpUnauthorizedErrorHandler
|
||||
} from './helpers/handlers'
|
||||
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||
import server from './helpers/server'
|
||||
import CustomClientStorage from './helpers/storage'
|
||||
import { GeneralEnableMfaState } from './helpers/types'
|
||||
import fakeUser from './helpers/__mocks__/user'
|
||||
|
||||
type EnableMfaState = GeneralEnableMfaState<Typegen0>
|
||||
|
||||
@@ -41,15 +36,7 @@ authClient.interpreter = interpret(
|
||||
clientUrl: 'http://localhost:3000',
|
||||
clientStorageType: 'custom',
|
||||
clientStorage: customStorage
|
||||
}).withContext({
|
||||
...INITIAL_MACHINE_CONTEXT,
|
||||
user: fakeUser,
|
||||
accessToken: {
|
||||
value: faker.datatype.string(40),
|
||||
expiresAt: faker.date.future()
|
||||
},
|
||||
refreshToken: { value: faker.datatype.uuid() }
|
||||
})
|
||||
}).withContext(contextWithUser)
|
||||
).start()
|
||||
|
||||
describe(`Generation`, () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import faker from '@faker-js/faker'
|
||||
import { rest } from 'msw'
|
||||
import { NhostSession } from '../../../src/types'
|
||||
import { BASE_URL } from '../config'
|
||||
import fakeUser from '../__mocks__/user'
|
||||
import fakeUser from '../mocks/user'
|
||||
|
||||
/**
|
||||
* Request handler for MSW to mock a successful request for a new access token.
|
||||
|
||||
@@ -2,7 +2,7 @@ import faker from '@faker-js/faker'
|
||||
import { rest } from 'msw'
|
||||
import { Mfa, NhostSession } from '../../../src/types'
|
||||
import { BASE_URL } from '../config'
|
||||
import fakeUser from '../__mocks__/user'
|
||||
import fakeUser from '../mocks/user'
|
||||
|
||||
/**
|
||||
* Request handler for MSW to mock a network error when trying to sign in using the MFA TOTP sign in
|
||||
|
||||
@@ -2,7 +2,7 @@ import faker from '@faker-js/faker'
|
||||
import { rest } from 'msw'
|
||||
import { Mfa, NhostSession } from '../../../src/types'
|
||||
import { BASE_URL } from '../config'
|
||||
import fakeUser from '../__mocks__/user'
|
||||
import fakeUser from '../mocks/user'
|
||||
|
||||
/**
|
||||
* Request handler for MSW to mock a successful sign in request when using the email and password
|
||||
|
||||
@@ -2,7 +2,7 @@ import faker from '@faker-js/faker'
|
||||
import { rest } from 'msw'
|
||||
import { Mfa, NhostSession } from '../../../src/types'
|
||||
import { BASE_URL } from '../config'
|
||||
import fakeUser from '../__mocks__/user'
|
||||
import fakeUser from '../mocks/user'
|
||||
|
||||
/**
|
||||
* Request handler for MSW to mock a successful sign in request using the passwordless email sign in
|
||||
|
||||
@@ -2,7 +2,7 @@ import faker from '@faker-js/faker'
|
||||
import { rest } from 'msw'
|
||||
import { Mfa, NhostSession } from '../../../src/types'
|
||||
import { BASE_URL } from '../config'
|
||||
import fakeUser from '../__mocks__/user'
|
||||
import fakeUser from '../mocks/user'
|
||||
|
||||
/**
|
||||
* Request handler for MSW to mock a network error when trying to sign up.
|
||||
|
||||
17
packages/core/tests/helpers/mocks/contextWithUser.ts
Normal file
17
packages/core/tests/helpers/mocks/contextWithUser.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import faker from '@faker-js/faker'
|
||||
import { AuthContext, INITIAL_MACHINE_CONTEXT } from '../../../src/machines/context'
|
||||
import fakeUser from './user'
|
||||
|
||||
export const contextWithUser: AuthContext = {
|
||||
...INITIAL_MACHINE_CONTEXT,
|
||||
accessToken: {
|
||||
value: faker.datatype.string(40),
|
||||
expiresAt: faker.date.future()
|
||||
},
|
||||
refreshToken: {
|
||||
value: faker.datatype.uuid()
|
||||
},
|
||||
user: fakeUser
|
||||
}
|
||||
|
||||
export default contextWithUser
|
||||
@@ -3,7 +3,6 @@ import { interpret } from 'xstate'
|
||||
import { waitFor } from 'xstate/lib/waitFor'
|
||||
import { INVALID_EMAIL_ERROR, INVALID_PASSWORD_ERROR } from '../src/errors'
|
||||
import { createAuthMachine } from '../src/machines'
|
||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
||||
import { Typegen0 } from '../src/machines/index.typegen'
|
||||
import { BASE_URL } from './helpers/config'
|
||||
import {
|
||||
@@ -13,10 +12,11 @@ import {
|
||||
incorrectEmailPasswordHandler,
|
||||
unverifiedEmailErrorHandler
|
||||
} from './helpers/handlers'
|
||||
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||
import fakeUser from './helpers/mocks/user'
|
||||
import server from './helpers/server'
|
||||
import CustomClientStorage from './helpers/storage'
|
||||
import { GeneralAuthState } from './helpers/types'
|
||||
import fakeUser from './helpers/__mocks__/user'
|
||||
|
||||
type AuthState = GeneralAuthState<Typegen0>
|
||||
|
||||
@@ -245,18 +245,8 @@ test(`should succeed if correct credentials are provided`, async () => {
|
||||
|
||||
test(`should transition to signed in state if user is already signed in`, async () => {
|
||||
const user = { ...fakeUser }
|
||||
const accessToken = faker.datatype.string(40)
|
||||
const refreshToken = faker.datatype.uuid()
|
||||
const expiresAt = new Date(Date.now() * 900000)
|
||||
|
||||
const authServiceWithInitialUser = interpret(
|
||||
authMachine.withContext({
|
||||
...INITIAL_MACHINE_CONTEXT,
|
||||
user,
|
||||
accessToken: { value: accessToken, expiresAt },
|
||||
refreshToken: { value: refreshToken }
|
||||
})
|
||||
)
|
||||
const authServiceWithInitialUser = interpret(authMachine.withContext(contextWithUser))
|
||||
|
||||
authServiceWithInitialUser.start()
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@ import faker from '@faker-js/faker'
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, test, vi } from 'vitest'
|
||||
import { BaseActionObject, interpret, Interpreter, ResolveTypegenMeta, ServiceMap } from 'xstate'
|
||||
import { waitFor } from 'xstate/lib/waitFor'
|
||||
import { NHOST_JWT_EXPIRES_AT_KEY, NHOST_REFRESH_TOKEN_KEY } from '../src/constants'
|
||||
import {
|
||||
NHOST_JWT_EXPIRES_AT_KEY,
|
||||
NHOST_REFRESH_TOKEN_KEY,
|
||||
TOKEN_REFRESH_MARGIN
|
||||
} from '../src/constants'
|
||||
import { INVALID_REFRESH_TOKEN } from '../src/errors'
|
||||
import { AuthContext, AuthEvents, createAuthMachine } from '../src/machines'
|
||||
import { Typegen0 } from '../src/machines/index.typegen'
|
||||
@@ -12,13 +16,190 @@ import {
|
||||
authTokenNetworkErrorHandler,
|
||||
authTokenUnauthorizedHandler
|
||||
} from './helpers/handlers'
|
||||
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||
import fakeUser from './helpers/mocks/user'
|
||||
import server from './helpers/server'
|
||||
import CustomClientStorage from './helpers/storage'
|
||||
import { GeneralAuthState } from './helpers/types'
|
||||
import fakeUser from './helpers/__mocks__/user'
|
||||
|
||||
type AuthState = GeneralAuthState<Typegen0>
|
||||
|
||||
describe(`Time based token refresh`, () => {
|
||||
const initialToken = faker.datatype.uuid()
|
||||
const initialExpiration = faker.date.future()
|
||||
const customStorage = new CustomClientStorage(new Map())
|
||||
|
||||
const authMachineWithInitialSession = createAuthMachine({
|
||||
backendUrl: BASE_URL,
|
||||
clientUrl: 'http://localhost:3000',
|
||||
clientStorage: customStorage,
|
||||
clientStorageType: 'custom',
|
||||
autoSignIn: false
|
||||
}).withContext({
|
||||
...contextWithUser,
|
||||
accessToken: {
|
||||
value: initialToken,
|
||||
expiresAt: initialExpiration
|
||||
}
|
||||
})
|
||||
|
||||
const authServiceWithInitialSession = interpret(authMachineWithInitialSession).start()
|
||||
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
|
||||
afterAll(() => server.close())
|
||||
|
||||
beforeEach(() => {
|
||||
customStorage.setItem(NHOST_JWT_EXPIRES_AT_KEY, faker.date.future().toISOString())
|
||||
customStorage.setItem(NHOST_REFRESH_TOKEN_KEY, faker.datatype.uuid())
|
||||
authServiceWithInitialSession.start()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
authServiceWithInitialSession.stop()
|
||||
customStorage.clear()
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
test(`token refresh should fail if the signed-in user's refresh token was invalid`, async () => {
|
||||
server.use(authTokenUnauthorizedHandler)
|
||||
|
||||
// Fast forwarding to initial expiration date
|
||||
vi.setSystemTime(initialExpiration)
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const state: AuthState = await waitFor(authServiceWithInitialSession, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
expect(state.context.refreshTimer.attempts).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test(`access token should always be refreshed when reaching the expiration margin`, async () => {
|
||||
// Fast forward to the initial expiration date
|
||||
vi.setSystemTime(new Date(initialExpiration.getTime() - TOKEN_REFRESH_MARGIN * 1000))
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const firstRefreshState: AuthState = await waitFor(
|
||||
authServiceWithInitialSession,
|
||||
(state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
const firstRefreshAccessToken = firstRefreshState.context.accessToken.value
|
||||
const firstRefreshAccessTokenExpiration = firstRefreshState.context.accessToken.expiresAt
|
||||
|
||||
expect(firstRefreshAccessToken).not.toBeNull()
|
||||
expect(firstRefreshAccessToken).not.toBe(initialToken)
|
||||
expect(firstRefreshAccessTokenExpiration.getTime()).toBeGreaterThan(initialExpiration.getTime())
|
||||
|
||||
// Fast forward to the expiration date of the access token
|
||||
vi.setSystemTime(
|
||||
new Date(firstRefreshAccessTokenExpiration.getTime() - TOKEN_REFRESH_MARGIN * 1000)
|
||||
)
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const secondRefreshState: AuthState = await waitFor(
|
||||
authServiceWithInitialSession,
|
||||
(state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
const secondRefreshAccessToken = secondRefreshState.context.accessToken.value
|
||||
const secondRefreshAccessTokenExpiration = secondRefreshState.context.accessToken.expiresAt
|
||||
|
||||
expect(secondRefreshAccessToken).not.toBeNull()
|
||||
expect(secondRefreshAccessToken).not.toBe(firstRefreshAccessToken)
|
||||
expect(secondRefreshAccessTokenExpiration.getTime()).toBeGreaterThan(
|
||||
firstRefreshAccessTokenExpiration.getTime()
|
||||
)
|
||||
|
||||
// Fast forward to a time when the access token is still valid, so nothing should be refreshed
|
||||
vi.setSystemTime(
|
||||
new Date(secondRefreshAccessTokenExpiration.getTime() - TOKEN_REFRESH_MARGIN * 5 * 1000)
|
||||
)
|
||||
|
||||
const thirdRefreshState: AuthState = await waitFor(
|
||||
authServiceWithInitialSession,
|
||||
(state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
const thirdRefreshAccessToken = thirdRefreshState.context.accessToken.value
|
||||
const thirdRefreshAccessTokenExpiration = thirdRefreshState.context.accessToken.expiresAt
|
||||
|
||||
expect(thirdRefreshAccessToken).toBe(secondRefreshAccessToken)
|
||||
expect(thirdRefreshAccessTokenExpiration.getTime()).toBe(
|
||||
thirdRefreshAccessTokenExpiration.getTime()
|
||||
)
|
||||
})
|
||||
|
||||
test(`token should be refreshed every N seconds based on the refresh interval`, async () => {
|
||||
const refreshIntervalTime = faker.datatype.number({ min: 800, max: 900 })
|
||||
|
||||
const authMachineWithInitialSession = createAuthMachine({
|
||||
backendUrl: BASE_URL,
|
||||
clientUrl: 'http://localhost:3000',
|
||||
clientStorage: customStorage,
|
||||
clientStorageType: 'custom',
|
||||
refreshIntervalTime,
|
||||
autoSignIn: false
|
||||
}).withContext({
|
||||
...contextWithUser,
|
||||
accessToken: {
|
||||
value: initialToken,
|
||||
expiresAt: initialExpiration
|
||||
}
|
||||
})
|
||||
|
||||
const authServiceWithInitialSession = interpret(authMachineWithInitialSession).start()
|
||||
|
||||
// Fast N seconds to the refresh interval
|
||||
vi.setSystemTime(new Date(Date.now() + refreshIntervalTime * 1000))
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const firstRefreshState: AuthState = await waitFor(
|
||||
authServiceWithInitialSession,
|
||||
(state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
expect(firstRefreshState.context.accessToken.value).not.toBeNull()
|
||||
expect(firstRefreshState.context.accessToken.value).not.toBe(initialToken)
|
||||
|
||||
// Fast N seconds to the refresh interval
|
||||
vi.setSystemTime(new Date(Date.now() + refreshIntervalTime * 1000))
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const secondRefreshState: AuthState = await waitFor(
|
||||
authServiceWithInitialSession,
|
||||
(state: AuthState) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
expect(secondRefreshState.context.accessToken.value).not.toBeNull()
|
||||
expect(secondRefreshState.context.accessToken.value).not.toBe(
|
||||
firstRefreshState.context.accessToken.value
|
||||
)
|
||||
|
||||
authServiceWithInitialSession.stop()
|
||||
})
|
||||
})
|
||||
|
||||
describe('General and disabled auto-sign in', () => {
|
||||
const customStorage = new CustomClientStorage(new Map())
|
||||
|
||||
@@ -209,7 +390,8 @@ describe(`Auto sign-in`, () => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
test(`should throw an error if "error" was in the URL`, async () => {
|
||||
test(`should throw an error if "error" was in the URL when opening the application`, async () => {
|
||||
// Scenario 1: Testing when `errorDescription` is provided.
|
||||
windowSpy.mockImplementation(() => ({
|
||||
...originalWindow,
|
||||
location: {
|
||||
@@ -220,11 +402,11 @@ describe(`Auto sign-in`, () => {
|
||||
|
||||
authService.start()
|
||||
|
||||
const state: AuthState = await waitFor(authService, (state: AuthState) =>
|
||||
const firstState: AuthState = await waitFor(authService, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedOut: 'noErrors' } })
|
||||
)
|
||||
|
||||
expect(state.context.errors).toMatchInlineSnapshot(`
|
||||
expect(firstState.context.errors).toMatchInlineSnapshot(`
|
||||
{
|
||||
"authentication": {
|
||||
"error": "invalid-refresh-token",
|
||||
@@ -233,6 +415,33 @@ describe(`Auto sign-in`, () => {
|
||||
},
|
||||
}
|
||||
`)
|
||||
|
||||
authService.stop()
|
||||
|
||||
// Scenario 2: Testing when `errorDescription` is not provided.
|
||||
windowSpy.mockImplementation(() => ({
|
||||
...originalWindow,
|
||||
location: {
|
||||
...originalWindow.location,
|
||||
href: `http://localhost:3000/?error=${INVALID_REFRESH_TOKEN.error}`
|
||||
}
|
||||
}))
|
||||
|
||||
authService.start()
|
||||
|
||||
const secondState: AuthState = await waitFor(authService, (state: AuthState) =>
|
||||
state.matches({ authentication: { signedOut: 'noErrors' } })
|
||||
)
|
||||
|
||||
expect(secondState.context.errors).toMatchInlineSnapshot(`
|
||||
{
|
||||
"authentication": {
|
||||
"error": "invalid-refresh-token",
|
||||
"message": "invalid-refresh-token",
|
||||
"status": 10,
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test(`should fail if network is unavailable`, async () => {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/hasura-auth-js
|
||||
|
||||
## 1.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6c423394]
|
||||
- @nhost/core@0.5.6
|
||||
|
||||
## 1.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/hasura-auth-js",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.8",
|
||||
"description": "Hasura-auth client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 1.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6c423394]
|
||||
- @nhost/core@0.5.6
|
||||
- @nhost/react@0.7.7
|
||||
- @nhost/nhost-js@1.1.13
|
||||
|
||||
## 1.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "1.2.6",
|
||||
"version": "1.2.7",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/nhost-js
|
||||
|
||||
## 1.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/hasura-auth-js@1.1.8
|
||||
|
||||
## 1.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "1.1.12",
|
||||
"version": "1.1.13",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 4.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@0.7.7
|
||||
- @nhost/apollo@0.5.6
|
||||
|
||||
## 4.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "4.2.6",
|
||||
"version": "4.2.7",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react-auth
|
||||
|
||||
## 3.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/hasura-auth-js@1.1.8
|
||||
- @nhost/react@0.7.7
|
||||
|
||||
## 3.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-auth",
|
||||
"version": "3.0.4",
|
||||
"version": "3.0.5",
|
||||
"description": "Nhost React client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost/react
|
||||
|
||||
## 0.7.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6c423394]
|
||||
- @nhost/core@0.5.6
|
||||
- @nhost/nhost-js@1.1.13
|
||||
|
||||
## 0.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.7",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
Reference in New Issue
Block a user