Say which legacy protocols are off when only some are
inbuxa can now switch IMAP, POP3 and ManageSieve off one at a time, server-wide and per organization, and the session lists what is still allowed for the account (legacyAllowed). Where the webmail said "legacy protocols are off", it now also covers the case where only some are: - Security & sessions, above app passwords: "Your organization has turned off POP3 for mail apps. Mail apps that use it can't connect to this account; others still can." - The Administration dashboard: "Some legacy mail protocols are off for your organization: POP3." - An organization's sheet in Administration: its switch stays the all-or-nothing one; when only some are off it names them and points to the console, where they're switched one at a time, and "Turn legacy protocols back on" turns them all back on. With every protocol off, the existing wording shows, as before. From a server that doesn't send legacyAllowed nothing new appears. 3 new strings in all nine catalogs, unreviewed. Tested: unit tests for reading the session and a tenant's switches; the existing tests updated for the new field; typecheck; the whole suite (1475 tests); and in headless Chrome against a local server with POP3 off, where Security & sessions showed the new note.
This commit is contained in:
1 parent
ac0f8789f6
commit
44f8e30c45
17 files changed
+135
-10
No files matched your search
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { INBUXA_CAP, legacyProtocolsPartlyOff } from "../client";
|
||||
import { parseTenantLegacy } from "@/lib/admin/adminLegacyProtocols";
|
||||
import type { JmapSession } from "../types";
|
||||
|
||||
/**
|
||||
* INBUXA can switch IMAP, POP3 and ManageSieve off one at a time. The session
|
||||
* lists what is still allowed (`legacyAllowed`); the webmail names what isn't,
|
||||
* only while some but not all are off, and never from a server that doesn't
|
||||
* say.
|
||||
*/
|
||||
describe("legacyProtocolsPartlyOff", () => {
|
||||
const session = (cap: Record<string, unknown> | undefined) =>
|
||||
({
|
||||
accounts: { a: { name: "[email protected]", accountCapabilities: cap ? { [INBUXA_CAP]: cap } : {} } },
|
||||
}) as unknown as JmapSession;
|
||||
|
||||
it("names what is off when only some are", () => {
|
||||
const s = session({ legacyProtocols: "enabled", legacyAllowed: ["imap", "manageSieve", "submission"] });
|
||||
expect(legacyProtocolsPartlyOff(s, "a")).toEqual(["POP3"]);
|
||||
const two = session({ legacyProtocols: "enabled", legacyAllowed: ["pop3", "submission"] });
|
||||
expect(legacyProtocolsPartlyOff(two, "a")).toEqual(["IMAP", "ManageSieve"]);
|
||||
});
|
||||
|
||||
it("is empty with none off, all off, or an older server", () => {
|
||||
const all = ["imap", "pop3", "manageSieve", "submission"];
|
||||
expect(legacyProtocolsPartlyOff(session({ legacyProtocols: "enabled", legacyAllowed: all }), "a")).toEqual([]);
|
||||
expect(legacyProtocolsPartlyOff(session({ legacyProtocols: "disabled", legacyAllowed: [] }), "a")).toEqual([]);
|
||||
expect(legacyProtocolsPartlyOff(session({ legacyProtocols: "enabled" }), "a")).toEqual([]);
|
||||
expect(legacyProtocolsPartlyOff(null, "a")).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseTenantLegacy", () => {
|
||||
it("reads a tenant with only some off, and an older server's one switch", () => {
|
||||
expect(parseTenantLegacy({ legacyProtocols: "enabled", pop3: "disabled" }).partlyOff).toEqual(["POP3"]);
|
||||
const all = parseTenantLegacy({ legacyProtocols: "disabled", imap: "disabled", pop3: "disabled" });
|
||||
expect(all.off).toBe(true);
|
||||
expect(all.partlyOff).toEqual([]);
|
||||
expect(parseTenantLegacy({ legacyProtocols: "enabled" }).partlyOff).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,24 @@ export function legacyProtocolsOff(session: JmapSession | null, accountId: Id |
|
||||
return cap?.legacyProtocols === "disabled";
|
||||
}
|
||||
|
||||
/** The protocols the server can switch off one at a time, as it names them. */
|
||||
const SWITCHED = ["imap", "pop3", "manageSieve"] as const;
|
||||
const PROTOCOL_NAMES: Record<string, string> = { imap: "IMAP", pop3: "POP3", manageSieve: "ManageSieve" };
|
||||
|
||||
/**
|
||||
* Which of IMAP, POP3 and ManageSieve are off for this account, by name, when
|
||||
* only some are (INBUXA legacy-protocols, one switch per protocol). Empty when
|
||||
* none are, when all are (see `legacyProtocolsOff`), and from a server that
|
||||
* doesn't say which (`legacyAllowed`).
|
||||
*/
|
||||
export function legacyProtocolsPartlyOff(session: JmapSession | null, accountId: Id | null): string[] {
|
||||
if (!session || !accountId || legacyProtocolsOff(session, accountId)) return [];
|
||||
const cap = session.accounts[accountId]?.accountCapabilities?.[INBUXA_CAP] as { legacyAllowed?: unknown } | undefined;
|
||||
if (!Array.isArray(cap?.legacyAllowed)) return [];
|
||||
const allowed = cap.legacyAllowed;
|
||||
return SWITCHED.filter((p) => !allowed.includes(p)).map((p) => PROTOCOL_NAMES[p] ?? p);
|
||||
}
|
||||
|
||||
export class JmapMethodError extends Error {
|
||||
constructor(
|
||||
public readonly method: string,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { CONFIRM_PHRASE, impactEntries, parseTenantLegacy, phraseMatches } from
|
||||
|
||||
describe("a tenant's legacy mail protocols switch, as the server sends it", () => {
|
||||
it("reads the switch, and tells an older server from nobody", () => {
|
||||
expect(parseTenantLegacy({ legacyProtocols: "disabled", recentLegacyUse: [] })).toEqual({ off: true, recent: [] });
|
||||
expect(parseTenantLegacy({ legacyProtocols: "enabled" })).toEqual({ off: false, recent: null });
|
||||
expect(parseTenantLegacy({ legacyProtocols: "disabled", recentLegacyUse: [] })).toEqual({ off: true, partlyOff: [], recent: [] });
|
||||
expect(parseTenantLegacy({ legacyProtocols: "enabled" })).toEqual({ off: false, partlyOff: [], recent: null });
|
||||
});
|
||||
|
||||
it("puts each account on the panel once, with every protocol and its latest use", () => {
|
||||
|
||||
@@ -34,7 +34,10 @@ export interface RecentUse {
|
||||
}
|
||||
|
||||
export interface TenantLegacy {
|
||||
/** All of IMAP, POP3 and ManageSieve off: the kill-all's state. */
|
||||
off: boolean;
|
||||
/** When only some are off, which, by name; set one at a time in the console. */
|
||||
partlyOff: string[];
|
||||
/** Null from a server too old to say who uses legacy apps -- not the same as nobody. */
|
||||
recent: RecentUse[] | null;
|
||||
}
|
||||
@@ -49,8 +52,13 @@ function parseRecent(raw: unknown): RecentUse[] | null {
|
||||
});
|
||||
}
|
||||
|
||||
const PROTOCOL_NAMES: [string, string][] = [["imap", "IMAP"], ["pop3", "POP3"], ["manageSieve", "ManageSieve"]];
|
||||
|
||||
export function parseTenantLegacy(raw: Record<string, unknown>): TenantLegacy {
|
||||
return { off: raw.legacyProtocols === "disabled", recent: parseRecent(raw.recentLegacyUse) };
|
||||
const off = raw.legacyProtocols === "disabled";
|
||||
// An older server sends only legacyProtocols, which stands for all three.
|
||||
const partlyOff = off ? [] : PROTOCOL_NAMES.filter(([key]) => raw[key] === "disabled").map(([, name]) => name);
|
||||
return { off, partlyOff, recent: parseRecent(raw.recentLegacyUse) };
|
||||
}
|
||||
|
||||
/** The tenant's switch, or null where the server has none (not INBUXA, or too old). */
|
||||
|
||||
@@ -1754,6 +1754,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Einschätzung des Sprachmodells",
|
||||
"One of several signals the spam filter weighed": "Eines von mehreren Signalen, die der Spamfilter berücksichtigt hat",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Ihre Organisation hat {protocols} für Mail-Apps ausgeschaltet. Mail-Apps, die das nutzen, können sich nicht mit diesem Konto verbinden; andere schon.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Einige ältere Mailprotokolle sind für Ihre Organisation ausgeschaltet: {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Einige sind für {tenant} aus: {protocols}. In der Verwaltungskonsole lassen sie sich einzeln ein- und ausschalten.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Löschen, Sperren und rechtliche Aufbewahrungspflichten werden in der Verwaltungskonsole erledigt, die den Grund festhält und bewahrt, was eine Aufbewahrungspflicht umfasst.",
|
||||
"Open in the console": "In der Konsole öffnen",
|
||||
|
||||
@@ -1727,6 +1727,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Opinión del modelo de lenguaje",
|
||||
"One of several signals the spam filter weighed": "Una de varias señales que el filtro de spam ha tenido en cuenta",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Su organización ha desactivado {protocols} para las aplicaciones de correo. Las que lo usan no pueden conectarse a esta cuenta; las demás sí.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Algunos protocolos de correo heredados están desactivados para su organización: {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Algunos están desactivados para {tenant}: {protocols}. Actívelos o desactívelos uno a uno en la consola de administración.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Eliminar, bloquear y las retenciones legales se gestionan en la consola de administración, que registra el motivo y conserva lo que cubre una retención.",
|
||||
"Open in the console": "Abrir en la consola",
|
||||
|
||||
@@ -1732,6 +1732,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Avis du modèle de langage",
|
||||
"One of several signals the spam filter weighed": "Un signal parmi d'autres pris en compte par le filtre antispam",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Votre organisation a désactivé {protocols} pour les applications de messagerie. Celles qui l’utilisent ne peuvent pas se connecter à ce compte ; les autres le peuvent toujours.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Certains protocoles de messagerie historiques sont désactivés pour votre organisation : {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Certains sont désactivés pour {tenant} : {protocols}. Activez-les ou désactivez-les un par un dans la console d’administration.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "La suppression, le verrouillage et les conservations légales se font dans la console d’administration, qui enregistre le motif et conserve ce qu’une conservation couvre.",
|
||||
"Open in the console": "Ouvrir dans la console",
|
||||
|
||||
@@ -1735,6 +1735,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "言語モデルの見解",
|
||||
"One of several signals the spam filter weighed": "迷惑メールフィルターが考慮した複数の判断材料のひとつ",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "組織ではメールアプリ向けの {protocols} がオフになっています。これを使うメールアプリはこのアカウントに接続できませんが、ほかのアプリは接続できます。",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "組織では一部の従来のメールプロトコルがオフになっています: {protocols}。",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "{tenant} では一部がオフです: {protocols}。管理コンソールで 1 つずつ切り替えてください。",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "削除、ロック、訴訟ホールドは管理コンソールで行います。コンソールでは理由が記録され、ホールドの対象は保持されます。",
|
||||
"Open in the console": "コンソールで開く",
|
||||
|
||||
@@ -1727,6 +1727,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Oordeel van het taalmodel",
|
||||
"One of several signals the spam filter weighed": "Een van meerdere signalen die het spamfilter heeft meegewogen",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Uw organisatie heeft {protocols} uitgeschakeld voor mail-apps. Mail-apps die dat gebruiken, kunnen geen verbinding maken met dit account; andere nog wel.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Sommige verouderde mailprotocollen zijn uitgeschakeld voor uw organisatie: {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Sommige zijn uit voor {tenant}: {protocols}. In de beheerconsole zijn ze één voor één aan en uit te zetten.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Verwijderen, vergrendelen en juridische bewaarplichten gebeuren in de beheerconsole, die de reden vastlegt en bewaart wat onder een bewaarplicht valt.",
|
||||
"Open in the console": "Openen in de console",
|
||||
|
||||
@@ -1730,6 +1730,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Opinião do modelo de linguagem",
|
||||
"One of several signals the spam filter weighed": "Um dos vários sinais considerados pelo filtro de spam",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Sua organização desativou {protocols} para aplicativos de e-mail. Os que usam isso não conseguem se conectar a esta conta; os outros ainda conseguem.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Alguns protocolos de e-mail legados estão desativados para sua organização: {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Alguns estão desativados para {tenant}: {protocols}. Ative ou desative um de cada vez no console de administração.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Excluir, bloquear e retenções legais são feitos no console de administração, que registra o motivo e preserva o que uma retenção abrange.",
|
||||
"Open in the console": "Abrir no console",
|
||||
|
||||
@@ -1729,6 +1729,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Мнение языковой модели",
|
||||
"One of several signals the spam filter weighed": "Один из нескольких признаков, которые учёл спам-фильтр",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Ваша организация отключила {protocols} для почтовых приложений. Приложения, которые его используют, не могут подключиться к этому аккаунту; остальные могут.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Некоторые устаревшие почтовые протоколы отключены для вашей организации: {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Для {tenant} отключены некоторые: {protocols}. Включайте и отключайте их по одному в консоли администрирования.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Удаление, блокировка и юридическое удержание выполняются в консоли администрирования: она записывает причину и сохраняет всё, что охватывает удержание.",
|
||||
"Open in the console": "Открыть в консоли",
|
||||
|
||||
@@ -1723,6 +1723,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "Думка мовної моделі",
|
||||
"One of several signals the spam filter weighed": "Одна з кількох ознак, які врахував спам-фільтр",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Ваша організація вимкнула {protocols} для поштових програм. Програми, які його використовують, не можуть підключитися до цього облікового запису; інші можуть.",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "Деякі застарілі поштові протоколи вимкнено для вашої організації: {protocols}.",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Для {tenant} вимкнено деякі: {protocols}. Вмикайте й вимикайте їх по одному в консолі адміністрування.",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Видалення, блокування та юридичне утримання виконуються в консолі адміністрування: вона записує причину й зберігає все, що охоплює утримання.",
|
||||
"Open in the console": "Відкрити в консолі",
|
||||
|
||||
@@ -1734,6 +1734,10 @@ export const catalog: Catalog = {
|
||||
// ── Spam filter: the language model's opinion (inbuxa) ──────────
|
||||
"Language model's opinion": "语言模型的判断",
|
||||
"One of several signals the spam filter weighed": "垃圾邮件过滤考虑的多个信号之一",
|
||||
// inbuxa: legacy mail protocols, one switch per protocol
|
||||
"Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "您的组织已为邮件应用关闭 {protocols}。使用它的邮件应用无法连接到此账户;其他应用仍可连接。",
|
||||
"Some legacy mail protocols are off for your organization: {protocols}.": "您的组织已关闭部分传统邮件协议:{protocols}。",
|
||||
"Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "{tenant} 已关闭部分协议:{protocols}。请在管理控制台中逐个开启或关闭。",
|
||||
// inbuxa: deleting a person is the console's (audit-hold-lock spec)
|
||||
"Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "删除、锁定和法律保留均在管理控制台中进行,控制台会记录原因,并保留保留范围内的内容。",
|
||||
"Open in the console": "在控制台中打开",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { Link } from "wouter";
|
||||
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, ShieldCheck, Users } from "lucide-react";
|
||||
import { legacyProtocolsOff } from "@/jmap/client";
|
||||
import { legacyProtocolsOff, legacyProtocolsPartlyOff } from "@/jmap/client";
|
||||
import { useAppName } from "@/lib/brand";
|
||||
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
|
||||
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
|
||||
@@ -38,6 +38,7 @@ export function AdminDashboard() {
|
||||
const perms = usePermissions();
|
||||
const adminUrl = useSession((s) => s.session?.ihasmail?.server?.adminUrl ?? null);
|
||||
const legacyOff = useSession((s) => legacyProtocolsOff(s.session, s.accountId));
|
||||
const legacyPartlyOff = useSession((s) => legacyProtocolsPartlyOff(s.session, s.accountId).join(", "));
|
||||
const app = useAppName();
|
||||
const cards = dashboardCards(perms);
|
||||
const sections = adminSections(perms);
|
||||
@@ -113,6 +114,13 @@ export function AdminDashboard() {
|
||||
<span>{t("Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.", { app })}</span>
|
||||
</p>
|
||||
)}
|
||||
{legacyPartlyOff && (
|
||||
// INBUXA: one switch per protocol, some of them off
|
||||
<p className="admin-notice">
|
||||
<ShieldCheck size={16} />
|
||||
<span>{t("Some legacy mail protocols are off for your organization: {protocols}.", { protocols: legacyPartlyOff })}</span>
|
||||
</p>
|
||||
)}
|
||||
{shown.length ? (
|
||||
<div className="admin-cards-wrap">
|
||||
<div className="admin-cards">
|
||||
|
||||
@@ -68,10 +68,15 @@ export function TenantLegacyProtocols({ tenantId, tenantName }: { tenantId: stri
|
||||
<p>
|
||||
{state.off
|
||||
? t("Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.", { tenant: tenantName, app })
|
||||
: t("On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.", { tenant: tenantName })}
|
||||
: state.partlyOff.length > 0
|
||||
? t("Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.", {
|
||||
tenant: tenantName,
|
||||
protocols: state.partlyOff.join(", "),
|
||||
})
|
||||
: t("On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.", { tenant: tenantName })}
|
||||
</p>
|
||||
|
||||
{canChange && state.off && (
|
||||
{canChange && (state.off || state.partlyOff.length > 0) && (
|
||||
<button className="btn" disabled={busy} onClick={() => void turn(false)}>
|
||||
{t("Turn legacy protocols back on")}
|
||||
</button>
|
||||
|
||||
@@ -62,7 +62,7 @@ describe("a tenant's legacy mail protocols switch", () => {
|
||||
});
|
||||
|
||||
it("shows who would notice and what it means, then asks for the exact phrase", async () => {
|
||||
api.state = { off: false, recent: [{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
|
||||
api.state = { off: false, partlyOff: [], recent: [{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
|
||||
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||
await render();
|
||||
await act(async () => button(host, "Turn off legacy protocols…")!.click());
|
||||
@@ -84,7 +84,7 @@ describe("a tenant's legacy mail protocols switch", () => {
|
||||
});
|
||||
|
||||
it("turns it back on with one click", async () => {
|
||||
api.state = { off: true, recent: [] };
|
||||
api.state = { off: true, partlyOff: [], recent: [] };
|
||||
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||
await render();
|
||||
await act(async () => button(host, "Turn legacy protocols back on")!.click());
|
||||
@@ -92,7 +92,7 @@ describe("a tenant's legacy mail protocols switch", () => {
|
||||
});
|
||||
|
||||
it("shows the state but no switch to someone who can't change domains", async () => {
|
||||
api.state = { off: true, recent: null };
|
||||
api.state = { off: true, partlyOff: [], recent: null };
|
||||
signIn(["sysDomainGet"]);
|
||||
await render();
|
||||
expect(host.textContent).toContain("Off for Acme Corp");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useAppName } from "@/lib/brand";
|
||||
import { Copy, KeyRound, ShieldCheck, Smartphone } from "lucide-react";
|
||||
import { apiFetch, ApiError, legacyProtocolsOff } from "@/jmap/client";
|
||||
import { apiFetch, ApiError, legacyProtocolsOff, legacyProtocolsPartlyOff } from "@/jmap/client";
|
||||
import { useSession } from "@/store/session";
|
||||
import { formatFullDate } from "@/lib/format";
|
||||
import { toast } from "@/ui/toast";
|
||||
@@ -89,6 +89,14 @@ export function SecuritySettings() {
|
||||
{t("Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.", { app: appName })}
|
||||
</p>
|
||||
)}
|
||||
{legacyProtocolsPartlyOff(session, accountId).length > 0 && (
|
||||
// INBUXA: one switch per protocol; mail apps using the rest still work
|
||||
<p className="info-box mb-16">
|
||||
{t("Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.", {
|
||||
protocols: legacyProtocolsPartlyOff(session, accountId).join(", "),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{unsupported ? (
|
||||
<p className="hint">{t("App passwords are managed by your mail administrator.")}</p>
|
||||
) : (
|
||||
|
||||
Reference in new issue
Block a user