(feat): improve secure storage
This commit is contained in:
parent
9011768a2e
commit
cda0c48454
7 changed files with 71 additions and 31 deletions
|
|
@ -36,6 +36,13 @@ if (verbose) {
|
||||||
app.commandLine.appendSwitch("log-level", "0");
|
app.commandLine.appendSwitch("log-level", "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.platform === "linux" &&
|
||||||
|
!app.commandLine.hasSwitch("password-store")
|
||||||
|
) {
|
||||||
|
app.commandLine.appendSwitch("password-store", "gnome-libsecret");
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.platform === "linux" &&
|
process.platform === "linux" &&
|
||||||
process.env.XDG_SESSION_TYPE === "wayland" &&
|
process.env.XDG_SESSION_TYPE === "wayland" &&
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,6 @@ function validateValue(value: unknown): asserts value is string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSecureStorageStatus(): DesktopSecureStorageStatus {
|
export function getSecureStorageStatus(): DesktopSecureStorageStatus {
|
||||||
if (!safeStorage.isEncryptionAvailable()) {
|
|
||||||
return { available: false, backend: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const backend =
|
const backend =
|
||||||
process.platform === "linux"
|
process.platform === "linux"
|
||||||
? safeStorage.getSelectedStorageBackend()
|
? safeStorage.getSelectedStorageBackend()
|
||||||
|
|
@ -48,7 +44,9 @@ export function getSecureStorageStatus(): DesktopSecureStorageStatus {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
available: process.platform !== "linux" || backend !== "basic_text",
|
available:
|
||||||
|
safeStorage.isEncryptionAvailable() &&
|
||||||
|
(process.platform !== "linux" || backend !== "basic_text"),
|
||||||
backend,
|
backend,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import { Button } from "@tensamin/ui";
|
import {
|
||||||
import { Checkbox } from "@tensamin/ui";
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CreateScreen,
|
||||||
|
ErrorScreen,
|
||||||
|
Label,
|
||||||
|
Link,
|
||||||
|
Spinner,
|
||||||
|
} from "@tensamin/ui";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ErrorScreen } from "@tensamin/ui";
|
|
||||||
|
|
||||||
import { legalDocsSchema } from "@tensamin/shared/features/legal/schema";
|
import { legalDocsSchema } from "@tensamin/shared/features/legal/schema";
|
||||||
import { log } from "@tensamin/shared/log";
|
import { log } from "@tensamin/shared/log";
|
||||||
import { Link } from "@tensamin/ui";
|
|
||||||
import { Label } from "@tensamin/ui";
|
|
||||||
import { CreateScreen } from "@tensamin/ui";
|
|
||||||
|
|
||||||
// Prevents the user from using Tensamin without accepting the privacy policy and terms of service.
|
// Prevents the user from using Tensamin without accepting the privacy policy and terms of service.
|
||||||
export default function Screen(props: { children: React.ReactNode }) {
|
export default function Screen(props: { children: React.ReactNode }) {
|
||||||
|
|
@ -27,6 +29,7 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [showLoading, setShowLoading] = useState(false);
|
||||||
|
|
||||||
const [acceptedPP, acceptPP] = useState(false);
|
const [acceptedPP, acceptPP] = useState(false);
|
||||||
const [acceptedTOS, acceptTOS] = useState(false);
|
const [acceptedTOS, acceptTOS] = useState(false);
|
||||||
|
|
@ -49,6 +52,7 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true;
|
let active = true;
|
||||||
|
let loadingTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -63,22 +67,17 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = await fetch("https://legal.tensamin.net/api/current")
|
loadingTimer = setTimeout(() => {
|
||||||
.then((res) => res.json())
|
if (active) setShowLoading(true);
|
||||||
.catch((err) => {
|
}, 200);
|
||||||
if (!active) {
|
|
||||||
return undefined;
|
const response = await fetch("https://legal.tensamin.net/api/current");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Legal documents request failed: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
const current: unknown = await response.json();
|
||||||
|
|
||||||
setError("Failed to load legal documents");
|
if (!active) {
|
||||||
setErrorDescription(
|
|
||||||
"An error occurred while fetching the legal documents from the server. Please try again later.",
|
|
||||||
);
|
|
||||||
log(0, "Legal", "red", "Failed to fetch legal documents", err);
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!active || current === undefined) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +121,16 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
!!currentLocalDocs &&
|
!!currentLocalDocs &&
|
||||||
currentLocalDocs.tos.hash === safeCurrent.data.tos.hash,
|
currentLocalDocs.tos.hash === safeCurrent.data.tos.hash,
|
||||||
);
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!active) return;
|
||||||
|
|
||||||
|
setError("Failed to load legal documents");
|
||||||
|
setErrorDescription(
|
||||||
|
"An error occurred while fetching the legal documents from the server. Please try again later.",
|
||||||
|
);
|
||||||
|
log(0, "Legal", "red", "Failed to fetch legal documents", err);
|
||||||
} finally {
|
} finally {
|
||||||
|
clearTimeout(loadingTimer);
|
||||||
if (active) {
|
if (active) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -131,6 +139,7 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
active = false;
|
active = false;
|
||||||
|
clearTimeout(loadingTimer);
|
||||||
};
|
};
|
||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
|
|
@ -139,7 +148,22 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || userId === undefined) {
|
if (loading || userId === undefined) {
|
||||||
return null;
|
if (!showLoading) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CreateScreen>
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<Spinner className="size-8" />
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Fetching legal documents...
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
label="status.methanium.net"
|
||||||
|
link="https://status.methanium.net"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CreateScreen>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsMatch =
|
const docsMatch =
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
libglvnd
|
libglvnd
|
||||||
libnotify
|
libnotify
|
||||||
libpulseaudio
|
libpulseaudio
|
||||||
|
libsecret
|
||||||
libuuid
|
libuuid
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
mesa
|
mesa
|
||||||
|
|
@ -104,7 +105,8 @@
|
||||||
cp -r usr/* "$out/"
|
cp -r usr/* "$out/"
|
||||||
|
|
||||||
mkdir -p "$out/bin"
|
mkdir -p "$out/bin"
|
||||||
makeWrapper "$out/opt/Tensamin/tensamin" "$out/bin/tensamin"
|
makeWrapper "$out/opt/Tensamin/tensamin" "$out/bin/tensamin" \
|
||||||
|
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath electronRuntimeLibs}"
|
||||||
|
|
||||||
substituteInPlace "$out/share/applications/Tensamin.desktop" \
|
substituteInPlace "$out/share/applications/Tensamin.desktop" \
|
||||||
--replace-fail "Exec=/opt/Tensamin/tensamin" "Exec=tensamin"
|
--replace-fail "Exec=/opt/Tensamin/tensamin" "Exec=tensamin"
|
||||||
|
|
@ -172,6 +174,7 @@
|
||||||
libglvnd
|
libglvnd
|
||||||
libnotify
|
libnotify
|
||||||
libpulseaudio
|
libpulseaudio
|
||||||
|
libsecret
|
||||||
libuuid
|
libuuid
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
mesa
|
mesa
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"build": "tsc -p tsconfig.json --noEmit"
|
"build": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/cache": "workspace:*",
|
"@tensamin/cache": "workspace:*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/mtp": "workspace:*",
|
"@tensamin/mtp": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import type {} from "@tensamin/shared/desktopMedia";
|
import type {} from "@tensamin/shared/desktopMedia";
|
||||||
|
import { isTauri } from "@tauri-apps/api/core";
|
||||||
import { getDatabaseEntry, setDatabaseEntry } from "@tensamin/shared/indexedDb";
|
import { getDatabaseEntry, setDatabaseEntry } from "@tensamin/shared/indexedDb";
|
||||||
|
|
||||||
export type SecureStorageStatus = {
|
export type SecureStorageStatus = {
|
||||||
backend: "electron-keyring" | "webcrypto" | "indexeddb";
|
backend:
|
||||||
|
"electron-keyring" | "application-storage" | "webcrypto" | "indexeddb";
|
||||||
secure: boolean;
|
secure: boolean;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
};
|
};
|
||||||
|
|
@ -84,6 +86,7 @@ export function isSecureEnvelope(value: unknown): value is SecureEnvelope {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function encodeSecureValue(value: unknown): Promise<unknown> {
|
export async function encodeSecureValue(value: unknown): Promise<unknown> {
|
||||||
|
if (isTauri()) return value;
|
||||||
const key = await getKey();
|
const key = await getKey();
|
||||||
if (!key) return value;
|
if (!key) return value;
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
|
@ -127,6 +130,7 @@ export async function getSecureStorageStatus(): Promise<SecureStorageStatus> {
|
||||||
: "Electron secure storage is unavailable.",
|
: "Electron secure storage is unavailable.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (isTauri()) return { backend: "application-storage", secure: true };
|
||||||
return (await getKey())
|
return (await getKey())
|
||||||
? { backend: "webcrypto", secure: true }
|
? { backend: "webcrypto", secure: true }
|
||||||
: {
|
: {
|
||||||
|
|
|
||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -818,6 +818,9 @@ importers:
|
||||||
|
|
||||||
packages/storage:
|
packages/storage:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@tauri-apps/api':
|
||||||
|
specifier: ^2
|
||||||
|
version: 2.11.1
|
||||||
'@tensamin/cache':
|
'@tensamin/cache':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../cache
|
version: link:../cache
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue