unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
113 lines • 5.33 kB
JavaScript
import { ApiTokenType, SYSTEM_USER_AUDIT, } from '../types/index.js';
import { InvalidOperationError } from '../error/index.js';
import { decryptSecret, encryptSecret, } from '../features/edgetokens/edge-verification.js';
import { EdgeTokenStore } from '../features/edgetokens/edge-token-store.js';
import { createApiTokenService, createFakeApiTokenService, } from '../features/api-tokens/createApiTokenService.js';
import { FakeEdgeTokenStore } from '../features/edgetokens/fake-edge-token-store.js';
export const createTransactionalEdgeService = (db, config) => {
const edgeTokenStore = new EdgeTokenStore(db, config.eventBus, config);
const transactionalApiTokenService = createApiTokenService(db, config);
return new EdgeService({ edgeTokenStore }, { apiTokenService: transactionalApiTokenService }, config);
};
export const createFakeEdgeService = (config) => {
const fakeEdgeTokenStore = new FakeEdgeTokenStore();
const fakeApiTokenService = createFakeApiTokenService(config);
return new EdgeService({ edgeTokenStore: fakeEdgeTokenStore }, fakeApiTokenService, config);
};
export default class EdgeService {
constructor({ edgeTokenStore }, { apiTokenService }, { getLogger, edgeMasterKey, }) {
this.logger = getLogger('lib/services/edge-service.ts');
this.apiTokenService = apiTokenService;
this.edgeTokenStore = edgeTokenStore;
this.edgeMasterKey = edgeMasterKey;
}
async getValidTokens(tokens) {
// new behavior: use cached tokens when possible
// use the db to fetch the missing ones
// cache stores both missing and active so we don't hammer the db
const validatedTokens = [];
for (const token of tokens) {
const found = await this.apiTokenService.getTokenWithCache(token);
if (found) {
validatedTokens.push({
token: token,
type: found.type,
projects: found.projects,
environment: found.environment,
});
}
}
return { tokens: validatedTokens };
}
async notSeenBefore({ clientId, nonce, expiresAt, }) {
try {
await this.edgeTokenStore.registerNonce(clientId, nonce, expiresAt);
return true;
}
catch (_e) { }
return false;
}
async loadClient(clientId) {
return this.edgeTokenStore.loadClient(clientId);
}
decryptedClientSecret(client) {
if (this.edgeMasterKey === undefined) {
throw new InvalidOperationError('You have to define an EDGE_MASTER_SECRET for this to be supported');
}
return decryptSecret(Buffer.from(this.edgeMasterKey, 'base64'), client.secret_enc);
}
async saveClient(clientId, secret) {
if (this.edgeMasterKey === undefined) {
throw new InvalidOperationError('EDGE_MASTER_KEY was not defined');
}
const masterSecretBuffer = Buffer.from(this.edgeMasterKey, 'base64');
if (masterSecretBuffer.length !== 32) {
throw new InvalidOperationError('You must define a 32 byte secret in the EDGE_MASTER_SECRET environment variable');
}
const secretEnc = encryptSecret(masterSecretBuffer, secret);
await this.edgeTokenStore.saveClient(clientId, secretEnc);
this.logger.info('Successfully set client secret');
}
async getOrCreateTokens(clientId, tokenRequest) {
if (!this.edgeMasterKey) {
throw new InvalidOperationError('You must define a secret in the EDGE_MASTER_SECRET environment variable');
}
const tokens = [];
for (const tokenReq of tokenRequest.tokens) {
const existing = await this.edgeTokenStore.getToken(clientId, tokenReq.environment, tokenReq.projects);
if (existing !== undefined) {
tokens.push({
projects: existing.projects,
type: existing.type,
token: existing.secret,
environment: existing.environment,
});
}
else if (tokenReq.environment && tokenReq.projects) {
const newToken = await this.apiTokenService.createApiTokenWithProjects({
tokenName: `enterprise_edge_${tokenReq.environment}_${truncate(tokenReq.projects, 3)}`,
alias: `ee_${tokenReq.environment}`,
type: ApiTokenType.BACKEND,
environment: tokenReq.environment,
projects: tokenReq.projects,
}, SYSTEM_USER_AUDIT);
await this.edgeTokenStore.saveToken(clientId, newToken);
tokens.push({
projects: newToken.projects,
type: newToken.type,
token: newToken.secret,
environment: newToken.environment,
});
}
}
return { tokens };
}
async deleteExpiredNonces() {
await this.edgeTokenStore.cleanExpiredNonces();
}
async deleteAllTokens() {
await this.edgeTokenStore.deleteAll();
}
}
const truncate = (projects, max_length) => projects.length > max_length ? `[]` : projects.join('_');
//# sourceMappingURL=edge-service.js.map