-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmiddleware.ts
137 lines (118 loc) · 4.33 KB
/
middleware.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { supabaseAdmin } from "./app/lib/supabase/admin";
import { Ratelimit } from "@upstash/ratelimit";
import redis from "./app/lib/redis/client";
const ratelimit = new Ratelimit({
redis: redis,
limiter: Ratelimit.slidingWindow(10, "10 s"),
prefix: "@upstash/ratelimit",
});
// this middleware refreshes the user's session and must be run
// for any Server Component route that uses `createServerComponentSupabaseClient`
// TODO : Need rate limiting before performing any database queries for /api/v1/execute, /api/v1/models
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
const supabase = createMiddlewareClient({ req, res });
const {
data: { session },
error,
} = await supabase.auth.getSession();
if (req.nextUrl.pathname.startsWith("/api/v1/execute")) {
const authorization = req.headers.get("Authorization");
if (!authorization) {
return NextResponse.json(
{ error: "API Key not provided in the `Authorization` header." },
{ status: 401 }
);
} else {
const hashedApiKey = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(authorization)
);
const hash = Buffer.from(hashedApiKey).toString("hex");
// Cached user id from redis
// TODO : Ratelimting + caching seems to slow middleware from ~20ms to ~200-300ms, need to diagnose the latency
let cachedResult = await redis.get<{
user_id: string;
api_key_id: string;
}>(hash);
let api_key_id = cachedResult?.api_key_id;
let user_id = cachedResult?.user_id;
// TODO : Could be problematic in case of redis database failure, all requests will be blocked
let success = false;
if (!user_id) {
const { data: apiTableData, error } = await supabaseAdmin
.from("apikeys")
.select("*")
.eq("hash", hash)
.single();
if (error) {
return NextResponse.json(
{ error: "Invalid API Key" },
{ status: 401 }
);
}
// 1 day expiry
// TODO : Need to set expiry based on the user's plan or failed to pay stripe invoice
user_id = apiTableData.user_id;
api_key_id = apiTableData.id;
redis.set(
// NOTE: No await set to avoid blocking the request
hash,
{
user_id,
api_key_id,
},
{
ex: 60 * 60 * 24,
}
);
success = (await ratelimit.limit(apiTableData.user_id)).success;
} else {
// Takes around 300ms to execute, sometimes over 1 sec
success = (await ratelimit.limit(user_id)).success;
}
// If data is found in cache or in database, log the api usage and return the response
if (success) {
// if (error) {
// return NextResponse.json({ error: error.message }, { status: 500 });
// }
res.headers.set("UserId", user_id as string);
res.headers.set("APIKeyId", api_key_id as string);
return res;
// return NextResponse.json({ error: "Success" }, { status: 200 });
} else {
return NextResponse.json(
{ error: "Too many requests, please try again in few seconds" },
{ status: 429 }
);
}
}
}
// Checking if the user is logged in in ui for /api/v1/[models] route.
else if (req.nextUrl.pathname.startsWith("/api/v1/models")) {
if (session) {
// Return an unimplemented response for now
// return NextResponse.json(
// { error: "Not implemented yet" },
// { status: 501 }
// );
// Probably should be in the execution.ts extraHeaders
res.headers.set("UserId", session.user.id as string); // TODO : Is it going to cause any issues?
return res;
} else {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Dashboard
} else if (req.nextUrl.pathname.startsWith("/dashboard") && !session) {
return NextResponse.redirect(new URL("/login", req.url));
// / , /login , /signup and other pages
} else {
return res;
}
}
export const config = {
matcher: ["/api/:path*", "/dashboard/:path*"],
};