Merge pull request #14516 from supabase/thor/ef-upstash-redis-ratelimit

This commit is contained in:
Thor 雷神 Schaeff authored and GitHub committed 2023-05-23 01:19:20 +08:00
commit 27fa8bfed0
3 files changed
+97 -1

No files matched your search

@@ -18,7 +18,7 @@ export const meta = {
[Upstash](https://upstash.com/) provides an HTTP/REST based Redis client which is ideal for serverless use-cases and therefore works well with Supabase Edge Functions.
Find the code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/upstash-redis-counter).
Find the code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/upstash-redis-ratelimit).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -0,0 +1,33 @@
# Rate limiting with Upstash Redis in Supabase Edge Functions
[Redis](https://redis.io/docs/about/) is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. It is optimized for atomic operations like incrementing a value, for example for a view counter or rate limiting. We can even rate limit based on the user ID from Supabase Auth!
[Upstash](https://upstash.com/) provides an HTTP/REST based Redis client which is ideal for serverless use-cases and therefore works well with Supabase Edge Functions.
## Redis database setup
Create a Redis database using the [Upstash Console](https://console.upstash.com/) or [Upstash CLI](https://github.com/upstash/cli).
Select the `Global` type to minimize the latency from all edge locations. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your .env file. You'll find them under **Details > REST API > .env**.
```bash
cp supabase/.env.local.example supabase/.env.local
```
## Run locally
Make sure you have the latest version of the [Supabase CLI installed](https://supabase.com/docs/guides/cli#installation).
```bash
supabase start
supabase functions serve --env-file supabase/.env.local
```
Navigate to http://localhost:54321/functions/v1/upstash-redis-ratelimit.
## Deploy
```bash
supabase functions deploy upstash-redis-ratelimit
supabase secrets set --env-file supabase/.env.local
```
@@ -0,0 +1,63 @@
import { serve } from "std/server";
import { Redis } from "@upstash/redis";
import { Ratelimit } from "@upstash/ratelimit";
import { createClient } from "@supabase/supabase-js";
console.log(`Function "upstash-redis-counter" up and running!`);
serve(async (req) => {
try {
// Create a Supabase client with the Auth context of the logged in user.
const supabaseClient = createClient(
// Supabase API URL - env var exported by default.
Deno.env.get("SUPABASE_URL") ?? "",
// Supabase API ANON KEY - env var exported by default.
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{
global: {
headers: { Authorization: req.headers.get("Authorization")! },
},
}
);
// Now we can get the session or user object
const {
data: { user },
} = await supabaseClient.auth.getUser();
if (!user) throw new Error("no user");
console.log(user.id);
const redis = new Redis({
url: Deno.env.get("UPSTASH_REDIS_REST_URL")!,
token: Deno.env.get("UPSTASH_REDIS_REST_TOKEN")!,
});
// Create a new ratelimiter, that allows 10 requests per 10 seconds
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(2, "10 s"),
analytics: true,
});
// Use a constant string to limit all requests with a single ratelimit
// Or use a userID, apiKey or ip address for individual limits.
const identifier = user.id;
const { success } = await ratelimit.limit(identifier);
if (!success) {
throw new Error("limit exceeded");
}
return new Response(JSON.stringify({ success }), { status: 200 });
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 200,
});
}
});
// curl -i --location --request POST 'http://localhost:54321/functions/v1/upstash-redis-ratelimit' \
// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjc4MDA4Mjk2LCJzdWIiOiJkZjQ3ZDU5My0zMjY1LTQ2OWEtOWI5OS1mZDk1OTdhOGU4YzciLCJlbWFpbCI6InRlc3RyQHRlc3Quc2ciLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7fSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTY3ODAwNDY5Nn1dLCJzZXNzaW9uX2lkIjoiMDNjMmFlYjUtMjk5MC00ZWU0LWIxYTYtZmU1Y2IwMGFhMjU3In0.WwqggrX34j0NINiulC9rsj-cd6HzV55ySxJkJlVsU4o' \
// --header 'Content-Type: application/json' \
// --data '{"name":"Functions"}'