mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
Stalwart refuses a push subscription's whole emailPush map as `forbidden` when it names a mailbox shared by ACL (0.16.16 and later, and 1.0). Bulwark put every account with mailboxes in the map, so enabling push showed raw JSON, and the background re-sync destroyed the working subscription after its refresh was refused and dropped its key: push stopped without a word and could not be turned back on while the share existed. A network error, a timeout or rate limiting during the refresh ended it the same way. - The map holds the primary account and the accounts that list `urn:ietf:params:jmap:emailpush`, which 1.0 leaves off ACL shares. 0.16 lists it everywhere, so a refused map narrows to the accounts that look owned, then to the primary account, as the mobile app does. Refused accounts are remembered under `bulwark.push.emailPushRefused.v1.<id>`; Re-register and disable clear the list. - A subscription ends only when a verified replacement exists, the user turns push off, or the server no longer has it. The old record is destroyed after the new one is verified. A failed listing, a network error or a refused method call leave it and its key alone. - PushSubscription/set failures throw typed errors (`jmapSetError` for a refused object, `jmapError` for a refused call) instead of JSON in the message or `false`, and the client exposes `getAccountInfo`. - Settings says that mailboxes others share don't send background notifications, and `forbidden` and `overQuota` read as plain text in settings and the onboarding prompt.
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { isJmapSetError, jmapError, jmapErrorProperties, jmapErrorType, jmapSetError } from '../jmap/jmap-error';
|
|
|
|
describe('jmapError', () => {
|
|
it('keeps the SetError type and properties on the thrown error', () => {
|
|
const error = jmapError('Could not save the contact', {
|
|
type: 'invalidProperties',
|
|
description: 'blobIds in media is not supported.',
|
|
properties: ['media'],
|
|
});
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error.message).toBe('Could not save the contact');
|
|
expect(jmapErrorType(error)).toBe('invalidProperties');
|
|
expect(jmapErrorProperties(error)).toEqual(['media']);
|
|
});
|
|
|
|
it('copies the properties so later edits to the response do not leak in', () => {
|
|
const properties = ['name'];
|
|
const error = jmapError('rejected', { type: 'invalidProperties', properties });
|
|
properties.push('parentId');
|
|
|
|
expect(jmapErrorProperties(error)).toEqual(['name']);
|
|
});
|
|
|
|
it('works without error details', () => {
|
|
const error = jmapError('failed', null);
|
|
|
|
expect(jmapErrorType(error)).toBeUndefined();
|
|
expect(jmapErrorProperties(error)).toEqual([]);
|
|
});
|
|
|
|
it('reads nothing from values that are not JMAP errors', () => {
|
|
expect(jmapErrorType(new Error('plain'))).toBeUndefined();
|
|
expect(jmapErrorType(undefined)).toBeUndefined();
|
|
expect(jmapErrorType('overQuota')).toBeUndefined();
|
|
expect(jmapErrorProperties(null)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('jmapSetError', () => {
|
|
it('tells a refused object apart from a refused method call of the same type', () => {
|
|
const setError = jmapSetError('PushSubscription/set create failed', {
|
|
type: 'forbidden',
|
|
description: 'No access to one of the accounts in the emailPush map.',
|
|
});
|
|
const methodError = jmapError('PushSubscription/set failed', { type: 'forbidden' });
|
|
|
|
expect(jmapErrorType(setError)).toBe('forbidden');
|
|
expect(isJmapSetError(setError)).toBe(true);
|
|
expect(jmapErrorType(methodError)).toBe('forbidden');
|
|
expect(isJmapSetError(methodError)).toBe(false);
|
|
});
|
|
|
|
it('is false for anything that is not a SetError', () => {
|
|
expect(isJmapSetError(new Error('Failed to fetch'))).toBe(false);
|
|
expect(isJmapSetError(undefined)).toBe(false);
|
|
expect(isJmapSetError({ jmapSetError: 'yes' })).toBe(false);
|
|
});
|
|
});
|