completely cleanup of the legacy hono router to leaner svelte remote functions
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user