feat: removing crypto from level

This commit is contained in:
Chubby Granny Chaser 2025-02-15 19:28:38 +00:00
parent 47e6d88dd9
commit 0f0e27e2e5
No known key found for this signature in database
12 changed files with 88 additions and 134 deletions

View file

@ -3,7 +3,6 @@ import jwt from "jsonwebtoken";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { db, levelKeys } from "@main/level"; import { db, levelKeys } from "@main/level";
import type { Auth } from "@types"; import type { Auth } from "@types";
import { Crypto } from "@main/services";
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => { const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
const auth = await db.get<string, Auth>(levelKeys.auth, { const auth = await db.get<string, Auth>(levelKeys.auth, {
@ -11,9 +10,7 @@ const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
}); });
if (!auth) return null; if (!auth) return null;
const payload = jwt.decode( const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload;
Crypto.decrypt(auth.accessToken)
) as jwt.JwtPayload;
if (!payload) return null; if (!payload) return null;

View file

@ -1,6 +1,6 @@
import { shell } from "electron"; import { shell } from "electron";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { Crypto, HydraApi } from "@main/services"; import { HydraApi } from "@main/services";
import { db, levelKeys } from "@main/level"; import { db, levelKeys } from "@main/level";
import type { Auth } from "@types"; import type { Auth } from "@types";
@ -14,7 +14,7 @@ const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
} }
const paymentToken = await HydraApi.post("/auth/payment", { const paymentToken = await HydraApi.post("/auth/payment", {
refreshToken: Crypto.decrypt(auth.refreshToken), refreshToken: auth.refreshToken,
}).then((response) => response.accessToken); }).then((response) => response.accessToken);
const params = new URLSearchParams({ const params = new URLSearchParams({

View file

@ -1,21 +1,10 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { db, levelKeys } from "@main/level"; import { db, levelKeys } from "@main/level";
import { Crypto } from "@main/services";
import type { UserPreferences } from "@types"; import type { UserPreferences } from "@types";
const getUserPreferences = async () => const getUserPreferences = async () =>
db db.get<string, UserPreferences | null>(levelKeys.userPreferences, {
.get<string, UserPreferences | null>(levelKeys.userPreferences, { valueEncoding: "json",
valueEncoding: "json", });
})
.then((userPreferences) => {
if (userPreferences?.realDebridApiToken) {
userPreferences.realDebridApiToken = Crypto.decrypt(
userPreferences.realDebridApiToken
);
}
return userPreferences;
});
registerEvent("getUserPreferences", getUserPreferences); registerEvent("getUserPreferences", getUserPreferences);

View file

@ -1,10 +1,4 @@
import { import { DownloadManager, logger, Ludusavi, startMainLoop } from "./services";
Crypto,
DownloadManager,
logger,
Ludusavi,
startMainLoop,
} from "./services";
import { RealDebridClient } from "./services/download/real-debrid"; import { RealDebridClient } from "./services/download/real-debrid";
import { HydraApi } from "./services/hydra-api"; import { HydraApi } from "./services/hydra-api";
import { uploadGamesBatch } from "./services/library-sync"; import { uploadGamesBatch } from "./services/library-sync";
@ -27,9 +21,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
Aria2.spawn(); Aria2.spawn();
if (userPreferences?.realDebridApiToken) { if (userPreferences?.realDebridApiToken) {
RealDebridClient.authorize( RealDebridClient.authorize(userPreferences.realDebridApiToken);
Crypto.decrypt(userPreferences.realDebridApiToken)
);
} }
Ludusavi.addManifestToLudusaviConfig(); Ludusavi.addManifestToLudusaviConfig();
@ -106,9 +98,7 @@ const migrateFromSqlite = async () => {
await db.put(levelKeys.userPreferences, { await db.put(levelKeys.userPreferences, {
...rest, ...rest,
realDebridApiToken: realDebridApiToken realDebridApiToken,
? Crypto.encrypt(realDebridApiToken)
: null,
preferQuitInsteadOfHiding: rest.preferQuitInsteadOfHiding === 1, preferQuitInsteadOfHiding: rest.preferQuitInsteadOfHiding === 1,
runAtStartup: rest.runAtStartup === 1, runAtStartup: rest.runAtStartup === 1,
startMinimized: rest.startMinimized === 1, startMinimized: rest.startMinimized === 1,
@ -171,8 +161,8 @@ const migrateFromSqlite = async () => {
await db.put<string, Auth>( await db.put<string, Auth>(
levelKeys.auth, levelKeys.auth,
{ {
accessToken: Crypto.encrypt(users[0].accessToken), accessToken: users[0].accessToken,
refreshToken: Crypto.encrypt(users[0].refreshToken), refreshToken: users[0].refreshToken,
tokenExpirationTimestamp: users[0].tokenExpirationTimestamp, tokenExpirationTimestamp: users[0].tokenExpirationTimestamp,
}, },
{ {

View file

@ -1,28 +0,0 @@
import { safeStorage } from "electron";
import { logger } from "./logger";
export class Crypto {
public static encrypt(str: string) {
if (safeStorage.isEncryptionAvailable()) {
return safeStorage.encryptString(str).toString("base64");
} else {
logger.warn(
"Encrypt method returned raw string because encryption is not available"
);
return str;
}
}
public static decrypt(b64: string) {
if (safeStorage.isEncryptionAvailable()) {
return safeStorage.decryptString(Buffer.from(b64, "base64"));
} else {
logger.warn(
"Decrypt method returned raw string because encryption is not available"
);
return b64;
}
}
}

View file

@ -12,7 +12,6 @@ import { isFuture, isToday } from "date-fns";
import { db } from "@main/level"; import { db } from "@main/level";
import { levelKeys } from "@main/level/sublevels"; import { levelKeys } from "@main/level/sublevels";
import type { Auth, User } from "@types"; import type { Auth, User } from "@types";
import { Crypto } from "./crypto";
interface HydraApiOptions { interface HydraApiOptions {
needsAuth?: boolean; needsAuth?: boolean;
@ -32,7 +31,9 @@ export class HydraApi {
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes
private static readonly ADD_LOG_INTERCEPTOR = true; private static readonly ADD_LOG_INTERCEPTOR = true;
private static secondsToMilliseconds = (seconds: number) => seconds * 1000; private static secondsToMilliseconds(seconds: number) {
return seconds * 1000;
}
private static userAuth: HydraApiUserAuth = { private static userAuth: HydraApiUserAuth = {
authToken: "", authToken: "",
@ -80,8 +81,8 @@ export class HydraApi {
db.put<string, Auth>( db.put<string, Auth>(
levelKeys.auth, levelKeys.auth,
{ {
accessToken: Crypto.encrypt(accessToken), accessToken,
refreshToken: Crypto.encrypt(refreshToken), refreshToken,
tokenExpirationTimestamp, tokenExpirationTimestamp,
}, },
{ valueEncoding: "json" } { valueEncoding: "json" }
@ -194,12 +195,8 @@ export class HydraApi {
const user = result.at(1) as User | undefined; const user = result.at(1) as User | undefined;
this.userAuth = { this.userAuth = {
authToken: userAuth?.accessToken authToken: userAuth?.accessToken ?? "",
? Crypto.decrypt(userAuth.accessToken) refreshToken: userAuth?.refreshToken ?? "",
: "",
refreshToken: userAuth?.refreshToken
? Crypto.decrypt(userAuth.refreshToken)
: "",
expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0, expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0,
subscription: user?.subscription subscription: user?.subscription
? { expiresAt: user.subscription?.expiresAt } ? { expiresAt: user.subscription?.expiresAt }
@ -248,7 +245,7 @@ export class HydraApi {
levelKeys.auth, levelKeys.auth,
{ {
...auth, ...auth,
accessToken: Crypto.encrypt(accessToken), accessToken,
tokenExpirationTimestamp, tokenExpirationTimestamp,
}, },
{ valueEncoding: "json" } { valueEncoding: "json" }

View file

@ -234,7 +234,7 @@ export class WindowManager {
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
editorWindow.loadURL( editorWindow.loadURL(
`${process.env["ELECTRON_RENDERER_URL"]}#/editor?themeId=${themeId}` `${process.env["ELECTRON_RENDERER_URL"]}#/theme-editor?themeId=${themeId}`
); );
} else { } else {
editorWindow.loadFile(path.join(__dirname, "../renderer/index.html"), { editorWindow.loadFile(path.join(__dirname, "../renderer/index.html"), {

View file

@ -30,7 +30,6 @@ import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-m
import { injectCustomCss } from "./helpers"; import { injectCustomCss } from "./helpers";
import "./app.scss"; import "./app.scss";
import { Theme } from "@types";
export interface AppProps { export interface AppProps {
children: React.ReactNode; children: React.ReactNode;
@ -214,22 +213,22 @@ export function App() {
const id = crypto.randomUUID(); const id = crypto.randomUUID();
const channel = new BroadcastChannel(`download_sources:sync:${id}`); const channel = new BroadcastChannel(`download_sources:sync:${id}`);
channel.onmessage = (event: MessageEvent<number>) => { channel.onmessage = async (event: MessageEvent<number>) => {
const newRepacksCount = event.data; const newRepacksCount = event.data;
window.electron.publishNewRepacksNotification(newRepacksCount); window.electron.publishNewRepacksNotification(newRepacksCount);
updateRepacks(); updateRepacks();
downloadSourcesTable.toArray().then((downloadSources) => { const downloadSources = await downloadSourcesTable.toArray();
downloadSources
.filter((source) => !source.fingerprint) downloadSources
.forEach((downloadSource) => { .filter((source) => !source.fingerprint)
window.electron .forEach(async (downloadSource) => {
.putDownloadSource(downloadSource.objectIds) const { fingerprint } = await window.electron.putDownloadSource(
.then(({ fingerprint }) => { downloadSource.objectIds
downloadSourcesTable.update(downloadSource.id, { fingerprint }); );
});
}); downloadSourcesTable.update(downloadSource.id, { fingerprint });
}); });
}; };
downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]);
@ -237,9 +236,9 @@ export function App() {
useEffect(() => { useEffect(() => {
const loadAndApplyTheme = async () => { const loadAndApplyTheme = async () => {
const activeTheme: Theme = await window.electron.getActiveCustomTheme(); const activeTheme = await window.electron.getActiveCustomTheme();
if (activeTheme.code) { if (activeTheme?.code) {
injectCustomCss(activeTheme.code); injectCustomCss(activeTheme.code);
} }
}; };

View file

@ -33,7 +33,9 @@ const Profile = React.lazy(() => import("./pages/profile/profile"));
const Achievements = React.lazy( const Achievements = React.lazy(
() => import("./pages/achievements/achievements") () => import("./pages/achievements/achievements")
); );
const Editor = React.lazy(() => import("./pages/editor/editor")); const ThemeEditor = React.lazy(
() => import("./pages/theme-editor/theme-editor")
);
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
@ -107,8 +109,8 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
</Route> </Route>
<Route <Route
path="/editor" path="/theme-editor"
element={<SuspenseWrapper Component={Editor} />} element={<SuspenseWrapper Component={ThemeEditor} />}
/> />
</Routes> </Routes>
</HashRouter> </HashRouter>

View file

@ -12,7 +12,7 @@ export function DeleteGameModal({
onClose, onClose,
visible, visible,
deleteGame, deleteGame,
}: DeleteGameModalProps) { }: Readonly<DeleteGameModalProps>) {
const { t } = useTranslation("downloads"); const { t } = useTranslation("downloads");
const handleDeleteGame = () => { const handleDeleteGame = () => {

View file

@ -1,22 +1,25 @@
@use "../../scss/globals.scss" as globals; @use "../../scss/globals.scss";
.editor { .theme-editor {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
width: 100%; width: 100%;
&__header { &__header {
height: 35px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px; padding: calc(globals.$spacing-unit * 2);
background-color: globals.$dark-background-color; background-color: globals.$dark-background-color;
font-size: 8px; font-size: 8px;
z-index: 50; z-index: 50;
-webkit-app-region: drag; -webkit-app-region: drag;
gap: 8px; gap: 8px;
&--darwin {
padding-top: calc(globals.$spacing-unit * 6);
}
h1 { h1 {
margin: 0; margin: 0;
line-height: 1; line-height: 1;

View file

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import "./editor.scss"; import "./theme-editor.scss";
import Editor from "@monaco-editor/react"; import Editor from "@monaco-editor/react";
import { Theme } from "@types"; import { Theme } from "@types";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
@ -10,8 +10,9 @@ import {
ProjectRoadmapIcon, ProjectRoadmapIcon,
} from "@primer/octicons-react"; } from "@primer/octicons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import cn from "classnames";
const EditorPage = () => { export default function ThemeEditor() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const [theme, setTheme] = useState<Theme | null>(null); const [theme, setTheme] = useState<Theme | null>(null);
const [code, setCode] = useState(""); const [code, setCode] = useState("");
@ -37,29 +38,7 @@ const EditorPage = () => {
} }
}, [themeId]); }, [themeId]);
useEffect(() => { const handleSave = useCallback(async () => {
const handleKeyDown = (event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
event.preventDefault();
handleSave();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [code, theme]);
const handleEditorChange = (value: string | undefined) => {
if (value !== undefined) {
setCode(value);
setHasUnsavedChanges(true);
}
};
const handleSave = async () => {
if (theme) { if (theme) {
const updatedTheme = { const updatedTheme = {
...theme, ...theme,
@ -74,13 +53,41 @@ const EditorPage = () => {
window.electron.injectCSS(code); window.electron.injectCSS(code);
} }
} }
}, [code, theme]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
event.preventDefault();
handleSave();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [code, handleSave, theme]);
const handleEditorChange = (value: string | undefined) => {
if (value !== undefined) {
setCode(value);
setHasUnsavedChanges(true);
}
}; };
return ( return (
<div className="editor"> <div className="theme-editor">
<div className="editor__header"> <div
className={cn("theme-editor__header", {
"theme-editor__header--darwin": window.electron.platform === "darwin",
})}
>
<h1>{theme?.name}</h1> <h1>{theme?.name}</h1>
{hasUnsavedChanges && <div className="editor__header__status"></div>} {hasUnsavedChanges && (
<div className="theme-editor__header__status"></div>
)}
</div> </div>
{activeTab === "code" && ( {activeTab === "code" && (
@ -100,15 +107,15 @@ const EditorPage = () => {
)} )}
{activeTab === "info" && ( {activeTab === "info" && (
<div className="editor__info"> <div className="theme-editor__info">
entao mano eu ate fiz isso aqui mas tava feio dms ai deu vergonha e entao mano eu ate fiz isso aqui mas tava feio dms ai deu vergonha e
removi kkkk removi kkkk
</div> </div>
)} )}
<div className="editor__footer"> <div className="theme-editor__footer">
<div className="editor__footer-actions"> <div className="theme-editor__footer-actions">
<div className="editor__footer-actions__tabs"> <div className="theme-editor__footer-actions__tabs">
<Button <Button
onClick={() => handleTabChange("code")} onClick={() => handleTabChange("code")}
theme="dark" theme="dark"
@ -135,6 +142,4 @@ const EditorPage = () => {
</div> </div>
</div> </div>
); );
}; }
export default EditorPage;