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+ // }
1182import { NextRequest , NextResponse } from "next/server" ;
2- import crypto from "crypto" ;
183+ import crypto , { createHash } from "crypto" ;
3184import { sha256 } from "js-sha256" ;
4185import { 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
7193export 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 ( / ^ B e a r e r \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