2024-08-26 18:48:00 -03:00
import { parseApi } from "../lib/parseApi" ;
import { getRateLimiter } from "../services/rate-limiter" ;
2024-08-12 13:42:09 -04:00
import {
AuthResponse ,
NotificationType ,
2024-08-21 20:54:39 -03:00
PlanType ,
2024-08-12 13:42:09 -04:00
RateLimiterMode ,
2024-08-26 18:48:00 -03:00
} from "../types" ;
import { supabase_service } from "../services/supabase" ;
import { withAuth } from "../lib/withAuth" ;
2024-05-14 18:08:31 -03:00
import { RateLimiterRedis } from "rate-limiter-flexible" ;
2024-08-26 18:48:00 -03:00
import { sendNotification } from "../services/notification/email_notification" ;
2024-11-07 20:57:33 +01:00
import { logger } from "../lib/logger" ;
2024-08-26 18:48:00 -03:00
import { redlock } from "../services/redlock" ;
2024-10-22 20:28:10 +02:00
import { deleteKey , getValue } from "../services/redis" ;
2024-08-26 18:48:00 -03:00
import { setValue } from "../services/redis" ;
2024-08-12 13:42:09 -04:00
import { validate } from "uuid" ;
2024-08-22 03:55:40 +02:00
import * as Sentry from "@sentry/node" ;
2024-09-25 19:25:18 +02:00
import { AuthCreditUsageChunk } from "./v1/types" ;
2024-08-26 19:57:27 -03:00
// const { data, error } = await supabase_service
// .from('api_keys')
// .select(`
// key,
// team_id,
// teams (
// subscriptions (
// price_id
// )
// )
// `)
// .eq('key', normalizedApi)
// .limit(1)
// .single();
2024-08-12 13:37:47 -04:00
function normalizedApiIsUuid ( potentialUuid : string ) : boolean {
// Check if the string is a valid UUID
return validate ( potentialUuid ) ;
}
2024-09-25 19:25:18 +02:00
2024-10-14 12:24:34 -03:00
export async function setCachedACUC (
api_key : string ,
acuc :
2024-11-07 20:57:33 +01:00
| AuthCreditUsageChunk | null
| ( ( acuc : AuthCreditUsageChunk ) = > AuthCreditUsageChunk | null )
2024-10-14 12:24:34 -03:00
) {
2024-09-25 19:25:18 +02:00
const cacheKeyACUC = ` acuc_ ${ api_key } ` ;
const redLockKey = ` lock_ ${ cacheKeyACUC } ` ;
try {
2024-10-14 12:24:34 -03:00
await redlock . using ( [ redLockKey ] , 10000 , { } , async ( signal ) = > {
2024-09-25 22:15:02 +02:00
if ( typeof acuc === "function" ) {
2024-11-07 20:57:33 +01:00
acuc = acuc ( JSON . parse ( await getValue ( cacheKeyACUC ) ? ? "null" ) ) ;
2024-09-25 22:15:02 +02:00
if ( acuc === null ) {
2024-09-25 22:47:42 +02:00
if ( signal . aborted ) {
throw signal . error ;
}
2024-09-25 22:15:02 +02:00
return ;
}
}
2024-09-25 22:47:42 +02:00
if ( signal . aborted ) {
throw signal . error ;
}
2024-09-25 19:25:18 +02:00
// Cache for 10 minutes. This means that changing subscription tier could have
// a maximum of 10 minutes of a delay. - mogery
2024-09-26 22:06:50 +02:00
await setValue ( cacheKeyACUC , JSON . stringify ( acuc ) , 600 , true ) ;
2024-09-25 22:47:42 +02:00
} ) ;
2024-09-25 19:25:18 +02:00
} catch ( error ) {
2024-11-07 20:57:33 +01:00
logger . error ( ` Error updating cached ACUC ${ cacheKeyACUC } : ${ error } ` ) ;
2024-09-25 19:25:18 +02:00
}
}
2024-10-14 12:24:34 -03:00
export async function getACUC (
api_key : string ,
2024-10-22 19:47:23 -03:00
cacheOnly = false ,
useCache = true
2024-10-14 12:24:34 -03:00
) : Promise < AuthCreditUsageChunk | null > {
2024-09-25 19:25:18 +02:00
const cacheKeyACUC = ` acuc_ ${ api_key } ` ;
2024-10-22 19:47:23 -03:00
if ( useCache ) {
const cachedACUC = await getValue ( cacheKeyACUC ) ;
if ( cachedACUC !== null ) {
return JSON . parse ( cachedACUC ) ;
}
}
2024-09-25 19:25:18 +02:00
2024-10-22 19:47:23 -03:00
if ( ! cacheOnly ) {
2024-10-14 12:24:34 -03:00
let data ;
let error ;
let retries = 0 ;
const maxRetries = 5 ;
while ( retries < maxRetries ) {
( { data , error } = await supabase_service . rpc (
2024-10-24 23:13:30 -03:00
"auth_credit_usage_chunk_test_21_credit_pack" ,
2024-12-06 13:06:26 -03:00
{ input_key : api_key } ,
{ get : true }
2024-10-14 12:24:34 -03:00
) ) ;
if ( ! error ) {
break ;
}
2024-11-07 20:57:33 +01:00
logger . warn (
2024-10-14 12:24:34 -03:00
` Failed to retrieve authentication and credit usage data after ${ retries } , trying again... `
) ;
retries ++ ;
if ( retries === maxRetries ) {
throw new Error (
"Failed to retrieve authentication and credit usage data after 3 attempts: " +
JSON . stringify ( error )
) ;
}
// Wait for a short time before retrying
await new Promise ( ( resolve ) = > setTimeout ( resolve , 200 ) ) ;
2024-09-25 19:25:18 +02:00
}
2024-10-14 12:24:34 -03:00
const chunk : AuthCreditUsageChunk | null =
data . length === 0 ? null : data [ 0 ] . team_id === null ? null : data [ 0 ] ;
2024-09-25 19:25:18 +02:00
// NOTE: Should we cache null chunks? - mogery
2024-10-22 19:47:23 -03:00
if ( chunk !== null && useCache ) {
2024-09-25 19:25:18 +02:00
setCachedACUC ( api_key , chunk ) ;
}
2024-10-24 23:13:30 -03:00
// console.log(chunk);
2024-09-25 19:25:18 +02:00
return chunk ;
2024-09-25 21:37:01 +02:00
} else {
return null ;
2024-09-25 19:25:18 +02:00
}
}
2024-10-22 20:28:10 +02:00
export async function clearACUC (
api_key : string ,
) : Promise < void > {
const cacheKeyACUC = ` acuc_ ${ api_key } ` ;
await deleteKey ( cacheKeyACUC ) ;
}
2024-08-12 13:42:09 -04:00
export async function authenticateUser (
req ,
res ,
mode? : RateLimiterMode
) : Promise < AuthResponse > {
2024-11-07 20:57:33 +01:00
return withAuth ( supaAuthenticateUser , { success : true , chunk : null , team_id : "bypass" } ) ( req , res , mode ) ;
2024-05-20 13:36:34 -07:00
}
2024-08-12 15:07:30 -04:00
2024-04-21 10:36:48 -07:00
export async function supaAuthenticateUser (
2024-04-20 16:38:05 -07:00
req ,
res ,
mode? : RateLimiterMode
2024-11-07 20:57:33 +01:00
) : Promise < AuthResponse > {
2024-10-14 12:24:34 -03:00
const authHeader =
req . headers . authorization ? ?
( req . headers [ "sec-websocket-protocol" ]
? ` Bearer ${ req . headers [ "sec-websocket-protocol" ] } `
: null ) ;
2024-04-20 16:38:05 -07:00
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 18:08:31 -03:00
const incomingIP = ( req . headers [ "x-forwarded-for" ] ||
req . socket . remoteAddress ) as string ;
const iptoken = incomingIP + token ;
let rateLimiter : RateLimiterRedis ;
2024-08-12 13:42:09 -04:00
let subscriptionData : { team_id : string ; plan : string } | null = null ;
2024-05-14 18:08:31 -03:00
let normalizedApi : string ;
2024-08-12 13:37:47 -04:00
let teamId : string | null = null ;
let priceId : string | null = null ;
2024-11-07 20:57:33 +01:00
let chunk : AuthCreditUsageChunk | null = null ;
2024-06-05 13:20:26 -07:00
2024-05-14 18:08:31 -03:00
if ( token == "this_is_just_a_preview_token" ) {
2024-08-28 14:18:05 -03:00
if ( mode == RateLimiterMode . CrawlStatus ) {
rateLimiter = getRateLimiter ( RateLimiterMode . CrawlStatus , token ) ;
} else {
rateLimiter = getRateLimiter ( RateLimiterMode . Preview , token ) ;
2024-10-14 12:24:34 -03:00
}
2024-08-12 13:37:47 -04:00
teamId = "preview" ;
2024-05-17 15:37:47 -03:00
} else {
2024-05-14 18:08:31 -03:00
normalizedApi = parseApi ( token ) ;
2024-08-12 13:42:09 -04:00
if ( ! normalizedApiIsUuid ( normalizedApi ) ) {
2024-08-12 13:37:47 -04:00
return {
success : false ,
error : "Unauthorized: Invalid token" ,
status : 401 ,
} ;
}
2024-08-12 15:07:30 -04:00
2024-09-25 19:25:18 +02:00
chunk = await getACUC ( normalizedApi ) ;
2024-08-12 13:37:47 -04:00
2024-09-25 19:25:18 +02:00
if ( chunk === null ) {
return {
success : false ,
error : "Unauthorized: Invalid token" ,
status : 401 ,
} ;
2024-08-12 13:37:47 -04:00
}
2024-05-14 18:08:31 -03:00
2024-09-25 19:25:18 +02:00
teamId = chunk . team_id ;
priceId = chunk . price_id ;
2024-05-14 18:08:31 -03:00
2024-08-12 13:37:47 -04:00
const plan = getPlanByPriceId ( priceId ) ;
2024-05-14 18:08:31 -03:00
subscriptionData = {
2024-08-12 13:37:47 -04:00
team_id : teamId ,
2024-09-25 19:25:18 +02:00
plan ,
2024-08-12 13:42:09 -04:00
} ;
2024-05-25 11:57:49 -04:00
switch ( mode ) {
2024-05-14 18:08:31 -03:00
case RateLimiterMode . Crawl :
2024-08-12 13:42:09 -04:00
rateLimiter = getRateLimiter (
RateLimiterMode . Crawl ,
token ,
subscriptionData . plan
) ;
2024-05-14 18:08:31 -03:00
break ;
case RateLimiterMode . Scrape :
2024-08-12 13:42:09 -04:00
rateLimiter = getRateLimiter (
RateLimiterMode . Scrape ,
token ,
2024-08-26 16:05:11 -03:00
subscriptionData . plan ,
teamId
2024-08-12 13:42:09 -04:00
) ;
2024-05-14 18:08:31 -03:00
break ;
2024-05-30 14:42:32 -07:00
case RateLimiterMode . Search :
2024-08-12 13:42:09 -04:00
rateLimiter = getRateLimiter (
RateLimiterMode . Search ,
token ,
subscriptionData . plan
) ;
2024-05-30 14:42:32 -07:00
break ;
2024-08-27 20:02:39 -03:00
case RateLimiterMode . Map :
rateLimiter = getRateLimiter (
RateLimiterMode . Map ,
token ,
subscriptionData . plan
) ;
break ;
2024-05-14 14:47:36 -07:00
case RateLimiterMode . CrawlStatus :
2024-05-15 08:34:49 -03:00
rateLimiter = getRateLimiter ( RateLimiterMode . CrawlStatus , token ) ;
2024-05-14 14:47:36 -07:00
break ;
2024-08-12 13:42:09 -04:00
2024-05-19 12:45:46 -07:00
case RateLimiterMode . Preview :
rateLimiter = getRateLimiter ( RateLimiterMode . Preview , token ) ;
break ;
2024-05-14 14:47:36 -07:00
default :
2024-05-15 08:34:49 -03:00
rateLimiter = getRateLimiter ( RateLimiterMode . Crawl , token ) ;
2024-05-14 14:47:36 -07:00
break ;
2024-05-14 18:08:31 -03:00
// case RateLimiterMode.Search:
// rateLimiter = await searchRateLimiter(RateLimiterMode.Search, token);
// break;
}
}
2024-08-12 13:42:09 -04:00
const team_endpoint_token =
token === "this_is_just_a_preview_token" ? iptoken : teamId ;
2024-06-05 13:20:26 -07:00
2024-04-20 16:38:05 -07:00
try {
2024-06-05 13:20:26 -07:00
await rateLimiter . consume ( team_endpoint_token ) ;
2024-04-20 16:38:05 -07:00
} catch ( rateLimiterRes ) {
2024-12-09 19:29:32 +01:00
logger . error ( ` Rate limit exceeded: ${ rateLimiterRes } ` , { teamId , priceId , plan : subscriptionData?.plan , mode , 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 13:20:26 -07: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 ) ;
2024-08-12 13:42:09 -04:00
2024-06-07 10:39:11 -07:00
// await sendNotification(team_id, NotificationType.RATE_LIMIT_REACHED, startDate.toISOString(), endDate.toISOString());
2024-08-12 13:37:47 -04:00
2024-04-20 16:38:05 -07:00
return {
success : false ,
2024-08-21 21:51:54 -03:00
error : ` Rate limit exceeded. Consumed (req/min): ${ rateLimiterRes . consumedPoints } , Remaining (req/min): ${ 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 16:38:05 -07:00
status : 429 ,
} ;
}
if (
token === "this_is_just_a_preview_token" &&
2024-08-12 13:42:09 -04:00
( mode === RateLimiterMode . Scrape ||
mode === RateLimiterMode . Preview ||
2024-08-27 20:02:39 -03:00
mode === RateLimiterMode . Map ||
2024-08-28 14:09:12 -03:00
mode === RateLimiterMode . Crawl ||
2024-08-28 14:17:59 -03:00
mode === RateLimiterMode . CrawlStatus ||
2024-08-12 13:42:09 -04:00
mode === RateLimiterMode . Search )
2024-04-20 16:38:05 -07:00
) {
2024-11-07 20:57:33 +01:00
return { success : true , team_id : "preview" , chunk : null } ;
2024-04-26 12:57:49 -07: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 16:38:05 -07:00
}
2024-08-12 13:42:09 -04:00
return {
success : true ,
2024-11-07 20:57:33 +01:00
team_id : teamId ? ? undefined ,
plan : ( subscriptionData ? . plan ? ? "" ) as PlanType ,
2024-09-25 19:25:18 +02:00
chunk ,
2024-08-12 13:42:09 -04:00
} ;
2024-04-20 16:38:05 -07:00
}
2024-11-07 20:57:33 +01:00
function getPlanByPriceId ( price_id : string | null ) : PlanType {
2024-05-14 18:08:31 -03:00
switch ( price_id ) {
2024-05-30 14:31:36 -07:00
case process . env . STRIPE_PRICE_ID_STARTER :
2024-08-12 13:42:09 -04:00
return "starter" ;
2024-05-14 18:08:31 -03:00
case process . env . STRIPE_PRICE_ID_STANDARD :
2024-08-12 13:42:09 -04:00
return "standard" ;
2024-05-14 18:08:31 -03:00
case process . env . STRIPE_PRICE_ID_SCALE :
2024-08-12 13:42:09 -04:00
return "scale" ;
2024-07-30 10:37:33 -03:00
case process . env . STRIPE_PRICE_ID_HOBBY :
case process . env . STRIPE_PRICE_ID_HOBBY_YEARLY :
2024-08-12 13:42:09 -04:00
return "hobby" ;
2024-07-30 10:37:33 -03:00
case process . env . STRIPE_PRICE_ID_STANDARD_NEW :
case process . env . STRIPE_PRICE_ID_STANDARD_NEW_YEARLY :
2024-08-12 13:42:09 -04:00
return "standardnew" ;
2024-07-30 10:37:33 -03:00
case process . env . STRIPE_PRICE_ID_GROWTH :
case process . env . STRIPE_PRICE_ID_GROWTH_YEARLY :
2024-10-29 16:06:12 -03:00
case process . env . STRIPE_PRICE_ID_SCALE_2M :
2024-08-12 13:42:09 -04:00
return "growth" ;
2024-08-15 18:37:19 -04:00
case process . env . STRIPE_PRICE_ID_GROWTH_DOUBLE_MONTHLY :
return "growthdouble" ;
2024-10-29 21:01:43 -03:00
case process . env . STRIPE_PRICE_ID_ETIER2C :
return "etier2c" ;
2024-11-04 18:14:38 -05:00
case process . env . STRIPE_PRICE_ID_ETIER1A_MONTHLY : //ocqh
return "etier1a" ;
2024-12-04 16:25:43 -03:00
case process . env . STRIPE_PRICE_ID_ETIER_SCALE_1_MONTHLY :
return "etierscale1" ;
2024-05-14 18:08:31 -03:00
default :
2024-08-12 13:42:09 -04:00
return "free" ;
2024-05-14 18:08:31 -03:00
}
2024-08-12 13:42:09 -04:00
}