Stealing from Google

https://news.ycombinator.com/rss Hits: 7
Summary

Modern frameworks like Next.js and Astro come with their own <Image> component. It’s great — you get optimizations, fewer layout shifts, and better performance for free.But there’s a catch: anyone can abuse your app to optimize their own images, which costs you compute.That’s why these frameworks require you to explicitly allowlist remote domains.In Next.js, that looks like:import type { NextConfig } from "next"; const nextConfig: NextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "lh3.googleusercontent.com", }, { protocol: "https", hostname: "images.marblecms.com", }, ], }, }; export default nextConfig;And in Astro:export default defineConfig({ image: { domains: ["images.marblecms.com", "avatars.githubusercontent.com"], }, });So to display a user’s avatar from Google or GitHub, you’d normally need to allowlist their domains too.But thats the part I didn’t like;it felt wrong to ask users to trust Google and GitHub’s image servers, when they could simply just allow marble and be done.The IdeaInstead of making users allow external domains, why not just take the avatar Google or GitHub gives me, upload it to my own bucket, and serve it from there?That way, users only trust one domain: mine.My SolutionI’m using Better Auth for authentication. which has a concept of hooks, which let you run code before or after certain events (like when a user is created).That’s the perfect place to swipe the avatar.Here’s the core of it — a Next.js server action that:Verifies the image is indeed from Google or GithubFetches the users image from the oAuth providerUploads it to Cloudflare r2 (which serves images from our custom domain).Updates the users profile in the database with the url from cloudflare."use server"; import { PutObjectCommand } from "@aws-sdk/client-s3"; import { db } from "@marble/db"; import type { User } from "better-auth"; import { nanoid } from "nanoid"; import { isAllowedAvatarUrl } from "@/lib/constants"; import { R2_BUCKET_NAME, R2_PUBLIC_...

First seen: 2025-10-02 18:49

Last seen: 2025-10-03 00:50