Compare commits

...

2 Commits

Author SHA1 Message Date
Owen
49a326cde7 Add trust proxy to the internal api
Fix access logs not having the right ip
2026-02-24 20:23:42 -08:00
Owen
63e208f4ec Use local cache in verify session 2026-02-24 19:56:16 -08:00
3 changed files with 31 additions and 26 deletions

View File

@@ -16,6 +16,11 @@ const internalPort = config.getRawConfig().server.internal_port;
export function createInternalServer() { export function createInternalServer() {
const internalServer = express(); const internalServer = express();
const trustProxy = config.getRawConfig().server.trust_proxy;
if (trustProxy) {
internalServer.set("trust proxy", trustProxy);
}
internalServer.use(helmet()); internalServer.use(helmet());
internalServer.use(cors()); internalServer.use(cors());
internalServer.use(stripDuplicateSesions); internalServer.use(stripDuplicateSesions);

View File

@@ -4,7 +4,7 @@ import { redisManager } from "@server/private/lib/redis";
// Create local cache with maxKeys limit to prevent memory leaks // Create local cache with maxKeys limit to prevent memory leaks
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient // With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
const localCache = new NodeCache({ export const localCache = new NodeCache({
stdTTL: 3600, stdTTL: 3600,
checkperiod: 120, checkperiod: 120,
maxKeys: 10000 maxKeys: 10000
@@ -41,12 +41,12 @@ class AdaptiveCache {
try { try {
const serialized = JSON.stringify(value); const serialized = JSON.stringify(value);
const success = await redisManager.set(key, serialized, effectiveTtl); const success = await redisManager.set(key, serialized, effectiveTtl);
if (success) { if (success) {
logger.debug(`Set key in Redis: ${key}`); logger.debug(`Set key in Redis: ${key}`);
return true; return true;
} }
// Redis failed, fall through to local cache // Redis failed, fall through to local cache
logger.debug(`Redis set failed for key ${key}, falling back to local cache`); logger.debug(`Redis set failed for key ${key}, falling back to local cache`);
} catch (error) { } catch (error) {
@@ -72,12 +72,12 @@ class AdaptiveCache {
if (this.useRedis()) { if (this.useRedis()) {
try { try {
const value = await redisManager.get(key); const value = await redisManager.get(key);
if (value !== null) { if (value !== null) {
logger.debug(`Cache hit in Redis: ${key}`); logger.debug(`Cache hit in Redis: ${key}`);
return JSON.parse(value) as T; return JSON.parse(value) as T;
} }
logger.debug(`Cache miss in Redis: ${key}`); logger.debug(`Cache miss in Redis: ${key}`);
return undefined; return undefined;
} catch (error) { } catch (error) {
@@ -114,11 +114,11 @@ class AdaptiveCache {
logger.debug(`Deleted key from Redis: ${k}`); logger.debug(`Deleted key from Redis: ${k}`);
} }
} }
if (deletedCount === keys.length) { if (deletedCount === keys.length) {
return deletedCount; return deletedCount;
} }
// Some Redis deletes failed, fall through to local cache // Some Redis deletes failed, fall through to local cache
logger.debug(`Some Redis deletes failed, falling back to local cache`); logger.debug(`Some Redis deletes failed, falling back to local cache`);
} catch (error) { } catch (error) {
@@ -169,7 +169,7 @@ class AdaptiveCache {
if (this.useRedis()) { if (this.useRedis()) {
try { try {
const results: (T | undefined)[] = []; const results: (T | undefined)[] = [];
for (const key of keys) { for (const key of keys) {
const value = await redisManager.get(key); const value = await redisManager.get(key);
if (value !== null) { if (value !== null) {
@@ -178,7 +178,7 @@ class AdaptiveCache {
results.push(undefined); results.push(undefined);
} }
} }
return results; return results;
} catch (error) { } catch (error) {
logger.error(`Redis mget error:`, error); logger.error(`Redis mget error:`, error);
@@ -241,7 +241,7 @@ class AdaptiveCache {
if (this.useRedis()) { if (this.useRedis()) {
logger.warn(`getTtl called for key ${key} but Redis TTL lookup is not implemented`); logger.warn(`getTtl called for key ${key} but Redis TTL lookup is not implemented`);
} }
const ttl = localCache.getTtl(key); const ttl = localCache.getTtl(key);
if (ttl === undefined) { if (ttl === undefined) {
return -1; return -1;
@@ -263,4 +263,4 @@ class AdaptiveCache {
// Export singleton instance // Export singleton instance
export const cache = new AdaptiveCache(); export const cache = new AdaptiveCache();
export default cache; export default cache;

View File

@@ -37,7 +37,7 @@ import {
enforceResourceSessionLength enforceResourceSessionLength
} from "#dynamic/lib/checkOrgAccessPolicy"; } from "#dynamic/lib/checkOrgAccessPolicy";
import { logRequestAudit } from "./logRequestAudit"; import { logRequestAudit } from "./logRequestAudit";
import cache from "@server/lib/cache"; import { localCache } from "@server/lib/cache";
import { APP_VERSION } from "@server/lib/consts"; import { APP_VERSION } from "@server/lib/consts";
import { isSubscribed } from "#dynamic/lib/isSubscribed"; import { isSubscribed } from "#dynamic/lib/isSubscribed";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
@@ -137,7 +137,7 @@ export async function verifyResourceSession(
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null; headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
org: Org; org: Org;
} }
| undefined = await cache.get(resourceCacheKey); | undefined = localCache.get(resourceCacheKey);
if (!resourceData) { if (!resourceData) {
const result = await getResourceByDomain(cleanHost); const result = await getResourceByDomain(cleanHost);
@@ -161,7 +161,7 @@ export async function verifyResourceSession(
} }
resourceData = result; resourceData = result;
await cache.set(resourceCacheKey, resourceData, 5); localCache.set(resourceCacheKey, resourceData, 5);
} }
const { const {
@@ -405,7 +405,7 @@ export async function verifyResourceSession(
// check for HTTP Basic Auth header // check for HTTP Basic Auth header
const clientHeaderAuthKey = `headerAuth:${clientHeaderAuth}`; const clientHeaderAuthKey = `headerAuth:${clientHeaderAuth}`;
if (headerAuth && clientHeaderAuth) { if (headerAuth && clientHeaderAuth) {
if (await cache.get(clientHeaderAuthKey)) { if (localCache.get(clientHeaderAuthKey)) {
logger.debug( logger.debug(
"Resource allowed because header auth is valid (cached)" "Resource allowed because header auth is valid (cached)"
); );
@@ -428,7 +428,7 @@ export async function verifyResourceSession(
headerAuth.headerAuthHash headerAuth.headerAuthHash
) )
) { ) {
await cache.set(clientHeaderAuthKey, clientHeaderAuth, 5); localCache.set(clientHeaderAuthKey, clientHeaderAuth, 5);
logger.debug("Resource allowed because header auth is valid"); logger.debug("Resource allowed because header auth is valid");
logRequestAudit( logRequestAudit(
@@ -520,7 +520,7 @@ export async function verifyResourceSession(
if (resourceSessionToken) { if (resourceSessionToken) {
const sessionCacheKey = `session:${resourceSessionToken}`; const sessionCacheKey = `session:${resourceSessionToken}`;
let resourceSession: any = await cache.get(sessionCacheKey); let resourceSession: any = localCache.get(sessionCacheKey);
if (!resourceSession) { if (!resourceSession) {
const result = await validateResourceSessionToken( const result = await validateResourceSessionToken(
@@ -529,7 +529,7 @@ export async function verifyResourceSession(
); );
resourceSession = result?.resourceSession; resourceSession = result?.resourceSession;
await cache.set(sessionCacheKey, resourceSession, 5); localCache.set(sessionCacheKey, resourceSession, 5);
} }
if (resourceSession?.isRequestToken) { if (resourceSession?.isRequestToken) {
@@ -662,7 +662,7 @@ export async function verifyResourceSession(
}:${resource.resourceId}`; }:${resource.resourceId}`;
let allowedUserData: BasicUserData | null | undefined = let allowedUserData: BasicUserData | null | undefined =
await cache.get(userAccessCacheKey); localCache.get(userAccessCacheKey);
if (allowedUserData === undefined) { if (allowedUserData === undefined) {
allowedUserData = await isUserAllowedToAccessResource( allowedUserData = await isUserAllowedToAccessResource(
@@ -671,7 +671,7 @@ export async function verifyResourceSession(
resourceData.org resourceData.org
); );
await cache.set(userAccessCacheKey, allowedUserData, 5); localCache.set(userAccessCacheKey, allowedUserData, 5);
} }
if ( if (
@@ -974,11 +974,11 @@ async function checkRules(
): Promise<"ACCEPT" | "DROP" | "PASS" | undefined> { ): Promise<"ACCEPT" | "DROP" | "PASS" | undefined> {
const ruleCacheKey = `rules:${resourceId}`; const ruleCacheKey = `rules:${resourceId}`;
let rules: ResourceRule[] | undefined = await cache.get(ruleCacheKey); let rules: ResourceRule[] | undefined = localCache.get(ruleCacheKey);
if (!rules) { if (!rules) {
rules = await getResourceRules(resourceId); rules = await getResourceRules(resourceId);
await cache.set(ruleCacheKey, rules, 5); localCache.set(ruleCacheKey, rules, 5);
} }
if (rules.length === 0) { if (rules.length === 0) {
@@ -1208,13 +1208,13 @@ async function isIpInAsn(
async function getAsnFromIp(ip: string): Promise<number | undefined> { async function getAsnFromIp(ip: string): Promise<number | undefined> {
const asnCacheKey = `asn:${ip}`; const asnCacheKey = `asn:${ip}`;
let cachedAsn: number | undefined = await cache.get(asnCacheKey); let cachedAsn: number | undefined = localCache.get(asnCacheKey);
if (!cachedAsn) { if (!cachedAsn) {
cachedAsn = await getAsnForIp(ip); // do it locally cachedAsn = await getAsnForIp(ip); // do it locally
// Cache for longer since IP ASN doesn't change frequently // Cache for longer since IP ASN doesn't change frequently
if (cachedAsn) { if (cachedAsn) {
await cache.set(asnCacheKey, cachedAsn, 300); // 5 minutes localCache.set(asnCacheKey, cachedAsn, 300); // 5 minutes
} }
} }
@@ -1224,14 +1224,14 @@ async function getAsnFromIp(ip: string): Promise<number | undefined> {
async function getCountryCodeFromIp(ip: string): Promise<string | undefined> { async function getCountryCodeFromIp(ip: string): Promise<string | undefined> {
const geoIpCacheKey = `geoip:${ip}`; const geoIpCacheKey = `geoip:${ip}`;
let cachedCountryCode: string | undefined = await cache.get(geoIpCacheKey); let cachedCountryCode: string | undefined = localCache.get(geoIpCacheKey);
if (!cachedCountryCode) { if (!cachedCountryCode) {
cachedCountryCode = await getCountryCodeForIp(ip); // do it locally cachedCountryCode = await getCountryCodeForIp(ip); // do it locally
// Only cache successful lookups to avoid filling cache with undefined values // Only cache successful lookups to avoid filling cache with undefined values
if (cachedCountryCode) { if (cachedCountryCode) {
// Cache for longer since IP geolocation doesn't change frequently // Cache for longer since IP geolocation doesn't change frequently
await cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes localCache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
} }
} }