Security says why a mail app won't connect, when legacy protocols are off #4
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { INBUXA_CAP, legacyProtocolsOff } from "../client";
|
||||||
|
import type { JmapSession } from "../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server says, per account, whether legacy mail protocols are off for it
|
||||||
|
* (legacy-protocols LP-19, contract C-1). Anything short of a plain
|
||||||
|
* "disabled" -- an older server, another server, no session yet -- reads as
|
||||||
|
* on, so the notice never appears where it isn't true.
|
||||||
|
*/
|
||||||
|
describe("legacyProtocolsOff", () => {
|
||||||
|
const session = (cap: Record<string, unknown> | undefined) =>
|
||||||
|
({
|
||||||
|
accounts: { a: { name: "[email protected]", accountCapabilities: cap ? { [INBUXA_CAP]: cap } : {} } },
|
||||||
|
}) as unknown as JmapSession;
|
||||||
|
|
||||||
|
it("is on only when the account's capability says disabled", () => {
|
||||||
|
expect(legacyProtocolsOff(session({ legacyProtocols: "disabled" }), "a")).toBe(true);
|
||||||
|
expect(legacyProtocolsOff(session({ legacyProtocols: "enabled" }), "a")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads an older or other server, or no session, as on", () => {
|
||||||
|
expect(legacyProtocolsOff(session({ logo: null }), "a")).toBe(false);
|
||||||
|
expect(legacyProtocolsOff(session(undefined), "a")).toBe(false);
|
||||||
|
expect(legacyProtocolsOff(session({ legacyProtocols: "disabled" }), "b")).toBe(false);
|
||||||
|
expect(legacyProtocolsOff(null, "a")).toBe(false);
|
||||||
|
expect(legacyProtocolsOff(session({ legacyProtocols: "disabled" }), null)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,6 +22,21 @@ export const CAP = {
|
|||||||
/** 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:stalwart:jmap";
|
||||||
|
|
||||||
|
/** INBUXA's own capability (contract C-1), on the signed-in account. */
|
||||||
|
export const INBUXA_CAP = "urn:inbuxa:jmap";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether legacy mail protocols -- IMAP, POP3, ManageSieve, sending from mail
|
||||||
|
* apps -- are off for this account: the stricter of the server's switch and
|
||||||
|
* its organization's, as the server reports it (legacy-protocols LP-19). Any
|
||||||
|
* server that doesn't say reads as on.
|
||||||
|
*/
|
||||||
|
export function legacyProtocolsOff(session: JmapSession | null, accountId: Id | null): boolean {
|
||||||
|
if (!session || !accountId) return false;
|
||||||
|
const cap = session.accounts[accountId]?.accountCapabilities?.[INBUXA_CAP] as { legacyProtocols?: string } | undefined;
|
||||||
|
return cap?.legacyProtocols === "disabled";
|
||||||
|
}
|
||||||
|
|
||||||
export class JmapMethodError extends Error {
|
export class JmapMethodError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly method: string,
|
public readonly method: string,
|
||||||
|
|||||||
@@ -924,6 +924,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Zwei-Faktor-Authentifizierung deaktivieren",
|
"Turn off two-factor authentication": "Zwei-Faktor-Authentifizierung deaktivieren",
|
||||||
"Your password alone will be enough to sign in again.": "Ihr Passwort allein genügt dann wieder zum Anmelden.",
|
"Your password alone will be enough to sign in again.": "Ihr Passwort allein genügt dann wieder zum Anmelden.",
|
||||||
"App passwords": "App-Passwörter",
|
"App passwords": "App-Passwörter",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Ihre Organisation erlaubt nur {app} und JMAP-Apps. Mail-Apps auf Smartphones und Computern können sich daher nicht mit diesem Konto verbinden.",
|
||||||
"New app password for": "Neues App-Passwort für",
|
"New app password for": "Neues App-Passwort für",
|
||||||
"Your new app password": "Ihr neues App-Passwort",
|
"Your new app password": "Ihr neues App-Passwort",
|
||||||
"Secret": "Geheimnis",
|
"Secret": "Geheimnis",
|
||||||
|
|||||||
@@ -930,6 +930,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Desactivar la autenticación en dos pasos",
|
"Turn off two-factor authentication": "Desactivar la autenticación en dos pasos",
|
||||||
"Your password alone will be enough to sign in again.": "Su contraseña por sí sola bastará para volver a iniciar sesión.",
|
"Your password alone will be enough to sign in again.": "Su contraseña por sí sola bastará para volver a iniciar sesión.",
|
||||||
"App passwords": "Contraseñas de aplicación",
|
"App passwords": "Contraseñas de aplicación",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Su organización solo permite {app} y aplicaciones JMAP, así que las aplicaciones de correo del teléfono y del ordenador no pueden conectarse a esta cuenta.",
|
||||||
"New app password for": "Contraseña de aplicación nueva para",
|
"New app password for": "Contraseña de aplicación nueva para",
|
||||||
"Your new app password": "Su contraseña de aplicación nueva",
|
"Your new app password": "Su contraseña de aplicación nueva",
|
||||||
"Secret": "Secreto",
|
"Secret": "Secreto",
|
||||||
|
|||||||
@@ -936,6 +936,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Désactiver l'authentification à deux facteurs",
|
"Turn off two-factor authentication": "Désactiver l'authentification à deux facteurs",
|
||||||
"Your password alone will be enough to sign in again.": "Votre mot de passe seul suffira pour vous reconnecter.",
|
"Your password alone will be enough to sign in again.": "Votre mot de passe seul suffira pour vous reconnecter.",
|
||||||
"App passwords": "Mots de passe d'application",
|
"App passwords": "Mots de passe d'application",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Votre organisation n'autorise que {app} et les applications JMAP : les applications de messagerie sur téléphone et ordinateur ne peuvent donc pas se connecter à ce compte.",
|
||||||
"New app password for": "Nouveau mot de passe d'application pour",
|
"New app password for": "Nouveau mot de passe d'application pour",
|
||||||
"Your new app password": "Votre nouveau mot de passe d'application",
|
"Your new app password": "Votre nouveau mot de passe d'application",
|
||||||
"Secret": "Secret",
|
"Secret": "Secret",
|
||||||
|
|||||||
@@ -930,6 +930,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "2 段階認証をオフにする",
|
"Turn off two-factor authentication": "2 段階認証をオフにする",
|
||||||
"Your password alone will be enough to sign in again.": "以後はパスワードだけでサインインできるようになります。",
|
"Your password alone will be enough to sign in again.": "以後はパスワードだけでサインインできるようになります。",
|
||||||
"App passwords": "アプリパスワード",
|
"App passwords": "アプリパスワード",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "組織では {app} と JMAP アプリのみが許可されているため、スマートフォンやパソコンのメールアプリはこのアカウントに接続できません。",
|
||||||
"New app password for": "アプリパスワードの用途",
|
"New app password for": "アプリパスワードの用途",
|
||||||
"Your new app password": "新しいアプリパスワード",
|
"Your new app password": "新しいアプリパスワード",
|
||||||
"Secret": "シークレット",
|
"Secret": "シークレット",
|
||||||
|
|||||||
@@ -928,6 +928,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Tweefactorauthenticatie uitschakelen",
|
"Turn off two-factor authentication": "Tweefactorauthenticatie uitschakelen",
|
||||||
"Your password alone will be enough to sign in again.": "Uw wachtwoord alleen volstaat dan weer om in te loggen.",
|
"Your password alone will be enough to sign in again.": "Uw wachtwoord alleen volstaat dan weer om in te loggen.",
|
||||||
"App passwords": "App-wachtwoorden",
|
"App passwords": "App-wachtwoorden",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Uw organisatie staat alleen {app} en JMAP-apps toe, dus mail-apps op telefoon en computer kunnen geen verbinding maken met dit account.",
|
||||||
"New app password for": "Nieuw app-wachtwoord voor",
|
"New app password for": "Nieuw app-wachtwoord voor",
|
||||||
"Your new app password": "Uw nieuwe app-wachtwoord",
|
"Your new app password": "Uw nieuwe app-wachtwoord",
|
||||||
"Secret": "Geheim",
|
"Secret": "Geheim",
|
||||||
|
|||||||
@@ -933,6 +933,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Desativar a autenticação em duas etapas",
|
"Turn off two-factor authentication": "Desativar a autenticação em duas etapas",
|
||||||
"Your password alone will be enough to sign in again.": "Sua senha sozinha bastará para entrar de novo.",
|
"Your password alone will be enough to sign in again.": "Sua senha sozinha bastará para entrar de novo.",
|
||||||
"App passwords": "Senhas de aplicativo",
|
"App passwords": "Senhas de aplicativo",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Sua organização permite apenas {app} e aplicativos JMAP, então aplicativos de e-mail no celular e no computador não conseguem se conectar a esta conta.",
|
||||||
"New app password for": "Nova senha de aplicativo para",
|
"New app password for": "Nova senha de aplicativo para",
|
||||||
"Your new app password": "Sua nova senha de aplicativo",
|
"Your new app password": "Sua nova senha de aplicativo",
|
||||||
"Secret": "Segredo",
|
"Secret": "Segredo",
|
||||||
|
|||||||
@@ -933,6 +933,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Отключить двухфакторную аутентификацию",
|
"Turn off two-factor authentication": "Отключить двухфакторную аутентификацию",
|
||||||
"Your password alone will be enough to sign in again.": "Для входа снова будет достаточно одного пароля.",
|
"Your password alone will be enough to sign in again.": "Для входа снова будет достаточно одного пароля.",
|
||||||
"App passwords": "Пароли приложений",
|
"App passwords": "Пароли приложений",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Ваша организация разрешает только {app} и приложения JMAP, поэтому почтовые приложения на телефоне и компьютере не могут подключиться к этой учётной записи.",
|
||||||
"New app password for": "Новый пароль приложения для",
|
"New app password for": "Новый пароль приложения для",
|
||||||
"Your new app password": "Ваш новый пароль приложения",
|
"Your new app password": "Ваш новый пароль приложения",
|
||||||
"Secret": "Секрет",
|
"Secret": "Секрет",
|
||||||
|
|||||||
@@ -927,6 +927,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "Вимкнути двофакторну автентифікацію",
|
"Turn off two-factor authentication": "Вимкнути двофакторну автентифікацію",
|
||||||
"Your password alone will be enough to sign in again.": "Для входу знову буде достатньо самого пароля.",
|
"Your password alone will be enough to sign in again.": "Для входу знову буде достатньо самого пароля.",
|
||||||
"App passwords": "Паролі програм",
|
"App passwords": "Паролі програм",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "Ваша організація дозволяє лише {app} і програми JMAP, тому поштові програми на телефоні й комп'ютері не можуть підключитися до цього облікового запису.",
|
||||||
"New app password for": "Новий пароль програми для",
|
"New app password for": "Новий пароль програми для",
|
||||||
"Your new app password": "Ваш новий пароль програми",
|
"Your new app password": "Ваш новий пароль програми",
|
||||||
"Secret": "Секрет",
|
"Secret": "Секрет",
|
||||||
|
|||||||
@@ -929,6 +929,7 @@ export const catalog: Catalog = {
|
|||||||
"Turn off two-factor authentication": "关闭两步验证",
|
"Turn off two-factor authentication": "关闭两步验证",
|
||||||
"Your password alone will be enough to sign in again.": "之后仅凭密码即可重新登录。",
|
"Your password alone will be enough to sign in again.": "之后仅凭密码即可重新登录。",
|
||||||
"App passwords": "应用专用密码",
|
"App passwords": "应用专用密码",
|
||||||
|
"Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.": "您的组织只允许使用 {app} 和 JMAP 应用,因此手机和电脑上的邮件应用无法连接到此账户。",
|
||||||
"New app password for": "新建应用专用密码,用于",
|
"New app password for": "新建应用专用密码,用于",
|
||||||
"Your new app password": "您的新应用专用密码",
|
"Your new app password": "您的新应用专用密码",
|
||||||
"Secret": "密钥",
|
"Secret": "密钥",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useAppName } from "@/lib/brand";
|
import { useAppName } from "@/lib/brand";
|
||||||
import { Copy, KeyRound, ShieldCheck, Smartphone } from "lucide-react";
|
import { Copy, KeyRound, ShieldCheck, Smartphone } from "lucide-react";
|
||||||
import { apiFetch, ApiError } from "@/jmap/client";
|
import { apiFetch, ApiError, legacyProtocolsOff } from "@/jmap/client";
|
||||||
import { useSession } from "@/store/session";
|
import { useSession } from "@/store/session";
|
||||||
import { formatFullDate } from "@/lib/format";
|
import { formatFullDate } from "@/lib/format";
|
||||||
import { toast } from "@/ui/toast";
|
import { toast } from "@/ui/toast";
|
||||||
@@ -39,7 +39,9 @@ export function SecuritySettings() {
|
|||||||
/** Set when the server has no self-service API at all (a proxy, say). */
|
/** Set when the server has no self-service API at all (a proxy, say). */
|
||||||
const [unsupported, setUnsupported] = useState<string | null>(null);
|
const [unsupported, setUnsupported] = useState<string | null>(null);
|
||||||
const session = useSession((s) => s.session);
|
const session = useSession((s) => s.session);
|
||||||
|
const accountId = useSession((s) => s.accountId);
|
||||||
const logout = useSession((s) => s.logout);
|
const logout = useSession((s) => s.logout);
|
||||||
|
const appName = useAppName();
|
||||||
|
|
||||||
const load = () => apiFetch<{ current: string; sessions: SessionRow[] }>("/api/auth/sessions").then((r) => { setRows(r.sessions); setCurrent(r.current); }).catch(() => setRows([]));
|
const load = () => apiFetch<{ current: string; sessions: SessionRow[] }>("/api/auth/sessions").then((r) => { setRows(r.sessions); setCurrent(r.current); }).catch(() => setRows([]));
|
||||||
|
|
||||||
@@ -82,6 +84,11 @@ export function SecuritySettings() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<h2>{t("App passwords")}</h2>
|
<h2>{t("App passwords")}</h2>
|
||||||
|
{legacyProtocolsOff(session, accountId) && (
|
||||||
|
<p className="info-box mb-16">
|
||||||
|
{t("Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.", { app: appName })}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{unsupported ? (
|
{unsupported ? (
|
||||||
<p className="hint">{t("App passwords are managed by your mail administrator.")}</p>
|
<p className="hint">{t("App passwords are managed by your mail administrator.")}</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user