Compare commits
7 Commits
@nhost/rea
...
@nhost/cor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d1423158 | ||
|
|
e95881089b | ||
|
|
8726458df9 | ||
|
|
6c4233948d | ||
|
|
c16f630a7b | ||
|
|
4ecde10b99 | ||
|
|
0530bac1f1 |
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/apollo
|
# @nhost/apollo
|
||||||
|
|
||||||
|
## 0.5.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@1.1.13
|
||||||
|
|
||||||
## 0.5.5
|
## 0.5.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/apollo",
|
"name": "@nhost/apollo",
|
||||||
"version": "0.5.5",
|
"version": "0.5.6",
|
||||||
"description": "Nhost Apollo Client library",
|
"description": "Nhost Apollo Client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/core
|
# @nhost/core
|
||||||
|
|
||||||
|
## 0.5.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6c423394: Improved authentication state machine logic
|
||||||
|
|
||||||
## 0.5.5
|
## 0.5.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/core",
|
"name": "@nhost/core",
|
||||||
"version": "0.5.5",
|
"version": "0.5.6",
|
||||||
"description": "Nhost core client library",
|
"description": "Nhost core client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ export const createAuthMachine = ({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
saveMfaTicket: assign({
|
saveMfaTicket: assign({
|
||||||
mfa: (_, e: any) => e.data?.mfa ?? null
|
mfa: (_, e: any) => e.data?.mfa
|
||||||
}),
|
}),
|
||||||
|
|
||||||
resetTimer: assign({
|
resetTimer: assign({
|
||||||
@@ -637,7 +637,7 @@ export const createAuthMachine = ({
|
|||||||
if (refreshIntervalTime) {
|
if (refreshIntervalTime) {
|
||||||
// * If a refreshIntervalTime has been passed on as an option, it will notify
|
// * If a refreshIntervalTime has been passed on as an option, it will notify
|
||||||
// * the token should be refershed when this interval is overdue
|
// * 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) {
|
if (elapsed > refreshIntervalTime * 1_000) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import { AuthClient } from '../src/client'
|
|||||||
import { INVALID_EMAIL_ERROR } from '../src/errors'
|
import { INVALID_EMAIL_ERROR } from '../src/errors'
|
||||||
import { createAuthMachine, createChangeEmailMachine } from '../src/machines'
|
import { createAuthMachine, createChangeEmailMachine } from '../src/machines'
|
||||||
import { Typegen0 } from '../src/machines/change-email.typegen'
|
import { Typegen0 } from '../src/machines/change-email.typegen'
|
||||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
|
||||||
import { BASE_URL } from './helpers/config'
|
import { BASE_URL } from './helpers/config'
|
||||||
import { changeEmailInternalErrorHandler, changeEmailNetworkErrorHandler } from './helpers/handlers'
|
import { changeEmailInternalErrorHandler, changeEmailNetworkErrorHandler } from './helpers/handlers'
|
||||||
|
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||||
import server from './helpers/server'
|
import server from './helpers/server'
|
||||||
import CustomClientStorage from './helpers/storage'
|
import CustomClientStorage from './helpers/storage'
|
||||||
import { GeneralChangeEmailState } from './helpers/types'
|
import { GeneralChangeEmailState } from './helpers/types'
|
||||||
import fakeUser from './helpers/__mocks__/user'
|
|
||||||
|
|
||||||
type ChangeEmailState = GeneralChangeEmailState<Typegen0>
|
type ChangeEmailState = GeneralChangeEmailState<Typegen0>
|
||||||
|
|
||||||
@@ -23,15 +22,10 @@ const authClient = new AuthClient({
|
|||||||
})
|
})
|
||||||
|
|
||||||
authClient.interpreter = interpret(
|
authClient.interpreter = interpret(
|
||||||
createAuthMachine({ backendUrl: BASE_URL, clientUrl: 'http://localhost:3000' }).withContext({
|
createAuthMachine({
|
||||||
...INITIAL_MACHINE_CONTEXT,
|
backendUrl: BASE_URL,
|
||||||
user: fakeUser,
|
clientUrl: 'http://localhost:3000'
|
||||||
accessToken: {
|
}).withContext(contextWithUser)
|
||||||
value: faker.datatype.string(40),
|
|
||||||
expiresAt: faker.date.future()
|
|
||||||
},
|
|
||||||
refreshToken: { value: faker.datatype.uuid() }
|
|
||||||
})
|
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
const changeEmailMachine = createChangeEmailMachine(authClient)
|
const changeEmailMachine = createChangeEmailMachine(authClient)
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import { AuthClient } from '../src/client'
|
|||||||
import { INVALID_PASSWORD_ERROR } from '../src/errors'
|
import { INVALID_PASSWORD_ERROR } from '../src/errors'
|
||||||
import { createAuthMachine, createChangePasswordMachine } from '../src/machines'
|
import { createAuthMachine, createChangePasswordMachine } from '../src/machines'
|
||||||
import { Typegen0 } from '../src/machines/change-password.typegen'
|
import { Typegen0 } from '../src/machines/change-password.typegen'
|
||||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
|
||||||
import { BASE_URL } from './helpers/config'
|
import { BASE_URL } from './helpers/config'
|
||||||
import {
|
import {
|
||||||
changePasswordInternalErrorHandler,
|
changePasswordInternalErrorHandler,
|
||||||
changePasswordNetworkErrorHandler
|
changePasswordNetworkErrorHandler
|
||||||
} from './helpers/handlers'
|
} from './helpers/handlers'
|
||||||
|
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||||
import server from './helpers/server'
|
import server from './helpers/server'
|
||||||
import CustomClientStorage from './helpers/storage'
|
import CustomClientStorage from './helpers/storage'
|
||||||
import { GeneralChangePasswordState } from './helpers/types'
|
import { GeneralChangePasswordState } from './helpers/types'
|
||||||
import fakeUser from './helpers/__mocks__/user'
|
|
||||||
|
|
||||||
type ChangePasswordState = GeneralChangePasswordState<Typegen0>
|
type ChangePasswordState = GeneralChangePasswordState<Typegen0>
|
||||||
|
|
||||||
@@ -26,15 +25,10 @@ const authClient = new AuthClient({
|
|||||||
})
|
})
|
||||||
|
|
||||||
authClient.interpreter = interpret(
|
authClient.interpreter = interpret(
|
||||||
createAuthMachine({ backendUrl: BASE_URL, clientUrl: 'http://localhost:3000' }).withContext({
|
createAuthMachine({
|
||||||
...INITIAL_MACHINE_CONTEXT,
|
backendUrl: BASE_URL,
|
||||||
user: fakeUser,
|
clientUrl: 'http://localhost:3000'
|
||||||
accessToken: {
|
}).withContext(contextWithUser)
|
||||||
value: faker.datatype.string(40),
|
|
||||||
expiresAt: faker.date.future()
|
|
||||||
},
|
|
||||||
refreshToken: { value: faker.datatype.uuid() }
|
|
||||||
})
|
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
const changePasswordMachine = createChangePasswordMachine(authClient)
|
const changePasswordMachine = createChangePasswordMachine(authClient)
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import faker from '@faker-js/faker'
|
import faker from '@faker-js/faker'
|
||||||
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'
|
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'
|
||||||
import { interpret, Interpreter } from 'xstate'
|
import { interpret } from 'xstate'
|
||||||
import { waitFor } from 'xstate/lib/waitFor'
|
import { waitFor } from 'xstate/lib/waitFor'
|
||||||
import { AuthClient } from '../src/client'
|
import { AuthClient } from '../src/client'
|
||||||
import {
|
import { INVALID_MFA_CODE_ERROR, INVALID_MFA_TYPE_ERROR } from '../src/errors'
|
||||||
INVALID_MFA_CODE_ERROR,
|
|
||||||
INVALID_MFA_TICKET_ERROR,
|
|
||||||
INVALID_MFA_TYPE_ERROR
|
|
||||||
} from '../src/errors'
|
|
||||||
import { createAuthMachine, createEnableMfaMachine } from '../src/machines'
|
import { createAuthMachine, createEnableMfaMachine } from '../src/machines'
|
||||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
|
||||||
import { Typegen0 } from '../src/machines/enable-mfa.typegen'
|
import { Typegen0 } from '../src/machines/enable-mfa.typegen'
|
||||||
import { BASE_URL } from './helpers/config'
|
import { BASE_URL } from './helpers/config'
|
||||||
import {
|
import {
|
||||||
@@ -20,10 +15,10 @@ import {
|
|||||||
generateMfaTotpNetworkErrorHandler,
|
generateMfaTotpNetworkErrorHandler,
|
||||||
generateMfaTotpUnauthorizedErrorHandler
|
generateMfaTotpUnauthorizedErrorHandler
|
||||||
} from './helpers/handlers'
|
} from './helpers/handlers'
|
||||||
|
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||||
import server from './helpers/server'
|
import server from './helpers/server'
|
||||||
import CustomClientStorage from './helpers/storage'
|
import CustomClientStorage from './helpers/storage'
|
||||||
import { GeneralEnableMfaState } from './helpers/types'
|
import { GeneralEnableMfaState } from './helpers/types'
|
||||||
import fakeUser from './helpers/__mocks__/user'
|
|
||||||
|
|
||||||
type EnableMfaState = GeneralEnableMfaState<Typegen0>
|
type EnableMfaState = GeneralEnableMfaState<Typegen0>
|
||||||
|
|
||||||
@@ -41,15 +36,7 @@ authClient.interpreter = interpret(
|
|||||||
clientUrl: 'http://localhost:3000',
|
clientUrl: 'http://localhost:3000',
|
||||||
clientStorageType: 'custom',
|
clientStorageType: 'custom',
|
||||||
clientStorage: customStorage
|
clientStorage: customStorage
|
||||||
}).withContext({
|
}).withContext(contextWithUser)
|
||||||
...INITIAL_MACHINE_CONTEXT,
|
|
||||||
user: fakeUser,
|
|
||||||
accessToken: {
|
|
||||||
value: faker.datatype.string(40),
|
|
||||||
expiresAt: faker.date.future()
|
|
||||||
},
|
|
||||||
refreshToken: { value: faker.datatype.uuid() }
|
|
||||||
})
|
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
describe(`Generation`, () => {
|
describe(`Generation`, () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import faker from '@faker-js/faker'
|
|||||||
import { rest } from 'msw'
|
import { rest } from 'msw'
|
||||||
import { NhostSession } from '../../../src/types'
|
import { NhostSession } from '../../../src/types'
|
||||||
import { BASE_URL } from '../config'
|
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.
|
* 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 { rest } from 'msw'
|
||||||
import { Mfa, NhostSession } from '../../../src/types'
|
import { Mfa, NhostSession } from '../../../src/types'
|
||||||
import { BASE_URL } from '../config'
|
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
|
* 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 { rest } from 'msw'
|
||||||
import { Mfa, NhostSession } from '../../../src/types'
|
import { Mfa, NhostSession } from '../../../src/types'
|
||||||
import { BASE_URL } from '../config'
|
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
|
* 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 { rest } from 'msw'
|
||||||
import { Mfa, NhostSession } from '../../../src/types'
|
import { Mfa, NhostSession } from '../../../src/types'
|
||||||
import { BASE_URL } from '../config'
|
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
|
* 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 { rest } from 'msw'
|
||||||
import { Mfa, NhostSession } from '../../../src/types'
|
import { Mfa, NhostSession } from '../../../src/types'
|
||||||
import { BASE_URL } from '../config'
|
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.
|
* 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 { waitFor } from 'xstate/lib/waitFor'
|
||||||
import { INVALID_EMAIL_ERROR, INVALID_PASSWORD_ERROR } from '../src/errors'
|
import { INVALID_EMAIL_ERROR, INVALID_PASSWORD_ERROR } from '../src/errors'
|
||||||
import { createAuthMachine } from '../src/machines'
|
import { createAuthMachine } from '../src/machines'
|
||||||
import { INITIAL_MACHINE_CONTEXT } from '../src/machines/context'
|
|
||||||
import { Typegen0 } from '../src/machines/index.typegen'
|
import { Typegen0 } from '../src/machines/index.typegen'
|
||||||
import { BASE_URL } from './helpers/config'
|
import { BASE_URL } from './helpers/config'
|
||||||
import {
|
import {
|
||||||
@@ -13,10 +12,11 @@ import {
|
|||||||
incorrectEmailPasswordHandler,
|
incorrectEmailPasswordHandler,
|
||||||
unverifiedEmailErrorHandler
|
unverifiedEmailErrorHandler
|
||||||
} from './helpers/handlers'
|
} from './helpers/handlers'
|
||||||
|
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||||
|
import fakeUser from './helpers/mocks/user'
|
||||||
import server from './helpers/server'
|
import server from './helpers/server'
|
||||||
import CustomClientStorage from './helpers/storage'
|
import CustomClientStorage from './helpers/storage'
|
||||||
import { GeneralAuthState } from './helpers/types'
|
import { GeneralAuthState } from './helpers/types'
|
||||||
import fakeUser from './helpers/__mocks__/user'
|
|
||||||
|
|
||||||
type AuthState = GeneralAuthState<Typegen0>
|
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 () => {
|
test(`should transition to signed in state if user is already signed in`, async () => {
|
||||||
const user = { ...fakeUser }
|
const user = { ...fakeUser }
|
||||||
const accessToken = faker.datatype.string(40)
|
|
||||||
const refreshToken = faker.datatype.uuid()
|
|
||||||
const expiresAt = new Date(Date.now() * 900000)
|
|
||||||
|
|
||||||
const authServiceWithInitialUser = interpret(
|
const authServiceWithInitialUser = interpret(authMachine.withContext(contextWithUser))
|
||||||
authMachine.withContext({
|
|
||||||
...INITIAL_MACHINE_CONTEXT,
|
|
||||||
user,
|
|
||||||
accessToken: { value: accessToken, expiresAt },
|
|
||||||
refreshToken: { value: refreshToken }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
authServiceWithInitialUser.start()
|
authServiceWithInitialUser.start()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import faker from '@faker-js/faker'
|
|||||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, test, vi } from 'vitest'
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, test, vi } from 'vitest'
|
||||||
import { BaseActionObject, interpret, Interpreter, ResolveTypegenMeta, ServiceMap } from 'xstate'
|
import { BaseActionObject, interpret, Interpreter, ResolveTypegenMeta, ServiceMap } from 'xstate'
|
||||||
import { waitFor } from 'xstate/lib/waitFor'
|
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 { INVALID_REFRESH_TOKEN } from '../src/errors'
|
||||||
import { AuthContext, AuthEvents, createAuthMachine } from '../src/machines'
|
import { AuthContext, AuthEvents, createAuthMachine } from '../src/machines'
|
||||||
import { Typegen0 } from '../src/machines/index.typegen'
|
import { Typegen0 } from '../src/machines/index.typegen'
|
||||||
@@ -12,13 +16,190 @@ import {
|
|||||||
authTokenNetworkErrorHandler,
|
authTokenNetworkErrorHandler,
|
||||||
authTokenUnauthorizedHandler
|
authTokenUnauthorizedHandler
|
||||||
} from './helpers/handlers'
|
} from './helpers/handlers'
|
||||||
|
import contextWithUser from './helpers/mocks/contextWithUser'
|
||||||
|
import fakeUser from './helpers/mocks/user'
|
||||||
import server from './helpers/server'
|
import server from './helpers/server'
|
||||||
import CustomClientStorage from './helpers/storage'
|
import CustomClientStorage from './helpers/storage'
|
||||||
import { GeneralAuthState } from './helpers/types'
|
import { GeneralAuthState } from './helpers/types'
|
||||||
import fakeUser from './helpers/__mocks__/user'
|
|
||||||
|
|
||||||
type AuthState = GeneralAuthState<Typegen0>
|
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', () => {
|
describe('General and disabled auto-sign in', () => {
|
||||||
const customStorage = new CustomClientStorage(new Map())
|
const customStorage = new CustomClientStorage(new Map())
|
||||||
|
|
||||||
@@ -209,7 +390,8 @@ describe(`Auto sign-in`, () => {
|
|||||||
vi.restoreAllMocks()
|
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(() => ({
|
windowSpy.mockImplementation(() => ({
|
||||||
...originalWindow,
|
...originalWindow,
|
||||||
location: {
|
location: {
|
||||||
@@ -220,11 +402,11 @@ describe(`Auto sign-in`, () => {
|
|||||||
|
|
||||||
authService.start()
|
authService.start()
|
||||||
|
|
||||||
const state: AuthState = await waitFor(authService, (state: AuthState) =>
|
const firstState: AuthState = await waitFor(authService, (state: AuthState) =>
|
||||||
state.matches({ authentication: { signedOut: 'noErrors' } })
|
state.matches({ authentication: { signedOut: 'noErrors' } })
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(state.context.errors).toMatchInlineSnapshot(`
|
expect(firstState.context.errors).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"authentication": {
|
"authentication": {
|
||||||
"error": "invalid-refresh-token",
|
"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 () => {
|
test(`should fail if network is unavailable`, async () => {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/hasura-auth-js
|
# @nhost/hasura-auth-js
|
||||||
|
|
||||||
|
## 1.1.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6c423394]
|
||||||
|
- @nhost/core@0.5.6
|
||||||
|
|
||||||
## 1.1.7
|
## 1.1.7
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/hasura-auth-js",
|
"name": "@nhost/hasura-auth-js",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"description": "Hasura-auth client",
|
"description": "Hasura-auth client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# @nhost/nextjs
|
# @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
|
## 1.2.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nextjs",
|
"name": "@nhost/nextjs",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"description": "Nhost NextJS library",
|
"description": "Nhost NextJS library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/nhost-js
|
# @nhost/nhost-js
|
||||||
|
|
||||||
|
## 1.1.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/hasura-auth-js@1.1.8
|
||||||
|
|
||||||
## 1.1.12
|
## 1.1.12
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nhost-js",
|
"name": "@nhost/nhost-js",
|
||||||
"version": "1.1.12",
|
"version": "1.1.13",
|
||||||
"description": "Nhost JavaScript SDK",
|
"description": "Nhost JavaScript SDK",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/react-apollo
|
# @nhost/react-apollo
|
||||||
|
|
||||||
|
## 4.2.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@0.7.7
|
||||||
|
- @nhost/apollo@0.5.6
|
||||||
|
|
||||||
## 4.2.6
|
## 4.2.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-apollo",
|
"name": "@nhost/react-apollo",
|
||||||
"version": "4.2.6",
|
"version": "4.2.7",
|
||||||
"description": "Nhost React Apollo client",
|
"description": "Nhost React Apollo client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/react-auth
|
# @nhost/react-auth
|
||||||
|
|
||||||
|
## 3.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/hasura-auth-js@1.1.8
|
||||||
|
- @nhost/react@0.7.7
|
||||||
|
|
||||||
## 3.0.4
|
## 3.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-auth",
|
"name": "@nhost/react-auth",
|
||||||
"version": "3.0.4",
|
"version": "3.0.5",
|
||||||
"description": "Nhost React client",
|
"description": "Nhost React client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @nhost/react
|
# @nhost/react
|
||||||
|
|
||||||
|
## 0.7.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6c423394]
|
||||||
|
- @nhost/core@0.5.6
|
||||||
|
- @nhost/nhost-js@1.1.13
|
||||||
|
|
||||||
## 0.7.6
|
## 0.7.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react",
|
"name": "@nhost/react",
|
||||||
"version": "0.7.6",
|
"version": "0.7.7",
|
||||||
"description": "Nhost React library",
|
"description": "Nhost React library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
Reference in New Issue
Block a user