2024-04-20 19:38:05 -04:00
import { parseApi } from "../../src/lib/parseApi" ;
2024-05-25 11:57:49 -04:00
import { getRateLimiter , } from "../../src/services/rate-limiter" ;
2024-06-05 16:20:26 -04:00
import { AuthResponse , NotificationType , RateLimiterMode } from "../../src/types" ;
2024-04-20 19:38:05 -04:00
import { supabase_service } from "../../src/services/supabase" ;
2024-04-21 13:36:48 -04:00
import { withAuth } from "../../src/lib/withAuth" ;
2024-05-14 17:08:31 -04:00
import { RateLimiterRedis } from "rate-limiter-flexible" ;
2024-05-20 16:36:34 -04:00
import { setTraceAttributes } from '@hyperdx/node-opentelemetry' ;
2024-06-05 16:20:26 -04:00
import { sendNotification } from "../services/notification/email_notification" ;
2024-04-21 12:31:22 -04:00
2024-05-25 11:57:49 -04:00
export async function authenticateUser ( req , res , mode? : RateLimiterMode ) : Promise < AuthResponse > {
2024-04-21 13:36:48 -04:00
return withAuth ( supaAuthenticateUser ) ( req , res , mode ) ;
}
2024-05-20 16:36:34 -04:00
function setTrace ( team_id : string , api_key : string ) {
try {
setTraceAttributes ( {
team_id ,
api_key
} ) ;
} catch ( error ) {
console . error ( 'Error setting trace attributes:' , error ) ;
}
2024-05-25 11:57:49 -04:00
2024-05-20 16:36:34 -04:00
}
2024-04-21 13:36:48 -04:00
export async function supaAuthenticateUser (
2024-04-20 19:38:05 -04:00
req ,
res ,
mode? : RateLimiterMode
) : Promise < {
success : boolean ;
team_id? : string ;
error? : string ;
status? : number ;
2024-05-30 17:46:55 -04:00
plan? : string ;
2024-04-20 19:38:05 -04:00
} > {
const authHeader = req . headers . authorization ;
if ( ! authHeader ) {
return { success : false , error : "Unauthorized" , status : 401 } ;
}
const token = authHeader . split ( " " ) [ 1 ] ; // Extract the token from "Bearer <token>"
if ( ! token ) {
return {
success : false ,
error : "Unauthorized: Token missing" ,
status : 401 ,
} ;
}
2024-05-14 17:08:31 -04:00
const incomingIP = ( req . headers [ "x-forwarded-for" ] ||
req . socket . remoteAddress ) as string ;
const iptoken = incomingIP + token ;
let rateLimiter : RateLimiterRedis ;
let subscriptionData : { team_id : string , plan : string } | null = null ;
let normalizedApi : string ;
2024-06-05 16:20:26 -04:00
let team_id : string ;
2024-05-14 17:08:31 -04:00
if ( token == "this_is_just_a_preview_token" ) {
2024-05-17 14:37:47 -04:00
rateLimiter = getRateLimiter ( RateLimiterMode . Preview , token ) ;
2024-06-05 16:20:26 -04:00
team_id = "preview" ;
2024-05-17 14:37:47 -04:00
} else {
2024-05-14 17:08:31 -04:00
normalizedApi = parseApi ( token ) ;
const { data , error } = await supabase_service . rpc (
2024-05-14 17:47:21 -04:00
'get_key_and_price_id_2' , { api_key : normalizedApi }
) ;
2024-05-15 07:40:21 -04:00
// get_key_and_price_id_2 rpc definition:
// create or replace function get_key_and_price_id_2(api_key uuid)
// returns table(key uuid, team_id uuid, price_id text) as $$
// begin
// if api_key is null then
// return query
// select null::uuid as key, null::uuid as team_id, null::text as price_id;
// end if;
// return query
// select ak.key, ak.team_id, s.price_id
// from api_keys ak
// left join subscriptions s on ak.team_id = s.team_id
// where ak.key = api_key;
// end;
// $$ language plpgsql;
2024-05-14 17:08:31 -04:00
if ( error ) {
console . error ( 'Error fetching key and price_id:' , error ) ;
} else {
2024-05-19 15:45:46 -04:00
// console.log('Key and Price ID:', data);
2024-05-14 17:08:31 -04:00
}
if ( error || ! data || data . length === 0 ) {
return {
success : false ,
error : "Unauthorized: Invalid token" ,
status : 401 ,
} ;
}
2024-06-05 16:20:26 -04:00
const internal_team_id = data [ 0 ] . team_id ;
team_id = internal_team_id ;
2024-05-20 16:36:34 -04:00
const plan = getPlanByPriceId ( data [ 0 ] . price_id ) ;
// HyperDX Logging
setTrace ( team_id , normalizedApi ) ;
2024-05-14 17:08:31 -04:00
subscriptionData = {
2024-05-20 16:36:34 -04:00
team_id : team_id ,
plan : plan
2024-05-14 17:08:31 -04:00
}
2024-05-25 11:57:49 -04:00
switch ( mode ) {
2024-05-14 17:08:31 -04:00
case RateLimiterMode . Crawl :
2024-05-19 15:45:46 -04:00
rateLimiter = getRateLimiter ( RateLimiterMode . Crawl , token , subscriptionData . plan ) ;
2024-05-14 17:08:31 -04:00
break ;
case RateLimiterMode . Scrape :
2024-05-19 15:45:46 -04:00
rateLimiter = getRateLimiter ( RateLimiterMode . Scrape , token , subscriptionData . plan ) ;
2024-05-14 17:08:31 -04:00
break ;
2024-05-30 17:42:32 -04:00
case RateLimiterMode . Search :
rateLimiter = getRateLimiter ( RateLimiterMode . Search , token , subscriptionData . plan ) ;
break ;
2024-05-14 17:47:36 -04:00
case RateLimiterMode . CrawlStatus :
2024-05-15 07:34:49 -04:00
rateLimiter = getRateLimiter ( RateLimiterMode . CrawlStatus , token ) ;
2024-05-14 17:47:36 -04:00
break ;
2024-05-30 17:42:32 -04:00
2024-05-19 15:45:46 -04:00
case RateLimiterMode . Preview :
rateLimiter = getRateLimiter ( RateLimiterMode . Preview , token ) ;
break ;
2024-05-14 17:47:36 -04:00
default :
2024-05-15 07:34:49 -04:00
rateLimiter = getRateLimiter ( RateLimiterMode . Crawl , token ) ;
2024-05-14 17:47:36 -04:00
break ;
2024-05-14 17:08:31 -04:00
// case RateLimiterMode.Search:
// rateLimiter = await searchRateLimiter(RateLimiterMode.Search, token);
// break;
}
}
2024-06-05 16:20:26 -04:00
const team_endpoint_token = team_id ;
2024-04-20 19:38:05 -04:00
try {
2024-06-05 16:20:26 -04:00
await rateLimiter . consume ( team_endpoint_token ) ;
2024-04-20 19:38:05 -04:00
} catch ( rateLimiterRes ) {
console . error ( rateLimiterRes ) ;
2024-05-25 11:57:49 -04:00
const secs = Math . round ( rateLimiterRes . msBeforeNext / 1000 ) || 1 ;
const retryDate = new Date ( Date . now ( ) + rateLimiterRes . msBeforeNext ) ;
2024-06-05 16:20:26 -04:00
// We can only send a rate limit email every 7 days, send notification already has the date in between checking
const startDate = new Date ( ) ;
const endDate = new Date ( ) ;
endDate . setDate ( endDate . getDate ( ) + 7 ) ;
await sendNotification ( team_id , NotificationType . RATE_LIMIT_REACHED , startDate . toISOString ( ) , endDate . toISOString ( ) ) ;
2024-04-20 19:38:05 -04:00
return {
success : false ,
2024-05-31 14:39:55 -04:00
error : ` Rate limit exceeded. Consumed points: ${ rateLimiterRes . consumedPoints } , Remaining points: ${ rateLimiterRes . remainingPoints } . Upgrade your plan at https://firecrawl.dev/pricing for increased rate limits or please retry after ${ secs } s, resets at ${ retryDate } ` ,
2024-04-20 19:38:05 -04:00
status : 429 ,
} ;
}
if (
token === "this_is_just_a_preview_token" &&
2024-04-26 15:57:49 -04:00
( mode === RateLimiterMode . Scrape || mode === RateLimiterMode . Preview || mode === RateLimiterMode . Search )
2024-04-20 19:38:05 -04:00
) {
return { success : true , team_id : "preview" } ;
2024-04-26 15:57:49 -04:00
// check the origin of the request and make sure its from firecrawl.dev
// const origin = req.headers.origin;
// if (origin && origin.includes("firecrawl.dev")){
// return { success: true, team_id: "preview" };
// }
// if(process.env.ENV !== "production") {
// return { success: true, team_id: "preview" };
// }
// return { success: false, error: "Unauthorized: Invalid token", status: 401 };
2024-04-20 19:38:05 -04:00
}
// make sure api key is valid, based on the api_keys table in supabase
2024-05-14 17:08:31 -04:00
if ( ! subscriptionData ) {
normalizedApi = parseApi ( token ) ;
const { data , error } = await supabase_service
2024-05-25 11:57:49 -04:00
. from ( "api_keys" )
. select ( "*" )
. eq ( "key" , normalizedApi ) ;
2024-05-14 17:08:31 -04:00
if ( error || ! data || data . length === 0 ) {
return {
success : false ,
error : "Unauthorized: Invalid token" ,
status : 401 ,
} ;
}
subscriptionData = data [ 0 ] ;
2024-04-20 19:38:05 -04:00
}
2024-05-30 17:46:55 -04:00
return { success : true , team_id : subscriptionData.team_id , plan : subscriptionData.plan ? ? "" } ;
2024-04-20 19:38:05 -04:00
}
2024-05-14 17:08:31 -04:00
function getPlanByPriceId ( price_id : string ) {
switch ( price_id ) {
2024-05-30 17:31:36 -04:00
case process . env . STRIPE_PRICE_ID_STARTER :
return 'starter' ;
2024-05-14 17:08:31 -04:00
case process . env . STRIPE_PRICE_ID_STANDARD :
return 'standard' ;
case process . env . STRIPE_PRICE_ID_SCALE :
return 'scale' ;
2024-05-30 17:31:36 -04:00
case process . env . STRIPE_PRICE_ID_HOBBY || process . env . STRIPE_PRICE_ID_HOBBY_YEARLY :
return 'hobby' ;
case process . env . STRIPE_PRICE_ID_STANDARD_NEW || process . env . STRIPE_PRICE_ID_STANDARD_NEW_YEARLY :
return 'standard-new' ;
case process . env . STRIPE_PRICE_ID_GROWTH || process . env . STRIPE_PRICE_ID_GROWTH_YEARLY :
return 'growth' ;
2024-05-14 17:08:31 -04:00
default :
2024-05-30 17:31:36 -04:00
return 'free' ;
2024-05-14 17:08:31 -04:00
}
}