diff --git a/redirect/bun.lockb b/redirect/bun.lockb index 0983070..b9abd06 100755 Binary files a/redirect/bun.lockb and b/redirect/bun.lockb differ diff --git a/redirect/package.json b/redirect/package.json index 725a9f3..31b46c7 100644 --- a/redirect/package.json +++ b/redirect/package.json @@ -10,7 +10,8 @@ "@maxmind/geoip2-node": "^5.0.0", "@types/pg": "^8.10.2", "@types/ua-parser-js": "^0.7.39", - "elysia": "latest", + "elysia": "^1.1.5", + "elysia-rate-limit": "^4.1.0", "kysely": "^0.26.3", "magic-regexp": "^0.7.0", "nanoid": "^5.0.1", diff --git a/redirect/src/index.ts b/redirect/src/index.ts index f2dc4d0..a2f65de 100644 --- a/redirect/src/index.ts +++ b/redirect/src/index.ts @@ -2,14 +2,28 @@ import { Elysia } from 'elysia' import { db } from './database' import { cors } from '@elysiajs/cors' import { UAParser } from 'ua-parser-js' +import geoip2 from '@maxmind/geoip2-node' +import { rateLimit } from 'elysia-rate-limit' +import { LRUCache } from 'lru-cache' + +const WebServiceClient = geoip2.WebServiceClient const fallback_url = Bun.env.FALLBACK_URL ?? 'https://app.kon.sh' const app_url = Bun.env.APP_URL ?? 'kon.sh' -const geoipupdate_account_id = Bun.env.GEOIPUPDATE_ACCOUNT_ID -const geoipupdate_license_key = Bun.env.GEOIPUPDATE_LICENSE_KEY const hosting_provider = Bun.env.HOSTING_PROVIDER -const app = new Elysia().use(cors()) +const client = new WebServiceClient( + Bun.env.GEOIPUPDATE_ACCOUNT_ID || '', + Bun.env.GEOIPUPDATE_LICENSE_KEY || '', + { host: 'geolite.info' } +) + +const clickLimiter = new LRUCache({ + ttl: 60 * 60 * 60 * 1000, // 1 hr + ttlAutopurge: true, +}) + +const app = new Elysia().use(cors()).use(rateLimit({ duration: 1000 })) app.get('/', ({ set }) => (set.redirect = fallback_url + '/landing')) app.get('/invalid', () => 'Invalid Shortener') @@ -17,7 +31,7 @@ app.get('/robots.txt', () => Bun.file('public/robots.txt')) app.get( '/:shortenerCode', - async ({ params: { shortenerCode }, set, request }) => { + async ({ params: { shortenerCode }, set, request, cookie }) => { try { const request_domain = request.headers.get('host') const domain = request_domain !== app_url ? request_domain : null @@ -28,21 +42,6 @@ app.get( : 'x-forwarded-for' ) - const WebServiceClient = - require('@maxmind/geoip2-node').WebServiceClient - - const client = new WebServiceClient( - geoipupdate_account_id, - geoipupdate_license_key, - { host: 'geolite.info' } - ) - - const geolocation = await client.city(ip) - - const user_agent = request.headers.get('User-Agent') - - const ua_parser = new UAParser(user_agent ?? '') - const query = db .selectFrom('shortener') .selectAll('shortener') @@ -60,26 +59,15 @@ app.get( const shortener = await query.execute() - console.log('shortener', shortener) + const user_agent = request.headers.get('User-Agent') + + const ua_parser = new UAParser(user_agent ?? '') if (!shortener.length || !shortener[0].active) { set.redirect = '/invalid' return } - const visitor_data = { - shortener_id: shortener[0].id, - country: geolocation.country.names.en as string, - country_code: geolocation.country.isoCode as string, - city: geolocation.city.names.en as string, - device_type: ua_parser.getDevice().type || 'desktop', - device_vendor: ua_parser.getDevice().vendor, - browser: ua_parser.getBrowser().name, - os: ua_parser.getOS().name, - } - - await db.insertInto('visitor').values(visitor_data).execute() - if ( ua_parser.getOS().name === 'iOS' && shortener[0].ios && @@ -95,6 +83,28 @@ app.get( } else { set.redirect = shortener[0].link } + + const clickKey = `${ip}_${shortener[0].id}` + const clickLimited = clickLimiter.has(clickKey) + + if (clickLimited) return + + clickLimiter.set(clickKey, 1) + + const geolocation = await client.city(ip || '') + + const visitor_data = { + shortener_id: shortener[0].id, + country: geolocation.country!.names.en, + country_code: geolocation.country!.isoCode, + city: geolocation.city!.names.en, + device_type: ua_parser.getDevice().type || 'desktop', + device_vendor: ua_parser.getDevice().vendor, + browser: ua_parser.getBrowser().name, + os: ua_parser.getOS().name, + } + + await db.insertInto('visitor').values(visitor_data).execute() } catch (error) { console.error(error) set.redirect = fallback_url