Use the server's renamed registry capability, urn:inbuxa:jmap:registry #14
@@ -47,7 +47,7 @@ after(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stalwart advertises `urn:stalwart:jmap` only per-account, never in the
|
* Stalwart advertises `urn:inbuxa:jmap:registry` only per-account, never in the
|
||||||
* session-level capabilities. Looking for it at the top level alone reported
|
* session-level capabilities. Looking for it at the top level alone reported
|
||||||
* every real 0.16 server as older than 0.16 — and now that the same check
|
* every real 0.16 server as older than 0.16 — and now that the same check
|
||||||
* decides whether a sign-in is allowed at all, that mistake would lock
|
* decides whether a sign-in is allowed at all, that mistake would lock
|
||||||
@@ -57,8 +57,8 @@ test("the session is accepted on a server that advertises the registry per-accou
|
|||||||
const res = await call("/api/auth/session");
|
const res = await call("/api/auth/session");
|
||||||
assert.equal(res.status, 200);
|
assert.equal(res.status, 200);
|
||||||
assert.equal(res.body.ihasmail.server.edition, "oss");
|
assert.equal(res.body.ihasmail.server.edition, "oss");
|
||||||
assert.equal(res.body.capabilities["urn:stalwart:jmap"], undefined, "not where a client would first look");
|
assert.equal(res.body.capabilities["urn:inbuxa:jmap:registry"], undefined, "not where a client would first look");
|
||||||
assert.ok("urn:stalwart:jmap" in res.body.primaryAccounts, "but here, as on a real server");
|
assert.ok("urn:inbuxa:jmap:registry" in res.body.primaryAccounts, "but here, as on a real server");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the registry reports an account with nothing set up yet", async () => {
|
test("the registry reports an account with nothing set up yet", async () => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { generateSecret, otpauthUrl, parseOtpauthUrl, verifyTotp } from "./totp.
|
|||||||
* the registry is known to be there.
|
* the registry is known to be there.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const STALWART_CAP = "urn:stalwart:jmap";
|
const STALWART_CAP = "urn:inbuxa:jmap:registry";
|
||||||
const JMAP_CORE = "urn:ietf:params:jmap:core";
|
const JMAP_CORE = "urn:ietf:params:jmap:core";
|
||||||
/** Stalwart's id for a singleton object; the number it encodes spells this. */
|
/** Stalwart's id for a singleton object; the number it encodes spells this. */
|
||||||
const SINGLETON = "singleton";
|
const SINGLETON = "singleton";
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ test("locales that carry no language are dropped, not passed through", () => {
|
|||||||
|
|
||||||
test("a server without the registry is not asked for anything", async () => {
|
test("a server without the registry is not asked for anything", async () => {
|
||||||
// Sign-in refuses these, so getAccountInfo should never reach the wire for
|
// Sign-in refuses these, so getAccountInfo should never reach the wire for
|
||||||
// one - and must not, since a server that cannot parse `urn:stalwart:jmap`
|
// one - and must not, since a server that cannot parse `urn:inbuxa:jmap:registry`
|
||||||
// fails the whole request rather than the one call.
|
// fails the whole request rather than the one call.
|
||||||
const session = { capabilities: { "urn:ietf:params:jmap:core": {}, "urn:ietf:params:jmap:mail": {} }, accounts: {}, primaryAccounts: {} };
|
const session = { capabilities: { "urn:ietf:params:jmap:core": {}, "urn:ietf:params:jmap:mail": {} }, accounts: {}, primaryAccounts: {} };
|
||||||
const info = await getAccountInfo("session-unsupported", "Basic x", session as never);
|
const info = await getAccountInfo("session-unsupported", "Basic x", session as never);
|
||||||
@@ -57,7 +57,7 @@ test("no capabilities at all is treated the same way", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where Stalwart actually advertises `urn:stalwart:jmap`.
|
* Where Stalwart actually advertises `urn:inbuxa:jmap:registry`.
|
||||||
*
|
*
|
||||||
* Not in the session-level `capabilities`: `Session::new` builds those from a
|
* Not in the session-level `capabilities`: `Session::new` builds those from a
|
||||||
* fixed list that has never carried this capability, in any 0.16.x. It is
|
* fixed list that has never carried this capability, in any 0.16.x. It is
|
||||||
@@ -70,7 +70,7 @@ test("no capabilities at all is treated the same way", async () => {
|
|||||||
* This check now decides whether a sign-in is allowed at all, so getting it
|
* This check now decides whether a sign-in is allowed at all, so getting it
|
||||||
* wrong would lock every user out of a perfectly good server.
|
* wrong would lock every user out of a perfectly good server.
|
||||||
*/
|
*/
|
||||||
const STALWART = "urn:stalwart:jmap";
|
const STALWART = "urn:inbuxa:jmap:registry";
|
||||||
const baseCaps = { "urn:ietf:params:jmap:core": {}, "urn:ietf:params:jmap:mail": {} };
|
const baseCaps = { "urn:ietf:params:jmap:core": {}, "urn:ietf:params:jmap:mail": {} };
|
||||||
|
|
||||||
test("a 0.16 server is recognized from primaryAccounts, where it advertises itself", () => {
|
test("a 0.16 server is recognized from primaryAccounts, where it advertises itself", () => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const PORT = 18799;
|
|||||||
process.env.MOCK_PORT = String(PORT);
|
process.env.MOCK_PORT = String(PORT);
|
||||||
process.env.MOCK_USER = "[email protected]";
|
process.env.MOCK_USER = "[email protected]";
|
||||||
process.env.MOCK_PASS = "demo-password";
|
process.env.MOCK_PASS = "demo-password";
|
||||||
process.env.MOCK_NO_REGISTRY = "1"; // a server without urn:stalwart:jmap
|
process.env.MOCK_NO_REGISTRY = "1"; // a server without urn:inbuxa:jmap:registry
|
||||||
process.env.MAIL_SERVER_URL = `http://127.0.0.1:${PORT}`;
|
process.env.MAIL_SERVER_URL = `http://127.0.0.1:${PORT}`;
|
||||||
process.env.APP_SECRET = "test-secret-for-login-guard";
|
process.env.APP_SECRET = "test-secret-for-login-guard";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const PERMISSION_SNAPSHOT = (JSON.parse(readFileSync(new URL("../../../we
|
|||||||
|
|
||||||
export const PORT = Number(process.env.MOCK_PORT ?? 8788);
|
export const PORT = Number(process.env.MOCK_PORT ?? 8788);
|
||||||
/**
|
/**
|
||||||
* Omit `urn:stalwart:jmap` from the session, so a sign-in can be tested
|
* Omit `urn:inbuxa:jmap:registry` from the session, so a sign-in can be tested
|
||||||
* against a server ihasmail does not support. This is only that: the rest of
|
* against a server ihasmail does not support. This is only that: the rest of
|
||||||
* the mock still behaves like 0.16. Emulating 0.15 properly went with the
|
* the mock still behaves like 0.16. Emulating 0.15 properly went with the
|
||||||
* support for it.
|
* support for it.
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ const session = () => ({
|
|||||||
* the only way this stays honest about what can be inferred from a
|
* the only way this stays honest about what can be inferred from a
|
||||||
* capability, which is nothing.
|
* capability, which is nothing.
|
||||||
*/
|
*/
|
||||||
accounts: { [SHARED_ACCOUNT]: { name: "[email protected]", isPersonal: false, isReadOnly: false, accountCapabilities: SHARED_CAPS }, [ACCOUNT]: { name: USER, isPersonal: true, isReadOnly: false, accountCapabilities: { "urn:ietf:params:jmap:mail": {}, "urn:ietf:params:jmap:submission": { maxDelayedSend: MAX_DELAYED_SEND, submissionExtensions: { FUTURERELEASE: [], SIZE: [], DSN: [], DELIVERYBY: [], "MT-PRIORITY": ["MIXER"], REQUIRETLS: [] } }, "urn:ietf:params:jmap:vacationresponse": {}, "urn:ietf:params:jmap:sieve": {}, "urn:ietf:params:jmap:calendars": {}, "urn:ietf:params:jmap:contacts": {}, "urn:ietf:params:jmap:principals": {}, "urn:ietf:params:jmap:quota": {}, "urn:ietf:params:jmap:filenode": {}, ...(NO_REGISTRY ? {} : { "urn:stalwart:jmap": {} }) } } },
|
accounts: { [SHARED_ACCOUNT]: { name: "[email protected]", isPersonal: false, isReadOnly: false, accountCapabilities: SHARED_CAPS }, [ACCOUNT]: { name: USER, isPersonal: true, isReadOnly: false, accountCapabilities: { "urn:ietf:params:jmap:mail": {}, "urn:ietf:params:jmap:submission": { maxDelayedSend: MAX_DELAYED_SEND, submissionExtensions: { FUTURERELEASE: [], SIZE: [], DSN: [], DELIVERYBY: [], "MT-PRIORITY": ["MIXER"], REQUIRETLS: [] } }, "urn:ietf:params:jmap:vacationresponse": {}, "urn:ietf:params:jmap:sieve": {}, "urn:ietf:params:jmap:calendars": {}, "urn:ietf:params:jmap:contacts": {}, "urn:ietf:params:jmap:principals": {}, "urn:ietf:params:jmap:quota": {}, "urn:ietf:params:jmap:filenode": {}, ...(NO_REGISTRY ? {} : { "urn:inbuxa:jmap:registry": {} }) } } },
|
||||||
primaryAccounts: { ...Object.fromEntries(["mail", "submission", "vacationresponse", "sieve", "calendars", "contacts", "principals", "quota", "filenode", "blob"].map((c) => [`urn:ietf:params:jmap:${c}`, ACCOUNT])), ...(NO_REGISTRY ? {} : { "urn:stalwart:jmap": ACCOUNT }) },
|
primaryAccounts: { ...Object.fromEntries(["mail", "submission", "vacationresponse", "sieve", "calendars", "contacts", "principals", "quota", "filenode", "blob"].map((c) => [`urn:ietf:params:jmap:${c}`, ACCOUNT])), ...(NO_REGISTRY ? {} : { "urn:inbuxa:jmap:registry": ACCOUNT }) },
|
||||||
username: USER,
|
username: USER,
|
||||||
apiUrl: `http://127.0.0.1:${PORT}/jmap/`,
|
apiUrl: `http://127.0.0.1:${PORT}/jmap/`,
|
||||||
downloadUrl: `http://127.0.0.1:${PORT}/jmap/download/{accountId}/{blobId}/{name}?accept={type}`,
|
downloadUrl: `http://127.0.0.1:${PORT}/jmap/download/{accountId}/{blobId}/{name}?accept={type}`,
|
||||||
@@ -103,7 +103,7 @@ export const server = createServer(async (req, res) => {
|
|||||||
// call that wanted it - which is why an over-eager `using` is so damaging.
|
// call that wanted it - which is why an over-eager `using` is so damaging.
|
||||||
// Stalwart decides this by parsing the urn, not by looking it up in the
|
// Stalwart decides this by parsing the urn, not by looking it up in the
|
||||||
// session, so a capability it hands out per-account is still usable here:
|
// session, so a capability it hands out per-account is still usable here:
|
||||||
// `urn:stalwart:jmap` never appears in the session-level capabilities and
|
// `urn:inbuxa:jmap:registry` never appears in the session-level capabilities and
|
||||||
// the registry calls that name it work all the same.
|
// the registry calls that name it work all the same.
|
||||||
const known = new Set([...Object.keys(session().capabilities), ...Object.keys(session().accounts[ACCOUNT]?.accountCapabilities ?? {})]);
|
const known = new Set([...Object.keys(session().capabilities), ...Object.keys(session().accounts[ACCOUNT]?.accountCapabilities ?? {})]);
|
||||||
const unknown = (body.using ?? []).find((u) => !known.has(u));
|
const unknown = (body.using ?? []).find((u) => !known.has(u));
|
||||||
|
|||||||
@@ -178,14 +178,14 @@ export function forgetUpstreamSession(sessionId: string): void {
|
|||||||
/* Account locale */
|
/* Account locale */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
const STALWART_CAP = "urn:stalwart:jmap";
|
const STALWART_CAP = "urn:inbuxa:jmap:registry";
|
||||||
const JMAP_CORE = "urn:ietf:params:jmap:core";
|
const JMAP_CORE = "urn:ietf:params:jmap:core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this server has Stalwart's JMAP registry — the `x:` objects that
|
* Whether this server has Stalwart's JMAP registry — the `x:` objects that
|
||||||
* carry credentials, account settings and the newer FileNode shape.
|
* carry credentials, account settings and the newer FileNode shape.
|
||||||
*
|
*
|
||||||
* `urn:stalwart:jmap` is the marker, but **not** in the session-level
|
* `urn:inbuxa:jmap:registry` is the marker, but **not** in the session-level
|
||||||
* `capabilities`, which is where a JMAP client would naturally look. Stalwart
|
* `capabilities`, which is where a JMAP client would naturally look. Stalwart
|
||||||
* builds that list from a fixed set that has never included this capability;
|
* builds that list from a fixed set that has never included this capability;
|
||||||
* it hands it out per-account instead, so it turns up in `primaryAccounts` and
|
* it hands it out per-account instead, so it turns up in `primaryAccounts` and
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const CAP = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Stalwart's own capability, which carries its `x:` registry methods. */
|
/** Stalwart's own capability, which carries its `x:` registry methods. */
|
||||||
export const STALWART_CAP = "urn:stalwart:jmap";
|
export const STALWART_CAP = "urn:inbuxa:jmap:registry";
|
||||||
|
|
||||||
/** INBUXA's own capability (contract C-1), on the signed-in account. */
|
/** INBUXA's own capability (contract C-1), on the signed-in account. */
|
||||||
export const INBUXA_CAP = "urn:inbuxa:jmap";
|
export const INBUXA_CAP = "urn:inbuxa:jmap";
|
||||||
@@ -155,7 +155,7 @@ export class JmapClient {
|
|||||||
* Whether the server carries a capability at all, wherever it chose to
|
* Whether the server carries a capability at all, wherever it chose to
|
||||||
* advertise it.
|
* advertise it.
|
||||||
*
|
*
|
||||||
* Stalwart hands `urn:stalwart:jmap` out per-account rather than putting it
|
* Stalwart hands `urn:inbuxa:jmap:registry` out per-account rather than putting it
|
||||||
* in the session-level `capabilities`, so `hasCapability` alone reports every
|
* in the session-level `capabilities`, so `hasCapability` alone reports every
|
||||||
* real 0.16 server as though it were older. Look in all three places.
|
* real 0.16 server as though it were older. Look in all three places.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const HELPDESK = ["sysAccountGet", "sysAccountQuery", "sysAccountUpdate"];
|
|||||||
|
|
||||||
function signIn(permissions: string[], username = "[email protected]") {
|
function signIn(permissions: string[], username = "[email protected]") {
|
||||||
useSession.setState({
|
useSession.setState({
|
||||||
session: { capabilities: {}, accounts: {}, primaryAccounts: { "urn:stalwart:jmap": "self" }, username, ihasmail: { permissions } } as unknown as JmapSession,
|
session: { capabilities: {}, accounts: {}, primaryAccounts: { "urn:inbuxa:jmap:registry": "self" }, username, ihasmail: { permissions } } as unknown as JmapSession,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user