Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- gui: Download TAF tracks as separate .ogg files (ZIP when more than one track is selected) [https://github.com/toniebox-reverse-engineering/teddycloud/issues/169](https://github.com/toniebox-reverse-engineering/teddycloud/issues/169)
- gui: Box setup can create a port patch for CC3200 boxes (altPort.custom.json) and patch the port of ESP32 boxes, e.g. for a proxy that routes boxes by port
- gui: Certificate check per box (issued by TeddyCloud / pinned / other certificate) on the box card and in the certificates dialog [https://github.com/toniebox-reverse-engineering/teddycloud/issues/85](https://github.com/toniebox-reverse-engineering/teddycloud/issues/85)
- Added "listened" status tracking for library files, incl. auto-mark on sync (opt-out) [https://github.com/toniebox-reverse-engineering/teddycloud/pull/467](https://github.com/toniebox-reverse-engineering/teddycloud/pull/467)
- Added total audio length (`lengthSeconds`) to the TAF info of the file index and the tag index [https://github.com/toniebox-reverse-engineering/teddycloud/pull/537](https://github.com/toniebox-reverse-engineering/teddycloud/pull/537)
- gui: Optional web UI login with multiple users (all full UI rights) [https://github.com/toniebox-reverse-engineering/teddycloud/issues/85](https://github.com/toniebox-reverse-engineering/teddycloud/issues/85)
Expand Down
15 changes: 14 additions & 1 deletion public/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,20 @@
"uploadTonieboxCertificates": "Zertifikate für Toniebox{{name}} hochladen"
},
"warningUseAtYourOwnRisk": "Warnung: Nutzung auf eigene Gefahr!",
"warningUseAtYourOwnRiskText": "Das Flashen deiner Toniebox mit einer modifizierten Firmware führt zum Erlöschen der Garantie und kann deine Box unbrauchbar machen. Bitte folge den Anweisungen sorgfältig und stelle sicher, dass du die damit verbundenen Risiken verstehst."
"warningUseAtYourOwnRiskText": "Das Flashen deiner Toniebox mit einer modifizierten Firmware führt zum Erlöschen der Garantie und kann deine Box unbrauchbar machen. Bitte folge den Anweisungen sorgfältig und stelle sicher, dass du die damit verbundenen Risiken verstehst.",
"boxCertificate": {
"title": "Zertifikatsprüfung",
"status": "Status",
"presented": "Vorgezeigtes Zertifikat (SHA-256)",
"pin": "Gepinntes Zertifikat (SHA-256)",
"nextConnection": "Der Status wird bei der nächsten Verbindung der Box aktualisiert.",
"statusText": {
"none": "Noch nicht geprüft",
"trusted": "Zertifikat von TeddyCloud ausgestellt",
"pinned": "Gepinntes Zertifikat",
"mismatch": "Anderes Zertifikat als das gepinnte!"
}
}
},
"tonies": {
"addNewCustomTonie": "Neues Modell anlegen",
Expand Down
15 changes: 14 additions & 1 deletion public/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,20 @@
"uploadTonieboxCertificates": "Upload certificates for Toniebox{{name}}"
},
"warningUseAtYourOwnRisk": "Warning: Use at your own risk!",
"warningUseAtYourOwnRiskText": "Flashing your Toniebox with a patched firmware will void your warranty and may lead to a broken box. Please follow the instructions carefully and ensure you understand the risks involved."
"warningUseAtYourOwnRiskText": "Flashing your Toniebox with a patched firmware will void your warranty and may lead to a broken box. Please follow the instructions carefully and ensure you understand the risks involved.",
"boxCertificate": {
"title": "Certificate check",
"status": "Status",
"presented": "Presented certificate (SHA-256)",
"pin": "Pinned certificate (SHA-256)",
"nextConnection": "The status is updated with the next connection of the box.",
"statusText": {
"none": "Not checked yet",
"trusted": "Certificate issued by TeddyCloud",
"pinned": "Pinned certificate",
"mismatch": "Other certificate than the pinned one!"
}
}
},
"tonies": {
"addNewCustomTonie": "Create new model",
Expand Down
15 changes: 14 additions & 1 deletion public/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,20 @@
"uploadTonieboxCertificates": "Subir certificados para Toniebox{{name}}"
},
"warningUseAtYourOwnRisk": "Advertencia: úsalo bajo tu propia responsabilidad",
"warningUseAtYourOwnRiskText": "Flashear tu Toniebox con un firmware modificado anulará la garantía y puede dejar la caja inutilizable. Sigue las instrucciones cuidadosamente y asegúrate de comprender los riesgos asociados."
"warningUseAtYourOwnRiskText": "Flashear tu Toniebox con un firmware modificado anulará la garantía y puede dejar la caja inutilizable. Sigue las instrucciones cuidadosamente y asegúrate de comprender los riesgos asociados.",
"boxCertificate": {
"title": "Comprobación del certificado",
"status": "Estado",
"presented": "Certificado presentado (SHA-256)",
"pin": "Certificado fijado (SHA-256)",
"nextConnection": "El estado se actualiza con la próxima conexión de la Toniebox.",
"statusText": {
"none": "Aún no comprobado",
"trusted": "Certificado emitido por TeddyCloud",
"pinned": "Certificado fijado",
"mismatch": "¡Otro certificado distinto del fijado!"
}
}
},
"tonies": {
"addNewCustomTonie": "Crear nuevo modelo",
Expand Down
15 changes: 14 additions & 1 deletion public/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,20 @@
"uploadTonieboxCertificates": "Télécharger les certificats pour la Toniebox{{name}}"
},
"warningUseAtYourOwnRisk": "Avertissement : utilisation à tes propres risques",
"warningUseAtYourOwnRiskText": "Flasher ta Toniebox avec un firmware modifié annulera la garantie et peut rendre la box inutilisable. Suis attentivement les instructions et assure-toi de bien comprendre les risques encourus."
"warningUseAtYourOwnRiskText": "Flasher ta Toniebox avec un firmware modifié annulera la garantie et peut rendre la box inutilisable. Suis attentivement les instructions et assure-toi de bien comprendre les risques encourus.",
"boxCertificate": {
"title": "Vérification du certificat",
"status": "Statut",
"presented": "Certificat présenté (SHA-256)",
"pin": "Certificat épinglé (SHA-256)",
"nextConnection": "Le statut est mis à jour à la prochaine connexion de la Toniebox.",
"statusText": {
"none": "Pas encore vérifié",
"trusted": "Certificat émis par TeddyCloud",
"pinned": "Certificat épinglé",
"mismatch": "Autre certificat que celui épinglé !"
}
}
},
"tonies": {
"addNewCustomTonie": "Créer un nouveau modèle",
Expand Down
15 changes: 14 additions & 1 deletion public/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,20 @@
"uploadTonieboxCertificates": "Carica certificati per Toniebox{{name}}"
},
"warningUseAtYourOwnRisk": "Avviso: usa a tuo rischio!",
"warningUseAtYourOwnRiskText": "Flashare il Toniebox con un firmware modificato invaliderà la garanzia e potrebbe danneggiare il box. Segui attentamente le istruzioni e assicurati di comprendere i rischi."
"warningUseAtYourOwnRiskText": "Flashare il Toniebox con un firmware modificato invaliderà la garanzia e potrebbe danneggiare il box. Segui attentamente le istruzioni e assicurati di comprendere i rischi.",
"boxCertificate": {
"title": "Verifica del certificato",
"status": "Stato",
"presented": "Certificato presentato (SHA-256)",
"pin": "Certificato fissato (SHA-256)",
"nextConnection": "Lo stato viene aggiornato alla prossima connessione della Toniebox.",
"statusText": {
"none": "Non ancora verificato",
"trusted": "Certificato emesso da TeddyCloud",
"pinned": "Certificato fissato",
"mismatch": "Certificato diverso da quello fissato!"
}
}
},
"tonies": {
"addNewCustomTonie": "Crea nuovo modello",
Expand Down
15 changes: 14 additions & 1 deletion public/translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,20 @@
"uploadTonieboxCertificates": "Prześlij certyfikaty dla Tonieboxa {{name}}"
},
"warningUseAtYourOwnRisk": "Ostrzeżenie: używasz na własne ryzyko!",
"warningUseAtYourOwnRiskText": "Flashowanie Tonieboxa spatchowanym firmware unieważni gwarancję i może doprowadzić do uszkodzenia boxa. Postępuj dokładnie zgodnie z instrukcjami i upewnij się, że rozumiesz związane z tym ryzyko."
"warningUseAtYourOwnRiskText": "Flashowanie Tonieboxa spatchowanym firmware unieważni gwarancję i może doprowadzić do uszkodzenia boxa. Postępuj dokładnie zgodnie z instrukcjami i upewnij się, że rozumiesz związane z tym ryzyko.",
"boxCertificate": {
"title": "Sprawdzanie certyfikatu",
"status": "Status",
"presented": "Przedstawiony certyfikat (SHA-256)",
"pin": "Przypięty certyfikat (SHA-256)",
"nextConnection": "Status zostanie zaktualizowany przy następnym połączeniu Toniebox.",
"statusText": {
"none": "Jeszcze nie sprawdzono",
"trusted": "Certyfikat wystawiony przez TeddyCloud",
"pinned": "Przypięty certyfikat",
"mismatch": "Inny certyfikat niż przypięty!"
}
}
},
"tonies": {
"addNewCustomTonie": "Utwórz nowy model",
Expand Down
50 changes: 50 additions & 0 deletions src/components/tonieboxes/common/elements/BoxCertificateCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";
import { Descriptions, Tag, Typography } from "antd";
import { useTranslation } from "react-i18next";

import { BoxCertStatus, useBoxCertificate } from "../hooks/useBoxCertificate";

const { Paragraph, Text } = Typography;

export const boxCertStatusColor: Record<BoxCertStatus, string | undefined> = {
"": undefined,
trusted: "success",
pinned: "success",
mismatch: "error",
};

const Fingerprint: React.FC<{ value: string }> = ({ value }) =>
value ? (
<Text code copyable style={{ wordBreak: "break-all" }}>
{value}
</Text>
) : (
<Text type="secondary">-</Text>
);

export const BoxCertificateCheck: React.FC<{ overlayId?: string }> = ({ overlayId }) => {
const { t } = useTranslation();
const { status, presented, pin } = useBoxCertificate(overlayId, true);

return (
<>
<h4>{t("tonieboxes.boxCertificate.title")}</h4>
<Descriptions size="small" column={1} bordered>
<Descriptions.Item label={t("tonieboxes.boxCertificate.status")}>
<Tag color={boxCertStatusColor[status]}>
{t(`tonieboxes.boxCertificate.statusText.${status || "none"}`)}
</Tag>
</Descriptions.Item>
<Descriptions.Item label={t("tonieboxes.boxCertificate.presented")}>
<Fingerprint value={presented} />
</Descriptions.Item>
<Descriptions.Item label={t("tonieboxes.boxCertificate.pin")}>
<Fingerprint value={pin} />
</Descriptions.Item>
</Descriptions>
<Paragraph type="secondary" style={{ marginTop: 8 }}>
{t("tonieboxes.boxCertificate.nextConnection")}
</Paragraph>
</>
);
};
36 changes: 36 additions & 0 deletions src/components/tonieboxes/common/hooks/useBoxCertificate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useState } from "react";

