From 41aca21b710703a39b09a693fcac246149e7b894 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 18 May 2026 11:14:25 +0530 Subject: [PATCH] fix: add impersonation params to resource urls --- src/lib/appwrite/impersonation.ts | 27 ++++++++++++++- .../billing/alerts/paymentAuthRequired.svelte | 11 +++++-- src/lib/components/filePicker.svelte | 19 +++++------ .../billing/paymentHistory.svelte | 11 +++++-- .../billing/retryPaymentModal.svelte | 13 +++++--- .../invoices/[invoiceId]/download/+page.ts | 5 ++- .../invoices/[invoiceId]/view/+page.ts | 5 ++- .../settings/invoicesTable.svelte | 11 +++++-- .../functions/function-[function]/store.ts | 11 ++++--- .../(components)/deploymentActionMenu.svelte | 11 ++++--- .../sites/(components)/siteCard.svelte | 17 ++++++---- .../sites/grid.svelte | 17 ++++++---- .../storage/bucket-[bucket]/+page.svelte | 21 ++++++------ .../bucket-[bucket]/file-[file]/+page.svelte | 33 +++++++++++-------- 14 files changed, 141 insertions(+), 71 deletions(-) diff --git a/src/lib/appwrite/impersonation.ts b/src/lib/appwrite/impersonation.ts index 9339a9924..9f8d6b45f 100644 --- a/src/lib/appwrite/impersonation.ts +++ b/src/lib/appwrite/impersonation.ts @@ -1,4 +1,4 @@ -import { writable } from 'svelte/store'; +import { derived, writable } from 'svelte/store'; import { building } from '$app/environment'; const KEY_TARGET_USER_ID = 'console.impersonation.targetUserId'; @@ -82,6 +82,31 @@ export function readImpersonationTargetUserId(): string | null { return sessionStorage.getItem(KEY_TARGET_USER_ID); } +export function createImpersonatedResourceUrl( + url: string, + queryParams: Record = {} +): string { + const parsedUrl = new URL(url, globalThis.location?.origin); + const targetUserId = readImpersonationTargetUserId(); + + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + parsedUrl.searchParams.set(key, value.toString()); + } + } + + if (!targetUserId) return parsedUrl.toString(); + + parsedUrl.searchParams.set('impersonateUserId', targetUserId); + + return parsedUrl.toString(); +} + +export const impersonatedResourceUrl = derived( + impersonationRevision, + () => createImpersonatedResourceUrl +); + export function readOperatorSnapshot(): OperatorSnapshot | null { if (building) return null; const raw = sessionStorage.getItem(KEY_OPERATOR); diff --git a/src/lib/components/billing/alerts/paymentAuthRequired.svelte b/src/lib/components/billing/alerts/paymentAuthRequired.svelte index a67537ab0..2d6955e19 100644 --- a/src/lib/components/billing/alerts/paymentAuthRequired.svelte +++ b/src/lib/components/billing/alerts/paymentAuthRequired.svelte @@ -3,10 +3,17 @@ import { page } from '$app/state'; import { Button } from '$lib/elements/forms'; import { HeaderAlert } from '$lib/layout'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; import { actionRequiredInvoices, hideBillingHeaderRoutes } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { getApiEndpoint } from '$lib/stores/sdk'; const endpoint = getApiEndpoint(); + + function invoiceUrl(invoiceId: string) { + return $impersonatedResourceUrl( + `${endpoint}/organizations/${$organization.$id}/invoices/${invoiceId}/view` + ); + } {#if $actionRequiredInvoices && $actionRequiredInvoices?.invoices?.length && !hideBillingHeaderRoutes.includes(page.url.pathname)} @@ -14,9 +21,7 @@ Please authorize your upcoming payment for {$organization.name}. Your bank requires this security measure to proceed with payment. - + { return redirect( 302, - `${endpoint}/organizations/${params.organization}/invoices/${invoice.$id}/download` + createImpersonatedResourceUrl( + `${endpoint}/organizations/${params.organization}/invoices/${invoice.$id}/download` + ) ); }; diff --git a/src/routes/(console)/organization-[organization]/invoices/[invoiceId]/view/+page.ts b/src/routes/(console)/organization-[organization]/invoices/[invoiceId]/view/+page.ts index 3a4576785..9351393ae 100644 --- a/src/routes/(console)/organization-[organization]/invoices/[invoiceId]/view/+page.ts +++ b/src/routes/(console)/organization-[organization]/invoices/[invoiceId]/view/+page.ts @@ -1,4 +1,5 @@ import { getApiEndpoint, sdk } from '$lib/stores/sdk'; +import { createImpersonatedResourceUrl } from '$lib/appwrite/impersonation'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; @@ -13,6 +14,8 @@ export const load: PageLoad = async ({ params }) => { return redirect( 302, - `${endpoint}/organizations/${params.organization}/invoices/${invoice.$id}/view` + createImpersonatedResourceUrl( + `${endpoint}/organizations/${params.organization}/invoices/${invoice.$id}/view` + ) ); }; diff --git a/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte b/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte index 514e8a575..100c23554 100644 --- a/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte +++ b/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte @@ -5,6 +5,7 @@ import { Button } from '$lib/elements/forms'; import { formatCurrency } from '$lib/helpers/numbers'; import { trackEvent } from '$lib/actions/analytics'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; import { ActionMenu, Badge, Icon, Link, Popover, Table } from '@appwrite.io/pink-svelte'; import { IconDotsHorizontal, @@ -24,6 +25,12 @@ $selectedInvoice = invoice; $showRetryModal = true; } + + function invoiceUrl(invoiceId: string, action: 'view' | 'download') { + return $impersonatedResourceUrl( + `${endpoint}/organizations/${page.params.organization}/invoices/${invoiceId}/${action}` + ); + } + href={invoiceUrl(invoice.$id, 'view')}> View invoice + href={invoiceUrl(invoice.$id, 'download')}> Download PDF {#if status === 'overdue' || status === 'failed'} diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts index 42bf293ce..3f320daf1 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts @@ -3,6 +3,7 @@ import { DeploymentDownloadType, type Models } from '@appwrite.io/console'; import type { Column } from '$lib/helpers/types'; import { sdk } from '$lib/stores/sdk'; import { page } from '$app/stores'; +import { createImpersonatedResourceUrl } from '$lib/appwrite/impersonation'; export const func = derived(page, ($page) => $page.data.function as Models.Function); export const deploymentList = derived( @@ -150,22 +151,24 @@ export const columns = writable([ export function getOutputDownload(funcId: string, deploymentId: string) { const p = get(page); - return ( + return createImpersonatedResourceUrl( sdk.forProject(p.params.region, p.params.project).functions.getDeploymentDownload({ functionId: funcId, deploymentId, type: DeploymentDownloadType.Output - }) + '&mode=admin' + }), + { mode: 'admin' } ); } export function getSourceDownload(funcId: string, deploymentId: string) { const p = get(page); - return ( + return createImpersonatedResourceUrl( sdk.forProject(p.params.region, p.params.project).functions.getDeploymentDownload({ functionId: funcId, deploymentId, type: DeploymentDownloadType.Source - }) + '&mode=admin' + }), + { mode: 'admin' } ); } diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte index c0190429d..d3b6e657f 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/deploymentActionMenu.svelte @@ -17,6 +17,7 @@ import { ActionMenu, Icon, Tooltip } from '@appwrite.io/pink-svelte'; import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout'; import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; export let selectedDeployment: Models.Deployment; export let deployment: Models.Deployment; @@ -28,21 +29,23 @@ export let inCard = false; function getOutputDownload(deploymentId: string) { - return ( + return $impersonatedResourceUrl( sdk.forProject(page.params.region, page.params.project).sites.getDeploymentDownload({ siteId: page.params.site, deploymentId: deploymentId, type: DeploymentDownloadType.Output - }) + '&mode=admin' + }), + { mode: 'admin' } ); } function getSourceDownload(deploymentId: string) { - return ( + return $impersonatedResourceUrl( sdk.forProject(page.params.region, page.params.project).sites.getDeploymentDownload({ siteId: page.params.site, deploymentId: deploymentId, type: DeploymentDownloadType.Source - }) + '&mode=admin' + }), + { mode: 'admin' } ); } diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte index f85215900..294027c17 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte @@ -27,6 +27,7 @@ import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store'; import type { Snippet } from 'svelte'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; let { site, @@ -71,13 +72,15 @@ } function getFilePreview(fileId: string) { - return sdk.forConsoleIn(page.params.region).storage.getFilePreview({ - bucketId: 'screenshots', - fileId, - width: 1024, - height: 576, - output: ImageFormat.Avif - }); + return $impersonatedResourceUrl( + sdk.forConsoleIn(page.params.region).storage.getFilePreview({ + bucketId: 'screenshots', + fileId, + width: 1024, + height: 576, + output: ImageFormat.Avif + }) + ); } diff --git a/src/routes/(console)/project-[region]-[project]/sites/grid.svelte b/src/routes/(console)/project-[region]-[project]/sites/grid.svelte index 99029389e..704c20a16 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/grid.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/grid.svelte @@ -14,6 +14,7 @@ import { IconExclamation } from '@appwrite.io/pink-icons-svelte'; import { Link } from '$lib/elements'; import { getFrameworkIcon } from '$lib/stores/sites'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; export let siteList: Models.SiteList; @@ -33,13 +34,15 @@ } function getFilePreview(fileId: string) { - return sdk.forConsoleIn(page.params.region).storage.getFilePreview({ - bucketId: 'screenshots', - fileId, - width: 1024, - height: 576, - output: ImageFormat.Avif - }); + return $impersonatedResourceUrl( + sdk.forConsoleIn(page.params.region).storage.getFilePreview({ + bucketId: 'screenshots', + fileId, + width: 1024, + height: 576, + output: ImageFormat.Avif + }) + ); } diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte index ef5988aee..7e18d3e1f 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte @@ -34,6 +34,7 @@ import { isSmallViewport } from '$lib/stores/viewport'; import { ID } from '@appwrite.io/console'; import { addNotification } from '$lib/stores/notifications'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; import type { PageProps } from './$types'; const { data }: PageProps = $props(); @@ -44,17 +45,15 @@ let isDragging = $state(false); function getPreview(fileId: string) { - return ( - sdk - .forProject(page.params.region, page.params.project) - .storage.getFilePreview({ - bucketId: page.params.bucket, - fileId, - height: 128, - width: 128, - output: ImageFormat.Avif - }) - .toString() + '&mode=admin' + return $impersonatedResourceUrl( + sdk.forProject(page.params.region, page.params.project).storage.getFilePreview({ + bucketId: page.params.bucket, + fileId, + height: 128, + width: 128, + output: ImageFormat.Avif + }), + { mode: 'admin' } ); } diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+page.svelte index a940dc8f6..557bed67f 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/file-[file]/+page.svelte @@ -38,6 +38,7 @@ import { ImageFormat, type Models } from '@appwrite.io/console'; import { isSmallViewport } from '$lib/stores/viewport'; import { Menu } from '$lib/components/menu'; + import { impersonatedResourceUrl } from '$lib/appwrite/impersonation'; let showFileAlert = true; @@ -50,33 +51,39 @@ let showCopyUrlModal = false; let selectedFileToken: Models.ResourceToken | null = null; - const getPreview = (fileId: string) => - sdk - .forProject(page.params.region, page.params.project) - .storage.getFilePreview({ + function getPreview(fileId: string) { + return $impersonatedResourceUrl( + sdk.forProject(page.params.region, page.params.project).storage.getFilePreview({ bucketId: $file.bucketId, fileId, width: 640, height: 300, output: ImageFormat.Avif - }) - .toString() + '&mode=admin'; - const getView = (fileId: string) => - sdk - .forProject(page.params.region, page.params.project) - .storage.getFileView({ bucketId: $file.bucketId, fileId }) - .toString() + '&mode=admin'; + }), + { mode: 'admin' } + ); + } + + function getView(fileId: string) { + return $impersonatedResourceUrl( + sdk + .forProject(page.params.region, page.params.project) + .storage.getFileView({ bucketId: $file.bucketId, fileId }), + { mode: 'admin' } + ); + } $: if (filePermissions) { arePermsDisabled = !symmetricDifference(filePermissions, $file.$permissions).length; } function downloadFile() { - return ( + return $impersonatedResourceUrl( sdk .forProject(page.params.region, page.params.project) .storage.getFileDownload({ bucketId: $file.bucketId, fileId: $file.$id }) - .toString() + '&mode=admin' + .toString(), + { mode: 'admin' } ); }