mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: keep composers mounted while the app lock saves them, and tell the service worker nothing with the lock off
This commit is contained in:
@@ -122,28 +122,37 @@ describe('PopoutHost', () => {
|
||||
expect(JSON.stringify(heard)).not.toContain('Bearer main');
|
||||
});
|
||||
|
||||
it('gives no credentials while the app is locked, also to a pop-out that never heard of the lock', async () => {
|
||||
it('gives no credentials while the app is locked, to a pop-out that never heard of the lock', async () => {
|
||||
render(<PopoutHost />);
|
||||
await settle();
|
||||
setLockedHere(true);
|
||||
const channel = openPopoutChannel(() => {});
|
||||
const opener = await createSessionOpener();
|
||||
channel?.post({ type: 'session-request', requestId: 'r1', publicKey: opener.publicKey });
|
||||
popout.post({ type: 'session-request', requestId: 'r1', publicKey: opener.publicKey });
|
||||
await settle();
|
||||
channel?.close();
|
||||
expect(answers()).toEqual([]);
|
||||
await expect(requestWindowSession({}, 100)).resolves.toBeNull();
|
||||
setLockedHere(false);
|
||||
await expect(requestWindowSession({}, 2000)).resolves.toMatchObject({ activeAccountId: 'alice' });
|
||||
popout.post({ type: 'session-request', requestId: 'r2', publicKey: opener.publicKey });
|
||||
await vi.waitFor(() => expect(answers()).toHaveLength(1));
|
||||
});
|
||||
|
||||
it('a locked pop-out takes no session from the cookies either', async () => {
|
||||
it('a locked pop-out doesn\'t ask for a session', async () => {
|
||||
setLockedHere(true);
|
||||
await expect(requestWindowSession({}, 100)).resolves.toBeNull();
|
||||
setLockedHere(false);
|
||||
expect(heard.filter((message) => message.type === 'session-request')).toEqual([]);
|
||||
});
|
||||
|
||||
it('a locked pop-out takes no session from the cookies either, and asks nobody', async () => {
|
||||
const fetched = vi.fn(async () => new Response('{}'));
|
||||
vi.stubGlobal('fetch', fetched);
|
||||
setLockedHere(true);
|
||||
try {
|
||||
await expect(useAuthStore.getState().startSharedSession({})).resolves.toBe('no-session');
|
||||
await expect(useAuthStore.getState().startSharedSession({ accountId: 'alice' })).resolves.toBe('no-session');
|
||||
} finally {
|
||||
setLockedHere(false);
|
||||
}
|
||||
expect(fetched).not.toHaveBeenCalled();
|
||||
expect(useAuthStore.getState().client).not.toBeNull();
|
||||
});
|
||||
|
||||
it('says nothing while it has no session to lend, so a pop-out goes on to the cookies', async () => {
|
||||
|
||||
@@ -197,6 +197,14 @@ describe('while locked', () => {
|
||||
expect(isAppLockedNow()).toBe(true);
|
||||
});
|
||||
|
||||
it('a running page isn\'t held back while it saves: composers stay mounted until the reload', async () => {
|
||||
await enableAppLock(PIN);
|
||||
let holdDuringSave: Promise<void> | null | undefined;
|
||||
registerComposeFlush(async () => { holdDuringSave = appLockHold(); });
|
||||
await lockedAndReloaded();
|
||||
expect(holdDuringSave).toBeNull();
|
||||
});
|
||||
|
||||
it('notifications go by it: the check the OS code asks says locked', async () => {
|
||||
await enableAppLock(PIN);
|
||||
asNewDocument();
|
||||
@@ -286,15 +294,23 @@ describe('locking', () => {
|
||||
});
|
||||
|
||||
describe('the store when a tab inherits it without the key', () => {
|
||||
it('locks everywhere instead of opening or writing an unwrapped key', async () => {
|
||||
it('asks for the lock instead of opening, or writing a key outside the layer', async () => {
|
||||
await enableAppLock(PIN);
|
||||
// This tab has no key: the one that had it went.
|
||||
resetAppLockForTests();
|
||||
(window.navigator.locks.query as ReturnType<typeof vi.fn>).mockResolvedValue({ held: [{ name: 'bulwark-app-lock-holder' }], pending: [] });
|
||||
(window.navigator.locks.request as ReturnType<typeof vi.fn>).mockImplementation(() => new Promise(() => {}));
|
||||
bootAppLock();
|
||||
await vi.waitFor(() => expect(getAppLockPhase()).toBe('locked'));
|
||||
const layers = () => appLockKeyLayers();
|
||||
expect(layers).toThrow(expect.objectContaining({ name: 'StoreKeyLayerMissingError' }));
|
||||
// A new account can't get a key record outside the layer either.
|
||||
await vi.waitFor(() => expect(getAppLockPhase()).toBe('open'));
|
||||
const onKeyLocked = vi.fn();
|
||||
configureStore({ ...hooks, onKeyLocked });
|
||||
await expect(getStore().ensureOpen('new-account', { apiUrl: 'x' } as never).then(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
await getStore().ensureOpen('new-account');
|
||||
})).rejects.toMatchObject({ name: 'StoreKeyLayerMissingError' });
|
||||
expect(onKeyLocked).toHaveBeenCalled();
|
||||
expect(await getAccountRecord('new-account')).toBeNull();
|
||||
expect(lastWorker().posted.some((m) => m.kind === 'open')).toBe(false);
|
||||
});
|
||||
|
||||
it('a new account after unlock gets its key under the lock', async () => {
|
||||
|
||||
@@ -366,7 +366,6 @@ async function applyLockHere(): Promise<void> {
|
||||
const wasRunning = phase === 'open';
|
||||
setLockedHere(true);
|
||||
markDocument(true);
|
||||
hold ??= newHold();
|
||||
setPhase('locking');
|
||||
tellDesktopLocked();
|
||||
tellServiceWorker('locked');
|
||||
@@ -699,7 +698,7 @@ export function startAppLock(): () => void {
|
||||
startIdle();
|
||||
if (!heldKey) watchHolder();
|
||||
}
|
||||
tellServiceWorker(readAppLockFile().on ? (isAppLockedNow() ? 'locked' : 'unlocked') : 'off');
|
||||
if (readAppLockFile().on) tellServiceWorker(isAppLockedNow() ? 'locked' : 'unlocked');
|
||||
return () => {
|
||||
channel?.close();
|
||||
window.removeEventListener('storage', onStorage);
|
||||
|
||||
Reference in New Issue
Block a user