import { TeddyCloudApi } from "../../../../api";
import { defaultAPIConfig } from "../../../../config/defaultApiConfig";

const api = new TeddyCloudApi(defaultAPIConfig());

export type BoxCertStatus = "" | "trusted" | "pinned" | "mismatch";

const readSetting = async (key: string, overlay: string) =>
(await (await api.apiGetTeddyCloudSettingRaw(key, overlay)).text()).trim();

/** Status of the box certificate check; with details also the presented and the pinned fingerprint. */
export const useBoxCertificate = (overlay?: string, details = false) => {
const [status, setStatus] = useState<BoxCertStatus>("");
const [presented, setPresented] = useState("");
const [pin, setPin] = useState("");

useEffect(() => {
if (!overlay) return;
const load = async () => {
try {
setStatus((await readSetting("internal.boxCertStatus", overlay)) as BoxCertStatus);
if (details) {
setPresented(await readSetting("internal.boxCertSha256", overlay));
setPin(await readSetting("toniebox.certPin", overlay));
}
} catch {
// TeddyCloud without the box certificate check
}
};
load();
}, [overlay, details]);

return { status, presented, pin };
};
2 changes: 2 additions & 0 deletions src/components/tonieboxes/common/modals/CertificatesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";

import { OptionsList } from "../../../../api";
import { CertificateDragNDrop } from "../../../common/form/CertificatesDragAndDrop";
import { BoxCertificateCheck } from "../elements/BoxCertificateCheck";

