Offer administration only on a device marked as your own
A session signed in without "This is my own device" can no longer administer. The server withholds the account's permissions from it and the JMAP proxy refuses registry methods beyond the account's own, the same gate ADMINISTRATION=0 uses. A borrowed or shared machine is where nobody should be able to reset a password or remove a domain. An administrator in such a session still sees Administration in the account menu, greyed out, with the reason and the fix: sign in again with the box ticked. The server tells that session only that the account administers. The gate now reads the body only when it could name a registry method -- "x: in the text, or a \u escape that could spell one -- so ordinary mail traffic from an untrusted session is forwarded untouched. 1 new string, translated in all nine catalogues, quoting each language's own label for the tickbox; strings falling back to English stay at 16.
This commit is contained in:
@@ -39,8 +39,13 @@ export interface JmapSession {
|
||||
/** "oss" | "community" | "enterprise". Stalwart publishes no version. */
|
||||
edition?: string | null;
|
||||
};
|
||||
/** False when the operator has turned in-app administration off. */
|
||||
/**
|
||||
* False when this session may not administer: the operator turned it off,
|
||||
* or the session was signed in without "This is my own device".
|
||||
*/
|
||||
administration?: boolean;
|
||||
/** An administrator on a device not marked as their own; the menu says so. */
|
||||
administrationNeedsOwnDevice?: boolean;
|
||||
/**
|
||||
* The account's effective permissions on that server, as Stalwart reports
|
||||
* them. What the client offers is shaped by these; what is allowed is
|
||||
|
||||
@@ -113,6 +113,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "auch {names}",
|
||||
"The server did not say whether the domain was created.": "Der Server hat nicht mitgeteilt, ob die Domain angelegt wurde.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Nur auf einem Gerät, das Sie als Ihr eigenes markiert haben. Melden Sie sich erneut an und setzen Sie das Häkchen bei „Das ist mein eigenes Gerät“.",
|
||||
"Change your own password in {settings}.": "Ihr eigenes Passwort ändern Sie unter {settings}.",
|
||||
"Administration": "Verwaltung",
|
||||
"Directory": "Verzeichnis",
|
||||
|
||||
@@ -105,6 +105,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "también {names}",
|
||||
"The server did not say whether the domain was created.": "El servidor no indicó si el dominio se creó.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Solo en un dispositivo que haya marcado como suyo. Vuelva a iniciar sesión con «Este es mi propio dispositivo» marcado.",
|
||||
"Change your own password in {settings}.": "Cambie su propia contraseña en {settings}.",
|
||||
"Administration": "Administración",
|
||||
"Directory": "Directorio",
|
||||
|
||||
@@ -110,6 +110,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "aussi {names}",
|
||||
"The server did not say whether the domain was created.": "Le serveur n’a pas indiqué si le domaine a été créé.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Uniquement sur un appareil que vous avez indiqué comme le vôtre. Reconnectez-vous en cochant « Cet appareil est le mien ».",
|
||||
"Change your own password in {settings}.": "Modifiez votre propre mot de passe dans {settings}.",
|
||||
"Administration": "Administration",
|
||||
"Directory": "Annuaire",
|
||||
|
||||
@@ -104,6 +104,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "別名: {names}",
|
||||
"The server did not say whether the domain was created.": "ドメインが作成されたかどうか、サーバーから応答がありませんでした。",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "自分のデバイスとして指定した端末でのみ使えます。「これは自分のデバイスです」にチェックを入れて、もう一度サインインしてください。",
|
||||
"Change your own password in {settings}.": "ご自身のパスワードは{settings}で変更してください。",
|
||||
"Administration": "管理",
|
||||
"Directory": "ディレクトリ",
|
||||
|
||||
@@ -101,6 +101,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "ook {names}",
|
||||
"The server did not say whether the domain was created.": "De server heeft niet gemeld of het domein is aangemaakt.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Alleen op een apparaat dat u als uw eigen apparaat hebt aangemerkt. Log opnieuw in met ‘Dit is mijn eigen apparaat’ aangevinkt.",
|
||||
"Change your own password in {settings}.": "Wijzig uw eigen wachtwoord bij {settings}.",
|
||||
"Administration": "Beheer",
|
||||
"Directory": "Adreslijst",
|
||||
|
||||
@@ -108,6 +108,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "também {names}",
|
||||
"The server did not say whether the domain was created.": "O servidor não informou se o domínio foi criado.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Só em um dispositivo que você marcou como seu. Entre novamente com “Este dispositivo é meu” marcado.",
|
||||
"Change your own password in {settings}.": "Altere sua própria senha em {settings}.",
|
||||
"Administration": "Administração",
|
||||
"Directory": "Diretório",
|
||||
|
||||
@@ -107,6 +107,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "также {names}",
|
||||
"The server did not say whether the domain was created.": "Сервер не сообщил, создан ли домен.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Только на устройстве, отмеченном как ваше. Войдите снова, отметив «Это моё личное устройство».",
|
||||
"Change your own password in {settings}.": "Свой пароль можно изменить в разделе {settings}.",
|
||||
"Administration": "Администрирование",
|
||||
"Directory": "Каталог",
|
||||
|
||||
@@ -101,6 +101,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "також {names}",
|
||||
"The server did not say whether the domain was created.": "Сервер не повідомив, чи створено домен.",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Лише на пристрої, позначеному як ваш. Увійдіть знову, позначивши «Це мій власний пристрій».",
|
||||
"Change your own password in {settings}.": "Власний пароль можна змінити в розділі {settings}.",
|
||||
"Administration": "Адміністрування",
|
||||
"Directory": "Каталог",
|
||||
|
||||
@@ -103,6 +103,7 @@ export const catalog: Catalog = {
|
||||
"also {names}": "别名:{names}",
|
||||
"The server did not say whether the domain was created.": "服务器没有说明域名是否已创建。",
|
||||
// ── Administration: accounts ───────────────────────────────────
|
||||
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "仅限在您标记为自己设备的设备上使用。请勾选「这是我自己的设备」后重新登录。",
|
||||
"Change your own password in {settings}.": "请在{settings}中更改您自己的密码。",
|
||||
"Administration": "管理",
|
||||
"Directory": "目录",
|
||||
|
||||
@@ -45,6 +45,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
const appName = useSession((s) => s.session?.ihasmail?.appName) || DEFAULT_APP_NAME;
|
||||
const acctMenu = useMenu();
|
||||
const administers = hasAdministration(usePermissions());
|
||||
const needsOwnDevice = useSession((s) => Boolean(s.session?.ihasmail?.administrationNeedsOwnDevice));
|
||||
/*
|
||||
* "Go to folder" (#233), hosted here rather than in the mail view because
|
||||
* the `g` shortcuts are global: pressing it from the calendar should still
|
||||
@@ -162,6 +163,21 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
{/* Only for an account whose Stalwart role manages other accounts.
|
||||
Nobody else is shown an entry that would open onto refusals. */}
|
||||
{administers && <MenuItem icon={<ShieldCheck size={16} />} label={t("Administration")} active={section === "admin"} onClick={() => navigate("/admin")} />}
|
||||
{/* An administrator who signed in without "This is my own device". The
|
||||
server withholds administration from that session, so the entry is
|
||||
shown dead with the reason, rather than gone without one. */}
|
||||
{!administers && needsOwnDevice && (
|
||||
<MenuItem
|
||||
icon={<ShieldCheck size={16} />}
|
||||
disabled
|
||||
label={
|
||||
<>
|
||||
<span style={{ display: "block" }}>{t("Administration")}</span>
|
||||
<span className="hint" style={{ display: "block", whiteSpace: "normal" }}>{t("Only on a device you've marked as your own. Sign in again with “This is my own device” ticked.")}</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<MenuItem icon={<RefreshCw size={16} />} label={t("Refresh")} onClick={() => window.location.reload()} />
|
||||
<MenuItem icon={<LogOut size={16} />} label={t("Sign out")} onClick={() => void logout()} />
|
||||
</Popover>
|
||||
|
||||
Reference in New Issue
Block a user