@unleash/proxy
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -21,2 +21,3 @@ "use strict"; | ||
app.disable('x-powered-by'); | ||
app.set('trust proxy', config.trustProxy); | ||
app.use(cors_1.default(corsOptions)); | ||
@@ -23,0 +24,0 @@ app.use(`${config.proxyBasePath}/proxy`, cors_1.default(corsOptions), express_1.default.json(), proxy.middleware); |
@@ -10,3 +10,2 @@ "use strict"; | ||
const strategy_1 = require("unleash-client/lib/strategy"); | ||
const util_1 = require("./util"); | ||
class Client extends events_1.default { | ||
@@ -19,3 +18,2 @@ constructor(config, init = unleash_client_1.initialize) { | ||
this.logger = config.logger; | ||
const instanceId = util_1.generateInstanceId(); | ||
const customHeadersFunction = async () => ({ | ||
@@ -28,3 +26,3 @@ Authorization: this.unleashApiToken, | ||
appName: config.unleashAppName, | ||
instanceId, | ||
instanceId: config.unleashInstanceId, | ||
environment: this.environment, | ||
@@ -41,3 +39,3 @@ refreshInterval: config.refreshInterval, | ||
appName: config.unleashAppName, | ||
instanceId, | ||
instanceId: config.unleashInstanceId, | ||
strategies: strategy_1.defaultStrategies.map((s) => s.name), | ||
@@ -66,3 +64,3 @@ metricsInterval: config.metricsInterval, | ||
getEnabledToggles(inContext) { | ||
this.logger.info('Get enabled toggles'); | ||
this.logger.info('Get enabled feature toggles for provided context', inContext); | ||
const context = this.fixContext(inContext); | ||
@@ -69,0 +67,0 @@ const definitions = this.unleash.getFeatureToggleDefinitions() || []; |
@@ -5,2 +5,3 @@ "use strict"; | ||
const logger_1 = require("./logger"); | ||
const util_1 = require("./util"); | ||
function resolveStringToArray(value) { | ||
@@ -33,2 +34,12 @@ if (value) { | ||
} | ||
function loadTrustProxy(value = 'FALSE') { | ||
const upperValue = value.toUpperCase(); | ||
if (upperValue === 'FALSE') { | ||
return false; | ||
} | ||
if (upperValue === 'TRUE') { | ||
return true; | ||
} | ||
return value; | ||
} | ||
function createProxyConfig(option) { | ||
@@ -51,2 +62,6 @@ const unleashUrl = option.unleashUrl || process.env.UNLEASH_URL; | ||
const logLevel = option.logLevel || process.env.LOG_LEVEL; | ||
const trustProxy = option.trustProxy || loadTrustProxy(process.env.TRUST_PROXY); | ||
const unleashInstanceId = option.unleashInstanceId || | ||
process.env.UNLEASH_INSTANCE_ID || | ||
util_1.generateInstanceId(); | ||
return { | ||
@@ -58,2 +73,3 @@ unleashUrl, | ||
'unleash-proxy', | ||
unleashInstanceId, | ||
customStrategies, | ||
@@ -70,2 +86,3 @@ proxySecrets, | ||
logger: option.logger || new logger_1.SimpleLogger(logLevel), | ||
trustProxy, | ||
}; | ||
@@ -72,0 +89,0 @@ } |
@@ -8,3 +8,2 @@ "use strict"; | ||
Object.defineProperty(exports, "start", { enumerable: true, get: function () { return server_1.start; } }); | ||
server_1.start(); | ||
//# sourceMappingURL=index.js.map |
@@ -27,2 +27,8 @@ "use strict"; | ||
}; | ||
const stripEmptyArray = (arr) => { | ||
if (!arr || arr.length === 0) { | ||
return ''; | ||
} | ||
return arr; | ||
}; | ||
class SimpleLogger { | ||
@@ -37,3 +43,3 @@ constructor(logLevel = LogLevel.warn) { | ||
if (this.shouldLog(LogLevel.debug)) { | ||
console.log(`DEBUG: ${message}`, args); | ||
console.log(`DEBUG: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -43,3 +49,3 @@ } | ||
if (this.shouldLog(LogLevel.info)) { | ||
console.log(`INFO: ${message}`, args); | ||
console.log(`INFO: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -49,3 +55,3 @@ } | ||
if (this.shouldLog(LogLevel.warn)) { | ||
console.log(`WARN: ${message}`, args); | ||
console.log(`WARN: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -55,3 +61,3 @@ } | ||
if (this.shouldLog(LogLevel.error)) { | ||
console.log(`ERROR: ${message}`, args); | ||
console.log(`ERROR: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -61,3 +67,3 @@ } | ||
if (this.shouldLog(LogLevel.fatal)) { | ||
console.log(`FATAL: ${message}`, args); | ||
console.log(`FATAL: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -64,0 +70,0 @@ } |
@@ -43,2 +43,48 @@ "use strict"; | ||
}); | ||
test('should set trust proxy', () => { | ||
const config = config_1.createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
trustProxy: true, | ||
}); | ||
expect(config.trustProxy).toBe(true); | ||
}); | ||
test('should set instanceId', () => { | ||
const config = config_1.createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
unleashInstanceId: 'someId1', | ||
proxySecrets: ['s1'], | ||
}); | ||
expect(config.unleashInstanceId).toBe('someId1'); | ||
}); | ||
test('should generate instanceId', () => { | ||
const config = config_1.createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
}); | ||
expect(config.unleashInstanceId).toBeDefined(); | ||
expect(config.unleashInstanceId.length).toBeGreaterThan(3); | ||
}); | ||
test('should set trust proxy to "loopback"', () => { | ||
const config = config_1.createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
trustProxy: 'loopback', | ||
}); | ||
expect(config.trustProxy).toBe('loopback'); | ||
}); | ||
test('should set trust proxy via env var', () => { | ||
process.env.TRUST_PROXY = 'true'; | ||
const config = config_1.createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
}); | ||
expect(config.trustProxy).toBe(true); | ||
delete process.env.TRUST_PROXY; | ||
}); | ||
test('should allow options via env', () => { | ||
@@ -48,2 +94,3 @@ process.env.UNLEASH_URL = 'some'; | ||
process.env.UNLEASH_PROXY_SECRETS = 's1'; | ||
process.env.UNLEASH_INSTANCE_ID = 'i1'; | ||
const config = config_1.createProxyConfig({}); | ||
@@ -54,2 +101,3 @@ expect(config.unleashUrl).toBe('some'); | ||
expect(config.proxySecrets[0]).toBe('s1'); | ||
expect(config.unleashInstanceId).toBe('i1'); | ||
// cleanup | ||
@@ -56,0 +104,0 @@ delete process.env.UNLEASH_URL; |
@@ -117,2 +117,22 @@ "use strict"; | ||
}); | ||
test('Should send in context with ip as remoteAddress', async () => { | ||
const userIp = '123.13.13.42'; | ||
const toggles = [ | ||
{ | ||
name: 'test', | ||
enabled: true, | ||
}, | ||
]; | ||
const client = new client_mock_1.default(toggles); | ||
const proxySecrets = ['sdf']; | ||
const app = app_1.createApp({ proxySecrets, unleashUrl, unleashApiToken, trustProxy: true }, client); | ||
client.emit('ready'); | ||
await supertest_1.default(app) | ||
.get('/proxy?userId=123&tenantId=me') | ||
.set('Authorization', 'sdf') | ||
.set('X-Forwarded-For', userIp) | ||
.expect(200) | ||
.expect('Content-Type', /json/); | ||
expect(client.queriedContexts[0].remoteAddress).toEqual(userIp); | ||
}); | ||
test('Should remove "undefined" environment field from context', async () => { | ||
@@ -119,0 +139,0 @@ const toggles = [ |
@@ -10,2 +10,3 @@ const port = process.env.PORT || 3000; | ||
refreshInterval: 1000, | ||
// unleashInstanceId: '1337', | ||
// logLevel: 'info', | ||
@@ -12,0 +13,0 @@ // projectName: 'order-team', // optional |
{ | ||
"name": "@unleash/proxy", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "The Unleash Proxy (Open-Source)", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -108,2 +108,3 @@ # The Unleash Proxy | ||
| unleashAppName | `UNLEASH_APP_NAME` |"unleash-proxy"| no | App name to used when registering with Unleash | | ||
| unleashInstanceId | `UNLEASH_INSTANCE_ID` | `generated` | no | Unleash instance id to used when registering with Unleash | | ||
| refreshInterval | `UNLEASH_FETCH_INTERVAL` | 5000 | no | How often the proxy should query Unleash for updates, defined in ms. | | ||
@@ -116,2 +117,3 @@ | metricsInterval | `UNLEASH_METRICS_INTERVAL`| 30000 | no | How often the proxy should send usage metrics back to Unleash, defined in ms. | | ||
| customStrategies| `UNLEASH_CUSTOM_STRATEGIES_FILE` | [] | no | Use this option to inject implementation of custom activation strategies. If you are using `UNLEASH_CUSTOM_STRATEGIES_FILE` you need to provide a valid path to a javascript files which exports an array of custom activation strategies and the SDK will automatically load these | | ||
| trustProxy | `TRUST_PROXY ` | `false` | no | By enabling the trustProxy option, Unleash Proxy will have knowledge that it's sitting behind a proxy and that the X-Forwarded-* header fields may be trusted, which otherwise may be easily spoofed. The proxy will automatically enrich the ip address in the Unleash Context. Can either be `true/false` (Trust all proxies), trust only given IP/CIDR (e.g. `'127.0.0.1'`) as a `string`. May be a list of comma separated values (e.g. `'127.0.0.1,192.168.1.1/24'` | | ||
@@ -118,0 +120,0 @@ ### Run with Node.js: |
@@ -24,2 +24,3 @@ import express, { Application } from 'express'; | ||
app.disable('x-powered-by'); | ||
app.set('trust proxy', config.trustProxy); | ||
app.use(cors(corsOptions)); | ||
@@ -26,0 +27,0 @@ app.use( |
@@ -7,3 +7,2 @@ import EventEmitter from 'events'; | ||
import { Logger } from './logger'; | ||
import { generateInstanceId } from './util'; | ||
@@ -62,3 +61,2 @@ export type FeatureToggleStatus = { | ||
const instanceId = generateInstanceId(); | ||
const customHeadersFunction = async () => ({ | ||
@@ -72,3 +70,3 @@ Authorization: this.unleashApiToken, | ||
appName: config.unleashAppName, | ||
instanceId, | ||
instanceId: config.unleashInstanceId, | ||
environment: this.environment, | ||
@@ -86,3 +84,3 @@ refreshInterval: config.refreshInterval, | ||
appName: config.unleashAppName, | ||
instanceId, | ||
instanceId: config.unleashInstanceId, | ||
strategies: defaultStrategies.map((s) => s.name), | ||
@@ -115,3 +113,6 @@ metricsInterval: config.metricsInterval, | ||
getEnabledToggles(inContext: Context): FeatureToggleStatus[] { | ||
this.logger.info('Get enabled toggles'); | ||
this.logger.info( | ||
'Get enabled feature toggles for provided context', | ||
inContext, | ||
); | ||
const context = this.fixContext(inContext); | ||
@@ -118,0 +119,0 @@ |
import { Strategy } from 'unleash-client'; | ||
import { Logger, LogLevel, SimpleLogger } from './logger'; | ||
import { generateInstanceId } from './util'; | ||
@@ -8,2 +9,3 @@ export interface IProxyOption { | ||
unleashAppName?: string; | ||
unleashInstanceId?: string; | ||
customStrategies?: Strategy[]; | ||
@@ -19,2 +21,3 @@ proxySecrets?: string[]; | ||
logLevel?: LogLevel; | ||
trustProxy?: boolean | string | number; | ||
} | ||
@@ -26,2 +29,3 @@ | ||
unleashAppName: string; | ||
unleashInstanceId: string; | ||
customStrategies?: Strategy[]; | ||
@@ -36,2 +40,3 @@ proxySecrets: string[]; | ||
disableMetrics: boolean; | ||
trustProxy: boolean | string | number; | ||
} | ||
@@ -67,2 +72,13 @@ | ||
function loadTrustProxy(value: string = 'FALSE') { | ||
const upperValue = value.toUpperCase(); | ||
if (upperValue === 'FALSE') { | ||
return false; | ||
} | ||
if (upperValue === 'TRUE') { | ||
return true; | ||
} | ||
return value; | ||
} | ||
export function createProxyConfig(option: IProxyOption): IProxyConfig { | ||
@@ -99,2 +115,10 @@ const unleashUrl = option.unleashUrl || process.env.UNLEASH_URL; | ||
const trustProxy = | ||
option.trustProxy || loadTrustProxy(process.env.TRUST_PROXY); | ||
const unleashInstanceId = | ||
option.unleashInstanceId || | ||
process.env.UNLEASH_INSTANCE_ID || | ||
generateInstanceId(); | ||
return { | ||
@@ -107,2 +131,3 @@ unleashUrl, | ||
'unleash-proxy', | ||
unleashInstanceId, | ||
customStrategies, | ||
@@ -122,3 +147,4 @@ proxySecrets, | ||
logger: option.logger || new SimpleLogger(logLevel), | ||
trustProxy, | ||
}; | ||
} |
@@ -5,3 +5,1 @@ import { createApp } from './app'; | ||
export { createApp, start }; | ||
start(); |
@@ -27,2 +27,9 @@ /* eslint-disable class-methods-use-this */ | ||
const stripEmptyArray = (arr: any[]) => { | ||
if (!arr || arr.length === 0) { | ||
return ''; | ||
} | ||
return arr; | ||
}; | ||
export interface Logger { | ||
@@ -49,3 +56,3 @@ debug(message: any, ...args: any[]): void; | ||
if (this.shouldLog(LogLevel.debug)) { | ||
console.log(`DEBUG: ${message}`, args); | ||
console.log(`DEBUG: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -56,3 +63,3 @@ } | ||
if (this.shouldLog(LogLevel.info)) { | ||
console.log(`INFO: ${message}`, args); | ||
console.log(`INFO: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -63,3 +70,3 @@ } | ||
if (this.shouldLog(LogLevel.warn)) { | ||
console.log(`WARN: ${message}`, args); | ||
console.log(`WARN: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -70,3 +77,3 @@ } | ||
if (this.shouldLog(LogLevel.error)) { | ||
console.log(`ERROR: ${message}`, args); | ||
console.log(`ERROR: ${message}`, stripEmptyArray(args)); | ||
} | ||
@@ -77,5 +84,5 @@ } | ||
if (this.shouldLog(LogLevel.fatal)) { | ||
console.log(`FATAL: ${message}`, args); | ||
console.log(`FATAL: ${message}`, stripEmptyArray(args)); | ||
} | ||
} | ||
} |
@@ -29,2 +29,58 @@ import * as path from 'path'; | ||
test('should set trust proxy', () => { | ||
const config = createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
trustProxy: true, | ||
}); | ||
expect(config.trustProxy).toBe(true); | ||
}); | ||
test('should set instanceId', () => { | ||
const config = createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
unleashInstanceId: 'someId1', | ||
proxySecrets: ['s1'], | ||
}); | ||
expect(config.unleashInstanceId).toBe('someId1'); | ||
}); | ||
test('should generate instanceId', () => { | ||
const config = createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
}); | ||
expect(config.unleashInstanceId).toBeDefined(); | ||
expect(config.unleashInstanceId.length).toBeGreaterThan(3); | ||
}); | ||
test('should set trust proxy to "loopback"', () => { | ||
const config = createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
trustProxy: 'loopback', | ||
}); | ||
expect(config.trustProxy).toBe('loopback'); | ||
}); | ||
test('should set trust proxy via env var', () => { | ||
process.env.TRUST_PROXY = 'true'; | ||
const config = createProxyConfig({ | ||
unleashUrl: 'some', | ||
unleashApiToken: 'some', | ||
proxySecrets: ['s1'], | ||
}); | ||
expect(config.trustProxy).toBe(true); | ||
delete process.env.TRUST_PROXY; | ||
}); | ||
test('should allow options via env', () => { | ||
@@ -34,2 +90,3 @@ process.env.UNLEASH_URL = 'some'; | ||
process.env.UNLEASH_PROXY_SECRETS = 's1'; | ||
process.env.UNLEASH_INSTANCE_ID = 'i1'; | ||
const config = createProxyConfig({}); | ||
@@ -41,2 +98,3 @@ | ||
expect(config.proxySecrets[0]).toBe('s1'); | ||
expect(config.unleashInstanceId).toBe('i1'); | ||
@@ -43,0 +101,0 @@ // cleanup |
@@ -139,2 +139,30 @@ import request, { Response } from 'supertest'; | ||
test('Should send in context with ip as remoteAddress', async () => { | ||
const userIp = '123.13.13.42'; | ||
const toggles = [ | ||
{ | ||
name: 'test', | ||
enabled: true, | ||
}, | ||
]; | ||
const client = new MockClient(toggles); | ||
const proxySecrets = ['sdf']; | ||
const app = createApp( | ||
{ proxySecrets, unleashUrl, unleashApiToken, trustProxy: true }, | ||
client, | ||
); | ||
client.emit('ready'); | ||
await request(app) | ||
.get('/proxy?userId=123&tenantId=me') | ||
.set('Authorization', 'sdf') | ||
.set('X-Forwarded-For', userIp) | ||
.expect(200) | ||
.expect('Content-Type', /json/); | ||
expect(client.queriedContexts[0].remoteAddress).toEqual(userIp); | ||
}); | ||
test('Should remove "undefined" environment field from context', async () => { | ||
@@ -141,0 +169,0 @@ const toggles = [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
128912
2290
160
0
67
30