Skip to content

Commit 37db44e

Browse files
committed
api is now returning res and is cached
1 parent e390a81 commit 37db44e

16 files changed

Lines changed: 2327 additions & 1051 deletions

File tree

Lines changed: 286 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,194 @@
1+
// import { NextRequest, NextResponse } from "next/server";
2+
// import crypto, { createHash } from "crypto";
3+
// import { sha256 } from "js-sha256";
4+
// import { supabaseAdmin } from "@/db/supabase/supabase-admin";
5+
// import {
6+
// getCachedApiKey,
7+
// getCachedBotProfile,
8+
// setCachedApiKey,
9+
// setCachedBotProfile,
10+
// } from "@/lib/cache";
11+
12+
// export const runtime = "nodejs";
13+
14+
// /* ── helper to verify mesh token ──────────────────────────────── */
15+
// function verifyApiMeshToken(token: string) {
16+
// const [payloadB64, sig] = token.split(".");
17+
// if (!payloadB64 || !sig) return null;
18+
19+
// const secret = process.env.API_MESH_SECRET;
20+
// if (!secret) throw new Error("API_MESH_SECRET not set");
21+
22+
// // Re-create signature
23+
// const expected = crypto
24+
// .createHmac("sha256", secret)
25+
// .update(payloadB64)
26+
// .digest("hex");
27+
28+
// // timing-safe compare
29+
// if (
30+
// expected.length !== sig.length ||
31+
// !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
32+
// )
33+
// return null;
34+
35+
// // Decode payload: rawToken|bot_id|user_id|iat|exp
36+
// const [rawToken, botId, userId, iatStr, expStr] = Buffer.from(
37+
// payloadB64,
38+
// "base64url"
39+
// )
40+
// .toString()
41+
// .split("|");
42+
43+
// const exp = Number(expStr);
44+
// if (Date.now() > exp) return null;
45+
46+
// return { rawToken, botId, userId, iat: Number(iatStr), exp };
47+
// }
48+
49+
// /* ── POST /api/bot/[bot_id]/validate ──────────────────────────────────── */
50+
// export async function POST(
51+
// req: NextRequest,
52+
// { params }: { params: { bot_id: string } }
53+
// ) {
54+
// const botParam = params.bot_id;
55+
56+
// /* 1 — Grab & verify mesh token */
57+
// const authHeader =
58+
// req.headers.get("x-bot-auth") ??
59+
// req.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
60+
61+
// if (!authHeader) {
62+
// return NextResponse.json(
63+
// { error: "Missing API token", err_code: "TOKEN_MISSING" },
64+
// { status: 401 }
65+
// );
66+
// }
67+
68+
// const parts = verifyApiMeshToken(authHeader);
69+
// if (!parts) {
70+
// return NextResponse.json(
71+
// { error: "Invalid or expired token", err_code: "TOKEN_INVALID" },
72+
// { status: 401 }
73+
// );
74+
// }
75+
76+
// const { rawToken, botId: botInToken } = parts;
77+
78+
// /* 2 — Param / payload mismatch guard */
79+
// if (botInToken !== botParam || !rawToken) {
80+
// return NextResponse.json(
81+
// { error: "Bot mismatch", err_code: "BOT_MISMATCH" },
82+
// { status: 403 }
83+
// );
84+
// }
85+
86+
// /* 3 — DB lookup, revocation, permissions and chaching*/
87+
// const hash = sha256(rawToken);
88+
// let keyRow = await getCachedApiKey(hash);
89+
90+
// if (!keyRow) {
91+
// const { data, error } = await supabaseAdmin
92+
// .from("api_keys")
93+
// .select("api_id, permissions, name")
94+
// .eq("bot_id", botParam)
95+
// .eq("token_hash", hash)
96+
// .maybeSingle();
97+
98+
// if (error || !data) {
99+
// return NextResponse.json(
100+
// { error: "Key revoked or not found", err_code: "KEY_REVOKED" },
101+
// { status: 403 }
102+
// );
103+
// }
104+
105+
// keyRow = data;
106+
// await setCachedApiKey(hash, keyRow);
107+
// }
108+
109+
// let BotProfile = await getCachedBotProfile(botInToken);
110+
// if (BotProfile) {
111+
// const { data: config, error: configErr } = await supabaseAdmin
112+
// .from("bot_configs")
113+
// .select("*")
114+
// .eq("bot_id", botParam)
115+
// .maybeSingle();
116+
117+
// if (configErr || !config) {
118+
// return NextResponse.json(
119+
// { error: "Bot config not found", err_code: "CONFIG_MISSING" },
120+
// { status: 404 }
121+
// );
122+
// }
123+
// const [runtimeRes, settingsRes] = await Promise.all([
124+
// supabaseAdmin
125+
// .from("bot_runtime_settings")
126+
// .select("*")
127+
// .eq("bot_id", botParam)
128+
// .maybeSingle(),
129+
// supabaseAdmin
130+
// .from("bot_settings")
131+
// .select("*")
132+
// .eq("bot_id", botParam)
133+
// .maybeSingle(),
134+
// ]);
135+
136+
// if (
137+
// runtimeRes.error ||
138+
// !runtimeRes.data ||
139+
// settingsRes.error ||
140+
// !settingsRes.data
141+
// ) {
142+
// return NextResponse.json(
143+
// {
144+
// error: "Missing runtime or base settings",
145+
// err_code: "SETTINGS_MISSING",
146+
// },
147+
// { status: 404 }
148+
// );
149+
// }
150+
151+
// BotProfile = {
152+
// config,
153+
// runtime_settings: runtimeRes.data,
154+
// settings: settingsRes.data,
155+
// fetchedAt: new Date().toISOString(),
156+
// };
157+
158+
// if (BotProfile) {
159+
// await setCachedBotProfile(botInToken, BotProfile);
160+
// }
161+
162+
// if (!keyRow.permissions.includes("read")) {
163+
// return NextResponse.json(
164+
// { error: "Missing permission", err_code: "PERMISSION_DENIED" },
165+
// { status: 403 }
166+
// );
167+
// }
168+
// const configFingerprint = createHash("sha256")
169+
// .update(JSON.stringify(config))
170+
// .digest("hex");
171+
172+
// return NextResponse.json({
173+
// ok: true,
174+
// bot_id: botParam,
175+
// config,
176+
// runtime_settings: runtimeRes.data,
177+
// settings: settingsRes.data,
178+
// config_fingerprint: configFingerprint,
179+
// });
180+
// }
181+
// }
1182
import { NextRequest, NextResponse } from "next/server";
2-
import crypto from "crypto";
183+
import crypto, { createHash } from "crypto";
3184
import { sha256 } from "js-sha256";
4185
import { supabaseAdmin } from "@/db/supabase/supabase-admin";
5-
import { getCachedApiKey, setCachedApiKey } from "@/lib/cache";
186+
import {
187+
getCachedApiKey,
188+
getCachedBotProfile,
189+
setCachedApiKey,
190+
setCachedBotProfile,
191+
} from "@/lib/cache";
6192

