Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@unleash/proxy

Package Overview
Dependencies
Maintainers
4
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@unleash/proxy - npm Package Compare versions

Comparing version 0.6.1 to 0.7.0

dist/app.d.ts

8

CHANGELOG.md
# Changelog
### 0.7.0
- feat: generate type output to publish them to npm (#54)
- feat: experimental support for bootstrap (#44)
- fix: upgrade unleash-client to v3.11.3
- fix: require authorization header for /client/metrics (#55)
- fix: do not crash for invalid trustProxy option (#50)
### 0.6.1

@@ -4,0 +12,0 @@ - fix: upgrade unleash-client to v3.11.2

7

dist/app.js

@@ -22,3 +22,8 @@ "use strict";

app.disable('x-powered-by');
app.set('trust proxy', config.trustProxy);
try {
app.set('trust proxy', config.trustProxy);
}
catch (err) {
config.logger.error(`The provided "trustProxy" option was not valid ("${config.trustProxy}")`, err);
}
app.use(cors_1.default(corsOptions));

@@ -25,0 +30,0 @@ app.use(compression_1.default());

@@ -33,2 +33,3 @@ "use strict";

customHeadersFunction,
bootstrap: config.bootstrap,
});

@@ -89,2 +90,5 @@ // Custom metrics Instance

}
getFeatureToggleDefinitions() {
return this.unleash.getFeatureToggleDefinitions();
}
/*

@@ -91,0 +95,0 @@ * A very simplistic implementation which support counts.

@@ -55,2 +55,26 @@ "use strict";

}
function loadServerSideSdkConfig(option) {
if (option.expServerSideSdkConfig) {
return option.expServerSideSdkConfig;
}
const tokens = resolveStringToArray(process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS);
return tokens ? { tokens } : undefined;
}
function loadBootstrapOptions(option) {
if (option.expBootstrap) {
return option.expBootstrap;
}
const bootstrapUrl = process.env.EXP_BOOTSTRAP_URL;
const expBootstrapAuthorization = process.env.EXP_BOOTSTRAP_AUTHORIZATION;
const headers = expBootstrapAuthorization
? { Authorization: expBootstrapAuthorization }
: undefined;
if (bootstrapUrl) {
return {
url: bootstrapUrl,
urlHeaders: headers,
};
}
return undefined;
}
function createProxyConfig(option) {

@@ -101,2 +125,4 @@ const unleashUrl = option.unleashUrl || process.env.UNLEASH_URL;

'authorization',
serverSideSdkConfig: loadServerSideSdkConfig(option),
bootstrap: loadBootstrapOptions(option),
};

@@ -103,0 +129,0 @@ }

@@ -15,2 +15,5 @@ "use strict";

}
getFeatureToggleDefinitions() {
throw new Error('Method not implemented.');
}
isReady() {

@@ -17,0 +20,0 @@ return false;

@@ -221,2 +221,30 @@ "use strict";

});
test('should read serverSideSdkConfig from env vars', () => {
process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS = 'super1, super2';
const config = config_1.createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.serverSideSdkConfig?.tokens).toStrictEqual([
'super1',
'super2',
]);
delete process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS;
});
test('should read bootstrap from env vars', () => {
process.env.EXP_BOOTSTRAP_URL = 'https://boostrap.unleash.run';
process.env.EXP_BOOTSTRAP_AUTHORIZATION = 'AUTH-BOOTSTRAP';
const config = config_1.createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.bootstrap).toStrictEqual({
url: 'https://boostrap.unleash.run',
urlHeaders: { Authorization: 'AUTH-BOOTSTRAP' },
});
delete process.env.EXP_BOOTSTRAP_URL;
delete process.env.EXP_BOOTSTRAP_AUTHORIZATION;
});
//# sourceMappingURL=config.test.js.map

45

dist/unleash-proxy.js

@@ -12,2 +12,5 @@ "use strict";

this.clientKeys = config.clientKeys;
this.serverSideTokens = config.serverSideSdkConfig
? config.serverSideSdkConfig.tokens
: [];
this.clientKeysHeaderName = config.clientKeysHeaderName;

@@ -28,2 +31,3 @@ this.client = client;

router.post('/client/metrics', this.registerMetrics.bind(this));
router.get('/client/features', this.unleashApi.bind(this));
}

@@ -59,7 +63,7 @@ setReady() {

lookupToggles(req, res) {
const apiToken = req.header(this.clientKeysHeaderName);
const clientToken = req.header(this.clientKeysHeaderName);
if (!this.ready) {
res.status(503).send(NOT_READY);
}
else if (!apiToken || !this.clientKeys.includes(apiToken)) {
else if (!clientToken || !this.clientKeys.includes(clientToken)) {
res.sendStatus(401);

@@ -82,14 +86,35 @@ }

registerMetrics(req, res) {
const data = req.body;
const { error, value } = metrics_schema_1.clientMetricsSchema.validate(data);
if (error) {
this.logger.warn('Invalid metrics posted', error);
res.status(400).json(error);
return;
const token = req.header(this.clientKeysHeaderName);
const validTokens = [...this.clientKeys, ...this.serverSideTokens];
if (token && validTokens.includes(token)) {
const data = req.body;
const { error, value } = metrics_schema_1.clientMetricsSchema.validate(data);
if (error) {
this.logger.warn('Invalid metrics posted', error);
res.status(400).json(error);
return;
}
this.client.registerMetrics(value);
res.sendStatus(200);
}
this.client.registerMetrics(value);
res.sendStatus(200);
else {
res.sendStatus(401);
}
}
unleashApi(req, res) {
const apiToken = req.header(this.clientKeysHeaderName);
if (!this.ready) {
res.status(503).send(NOT_READY);
}
else if (apiToken && this.serverSideTokens.includes(apiToken)) {
const features = this.client.getFeatureToggleDefinitions();
res.set('Cache-control', 'public, max-age=2');
res.send({ version: 2, features });
}
else {
res.sendStatus(401);
}
}
}
exports.default = UnleashProxy;
//# sourceMappingURL=unleash-proxy.js.map

@@ -10,2 +10,12 @@ const port = process.env.PORT || 3000;

refreshInterval: 1000,
logLevel: 'trace',
expServerSideSdkConfig: {
tokens: ['server'],
},
expBootstrap: {
url: 'https://localhost:4000',
urlHeaders: {
Authorization: 'bootstrap-token',
},
},
// unleashInstanceId: '1337',

@@ -12,0 +22,0 @@ // logLevel: 'info',

{
"name": "@unleash/proxy",
"version": "0.6.1",
"version": "0.7.0",
"description": "The Unleash Proxy (Open-Source)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {

@@ -37,3 +38,3 @@ "build": "tsc --pretty",

"joi": "^17.4.0",
"unleash-client": "^3.11.2"
"unleash-client": "^3.11.3"
},

@@ -40,0 +41,0 @@ "devDependencies": {

@@ -128,2 +128,13 @@ ![Build & Tests](https://github.com/Unleash/unleash-proxy/workflows/Node.js%20CI/badge.svg?branch=main)

### Experimental options
Some functionality is under validation and introduced as experimental, to allow us to test new functionality early. You should expect these to change in any future feature release.
| Option | Environment Variable | Default value | Required | Description |
| ------------- |---------------------- |---------- |:--------:|---------------|
| expBootstrap | n/a | n/a | no | Where the Proxy can bootstrap configuration from. See [Node.js SDK](https://github.com/Unleash/unleash-client-node#bootstrap) for details. |
| expBootstrap.url | `EXP_BOOTSTRAP_URL` | n/a | no | Url where the Proxy can bootstrap configuration from. See [Node.js SDK](https://github.com/Unleash/unleash-client-node#bootstrap) for details. |
| expBootstrap.urlHeaders.Authorization | `EXP_BOOTSTRAP_AUTHORIZATION` | n/a | no | Authorization header value to be used when bootstrapping |
| expServerSideSdkConfig.tokens | `EXP_SERVER_SIDE_SDK_CONFIG_TOKENS` | n/a | no | API tokens that can be used by Server SDKs (and proxies) to read feature toggle configuration from this Proxy instance. |
### Run with Node.js:

@@ -130,0 +141,0 @@

@@ -25,3 +25,11 @@ import compression from 'compression';

app.disable('x-powered-by');
app.set('trust proxy', config.trustProxy);
try {
app.set('trust proxy', config.trustProxy);
} catch (err) {
config.logger.error(
`The provided "trustProxy" option was not valid ("${config.trustProxy}")`,
err,
);
}
app.use(cors(corsOptions));

@@ -28,0 +36,0 @@

import EventEmitter from 'events';
import { Context, initialize, Unleash, Variant } from 'unleash-client';
import { FeatureInterface } from 'unleash-client/lib/feature';
import Metrics from 'unleash-client/lib/metrics';

@@ -38,2 +39,3 @@ import { defaultStrategies } from 'unleash-client/lib/strategy';

) => FeatureToggleStatus[];
getFeatureToggleDefinitions(): FeatureInterface[];
registerMetrics(metrics: any): void;

@@ -79,2 +81,3 @@ isReady(): boolean;

customHeadersFunction,
bootstrap: config.bootstrap,
});

@@ -149,2 +152,6 @@

getFeatureToggleDefinitions(): FeatureInterface[] {
return this.unleash.getFeatureToggleDefinitions();
}
/*

@@ -151,0 +158,0 @@ * A very simplistic implementation which support counts.

import { Strategy, TagFilter } from 'unleash-client';
import { BootstrapOptions } from 'unleash-client/lib/repository/bootstrap-provider';
import { Logger, LogLevel, SimpleLogger } from './logger';
import { generateInstanceId } from './util';
export interface ServerSideSdkConfig {
tokens: string[];
}
export interface IProxyOption {

@@ -25,2 +29,5 @@ unleashUrl?: string;

clientKeysHeaderName?: string;
// experimental options
expBootstrap?: BootstrapOptions;
expServerSideSdkConfig?: ServerSideSdkConfig;
}

@@ -46,2 +53,4 @@

clientKeysHeaderName: string;
serverSideSdkConfig?: ServerSideSdkConfig;
bootstrap?: BootstrapOptions;
}

@@ -104,2 +113,36 @@

function loadServerSideSdkConfig(
option: IProxyOption,
): ServerSideSdkConfig | undefined {
if (option.expServerSideSdkConfig) {
return option.expServerSideSdkConfig;
}
const tokens = resolveStringToArray(
process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS,
);
return tokens ? { tokens } : undefined;
}
function loadBootstrapOptions(
option: IProxyOption,
): BootstrapOptions | undefined {
if (option.expBootstrap) {
return option.expBootstrap;
}
const bootstrapUrl = process.env.EXP_BOOTSTRAP_URL;
const expBootstrapAuthorization = process.env.EXP_BOOTSTRAP_AUTHORIZATION;
const headers = expBootstrapAuthorization
? { Authorization: expBootstrapAuthorization }
: undefined;
if (bootstrapUrl) {
return {
url: bootstrapUrl,
urlHeaders: headers,
};
}
return undefined;
}
export function createProxyConfig(option: IProxyOption): IProxyConfig {

@@ -173,3 +216,5 @@ const unleashUrl = option.unleashUrl || process.env.UNLEASH_URL;

'authorization',
serverSideSdkConfig: loadServerSideSdkConfig(option),
bootstrap: loadBootstrapOptions(option),
};
}
import EventEmitter from 'events';
import { Context } from 'unleash-client';
import { FeatureInterface } from 'unleash-client/lib/feature';
import { FeatureToggleStatus, IClient } from '../client';

@@ -20,2 +21,6 @@

getFeatureToggleDefinitions(): FeatureInterface[] {
throw new Error('Method not implemented.');
}
isReady(): boolean {

@@ -22,0 +27,0 @@ return false;

@@ -241,1 +241,33 @@ import * as path from 'path';

});
test('should read serverSideSdkConfig from env vars', () => {
process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS = 'super1, super2';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.serverSideSdkConfig?.tokens).toStrictEqual([
'super1',
'super2',
]);
delete process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS;
});
test('should read bootstrap from env vars', () => {
process.env.EXP_BOOTSTRAP_URL = 'https://boostrap.unleash.run';
process.env.EXP_BOOTSTRAP_AUTHORIZATION = 'AUTH-BOOTSTRAP';
const config = createProxyConfig({
unleashUrl: 'some',
unleashApiToken: 'some',
clientKeys: ['s1'],
});
expect(config.bootstrap).toStrictEqual({
url: 'https://boostrap.unleash.run',
urlHeaders: { Authorization: 'AUTH-BOOTSTRAP' },
});
delete process.env.EXP_BOOTSTRAP_URL;
delete process.env.EXP_BOOTSTRAP_AUTHORIZATION;
});

@@ -16,2 +16,4 @@ import { Request, Response, Router } from 'express';

private serverSideTokens: string[];
private clientKeysHeaderName: string;

@@ -28,2 +30,5 @@

this.clientKeys = config.clientKeys;
this.serverSideTokens = config.serverSideSdkConfig
? config.serverSideSdkConfig.tokens
: [];
this.clientKeysHeaderName = config.clientKeysHeaderName;

@@ -48,2 +53,3 @@ this.client = client;

router.post('/client/metrics', this.registerMetrics.bind(this));
router.get('/client/features', this.unleashApi.bind(this));
}

@@ -85,7 +91,7 @@

lookupToggles(req: Request, res: Response): void {
const apiToken = req.header(this.clientKeysHeaderName);
const clientToken = req.header(this.clientKeysHeaderName);
if (!this.ready) {
res.status(503).send(NOT_READY);
} else if (!apiToken || !this.clientKeys.includes(apiToken)) {
} else if (!clientToken || !this.clientKeys.includes(clientToken)) {
res.sendStatus(401);

@@ -109,15 +115,32 @@ } else {

registerMetrics(req: Request, res: Response): void {
const data = req.body;
const token = req.header(this.clientKeysHeaderName);
const validTokens = [...this.clientKeys, ...this.serverSideTokens];
const { error, value } = clientMetricsSchema.validate(data);
if (token && validTokens.includes(token)) {
const data = req.body;
const { error, value } = clientMetricsSchema.validate(data);
if (error) {
this.logger.warn('Invalid metrics posted', error);
res.status(400).json(error);
return;
}
this.client.registerMetrics(value);
res.sendStatus(200);
} else {
res.sendStatus(401);
}
}
if (error) {
this.logger.warn('Invalid metrics posted', error);
res.status(400).json(error);
return;
unleashApi(req: Request, res: Response): void {
const apiToken = req.header(this.clientKeysHeaderName);
if (!this.ready) {
res.status(503).send(NOT_READY);
} else if (apiToken && this.serverSideTokens.includes(apiToken)) {
const features = this.client.getFeatureToggleDefinitions();
res.set('Cache-control', 'public, max-age=2');
res.send({ version: 2, features });
} else {
res.sendStatus(401);
}
this.client.registerMetrics(value);
res.sendStatus(200);
}
}

@@ -13,3 +13,3 @@ {

// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */

@@ -16,0 +16,0 @@ "sourceMap": true, /* Generates corresponding '.map' file. */

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc