Skip to content

Commit

Permalink
fix missing backup cookie config, add missing backup cookie setter co…
Browse files Browse the repository at this point in the history
…ndition, add backup cookie tests
  • Loading branch information
dauglyon committed Sep 25, 2024
1 parent b1d934f commit e0cf6a3
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 30 deletions.
2 changes: 2 additions & 0 deletions scripts/build_deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface EnvironmentConfig {
public_url: string;
backup_cookie?: {
name: string;
domain: string;
};
}

Expand Down Expand Up @@ -60,6 +61,7 @@ const setEnvironment = (
REACT_APP_KBASE_DOMAIN: domain,
REACT_APP_KBASE_LEGACY_DOMAIN: legacy,
REACT_APP_KBASE_BACKUP_COOKIE_NAME: backupCookie?.name || '',
REACT_APP_KBASE_BACKUP_COOKIE_DOMAIN: backupCookie?.domain || '',
};
Object.assign(process.env, envsNew);
};
Expand Down
179 changes: 149 additions & 30 deletions src/features/auth/authSlice.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,27 +123,39 @@ describe('authSlice', () => {
ReturnType<typeof console.error>,
Parameters<typeof console.error>
>;
let mockCookieVal = '';
const setTokenCookieMock = jest.fn();
const clearTokenCookieMock = jest.fn();
let mockCookieVals: Record<string, string> = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let setTokenCookieMocks: Record<string, jest.Mock<any, any, any>> = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let clearTokenCookieMocks: Record<string, jest.Mock<any, any, any>> = {};
beforeAll(() => {
useCookieMock = jest.spyOn(cookies, 'useCookie');
useCookieMock.mockImplementation(() => [
mockCookieVal,
setTokenCookieMock,
clearTokenCookieMock,
]);
useCookieMock.mockImplementation((name?: string) => {
const key = name || 'UNKNOWN';
setTokenCookieMocks[key] ??= jest.fn((val) => {
if (val) mockCookieVals[key] = val;
});
clearTokenCookieMocks[key] ??= jest.fn(() => {
mockCookieVals[key] = '';
});
return [
mockCookieVals[key] || '',
setTokenCookieMocks[key],
clearTokenCookieMocks[key],
];
});
consoleErrorMock = jest.spyOn(console, 'error');
consoleErrorMock.mockImplementation(() => undefined);
});
beforeEach(() => {
setTokenCookieMock.mockClear();
clearTokenCookieMock.mockClear();
mockCookieVals = {};
setTokenCookieMocks = {};
clearTokenCookieMocks = {};
useCookieMock.mockClear();
consoleErrorMock.mockClear();
});
afterAll(() => {
setTokenCookieMock.mockRestore();
clearTokenCookieMock.mockRestore();
useCookieMock.mockRestore();
consoleErrorMock.mockRestore();
});

Expand All @@ -158,7 +170,7 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(clearTokenCookieMock).toHaveBeenCalledWith();
expect(clearTokenCookieMocks['kbase_session']).toHaveBeenCalledWith();
expect(consoleErrorMock).not.toHaveBeenCalled();
});
});
Expand All @@ -182,13 +194,117 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(setTokenCookieMock).toHaveBeenCalledWith('some-token', {
expires: new Date(auth.tokenInfo.expires),
expect(setTokenCookieMocks['kbase_session']).toHaveBeenCalledWith(
'some-token',
{
expires: new Date(auth.tokenInfo.expires),
}
);
expect(consoleErrorMock).not.toHaveBeenCalled();
});
});

test('useTokenCookie sets backup cookie if auth token exists with expiration', async () => {
const auth = {
token: 'some-token',
username: 'some-user',
tokenInfo: {
expires: Date.now() + Math.floor(Math.random() * 10000),
} as TokenInfo,
initialized: true,
};
const Component = () => {
useTokenCookie('kbase_session', 'backup_cookie', '.backup.domain');
return <></>;
};
render(
<Provider store={createTestStore({ auth })}>
<Component />
</Provider>
);
await waitFor(() => {
expect(useCookieMock).toHaveBeenCalledWith('backup_cookie', {
domain: '.backup.domain',
path: '/',
secure: true,
});
expect(setTokenCookieMocks['backup_cookie']).toHaveBeenCalledWith(
'some-token',
{
expires: new Date(auth.tokenInfo.expires),
}
);
expect(consoleErrorMock).not.toHaveBeenCalled();
});
});

test('useTokenCookie sets backup cookie if missing', async () => {
const auth = {
token: 'some-token',
username: 'some-user',
tokenInfo: {
expires: Date.now() + Math.floor(Math.random() * 10000),
} as TokenInfo,
initialized: true,
};
mockCookieVals['kbase_session'] = 'some-token';
const Component = () => {
useTokenCookie('kbase_session', 'backup_cookie', '.backup.domain');
return <></>;
};
render(
<Provider store={createTestStore({ auth })}>
<Component />
</Provider>
);
await waitFor(() => {
expect(useCookieMock).toHaveBeenCalledWith('backup_cookie', {
domain: '.backup.domain',
path: '/',
secure: true,
});
expect(setTokenCookieMocks['backup_cookie']).toHaveBeenCalledWith(
'some-token',
{
expires: new Date(auth.tokenInfo.expires),
}
);
expect(consoleErrorMock).not.toHaveBeenCalled();
});
});

