diff --git a/apps/main/package.json b/apps/main/package.json index 8fdc432..ca13d6b 100644 --- a/apps/main/package.json +++ b/apps/main/package.json @@ -21,19 +21,16 @@ "@opentelemetry/exporter-logs-otlp-proto": "^0.212.0", "@opentelemetry/exporter-metrics-otlp-proto": "^0.212.0", "@opentelemetry/exporter-trace-otlp-proto": "^0.212.0", - "@opentelemetry/sdk-node": "^0.212.0", "@opentelemetry/sdk-logs": "^0.212.0", + "@opentelemetry/sdk-node": "^0.212.0", "@pkg/db": "workspace:*", "@pkg/logger": "workspace:*", "@pkg/logic": "workspace:*", "@pkg/result": "workspace:*", "@pkg/settings": "workspace:*", - "@tanstack/svelte-query": "^6.0.18", "better-auth": "^1.4.20", "date-fns": "^4.1.0", - "hono": "^4.11.1", "import-in-the-middle": "^3.0.0", - "marked": "^17.0.1", "nanoid": "^5.1.6", "neverthrow": "^8.2.0", "qrcode": "^1.5.4", diff --git a/apps/main/src/lib/api.ts b/apps/main/src/lib/api.ts deleted file mode 100644 index 64d841e..0000000 --- a/apps/main/src/lib/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { usersRouter } from "@pkg/logic/domains/user/router"; -import { twofaRouter } from "@pkg/logic/domains/2fa/router"; -import { Hono } from "hono"; - -const baseRouter = new Hono() - .route("/users", usersRouter) - .route("/twofactor", twofaRouter); - -export const apiBasePath = "/api/v1"; - -export const api = new Hono().route(apiBasePath, baseRouter); - -export type Router = typeof baseRouter; diff --git a/apps/main/src/lib/components/molecules/markdown-renderer.svelte b/apps/main/src/lib/components/molecules/markdown-renderer.svelte deleted file mode 100644 index 6459612..0000000 --- a/apps/main/src/lib/components/molecules/markdown-renderer.svelte +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - -
- {@html marked(content)} -
- - diff --git a/apps/main/src/lib/core/server.utils.ts b/apps/main/src/lib/core/server.utils.ts index 10c586c..450c7dd 100644 --- a/apps/main/src/lib/core/server.utils.ts +++ b/apps/main/src/lib/core/server.utils.ts @@ -1,4 +1,5 @@ import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context"; +import type { Err } from "@pkg/result"; export async function getFlowExecCtxForRemoteFuncs( locals: App.Locals, @@ -9,3 +10,16 @@ export async function getFlowExecCtxForRemoteFuncs( sessionId: locals.session?.id, }; } + +export function unauthorized(fctx: FlowExecCtx) { + return { + data: null, + error: { + flowId: fctx.flowId, + code: "UNAUTHORIZED", + message: "User not authenticated", + description: "Please log in", + detail: "No user ID found in session", + } as Err, + }; +} diff --git a/apps/main/src/lib/currency.utils.ts b/apps/main/src/lib/currency.utils.ts deleted file mode 100644 index 1dc9712..0000000 --- a/apps/main/src/lib/currency.utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function formatCurrency(amount: number, currency = "EUR"): string { - return new Intl.NumberFormat("en-US", { style: "currency", currency }).format( - amount, - ); -} diff --git a/apps/main/src/lib/domains/account/account.remote.ts b/apps/main/src/lib/domains/account/account.remote.ts index aac3c36..3dc2e38 100644 --- a/apps/main/src/lib/domains/account/account.remote.ts +++ b/apps/main/src/lib/domains/account/account.remote.ts @@ -1,22 +1,168 @@ -import { ensureAccountExistsSchema } from "@pkg/logic/domains/user/data"; +import { + banUserSchema, + checkUsernameSchema, + ensureAccountExistsSchema, + rotatePasswordSchema, +} from "@pkg/logic/domains/user/data"; +import { + getFlowExecCtxForRemoteFuncs, + unauthorized, +} from "$lib/core/server.utils"; +import { getUserController } from "@pkg/logic/domains/user/controller"; import { command, getRequestEvent, query } from "$app/server"; +import * as v from "valibot"; + +const uc = getUserController(); export const getMyInfoSQ = query(async () => { const event = getRequestEvent(); - // .... do stuff usually done in a router here + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); - return { data: "testing" }; + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.getUserInfo(fctx, fctx.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; }); -export const getUsersInfoByIdSQ = query(async () => { - // .... do stuff usually done in a router here - return { data: "testing" }; -}); +export const getUserInfoByIdSQ = query( + v.object({ userId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); -export const ensureAccountExistsSQ = command( - ensureAccountExistsSchema, - async (payload) => { - // .... do stuff usually done in a router here - return { data: "testing" }; + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.getUserInfo(fctx, input.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const ensureAccountExistsSC = command( + ensureAccountExistsSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.ensureAccountExists(fctx, payload.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const checkUsernameSC = command(checkUsernameSchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.isUsernameAvailable(fctx, payload.username); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const update2faVerifiedSC = command( + v.object({ userId: v.string() }), + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.updateLastVerified2FaAtToNow(fctx, payload.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const banUserSC = command(banUserSchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.banUser( + fctx, + payload.userId, + payload.reason, + payload.banExpiresAt, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const isUserBannedSQ = query( + v.object({ userId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.isUserBanned(fctx, input.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const getBanInfoSQ = query( + v.object({ userId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.getBanInfo(fctx, input.userId); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const rotatePasswordSC = command( + rotatePasswordSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await uc.rotatePassword( + fctx, + payload.userId, + payload.password, + ); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; }, ); diff --git a/apps/main/src/lib/domains/account/account.vm.svelte.ts b/apps/main/src/lib/domains/account/account.vm.svelte.ts index 39bd03d..f17913f 100644 --- a/apps/main/src/lib/domains/account/account.vm.svelte.ts +++ b/apps/main/src/lib/domains/account/account.vm.svelte.ts @@ -1,10 +1,11 @@ +import { ResultAsync, errAsync, okAsync } from "neverthrow"; import type { User } from "@pkg/logic/domains/user/data"; -import { apiClient, user as userStore } from "$lib/global.stores"; +import { user as userStore } from "$lib/global.stores"; +import { rotatePasswordSC } from "./account.remote"; import { authClient } from "$lib/auth.client"; +import type { Err } from "@pkg/result"; import { toast } from "svelte-sonner"; import { get } from "svelte/store"; -import { ResultAsync, errAsync, okAsync } from "neverthrow"; -import type { Err } from "@pkg/result"; class AccountViewModel { loading = $state(false); @@ -20,22 +21,20 @@ class AccountViewModel { description: "Network request failed", detail: error instanceof Error ? error.message : String(error), }), - ) - .andThen((response) => { - if (response.error) { - return errAsync({ - code: "API_ERROR", - message: - response.error.message ?? - "Failed to update profile picture", - description: - response.error.statusText ?? - "Please try again later", - detail: response.error.statusText ?? "Unknown error", - }); - } - return okAsync(response.data); - }); + ).andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? + "Failed to update profile picture", + description: + response.error.statusText ?? "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); return result.match( () => { @@ -43,7 +42,8 @@ class AccountViewModel { return true; }, (error) => { - this.errorMessage = error.message ?? "Failed to update profile picture"; + this.errorMessage = + error.message ?? "Failed to update profile picture"; toast.error(this.errorMessage, { description: error.description, }); @@ -71,21 +71,19 @@ class AccountViewModel { description: "Network request failed", detail: error instanceof Error ? error.message : String(error), }), - ) - .andThen((response) => { - if (response.error) { - return errAsync({ - code: "API_ERROR", - message: - response.error.message ?? "Failed to update profile", - description: - response.error.statusText ?? - "Please try again later", - detail: response.error.statusText ?? "Unknown error", - }); - } - return okAsync(response.data); - }); + ).andThen((response) => { + if (response.error) { + return errAsync({ + code: "API_ERROR", + message: + response.error.message ?? "Failed to update profile", + description: + response.error.statusText ?? "Please try again later", + detail: response.error.statusText ?? "Unknown error", + }); + } + return okAsync(response.data); + }); const user = result.match( (data) => { @@ -117,52 +115,20 @@ class AccountViewModel { toast.error(this.errorMessage); return false; } - const client = get(apiClient); - if (!client) { - this.passwordLoading = false; - this.errorMessage = "API client not initialized"; - toast.error(this.errorMessage); - return false; - } - const result = await ResultAsync.fromPromise( - client.users["rotate-password"].$put({ - json: { userId: currentUser.id, password }, - }), + rotatePasswordSC({ userId: currentUser.id, password }), (error): Err => ({ code: "NETWORK_ERROR", message: "Failed to change password", 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 change password", - 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: any) => { - if (apiResult.error) { - return errAsync(apiResult.error); - } - return okAsync(apiResult.data); - }); - }); + ).andThen((apiResult: any) => { + if (apiResult?.error) { + return errAsync(apiResult.error); + } + return okAsync(apiResult?.data); + }); const success = result.match( () => { @@ -170,7 +136,8 @@ class AccountViewModel { return true; }, (error) => { - this.errorMessage = error.message ?? "Failed to change password"; + this.errorMessage = + (error.message as string) ?? "Failed to change password"; toast.error(this.errorMessage, { description: error.description, }); diff --git a/apps/main/src/lib/domains/account/sessions/sessions.remote.ts b/apps/main/src/lib/domains/account/sessions/sessions.remote.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/main/src/lib/domains/notifications/notifications.remote.ts b/apps/main/src/lib/domains/notifications/notifications.remote.ts index 0e105c8..1c8a8a4 100644 --- a/apps/main/src/lib/domains/notifications/notifications.remote.ts +++ b/apps/main/src/lib/domains/notifications/notifications.remote.ts @@ -3,27 +3,15 @@ import { getNotificationsSchema, } from "@pkg/logic/domains/notifications/data"; import { getNotificationController } from "@pkg/logic/domains/notifications/controller"; -import type { FlowExecCtx } from "@pkg/logic/core/flow.execution.context"; -import { getFlowExecCtxForRemoteFuncs } from "$lib/core/server.utils"; +import { + getFlowExecCtxForRemoteFuncs, + unauthorized, +} from "$lib/core/server.utils"; import { command, getRequestEvent, query } from "$app/server"; -import type { Err } from "@pkg/result"; import * as v from "valibot"; const nc = getNotificationController(); -export async function unauthorized(fctx: FlowExecCtx) { - return { - data: null, - error: { - flowId: fctx.flowId, - code: "UNAUTHORIZED", - message: "User not authenticated", - description: "Please log in", - detail: "No user found in request locals", - } as Err, - }; -} - export const getNotificationsSQ = query( getNotificationsSchema, async (input) => { diff --git a/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts b/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts index db56d95..c5ed5ab 100644 --- a/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts +++ b/apps/main/src/lib/domains/security/2fa-verify.vm.svelte.ts @@ -1,10 +1,12 @@ -import { apiClient, session, user } from "$lib/global.stores"; +import { session, user } from "$lib/global.stores"; import { authClient } from "$lib/auth.client"; +import { + startVerificationSessionSC, + verifySessionCodeSC, +} from "$lib/domains/security/twofa.remote"; import { toast } from "svelte-sonner"; import { get } from "svelte/store"; import { page } from "$app/state"; -import { ResultAsync, errAsync, okAsync } from "neverthrow"; -import type { Err } from "@pkg/result"; class TwoFactorVerifyViewModel { verifying = $state(false); @@ -32,64 +34,33 @@ class TwoFactorVerifyViewModel { return; } - const result = await ResultAsync.fromPromise( - get(apiClient).twofactor["start-verification-session"].$post({ - json: { userId: uid, sessionId: sid }, - }), - (error): Err => ({ - code: "NETWORK_ERROR", - message: "Failed to start verification", - 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 start verification", - 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?.verificationToken) { - return errAsync({ - code: "API_ERROR", - message: "No verification token received", - description: "Invalid response data", - detail: "Missing verificationToken in response", - }); - } - return okAsync(apiResult.data.verificationToken); + try { + const result = await startVerificationSessionSC({ + userId: uid, + sessionId: sid, }); - result.match( - (token) => { - this.verificationToken = token; - }, - (error) => { - this.errorMessage = error.message || "Failed to start verification"; + if (result?.error || !result?.data?.verificationToken) { + this.errorMessage = + result?.error?.message || "Failed to start verification"; toast.error("Failed to start verification", { - description: this.errorMessage || "Failed to start verification", + description: this.errorMessage, }); - }, - ); + return; + } - this.startingVerification = false; + this.verificationToken = result.data.verificationToken; + } catch (error) { + this.errorMessage = "Failed to start verification"; + toast.error("Failed to start verification", { + description: + error instanceof Error + ? error.message + : "Failed to start verification", + }); + } finally { + this.startingVerification = false; + } } async verifyCode() { @@ -106,69 +77,22 @@ class TwoFactorVerifyViewModel { this.verifying = true; this.errorMessage = null; - const result = await ResultAsync.fromPromise( - get(apiClient).twofactor["verify-session-code"].$post({ - json: { - verificationToken: this.verificationToken, - code: this.verificationCode, - }, - }), - (error): Err => ({ - code: "NETWORK_ERROR", - message: "Verification failed", - description: "Network request failed", - detail: error instanceof Error ? error.message : String(error), - }), - ) - .andThen((response) => { - if (!response.ok) { - return errAsync({ - code: "API_ERROR", - message: "Verification failed", - 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?.success) { - return errAsync({ - code: "VALIDATION_ERROR", - message: "Invalid verification code", - description: "Verification failed", - detail: "Code verification unsuccessful", - }); - } - return okAsync(apiResult.data); + try { + const result = await verifySessionCodeSC({ + verificationToken: this.verificationToken, + code: this.verificationCode, }); - result.match( - () => { - const redirectUrl = page.url.searchParams.get("redirect") || "/"; - window.location.href = redirectUrl; - }, - (error) => { - this.errorMessage = error.message || "Failed to verify code"; + if (result?.error || !result?.data?.success) { + this.errorMessage = + result?.error?.message || "Failed to verify code"; - if (error.code === "BANNED") { - authClient.signOut(); + if (result?.error?.code === "BANNED") { + await authClient.signOut(); window.location.href = "/auth/login"; return; } - // Don't show toast for invalid codes, just show in UI if ( this.errorMessage && !this.errorMessage.includes("Invalid") && @@ -178,10 +102,20 @@ class TwoFactorVerifyViewModel { description: this.errorMessage, }); } - }, - ); + return; + } - this.verifying = false; + const redirectUrl = page.url.searchParams.get("redirect") || "/"; + window.location.href = redirectUrl; + } catch (error) { + this.errorMessage = "Failed to verify code"; + toast.error("Verification failed", { + description: + error instanceof Error ? error.message : this.errorMessage, + }); + } finally { + this.verifying = false; + } } async handleBackupCode() { @@ -193,65 +127,25 @@ class TwoFactorVerifyViewModel { this.verifying = true; this.errorMessage = null; - const result = await ResultAsync.fromPromise( - get(apiClient).twofactor["verify-session-code"].$post({ - json: { - verificationToken: this.verificationToken, - code: this.verificationCode, - }, - }), - (error): Err => ({ - code: "NETWORK_ERROR", - message: "Verification failed", - description: "Network request failed", - detail: error instanceof Error ? error.message : String(error), - }), - ) - .andThen((response) => { - if (!response.ok) { - return errAsync({ - code: "API_ERROR", - message: "Verification failed", - 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?.success) { - return errAsync({ - code: "VALIDATION_ERROR", - message: "Invalid backup code", - description: "Verification failed", - detail: "Backup code verification unsuccessful", - }); - } - return okAsync(apiResult.data); + try { + const result = await verifySessionCodeSC({ + verificationToken: this.verificationToken, + code: this.verificationCode, }); - result.match( - () => { - const redirectUrl = page.url.searchParams.get("redirect") || "/"; - window.location.href = redirectUrl; - }, - (error) => { - this.errorMessage = error.message || "Invalid backup code"; - }, - ); + if (result?.error || !result?.data?.success) { + this.errorMessage = result?.error?.message || "Invalid backup code"; + return; + } - this.verifying = false; + const redirectUrl = page.url.searchParams.get("redirect") || "/"; + window.location.href = redirectUrl; + } catch (error) { + this.errorMessage = + error instanceof Error ? error.message : "Invalid backup code"; + } finally { + this.verifying = false; + } } reset() { diff --git a/apps/main/src/lib/domains/security/2fa.vm.svelte.ts b/apps/main/src/lib/domains/security/2fa.vm.svelte.ts index d1579db..b4c7420 100644 --- a/apps/main/src/lib/domains/security/2fa.vm.svelte.ts +++ b/apps/main/src/lib/domains/security/2fa.vm.svelte.ts @@ -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() { diff --git a/apps/main/src/lib/domains/security/twofa.remote.ts b/apps/main/src/lib/domains/security/twofa.remote.ts index e69de29..a8606d7 100644 --- a/apps/main/src/lib/domains/security/twofa.remote.ts +++ b/apps/main/src/lib/domains/security/twofa.remote.ts @@ -0,0 +1,205 @@ +import { + disable2FASchema, + enable2FACodeSchema, + startVerificationSchema, + verifyCodeSchema, +} from "@pkg/logic/domains/2fa/data"; +import { + getFlowExecCtxForRemoteFuncs, + unauthorized, +} from "$lib/core/server.utils"; +import { getTwofaController } from "@pkg/logic/domains/2fa/controller"; +import { command, getRequestEvent, query } from "$app/server"; +import { auth } from "@pkg/logic/domains/auth/config.base"; +import type { User } from "@pkg/logic/domains/user/data"; +import * as v from "valibot"; + +const tc = getTwofaController(); + +function buildIpAddress(headers: Headers) { + return ( + headers.get("x-forwarded-for") ?? headers.get("x-real-ip") ?? "unknown" + ); +} + +function buildUserAgent(headers: Headers) { + return headers.get("user-agent") ?? "unknown"; +} + +export const setupTwoFactorSC = command(v.object({}), async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.setup2FA(fctx, currentUser as User); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const verifyAndEnableTwoFactorSC = command( + enable2FACodeSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.verifyAndEnable2FA( + fctx, + currentUser as User, + payload.code, + event.request.headers, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const generateBackupCodesSQ = query(async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.generateBackupCodes(fctx, currentUser as User); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const disableTwoFactorSC = command(disable2FASchema, async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.disable(fctx, currentUser as User, payload.code); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const requiresVerificationSQ = query( + v.object({ sessionId: v.string() }), + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.requiresInitialVerification( + fctx, + currentUser as User, + input.sessionId, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const requiresSensitiveActionSQ = query(async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + const currentUser = event.locals.user; + + if (!fctx.userId || !currentUser) { + return unauthorized(fctx); + } + + const res = await tc.requiresSensitiveActionVerification( + fctx, + currentUser as User, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); + +export const startVerificationSessionSC = command( + startVerificationSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await tc.startVerification(fctx, { + userId: payload.userId, + sessionId: payload.sessionId, + ipAddress: buildIpAddress(event.request.headers), + userAgent: buildUserAgent(event.request.headers), + }); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const verifySessionCodeSC = command( + verifyCodeSchema, + async (payload) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + let currentUser = event.locals.user; + + if (!currentUser) { + const sess = await auth.api.getSession({ + headers: event.request.headers, + }); + currentUser = sess?.user as User | undefined; + } + + const res = await tc.verifyCode( + fctx, + { + verificationSessToken: payload.verificationToken, + code: payload.code, + }, + currentUser as User | undefined, + ); + + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + +export const cleanupExpiredSessionsSC = command(v.object({}), async () => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await tc.cleanupExpiredSessions(fctx); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; +}); diff --git a/apps/main/src/routes/(main)/+page.svelte b/apps/main/src/routes/(main)/+page.svelte index 93b9d02..dcb920b 100644 --- a/apps/main/src/routes/(main)/+page.svelte +++ b/apps/main/src/routes/(main)/+page.svelte @@ -2,13 +2,7 @@ import { goto } from "$app/navigation"; import { mainNavTree } from "$lib/core/constants"; import { breadcrumbs } from "$lib/global.stores"; - import { makeClient } from "$lib/make-client.js"; import { onMount } from "svelte"; - import type { PageData } from "./$types"; - - let { data }: { data: PageData } = $props(); - - const client = makeClient(fetch); breadcrumbs.set([mainNavTree[0]]); diff --git a/apps/main/src/routes/+layout.svelte b/apps/main/src/routes/+layout.svelte index 345a779..a374c05 100644 --- a/apps/main/src/routes/+layout.svelte +++ b/apps/main/src/routes/+layout.svelte @@ -1,25 +1,11 @@ @@ -31,6 +17,4 @@ - - {@render children()} - +{@render children()} diff --git a/packages/logic/domains/2fa/router.ts b/packages/logic/domains/2fa/router.ts deleted file mode 100644 index d682399..0000000 --- a/packages/logic/domains/2fa/router.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - disable2FASchema, - enable2FACodeSchema, - startVerificationSchema, - verifyCodeSchema, -} from "./data"; -import { sValidator } from "@hono/standard-validator"; -import { HonoContext } from "@/core/hono.helpers"; -import { getTwofaController } from "./controller"; -import { auth } from "@domains/auth/config.base"; -import { Hono } from "hono"; - -const twofaController = getTwofaController(); - -export const twofaRouter = new Hono() - .post("/setup", async (c) => { - const res = await twofaController.setup2FA( - c.env.locals.fCtx, - c.env.locals.user, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - .post( - "/verify-and-enable", - sValidator("json", enable2FACodeSchema), - async (c) => { - const data = c.req.valid("json"); - const res = await twofaController.verifyAndEnable2FA( - c.env.locals.fCtx, - c.env.locals.user, - data.code, - c.req.raw.headers, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }, - ) - .get("/generate-backup-codes", async (c) => { - const res = await twofaController.generateBackupCodes( - c.env.locals.fCtx, - c.env.locals.user, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - .delete("/disable", sValidator("json", disable2FASchema), async (c) => { - const data = c.req.valid("json"); - const res = await twofaController.disable( - c.env.locals.fCtx, - c.env.locals.user, - data.code, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - .get("/requires-verification", async (c) => { - const user = c.env.locals.user; - const sessionId = c.req.query("sessionId")?.toString() ?? ""; - const res = await twofaController.requiresInitialVerification( - c.env.locals.fCtx, - user, - sessionId, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - .get("/requires-sensitive-action", async (c) => { - const res = await twofaController.requiresSensitiveActionVerification( - c.env.locals.fCtx, - c.env.locals.user, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - .post( - "/start-verification-session", - sValidator("json", startVerificationSchema), - async (c) => { - const data = c.req.valid("json"); - - const ipAddress = - c.req.header("x-forwarded-for") || - c.req.header("x-real-ip") || - "unknown"; - const userAgent = c.req.header("user-agent") || "unknown"; - - const res = await twofaController.startVerification( - c.env.locals.fCtx, - { - userId: data.userId, - sessionId: data.sessionId, - ipAddress, - userAgent, - }, - ); - - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - ); - }, - ) - .post( - "/verify-session-code", - sValidator("json", verifyCodeSchema), - async (c) => { - const data = c.req.valid("json"); - - let user = c.env.locals.user; - if (!user) { - const out = await auth.api.getSession({ - headers: c.req.raw.headers, - }); - user = out?.user as any; - } - - const res = await twofaController.verifyCode( - c.env.locals.fCtx, - { - verificationSessToken: data.verificationToken, - code: data.code, - }, - user, - ); - - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }, - ) - .post("/cleanup-expired-sessions", async (c) => { - const res = await twofaController.cleanupExpiredSessions( - c.env.locals.fCtx, - ); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }); diff --git a/packages/logic/domains/user/router.ts b/packages/logic/domains/user/router.ts deleted file mode 100644 index 6e9e91e..0000000 --- a/packages/logic/domains/user/router.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { - banUserSchema, - checkUsernameSchema, - ensureAccountExistsSchema, - rotatePasswordSchema, -} from "./data"; -import { HonoContext } from "@core/hono.helpers"; -import { sValidator } from "@hono/standard-validator"; -import { getUserController } from "./controller"; -import { Hono } from "hono"; - -const uc = getUserController(); - -export const usersRouter = new Hono() - // Get current user info - .get("/me", async (c) => { - const fctx = c.env.locals.fCtx; - const userId = c.env.locals.user?.id; - - if (!userId) { - return c.json( - { - error: { - code: "UNAUTHORIZED", - message: "User not authenticated", - description: "Please log in", - detail: "No user ID found in session", - }, - }, - 401, - ); - } - - const res = await uc.getUserInfo(fctx, userId); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - - // Get user info by ID - .get("/:userId", async (c) => { - const fctx = c.env.locals.fCtx; - const userId = c.req.param("userId"); - - const res = await uc.getUserInfo(fctx, userId); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - - // Ensure account exists - .put( - "/ensure-account-exists", - sValidator("json", ensureAccountExistsSchema), - async (c) => { - const fctx = c.env.locals.fCtx; - const data = c.req.valid("json"); - - const res = await uc.ensureAccountExists(fctx, data.userId); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }, - ) - - // Check username availability - .post( - "/check-username", - sValidator("json", checkUsernameSchema), - async (c) => { - const fctx = c.env.locals.fCtx; - const data = c.req.valid("json"); - - const res = await uc.isUsernameAvailable(fctx, data.username); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }, - ) - - // Update last 2FA verification time - .put("/update-2fa-verified/:userId", async (c) => { - const fctx = c.env.locals.fCtx; - const userId = c.req.param("userId"); - - const res = await uc.updateLastVerified2FaAtToNow(fctx, userId); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - - // Ban user - .post("/ban", sValidator("json", banUserSchema), async (c) => { - const fctx = c.env.locals.fCtx; - const data = c.req.valid("json"); - - const res = await uc.banUser(fctx, data.userId, data.reason, data.banExpiresAt); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - - // Check if user is banned - .get("/:userId/is-banned", async (c) => { - const fctx = c.env.locals.fCtx; - const userId = c.req.param("userId"); - - const res = await uc.isUserBanned(fctx, userId); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - - // Get ban info - .get("/:userId/ban-info", async (c) => { - const fctx = c.env.locals.fCtx; - const userId = c.req.param("userId"); - - const res = await uc.getBanInfo(fctx, userId); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }) - - // Rotate password - .put( - "/rotate-password", - sValidator("json", rotatePasswordSchema), - async (c) => { - const fctx = c.env.locals.fCtx; - const data = c.req.valid("json"); - - const res = await uc.rotatePassword(fctx, data.userId, data.password); - return c.json( - res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }, - res.isOk() ? 200 : 400, - ); - }, - ); diff --git a/packages/logic/package.json b/packages/logic/package.json index 9d3acd7..0aeeca0 100644 --- a/packages/logic/package.json +++ b/packages/logic/package.json @@ -4,14 +4,13 @@ "auth:schemagen": "pnpm dlx @better-auth/cli generate --config ./domains/auth/config.base.ts --output ../../packages/db/schema/better.auth.schema.ts" }, "dependencies": { - "@hono/standard-validator": "^0.2.1", "@opentelemetry/api": "^1.9.0", "@otplib/plugin-base32-scure": "^13.3.0", "@otplib/plugin-crypto-noble": "^13.3.0", "@otplib/totp": "^13.3.0", "@pkg/db": "workspace:*", - "@pkg/logger": "workspace:*", "@pkg/keystore": "workspace:*", + "@pkg/logger": "workspace:*", "@pkg/result": "workspace:*", "@pkg/settings": "workspace:*", "@types/pdfkit": "^0.14.0", @@ -19,17 +18,13 @@ "better-auth": "^1.4.7", "date-fns-tz": "^3.2.0", "dotenv": "^16.5.0", - "hono": "^4.11.1", "imapflow": "^1.0.188", "mailparser": "^3.7.3", "nanoid": "^5.1.5", "neverthrow": "^8.2.0", "otplib": "^13.3.0", - "pdfkit": "^0.17.1", - "tmp": "^0.2.3", "uuid": "^11.1.0", - "valibot": "^1.2.0", - "xlsx": "^0.18.5" + "valibot": "^1.2.0" }, "devDependencies": { "@types/bun": "latest", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f3429c..fd1ebd2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,24 +65,15 @@ importers: '@pkg/settings': specifier: workspace:* version: link:../../packages/settings - '@tanstack/svelte-query': - specifier: ^6.0.18 - version: 6.0.18(svelte@5.53.6) better-auth: specifier: ^1.4.20 version: 1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) date-fns: specifier: ^4.1.0 version: 4.1.0 - hono: - specifier: ^4.11.1 - version: 4.12.3 import-in-the-middle: specifier: ^3.0.0 version: 3.0.0 - marked: - specifier: ^17.0.1 - version: 17.0.3 nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -289,9 +280,6 @@ importers: packages/logic: dependencies: - '@hono/standard-validator': - specifier: ^0.2.1 - version: 0.2.2(@standard-schema/spec@1.1.0)(hono@4.12.3) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.0 @@ -334,9 +322,6 @@ importers: dotenv: specifier: ^16.5.0 version: 16.6.1 - hono: - specifier: ^4.11.1 - version: 4.12.3 imapflow: specifier: ^1.0.188 version: 1.2.10 @@ -352,12 +337,6 @@ importers: otplib: specifier: ^13.3.0 version: 13.3.0 - pdfkit: - specifier: ^0.17.1 - version: 0.17.2 - tmp: - specifier: ^0.2.3 - version: 0.2.5 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -367,9 +346,6 @@ importers: valibot: specifier: ^1.2.0 version: 1.2.0(typescript@5.9.3) - xlsx: - specifier: ^0.18.5 - version: 0.18.5 devDependencies: '@types/bun': specifier: latest @@ -930,12 +906,6 @@ packages: peerDependencies: hono: ^4 - '@hono/standard-validator@0.2.2': - resolution: {integrity: sha512-mJ7W84Bt/rSvoIl63Ynew+UZOHAzzRAoAXb3JaWuxAkM/Lzg+ZHTCUiz77KOtn2e623WNN8LkD57Dk0szqUrIw==} - peerDependencies: - '@standard-schema/spec': ^1.0.0 - hono: '>=3.9.0' - '@iconify/json@2.2.444': resolution: {integrity: sha512-z0UwFaVtaN/h/iWZ1kzEjqFU3sp0rRy93tzOtpepZU89DY39WsNeYZv2mxtft/2La6Bz2b4z1C/HkU5Cqv3gbw==} @@ -1872,14 +1842,6 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 - '@tanstack/query-core@5.90.20': - resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} - - '@tanstack/svelte-query@6.0.18': - resolution: {integrity: sha512-iGS8osfrIVUW5pkV4Ig6pspNIMtiNjGnVTNJKDas0m/QaNDFFIKbgg74rCzcjwrTIvO38tMpzb4VUKklvAmjxw==} - peerDependencies: - svelte: ^5.25.0 - '@tanstack/table-core@8.21.3': resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} @@ -2030,10 +1992,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adler-32@1.3.1: - resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} - engines: {node: '>=0.8'} - agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -2086,13 +2044,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@0.0.8: - resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} - engines: {node: '>= 0.4'} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - better-auth@1.4.20: resolution: {integrity: sha512-cUQaUhZ/EZwb7xgoL9wHl78yWp0eaxC/L++B/r8RJxk23L766Tk7fLjWG6bQK8eAHDDpfQNwXsJowiei8tJWJw==} peerDependencies: @@ -2176,9 +2127,6 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brotli@1.3.3: - resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2193,10 +2141,6 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - cfb@1.2.2: - resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} - engines: {node: '>=0.8'} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -2218,10 +2162,6 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2230,10 +2170,6 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - codepage@1.15.0: - resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} - engines: {node: '>=0.8'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2274,18 +2210,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - crc-32@1.2.2: - resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} - engines: {node: '>=0.8'} - hasBin: true - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-js@4.2.0: - resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} - cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2447,9 +2375,6 @@ packages: devalue@5.6.3: resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} - dfa@1.2.0: - resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} - dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -2666,9 +2591,6 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2692,9 +2614,6 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - fontkit@2.0.4: - resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -2713,10 +2632,6 @@ packages: forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} - frac@1.1.2: - resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} - engines: {node: '>=0.8'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2851,10 +2766,6 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} - jpeg-exif@1.1.4: - resolution: {integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -2967,9 +2878,6 @@ packages: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} - linebreak@1.1.0: - resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} - linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} @@ -3016,11 +2924,6 @@ packages: mailparser@3.9.3: resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==} - marked@17.0.3: - resolution: {integrity: sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==} - engines: {node: '>= 20'} - hasBin: true - memoize-weak@1.0.2: resolution: {integrity: sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==} @@ -3144,9 +3047,6 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} - pako@0.2.9: - resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} - paneforge@1.0.2: resolution: {integrity: sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA==} peerDependencies: @@ -3173,9 +3073,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pdfkit@0.17.2: - resolution: {integrity: sha512-UnwF5fXy08f0dnp4jchFYAROKMNTaPqb/xgR8GtCzIcqoTnbOqtp3bwKvO4688oHI6vzEEs8Q6vqqEnC5IUELw==} - peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} @@ -3213,9 +3110,6 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - png-js@1.0.0: - resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} - pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -3390,9 +3284,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - restructure@3.0.2: - resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} - rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true @@ -3516,10 +3407,6 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - ssf@0.11.2: - resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} - engines: {node: '>=0.8'} - stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} @@ -3642,9 +3529,6 @@ packages: tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} - tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -3664,10 +3548,6 @@ packages: resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} hasBin: true - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} - engines: {node: '>=14.14'} - toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} @@ -3752,12 +3632,6 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - unicode-properties@1.4.1: - resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} - - unicode-trie@2.0.0: - resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} - unplugin-icons@23.0.1: resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==} peerDependencies: @@ -3914,14 +3788,6 @@ packages: resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} engines: {node: '>= 12.0.0'} - wmf@1.0.2: - resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} - engines: {node: '>=0.8'} - - word@0.3.0: - resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} - engines: {node: '>=0.8'} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -3934,11 +3800,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - xlsx@0.18.5: - resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} - engines: {node: '>=0.8'} - hasBin: true - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4301,11 +4162,6 @@ snapshots: dependencies: hono: 4.12.3 - '@hono/standard-validator@0.2.2(@standard-schema/spec@1.1.0)(hono@4.12.3)': - dependencies: - '@standard-schema/spec': 1.1.0 - hono: 4.12.3 - '@iconify/json@2.2.444': dependencies: '@iconify/types': 2.0.0 @@ -5406,13 +5262,6 @@ snapshots: tailwindcss: 4.2.1 vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - '@tanstack/query-core@5.90.20': {} - - '@tanstack/svelte-query@6.0.18(svelte@5.53.6)': - dependencies: - '@tanstack/query-core': 5.90.20 - svelte: 5.53.6 - '@tanstack/table-core@8.21.3': {} '@types/aws-lambda@8.10.161': {} @@ -5585,8 +5434,6 @@ snapshots: acorn@8.16.0: {} - adler-32@1.3.1: {} - agent-base@7.1.4: {} ansi-regex@5.0.1: {} @@ -5629,10 +5476,6 @@ snapshots: balanced-match@1.0.2: {} - base64-js@0.0.8: {} - - base64-js@1.5.1: {} - better-auth@1.4.20(@sveltejs/kit@2.53.4(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))(svelte@5.53.6)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@better-auth/core': 1.4.20(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1) @@ -5680,10 +5523,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - brotli@1.3.3: - dependencies: - base64-js: 1.5.1 - buffer-from@1.1.2: {} bun-types@1.3.9: @@ -5695,11 +5534,6 @@ snapshots: camelcase@8.0.0: optional: true - cfb@1.2.2: - dependencies: - adler-32: 1.3.1 - crc-32: 1.2.2 - chai@6.2.2: {} chokidar@4.0.3: @@ -5727,14 +5561,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clone@2.1.2: {} - clsx@2.1.1: {} cluster-key-slot@1.1.2: {} - codepage@1.15.0: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -5766,16 +5596,12 @@ snapshots: cookie@0.6.0: {} - crc-32@1.2.2: {} - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - crypto-js@4.2.0: {} - cssesc@3.0.0: {} d3-array@2.12.1: @@ -5911,8 +5737,6 @@ snapshots: devalue@5.6.3: {} - dfa@1.2.0: {} - dijkstrajs@1.0.3: {} dlv@1.1.3: @@ -6106,8 +5930,6 @@ snapshots: pure-rand: 6.1.0 optional: true - fast-deep-equal@3.1.3: {} - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -6126,18 +5948,6 @@ snapshots: fn.name@1.1.0: {} - fontkit@2.0.4: - dependencies: - '@swc/helpers': 0.5.19 - brotli: 1.3.3 - clone: 2.1.2 - dfa: 1.2.0 - fast-deep-equal: 3.1.3 - restructure: 3.0.2 - tiny-inflate: 1.0.3 - unicode-properties: 1.4.1 - unicode-trie: 2.0.0 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -6155,8 +5965,6 @@ snapshots: forwarded-parse@2.1.2: {} - frac@1.1.2: {} - fsevents@2.3.3: optional: true @@ -6325,8 +6133,6 @@ snapshots: jose@6.1.3: {} - jpeg-exif@1.1.4: {} - json-bigint@1.0.0: dependencies: bignumber.js: 9.3.1 @@ -6439,11 +6245,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1 - linebreak@1.1.0: - dependencies: - base64-js: 0.0.8 - unicode-trie: 2.0.0 - linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 @@ -6500,8 +6301,6 @@ snapshots: punycode.js: 2.3.1 tlds: 1.261.0 - marked@17.0.3: {} - memoize-weak@1.0.2: {} memoize@10.2.0: @@ -6599,8 +6398,6 @@ snapshots: package-manager-detector@1.6.0: {} - pako@0.2.9: {} - paneforge@1.0.2(svelte@5.53.6): dependencies: runed: 0.23.4(svelte@5.53.6) @@ -6625,14 +6422,6 @@ snapshots: pathe@2.0.3: {} - pdfkit@0.17.2: - dependencies: - crypto-js: 4.2.0 - fontkit: 2.0.4 - jpeg-exif: 1.1.4 - linebreak: 1.1.0 - png-js: 1.0.0 - peberminta@0.9.0: {} pg-int8@1.0.1: {} @@ -6683,8 +6472,6 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 - png-js@1.0.0: {} - pngjs@5.0.0: {} postcss-selector-parser@6.0.10: @@ -6814,8 +6601,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restructure@3.0.2: {} - rimraf@5.0.10: dependencies: glob: 10.5.0 @@ -6949,10 +6734,6 @@ snapshots: split2@4.2.0: {} - ssf@0.11.2: - dependencies: - frac: 1.1.2 - stack-trace@0.0.10: {} stackback@0.0.2: {} @@ -7110,8 +6891,6 @@ snapshots: tiny-case@1.0.3: optional: true - tiny-inflate@1.0.3: {} - tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -7125,8 +6904,6 @@ snapshots: tlds@1.261.0: {} - tmp@0.2.5: {} - toposort@2.0.2: optional: true @@ -7191,16 +6968,6 @@ snapshots: undici-types@7.18.2: {} - unicode-properties@1.4.1: - dependencies: - base64-js: 1.5.1 - unicode-trie: 2.0.0 - - unicode-trie@2.0.0: - dependencies: - pako: 0.2.9 - tiny-inflate: 1.0.3 - unplugin-icons@23.0.1(svelte@5.53.6): dependencies: '@antfu/install-pkg': 1.1.0 @@ -7328,10 +7095,6 @@ snapshots: triple-beam: 1.4.1 winston-transport: 4.9.0 - wmf@1.0.2: {} - - word@0.3.0: {} - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -7350,16 +7113,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.2.0 - xlsx@0.18.5: - dependencies: - adler-32: 1.3.1 - cfb: 1.2.2 - codepage: 1.15.0 - crc-32: 1.2.2 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 - xtend@4.0.2: {} y18n@4.0.3: {}