completely cleanup of the legacy hono router to leaner svelte remote functions

This commit is contained in:
user
2026-03-01 04:21:55 +02:00
parent ca056b817d
commit 596dcc78fc
18 changed files with 610 additions and 1379 deletions

View File

@@ -1,9 +1,11 @@
import { apiClient } from "$lib/global.stores";
import {
disableTwoFactorSC,
generateBackupCodesSQ,
setupTwoFactorSC,
verifyAndEnableTwoFactorSC,
} from "$lib/domains/security/twofa.remote";
import { toast } from "svelte-sonner";
import { get } from "svelte/store";
import QRCode from "qrcode";
import { ResultAsync, errAsync, okAsync } from "neverthrow";
import type { Err } from "@pkg/result";
class TwoFactorViewModel {
twoFactorEnabled = $state(false);
@@ -20,86 +22,38 @@ class TwoFactorViewModel {
this.isLoading = true;
this.errorMessage = null;
const result = await ResultAsync.fromPromise(
get(apiClient).twofactor.setup.$post({
json: { code: "" },
}),
(error): Err => ({
code: "NETWORK_ERROR",
message: "Failed to set up two-factor authentication.",
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
)
.andThen((response) => {
if (!response.ok) {
return errAsync({
code: "API_ERROR",
message: "Failed to set up two-factor authentication.",
description: `Response failed with status ${response.status}`,
detail: `HTTP ${response.status}`,
});
}
return ResultAsync.fromPromise(
response.json(),
(error): Err => ({
code: "PARSING_ERROR",
message: "Failed to parse response",
description: "Invalid response format",
detail: error instanceof Error ? error.message : String(error),
}),
);
})
.andThen((apiResult) => {
if (apiResult.error) {
return errAsync(apiResult.error);
}
if (!apiResult.data || !apiResult.data.totpURI) {
return errAsync({
code: "API_ERROR",
message: "Failed to get 2FA setup information",
description: "Invalid response data",
detail: "Missing totpURI in response",
});
}
return okAsync(apiResult.data);
})
.andThen((data) => {
return ResultAsync.fromPromise(
QRCode.toDataURL(data.totpURI, {
width: 256,
margin: 2,
color: { dark: "#000000", light: "#FFFFFF" },
}),
(error): Err => ({
code: "PROCESSING_ERROR",
message: "Failed to generate QR code.",
description: "QR code generation failed",
detail: error instanceof Error ? error.message : String(error),
}),
).map((qrCodeDataUrl) => ({
...data,
qrCodeDataUrl,
}));
try {
const result = await setupTwoFactorSC({});
if (result?.error || !result?.data?.totpURI) {
this.errorMessage =
result?.error?.message || "Could not enable 2FA";
toast.error(this.errorMessage, {
description:
result?.error?.description || "Please try again later",
});
return;
}
const qrCodeDataUrl = await QRCode.toDataURL(result.data.totpURI, {
width: 256,
margin: 2,
color: { dark: "#000000", light: "#FFFFFF" },
});
result.match(
({ qrCodeDataUrl, secret, totpURI }) => {
this.qrCodeUrl = qrCodeDataUrl;
this.twoFactorSetupInProgress = true;
this.twoFactorSecret = secret;
this.twoFactorVerificationCode = "";
toast("Setup enabled");
},
(error) => {
this.errorMessage = error.message || "Could not enable 2FA";
toast.error(this.errorMessage || "Could not enable 2FA", {
description: error.description || "Please try again later",
});
},
);
this.isLoading = false;
this.qrCodeUrl = qrCodeDataUrl;
this.twoFactorSetupInProgress = true;
this.twoFactorSecret = result.data.secret;
this.twoFactorVerificationCode = "";
toast("Setup enabled");
} catch (error) {
this.errorMessage = "Could not enable 2FA";
toast.error(this.errorMessage, {
description:
error instanceof Error ? error.message : "Please try again later",
});
} finally {
this.isLoading = false;
}
}
async completeTwoFactorSetup() {
@@ -112,233 +66,109 @@ class TwoFactorViewModel {
this.isLoading = true;
this.errorMessage = null;
const verifyResult = await ResultAsync.fromPromise(
get(apiClient).twofactor["verify-and-enable"].$post({
json: { code: this.twoFactorVerificationCode },
}),
(error): Err => ({
code: "NETWORK_ERROR",
message: "Failed to verify code.",
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
)
.andThen((response) => {
if (!response.ok) {
return errAsync({
code: "API_ERROR",
message: "Failed to verify code.",
description: `Response failed with status ${response.status}`,
detail: `HTTP ${response.status}`,
});
}
return ResultAsync.fromPromise(
response.json(),
(error): Err => ({
code: "PARSING_ERROR",
message: "Failed to parse response",
description: "Invalid response format",
detail: error instanceof Error ? error.message : String(error),
}),
);
})
.andThen((apiResult) => {
if (apiResult.error) {
return errAsync(apiResult.error);
}
return okAsync(apiResult.data);
try {
const verifyResult = await verifyAndEnableTwoFactorSC({
code: this.twoFactorVerificationCode,
});
verifyResult.match(
async () => {
const backupCodesResult = await ResultAsync.fromPromise(
get(apiClient).twofactor["generate-backup-codes"].$get(),
(error): Err => ({
code: "NETWORK_ERROR",
message: "Failed to generate backup codes",
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
)
.andThen((response) => {
if (!response.ok) {
return errAsync({
code: "API_ERROR",
message: "Failed to generate backup codes",
description: `Response failed with status ${response.status}`,
detail: `HTTP ${response.status}`,
});
}
return ResultAsync.fromPromise(
response.json(),
(error): Err => ({
code: "PARSING_ERROR",
message: "Failed to parse response",
description: "Invalid response format",
detail: error instanceof Error ? error.message : String(error),
}),
);
})
.andThen((apiResult) => {
if (apiResult.error) {
return errAsync(apiResult.error);
}
return okAsync(apiResult.data || []);
});
backupCodesResult.match(
(backupCodes) => {
this.backupCodes = backupCodes;
this.showingBackupCodes = true;
},
() => {
toast.error(
"2FA enabled, but failed to generate backup codes",
{
description: "You can generate them later in settings",
},
);
},
);
this.twoFactorEnabled = true;
this.twoFactorSetupInProgress = false;
this.twoFactorVerificationCode = "";
toast.success("Two-factor authentication enabled", {
description: "Your account is now more secure",
if (verifyResult?.error) {
this.errorMessage =
verifyResult.error.message || "Invalid verification code";
toast.error(this.errorMessage, {
description:
verifyResult.error.description || "Please try again",
});
},
(error) => {
this.errorMessage = error.message || "Invalid verification code";
toast.error(this.errorMessage || "Invalid verification code", {
description: error.description || "Please try again",
});
},
);
return;
}
this.isLoading = false;
const backupCodesResult = await generateBackupCodesSQ();
if (backupCodesResult?.error || !Array.isArray(backupCodesResult?.data)) {
toast.error("2FA enabled, but failed to generate backup codes", {
description: "You can generate them later in settings",
});
} else {
this.backupCodes = backupCodesResult.data;
this.showingBackupCodes = true;
}
this.twoFactorEnabled = true;
this.twoFactorSetupInProgress = false;
this.twoFactorVerificationCode = "";
toast.success("Two-factor authentication enabled", {
description: "Your account is now more secure",
});
} catch (error) {
this.errorMessage = "Invalid verification code";
toast.error(this.errorMessage, {
description:
error instanceof Error ? error.message : "Please try again",
});
} finally {
this.isLoading = false;
}
}
async disableTwoFactor() {
this.isLoading = true;
this.errorMessage = null;
const result = await ResultAsync.fromPromise(
get(apiClient).twofactor.disable.$delete({
json: { code: "" },
}),
(error): Err => ({
code: "NETWORK_ERROR",
message: "Failed to disable two-factor authentication.",
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
)
.andThen((response) => {
if (!response.ok) {
return errAsync({
code: "API_ERROR",
message: "Failed to disable two-factor authentication.",
description: `Response failed with status ${response.status}`,
detail: `HTTP ${response.status}`,
});
}
return ResultAsync.fromPromise(
response.json(),
(error): Err => ({
code: "PARSING_ERROR",
message: "Failed to parse response",
description: "Invalid response format",
detail: error instanceof Error ? error.message : String(error),
}),
);
})
.andThen((apiResult) => {
if (apiResult.error) {
return errAsync(apiResult.error);
}
return okAsync(apiResult.data);
});
result.match(
() => {
this.twoFactorEnabled = false;
this.backupCodes = [];
this.qrCodeUrl = null;
this.showingBackupCodes = false;
this.twoFactorSecret = null;
toast.success("Two-factor authentication disabled");
},
(error) => {
this.errorMessage = error.message || "Failed to disable 2FA";
toast.error(this.errorMessage || "Failed to disable 2FA", {
description:
error.description || "Please try again later",
try {
const result = await disableTwoFactorSC({ code: "" });
if (result?.error) {
this.errorMessage = result.error.message || "Failed to disable 2FA";
toast.error(this.errorMessage, {
description: result.error.description || "Please try again later",
});
},
);
return;
}
this.isLoading = false;
this.twoFactorEnabled = false;
this.backupCodes = [];
this.qrCodeUrl = null;
this.showingBackupCodes = false;
this.twoFactorSecret = null;
toast.success("Two-factor authentication disabled");
} catch (error) {
this.errorMessage = "Failed to disable 2FA";
toast.error(this.errorMessage, {
description:
error instanceof Error ? error.message : "Please try again later",
});
} finally {
this.isLoading = false;
}
}
async generateNewBackupCodes() {
this.isLoading = true;
this.errorMessage = null;
const result = await ResultAsync.fromPromise(
get(apiClient).twofactor["generate-backup-codes"].$get(),
(error): Err => ({
code: "NETWORK_ERROR",
message: "Failed to generate new backup codes.",
description: "Network request failed",
detail: error instanceof Error ? error.message : String(error),
}),
)
.andThen((response) => {
if (!response.ok) {
return errAsync({
code: "API_ERROR",
message: "Failed to generate new backup codes.",
description: `Response failed with status ${response.status}`,
detail: `HTTP ${response.status}`,
});
}
return ResultAsync.fromPromise(
response.json(),
(error): Err => ({
code: "PARSING_ERROR",
message: "Failed to parse response",
description: "Invalid response format",
detail: error instanceof Error ? error.message : String(error),
}),
);
})
.andThen((apiResult) => {
if (apiResult.error) {
return errAsync(apiResult.error);
}
return okAsync(apiResult.data || []);
});
result.match(
(backupCodes) => {
this.backupCodes = backupCodes;
this.showingBackupCodes = true;
toast.success("New backup codes generated", {
description: "Your previous backup codes are now invalid",
});
},
(error) => {
try {
const result = await generateBackupCodesSQ();
if (result?.error || !Array.isArray(result?.data)) {
this.errorMessage =
error.message || "Failed to generate new backup codes";
toast.error(this.errorMessage || "Failed to generate new backup codes", {
result?.error?.message || "Failed to generate new backup codes";
toast.error(this.errorMessage, {
description:
error.description || "Please try again later",
result?.error?.description || "Please try again later",
});
},
);
return;
}
this.isLoading = false;
this.backupCodes = result.data;
this.showingBackupCodes = true;
toast.success("New backup codes generated", {
description: "Your previous backup codes are now invalid",
});
} catch (error) {
this.errorMessage = "Failed to generate new backup codes";
toast.error(this.errorMessage, {
description:
error instanceof Error ? error.message : "Please try again later",
});
} finally {
this.isLoading = false;
}
}
copyAllBackupCodes() {