test('useTokenCookie wont set backup cookie if domain is empty', async () => {
const auth = {
token: 'some-token',
username: 'some-user',
tokenInfo: {
expires: Date.now() + Math.floor(Math.random() * 10000),
} as TokenInfo,
initialized: true,
};
mockCookieVals['kbase_session'] = 'some-token';
const Component = () => {
useTokenCookie('kbase_session', 'backup_cookie', '');
return <></>;
};
render(
<Provider store={createTestStore({ auth })}>
<Component />
</Provider>
);
await waitFor(() => {
expect(useCookieMock).toHaveBeenCalledWith('backup_cookie', {
domain: '',
path: '/',
secure: true,
});
expect(setTokenCookieMocks['backup_cookie']).not.toHaveBeenCalled();
expect(consoleErrorMock).toHaveBeenCalledWith(
'Backup cookie cannot be set due to bad configuration.'
);
});
});

test('useTokenCookie sets cookie in development mode', async () => {
const processEnv = process.env;
process.env = { ...processEnv, NODE_ENV: 'development' };
Expand All @@ -210,9 +326,12 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(setTokenCookieMock).toHaveBeenCalledWith('some-token', {
expires: new Date(auth.tokenInfo.expires),
});
expect(setTokenCookieMocks['kbase_session']).toHaveBeenCalledWith(
'some-token',
{
expires: new Date(auth.tokenInfo.expires),
}
);
expect(consoleErrorMock).not.toHaveBeenCalled();
});
process.env = processEnv;
Expand All @@ -239,13 +358,13 @@ describe('authSlice', () => {
expect(consoleErrorMock).toHaveBeenCalledWith(
'Could not set token cookie, missing expire time'
);
expect(setTokenCookieMock).not.toHaveBeenCalled();
expect(setTokenCookieMocks['kbase_session']).not.toHaveBeenCalled();
});
});

test('useTokenCookie clears cookie for bad cookie token and empty auth state', async () => {
const auth = { initialized: false };
mockCookieVal = 'AAAAAA';
mockCookieVals['kbase_session'] = 'AAAAAA';
const mock = jest.spyOn(authFromToken, 'useQuery');
mock.mockImplementation(() => {
return {
Expand All @@ -264,8 +383,8 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(setTokenCookieMock).not.toBeCalled();
expect(clearTokenCookieMock).toBeCalled();
expect(setTokenCookieMocks['kbase_session']).not.toBeCalled();
expect(clearTokenCookieMocks['kbase_session']).toBeCalled();
});
mock.mockClear();
});
Expand All @@ -279,7 +398,7 @@ describe('authSlice', () => {
} as TokenInfo,
initialized: true,
};
mockCookieVal = 'AAAAAA';
mockCookieVals['kbase_session'] = 'AAAAAA';
const mock = jest.spyOn(authFromToken, 'useQuery');
mock.mockImplementation(() => {
return {
Expand All @@ -298,15 +417,15 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(setTokenCookieMock).toBeCalled();
expect(clearTokenCookieMock).not.toBeCalled();
expect(setTokenCookieMocks['kbase_session']).toBeCalled();
expect(clearTokenCookieMocks['kbase_session']).not.toBeCalled();
});
mock.mockClear();
});

test('useTokenCookie does not set cookie while awaiting auth response', async () => {
const auth = { initialized: false };
mockCookieVal = 'AAAAAA';
mockCookieVals['kbase_session'] = 'AAAAAA';
const mock = jest.spyOn(authFromToken, 'useQuery');
mock.mockImplementation(() => {
return {
Expand All @@ -325,8 +444,8 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(setTokenCookieMock).not.toBeCalled();
expect(clearTokenCookieMock).not.toBeCalled();
expect(setTokenCookieMocks['kbase_session']).not.toBeCalled();
expect(clearTokenCookieMocks['kbase_session']).not.toBeCalled();
});
mock.mockImplementation(() => {
return {
Expand All @@ -342,10 +461,10 @@ describe('authSlice', () => {
</Provider>
);
await waitFor(() => {
expect(setTokenCookieMock).toBeCalledWith('AAAAAA', {
expect(setTokenCookieMocks['kbase_session']).toBeCalledWith('AAAAAA', {
expires: new Date(10),
});
expect(clearTokenCookieMock).not.toBeCalled();
expect(clearTokenCookieMocks['kbase_session']).not.toBeCalled();
});
mock.mockClear();
});
Expand Down
7 changes: 7 additions & 0 deletions src/features/auth/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export const useTokenCookie = (
useEffect(() => {
if (
Boolean(backupCookieName) &&
Boolean(backupCookieDomain) &&
initialized &&
currentToken &&
backupCookieToken !== currentToken
Expand All @@ -137,6 +138,12 @@ export const useTokenCookie = (
expires: new Date(currentExpires),
});
}
} else if (
(Boolean(backupCookieName) || Boolean(backupCookieDomain)) &&
(!backupCookieDomain || !backupCookieName)
) {
// eslint-disable-next-line no-console
console.error('Backup cookie cannot be set due to bad configuration.');
}
}, [
backupCookieDomain,
Expand Down

0 comments on commit e0cf6a3

Please sign in to comment.