fix: add impersonation params to resource urls

This commit is contained in:
harsh mahajan
2026-05-18 11:14:25 +05:30
parent c4f6f7947f
commit 41aca21b71
14 changed files with 141 additions and 71 deletions
+26 -1
View File
@@ -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, string | number | boolean | undefined> = {}
): 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);
@@ -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`
);
}
</script>
{#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.
<svelte:fragment slot="buttons">
<Button
text
href={`${endpoint}/organizations/${$organization.$id}/invoices/${$actionRequiredInvoices.invoices[0].$id}/view`}>
<Button text href={invoiceUrl($actionRequiredInvoices.invoices[0].$id)}>
View invoice
</Button>
<Button
+9 -10
View File
@@ -36,6 +36,7 @@
import { addNotification } from '$lib/stores/notifications';
import { isCloud } from '$lib/system';
import { currentPlan } from '$lib/stores/organization';
import { impersonatedResourceUrl } from '$lib/appwrite/impersonation';
export let show: boolean;
export let mimeTypeQuery: string = 'image/';
@@ -82,16 +83,14 @@
}
function getPreview(bucketId: string, fileId: string, size: number = 64) {
return (
sdk
.forProject(page.params.region, page.params.project)
.storage.getFilePreview({
bucketId,
fileId,
width: size,
height: size
})
.toString() + '&mode=admin'
return $impersonatedResourceUrl(
sdk.forProject(page.params.region, page.params.project).storage.getFilePreview({
bucketId,
fileId,
width: size,
height: size
}),
{ mode: 'admin' }
);
}
@@ -9,6 +9,7 @@
import { type Models, Query } from '@appwrite.io/console';
import { trackEvent } from '$lib/actions/analytics';
import { selectedInvoice, showRetryModal } from './store';
import { impersonatedResourceUrl } from '$lib/appwrite/impersonation';
import {
ActionMenu,
Badge,
@@ -64,6 +65,12 @@
$showRetryModal = true;
}
function invoiceUrl(invoiceId: string, action: 'view' | 'download') {
return $impersonatedResourceUrl(
`${endpoint}/organizations/${page.params.organization}/invoices/${invoiceId}/${action}`
);
}
$effect(() => {
if (page.url.searchParams.get('type') === 'validate-invoice') {
window.history.replaceState({}, '', page.url.pathname);
@@ -155,12 +162,12 @@
<ActionMenu.Item.Anchor
leadingIcon={IconExternalLink}
external
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
href={invoiceUrl(invoice.$id, 'view')}>
View invoice
</ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
leadingIcon={IconDownload}
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
href={invoiceUrl(invoice.$id, 'download')}>
Download PDF
</ActionMenu.Item.Anchor>
{#if status === 'overdue' || status === 'failed' || status === 'abandoned'}
@@ -20,6 +20,7 @@
import { getApiEndpoint, sdk } from '$lib/stores/sdk';
import { formatCurrency } from '$lib/helpers/numbers';
import { resolve } from '$app/paths';
import { impersonatedResourceUrl } from '$lib/appwrite/impersonation';
import type { PaymentMethod as StripePaymentMethod } from '@stripe/stripe-js';
import type { Models } from '@appwrite.io/console';
@@ -37,6 +38,12 @@
const endpoint = getApiEndpoint();
function invoiceUrl(invoiceId: string) {
return $impersonatedResourceUrl(
`${endpoint}/organizations/${page.params.organization}/invoices/${invoiceId}/view`
);
}
onMount(async () => {
if (!$organization.paymentMethodId && !$organization.backupPaymentMethodId) {
paymentMethodId = $paymentMethods?.total ? $paymentMethods.paymentMethods[0].$id : null;
@@ -173,11 +180,7 @@
)} has failed. Retry your payment to avoid service interruptions with your projects.
</p>
<Button
external
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
View invoice
</Button>
<Button external href={invoiceUrl(invoice.$id)}>View invoice</Button>
<PaymentBoxes
bind:paymentMethod
@@ -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';
@@ -12,6 +13,8 @@ export const load: PageLoad = async ({ params }) => {
return redirect(
302,
`${endpoint}/organizations/${params.organization}/invoices/${invoice.$id}/download`
createImpersonatedResourceUrl(
`${endpoint}/organizations/${params.organization}/invoices/${invoice.$id}/download`
)
);
};
@@ -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`
)
);
};
@@ -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}`
);
}
</script>
<Table.Root
@@ -106,12 +113,12 @@
<ActionMenu.Item.Anchor
trailingIcon={IconExternalLink}
external
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/view`}>
href={invoiceUrl(invoice.$id, 'view')}>
View invoice
</ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
trailingIcon={IconDownload}
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
href={invoiceUrl(invoice.$id, 'download')}>
Download PDF
</ActionMenu.Item.Anchor>
{#if status === 'overdue' || status === 'failed'}
@@ -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<Column[]>([
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' }
);
}
@@ -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' }
);
}
</script>
@@ -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
})
);
}
</script>
@@ -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
})
);
}
</script>
@@ -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' }
);
}
@@ -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' }
);
}