const { Paragraph } = Typography;

Expand Down Expand Up @@ -53,6 +54,7 @@ export const CertificatesModal: React.FC<CertificatesModalProps> = ({
)}
</Paragraph>
<CertificateDragNDrop overlay={overlayId} />
{overlayId && <BoxCertificateCheck overlayId={overlayId} />}
</Modal>
);
};
25 changes: 21 additions & 4 deletions src/components/tonieboxes/tonieboxcard/TonieboxCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import defaultBoxImage from "../../../assets/unknown_box.png";

import { EditBoxModal } from "./modals/EditTonieModal";
import { CertificatesModal } from "../common/modals/CertificatesModal";
import { useBoxCertificate } from "../common/hooks/useBoxCertificate";
import { SettingsModal } from "./modals/SettingsModal";
import { DeleteModal } from "./modals/DeleteModal";
import { useTriggerWriteConfig } from "./hooks/useTriggerWriteConfig";
Expand Down Expand Up @@ -67,6 +68,7 @@ export const TonieboxCard: React.FC<{
const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] = useState(false);
const [tonieboxAccessApi, setTonieboxAccessApi] = useState<boolean>(true);
const [modalKey, setModalKey] = useState(0);
const { status: certStatus } = useBoxCertificate(tonieboxCard.ID);

