From 82f96493ceb065ad25cab5c32dd3e9fb44bd78ea Mon Sep 17 00:00:00 2001 From: rdbsolutions Date: Tue, 12 Aug 2025 12:49:42 +0100 Subject: [PATCH] storage account report --- actions/azurestorage.js | 42 ++++++++++++++++++ pages/storageAdmin.js | 94 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 pages/storageAdmin.js diff --git a/actions/azurestorage.js b/actions/azurestorage.js index ee392b9f..aa5617bc 100644 --- a/actions/azurestorage.js +++ b/actions/azurestorage.js @@ -1693,3 +1693,45 @@ export const listBlobHierarchical = async ( } } }; +export async function listBlobHierarchicalForUser(containerClient) { + const blobNames = []; + for await (const blob of containerClient.listBlobsFlat()) { + blobNames.push(blob.name); + } + return blobNames; +} + +export async function listContainersForUser( + blobServiceClient, + containerNamePrefix, + emailAddress +) { + const options = { + includeDeleted: false, + includeMetadata: true, + includeSystem: true, + prefix: containerNamePrefix, + }; + + const results = []; + + for await (const containerItem of blobServiceClient.listContainers( + options + )) { + const containerClient = blobServiceClient.getContainerClient( + containerItem.name + ); + const blobNames = await listBlobHierarchicalForUser( + containerClient + ).catch(console.error); + + if (blobNames && blobNames.length > 0) { + results.push({ + container: containerItem.name, + blobs: blobNames, + }); + } + } + + return results; // array of non-empty containers +} diff --git a/pages/storageAdmin.js b/pages/storageAdmin.js new file mode 100644 index 00000000..77cd088b --- /dev/null +++ b/pages/storageAdmin.js @@ -0,0 +1,94 @@ +import { DefaultAzureCredential } from "@azure/identity"; +import { BlobServiceClient, ContainerClient } from "@azure/storage-blob"; +import { v4 as uuidv4 } from "uuid"; +import { + createContainerSas, + listContainers, + listBlobHierarchical, + listContainersForUser, +} from "../actions/azurestorage"; + +import { PrismaClient } from "@prisma/client"; + +import { consoleLogger, getIP } from "../actions"; + +export async function getServerSideProps({ req }) { + // Load allowed IPs from env and trim spaces + const ALLOWED_IPS = process.env.ALLOWED_IPS + ? process.env.ALLOWED_IPS.split(",").map((ip) => ip.trim()) + : []; + + // Always allow localhost addresses + const LOCALHOST_IPS = ["127.0.0.1", "::1"]; + + // Get IP address from headers or socket + const forwarded = req.headers["x-forwarded-for"]; + const ip = + typeof forwarded === "string" + ? forwarded.split(",")[0] + : req.socket.remoteAddress; + + console.log("Visitor IP:", ip); + + // Check whitelist + localhost + if (![...ALLOWED_IPS, ...LOCALHOST_IPS].includes(ip)) { + return { + redirect: { + destination: "/403", // custom "Access Denied" page + permanent: false, + }, + }; + } + const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME; + + const creds = new DefaultAzureCredential(); + + const globalForPrisma = global; + + const prisma = globalForPrisma.prisma || new PrismaClient(); + + const blobServiceClient = new BlobServiceClient( + `https://${accountName}.blob.core.windows.net`, + creds + ); + + const users = await prisma.user.findMany(); + + const data = await Promise.all( + users.map(async (user) => { + const containers = await listContainersForUser( + blobServiceClient, + `${user.id}`, + user.email + ); + return containers.length > 0 + ? { id: user.id, email: user.email, containers } + : null; + }) + ); + + return { props: { data: data.filter(Boolean) } }; +} +export default function StoragePage({ data }) { + return ( +
+

Storage Account Contents

+ {data.map((user) => ( +
+ {user.containers.map((c) => ( +
+

+ {c.container} - {user.email} +

+
    + {c.blobs.map((blob) => ( +
  • {blob}
  • + ))} +
+
+ ))} +
+ ))} +
+ ); +}