7193
export const runtime = "nodejs";
8194

@@ -14,20 +200,17 @@ function verifyApiMeshToken(token: string) {
14200
const secret = process.env.API_MESH_SECRET;
15201
if (!secret) throw new Error("API_MESH_SECRET not set");
16202

17-
// Re-create signature
18203
const expected = crypto
19204
.createHmac("sha256", secret)
20205
.update(payloadB64)
21206
.digest("hex");
22207

23-
// timing-safe compare
24208
if (
25209
expected.length !== sig.length ||
26210
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
27211
)
28212
return null;
29213

30-
// Decode payload: rawToken|bot_id|user_id|iat|exp
31214
const [rawToken, botId, userId, iatStr, expStr] = Buffer.from(
32215
payloadB64,
33216
"base64url"
@@ -42,26 +225,27 @@ function verifyApiMeshToken(token: string) {
42225
}
43226

44227
/* ── POST /api/bot/[bot_id]/validate ──────────────────────────────────── */
45-
export async function POST(
46-
req: NextRequest,
47-
{ params }: { params: { bot_id: string } }
48-
) {
228+
export async function POST(req: NextRequest, params: { bot_id: string }) {
49229
const botParam = params.bot_id;
230+
console.log(`[Auth] Request to validate bot ${botParam}`);
50231

51-
/* 1 — Grab & verify mesh token */
232+
// 1 — Auth header
52233
const authHeader =
53234
req.headers.get("x-bot-auth") ??
54235
req.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
55236

56237
if (!authHeader) {
238+
console.warn(`[Auth] Missing token for bot ${botParam}`);
57239
return NextResponse.json(
58240
{ error: "Missing API token", err_code: "TOKEN_MISSING" },
59241
{ status: 401 }
60242
);
61243
}
62244

245+
// 2 — Verify token
63246
const parts = verifyApiMeshToken(authHeader);
64247
if (!parts) {
248+
console.warn(`[Auth] Invalid or expired token for bot ${botParam}`);
65249
return NextResponse.json(
66250
{ error: "Invalid or expired token", err_code: "TOKEN_INVALID" },
67251
{ status: 401 }
@@ -70,21 +254,26 @@ export async function POST(
70254

71255
const { rawToken, botId: botInToken } = parts;
72256

73-
/* 2 — Param / payload mismatch guard */
74257
if (botInToken !== botParam || !rawToken) {
258+
console.warn(
259+
`[Auth] Token bot_id mismatch: token(${botInToken}) vs param(${botParam})`
260+
);
75261
return NextResponse.json(
76262
{ error: "Bot mismatch", err_code: "BOT_MISMATCH" },
77263
{ status: 403 }
78264
);
79265
}
80266

81-
/* 3 — DB lookup, revocation, permissions and chaching*/
267+
// 3 — Check cached API key
82268
const hash = sha256(rawToken);
83-
84269
let keyRow = await getCachedApiKey(hash);
85-
// const foundInCache = !!keyRow;
86-
// console.log(foundInCache);
87-
if (!keyRow) {
270+
271+
if (keyRow) {
272+
console.log(`[Cache] API key cache hit for bot ${botParam}`);
273+
} else {
274+
console.log(
275+
`[DB] API key cache miss. Fetching from DB for bot ${botParam}`
276+
);
88277
const { data, error } = await supabaseAdmin
89278
.from("api_keys")
90279
.select("api_id, permissions, name")
@@ -93,6 +282,7 @@ export async function POST(
93282
.maybeSingle();
94283

95284
if (error || !data) {
285+
console.warn(`[Auth] API key not found or revoked for bot ${botParam}`);
96286
return NextResponse.json(
97287
{ error: "Key revoked or not found", err_code: "KEY_REVOKED" },
98288
{ status: 403 }
@@ -101,25 +291,100 @@ export async function POST(
101291

102292
keyRow = data;
103293
await setCachedApiKey(hash, keyRow);
294+
console.log(`[Cache] API key cached for bot ${botParam}`);
295+
}
296+
297+
// 4 — Check cached bot profile
298+
let BotProfile = await getCachedBotProfile(botParam);
299+
if (BotProfile) {
300+
console.log(`[Cache] Bot profile cache hit for bot ${botParam}`);
301+
} else {
302+
console.log(
303+
`[DB] Bot profile cache miss. Fetching config/settings/runtime for bot ${botParam}`
304+
);
305+
306+
const { data: config, error: configErr } = await supabaseAdmin
307+
.from("bot_configs")
308+
.select("*")
309+
.eq("bot_id", botParam)
310+
.maybeSingle();
311+
312+
if (configErr || !config) {
313+
console.error(`[DB] Bot config missing for bot ${botParam}`);
314+
return NextResponse.json(
315+
{ error: "Bot config not found", err_code: "CONFIG_MISSING" },
316+
{ status: 404 }
317+
);
318+
}
319+
320+
const [runtimeRes, settingsRes] = await Promise.all([
321+
supabaseAdmin
322+
.from("bot_runtime_settings")
323+
.select("*")
324+
.eq("bot_id", botParam)
325+
.maybeSingle(),
326+
supabaseAdmin
327+
.from("bot_settings")
328+
.select("*")
329+
.eq("bot_id", botParam)
330+
.maybeSingle(),
331+
]);
332+
333+
if (
334+
runtimeRes.error ||
335+
!runtimeRes.data ||
336+
settingsRes.error ||
337+
!settingsRes.data
338+
) {
339+
console.error(
340+
`[DB] Bot settings or runtime settings missing for bot ${botParam}`
341+
);
342+
return NextResponse.json(
343+
{
344+
error: "Missing runtime or base settings",
345+
err_code: "SETTINGS_MISSING",
346+
},
347+
{ status: 404 }
348+
);
349+
}
350+
351+
BotProfile = {
352+
config,
353+
runtime_settings: runtimeRes.data,
354+
settings: settingsRes.data,
355+
fetchedAt: new Date().toISOString(),
356+
};
357+
358+
await setCachedBotProfile(botParam, BotProfile);
359+
console.log(`[Cache] Bot profile cached for bot ${botParam}`);
104360
}
105361

106-
/* 4 — OPTIONAL: enforce fine-grained permission */
362+
// 5 — Permissions check
107363
if (!keyRow.permissions.includes("read")) {
364+
console.warn(`[Auth] Permission "read" missing for key on bot ${botParam}`);
108365
return NextResponse.json(
109366
{ error: "Missing permission", err_code: "PERMISSION_DENIED" },
110367
{ status: 403 }
111368
);
112369
}
113370

371+
const configFingerprint = createHash("sha256")
372+
.update(JSON.stringify(BotProfile.config))
373+
.digest("hex");
374+
114375
console.log(
115-
`[Auth] Bot ${botParam} validated with ${keyRow.permissions.length} permissions`
376+
`[Auth] ✅ Bot ${botParam} validated. Key: ${
377+
keyRow.name
378+
}, Permissions: ${keyRow.permissions.join(", ")}`
116379
);
117380

118381
return NextResponse.json({
119382
ok: true,
120383
bot_id: botParam,
121-
permissions: keyRow.permissions,
122-
msg: "API token validated ✅",
123-
apiName: keyRow.name,
384+
config: BotProfile.config,
385+
runtime_settings: BotProfile.runtime_settings,
386+
settings: BotProfile.settings,
387+
config_fingerprint: configFingerprint,
388+
from_cache: true,
124389
});
125390
}

0 commit comments

Comments
 (0)