useEffect(() => {
const fetchTonieboxApiAccess = async () => {
Expand Down Expand Up @@ -233,6 +235,14 @@ export const TonieboxCard: React.FC<{
};

// certificates
const certStatusColor = () =>
({
trusted: token.colorSuccess,
pinned: token.colorSuccess,
mismatch: token.colorError,
"": undefined,
})[certStatus];

const handleUploadCertificatesClick = () => {
const fetchOptions = async () => {
const optionsRequest = (await api.apiGetIndexGet(tonieboxCard.ID)) as OptionsList;
Expand Down Expand Up @@ -628,11 +638,18 @@ export const TonieboxCard: React.FC<{
)}
</>,
<EditOutlined key="edit" onClick={() => showModelModal()} />,
<SafetyCertificateOutlined
<Tooltip
key="certificate"
style={{ marginRight: 8 }}
onClick={handleUploadCertificatesClick}
/>,
open={!canHover || !certStatus ? false : undefined}
title={t(
`tonieboxes.boxCertificate.statusText.${certStatus || "none"}`,
)}
>
<SafetyCertificateOutlined
style={{ marginRight: 8, color: certStatusColor() }}
onClick={handleUploadCertificatesClick}
/>
</Tooltip>,
<SettingOutlined
key="settings"
style={{ marginRight: 8 }}
Expand Down
Loading