mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
chore: edge active tokens cache flag removal (#7094)
## About the changes EdgeService is the only place where we use active tokens validation in bulk. By switching to validating from the cache, we no longer need a method to return all active tokens from the DB.
This commit is contained in:
parent
6a1b6fd024
commit
8ac8d873b4
@ -142,15 +142,6 @@ export class PublicSignupTokenStore implements IPublicSignupTokenStore {
|
|||||||
return toTokens(rows);
|
return toTokens(rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllActive(): Promise<PublicSignupTokenSchema[]> {
|
|
||||||
const stopTimer = this.timer('getAllActive');
|
|
||||||
const rows = await this.makeTokenUsersQuery()
|
|
||||||
.where('expires_at', 'IS', null)
|
|
||||||
.orWhere('expires_at', '>', 'now()');
|
|
||||||
stopTimer();
|
|
||||||
return toTokens(rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addTokenUser(secret: string, userId: number): Promise<void> {
|
async addTokenUser(secret: string, userId: number): Promise<void> {
|
||||||
await this.db<ITokenUserRow>(TOKEN_USERS_TABLE).insert(
|
await this.db<ITokenUserRow>(TOKEN_USERS_TABLE).insert(
|
||||||
{ user_id: userId, secret },
|
{ user_id: userId, secret },
|
||||||
|
@ -185,10 +185,6 @@ export class ApiTokenService {
|
|||||||
return this.store.getAll();
|
return this.store.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllActiveTokens(): Promise<IApiToken[]> {
|
|
||||||
return this.store.getAllActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async initApiTokens(tokens: ILegacyApiTokenCreate[]) {
|
private async initApiTokens(tokens: ILegacyApiTokenCreate[]) {
|
||||||
const tokenCount = await this.store.count();
|
const tokenCount = await this.store.count();
|
||||||
if (tokenCount > 0) {
|
if (tokenCount > 0) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { IFlagResolver, IUnleashConfig } from '../types';
|
import type { IUnleashConfig } from '../types';
|
||||||
import type { Logger } from '../logger';
|
import type { Logger } from '../logger';
|
||||||
import type { EdgeTokenSchema } from '../openapi/spec/edge-token-schema';
|
import type { EdgeTokenSchema } from '../openapi/spec/edge-token-schema';
|
||||||
import { constantTimeCompare } from '../util/constantTimeCompare';
|
|
||||||
import type { ValidatedEdgeTokensSchema } from '../openapi/spec/validated-edge-tokens-schema';
|
import type { ValidatedEdgeTokensSchema } from '../openapi/spec/validated-edge-tokens-schema';
|
||||||
import type { ApiTokenService } from './api-token-service';
|
import type { ApiTokenService } from './api-token-service';
|
||||||
import metricsHelper from '../util/metrics-helper';
|
import metricsHelper from '../util/metrics-helper';
|
||||||
@ -12,21 +11,17 @@ export default class EdgeService {
|
|||||||
|
|
||||||
private apiTokenService: ApiTokenService;
|
private apiTokenService: ApiTokenService;
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
|
||||||
|
|
||||||
private timer: Function;
|
private timer: Function;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ apiTokenService }: { apiTokenService: ApiTokenService },
|
{ apiTokenService }: { apiTokenService: ApiTokenService },
|
||||||
{
|
{
|
||||||
getLogger,
|
getLogger,
|
||||||
flagResolver,
|
|
||||||
eventBus,
|
eventBus,
|
||||||
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'eventBus'>,
|
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'eventBus'>,
|
||||||
) {
|
) {
|
||||||
this.logger = getLogger('lib/services/edge-service.ts');
|
this.logger = getLogger('lib/services/edge-service.ts');
|
||||||
this.apiTokenService = apiTokenService;
|
this.apiTokenService = apiTokenService;
|
||||||
this.flagResolver = flagResolver;
|
|
||||||
this.timer = (functionName: string) =>
|
this.timer = (functionName: string) =>
|
||||||
metricsHelper.wrapTimer(eventBus, FUNCTION_TIME, {
|
metricsHelper.wrapTimer(eventBus, FUNCTION_TIME, {
|
||||||
className: 'EdgeService',
|
className: 'EdgeService',
|
||||||
@ -35,49 +30,23 @@ export default class EdgeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getValidTokens(tokens: string[]): Promise<ValidatedEdgeTokensSchema> {
|
async getValidTokens(tokens: string[]): Promise<ValidatedEdgeTokensSchema> {
|
||||||
if (this.flagResolver.isEnabled('checkEdgeValidTokensFromCache')) {
|
const stopTimer = this.timer('validateTokensWithCache');
|
||||||
const stopTimer = this.timer('validateTokensWithCache');
|
// new behavior: use cached tokens when possible
|
||||||
// new behavior: use cached tokens when possible
|
// use the db to fetch the missing ones
|
||||||
// use the db to fetch the missing ones
|
// cache stores both missing and active so we don't hammer the db
|
||||||
// cache stores both missing and active so we don't hammer the db
|
const validatedTokens: EdgeTokenSchema[] = [];
|
||||||
const validatedTokens: EdgeTokenSchema[] = [];
|
for (const token of tokens) {
|
||||||
for (const token of tokens) {
|
const found = await this.apiTokenService.getTokenWithCache(token);
|
||||||
const found =
|
if (found) {
|
||||||
await this.apiTokenService.getTokenWithCache(token);
|
validatedTokens.push({
|
||||||
if (found) {
|
token: token,
|
||||||
validatedTokens.push({
|
type: found.type,
|
||||||
token: token,
|
projects: found.projects,
|
||||||
type: found.type,
|
});
|
||||||
projects: found.projects,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
stopTimer();
|
|
||||||
return { tokens: validatedTokens };
|
|
||||||
} else {
|
|
||||||
// old behavior: go to the db to fetch all tokens and then filter in memory
|
|
||||||
const stopTimer = this.timer('validateTokensWithoutCache');
|
|
||||||
const activeTokens =
|
|
||||||
await this.apiTokenService.getAllActiveTokens();
|
|
||||||
const edgeTokens = tokens.reduce(
|
|
||||||
(result: EdgeTokenSchema[], token) => {
|
|
||||||
const dbToken = activeTokens.find((activeToken) =>
|
|
||||||
constantTimeCompare(activeToken.secret, token),
|
|
||||||
);
|
|
||||||
if (dbToken) {
|
|
||||||
result.push({
|
|
||||||
token: token,
|
|
||||||
type: dbToken.type,
|
|
||||||
projects: dbToken.projects,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
stopTimer();
|
|
||||||
return { tokens: edgeTokens };
|
|
||||||
}
|
}
|
||||||
|
stopTimer();
|
||||||
|
return { tokens: validatedTokens };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,10 +70,6 @@ export class PublicSignupTokenService {
|
|||||||
return this.store.getAll();
|
return this.store.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllActiveTokens(): Promise<PublicSignupTokenSchema[]> {
|
|
||||||
return this.store.getAllActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async validate(secret: string): Promise<boolean> {
|
public async validate(secret: string): Promise<boolean> {
|
||||||
return this.store.isValid(secret);
|
return this.store.isValid(secret);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import type { IPublicSignupTokenCreate } from '../models/public-signup-token';
|
|||||||
|
|
||||||
export interface IPublicSignupTokenStore
|
export interface IPublicSignupTokenStore
|
||||||
extends Store<PublicSignupTokenSchema, string> {
|
extends Store<PublicSignupTokenSchema, string> {
|
||||||
getAllActive(): Promise<PublicSignupTokenSchema[]>;
|
|
||||||
insert(
|
insert(
|
||||||
newToken: IPublicSignupTokenCreate,
|
newToken: IPublicSignupTokenCreate,
|
||||||
): Promise<PublicSignupTokenSchema>;
|
): Promise<PublicSignupTokenSchema>;
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
type IApiToken,
|
type IApiToken,
|
||||||
} from '../../../lib/types/models/api-token';
|
} from '../../../lib/types/models/api-token';
|
||||||
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
||||||
import { addDays, subDays } from 'date-fns';
|
import { addDays } from 'date-fns';
|
||||||
import type ProjectService from '../../../lib/features/project/project-service';
|
import type ProjectService from '../../../lib/features/project/project-service';
|
||||||
import { createProjectService } from '../../../lib/features';
|
import { createProjectService } from '../../../lib/features';
|
||||||
import { EventService } from '../../../lib/services';
|
import { EventService } from '../../../lib/services';
|
||||||
@ -133,33 +133,6 @@ test('should update expiry of token', async () => {
|
|||||||
expect(updatedToken.expiresAt).toEqual(newTime);
|
expect(updatedToken.expiresAt).toEqual(newTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should only return valid tokens', async () => {
|
|
||||||
const now = Date.now();
|
|
||||||
const yesterday = subDays(now, 1);
|
|
||||||
const tomorrow = addDays(now, 1);
|
|
||||||
|
|
||||||
await apiTokenService.createApiToken({
|
|
||||||
tokenName: 'default-expired',
|
|
||||||
type: ApiTokenType.CLIENT,
|
|
||||||
expiresAt: yesterday,
|
|
||||||
project: '*',
|
|
||||||
environment: DEFAULT_ENV,
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeToken = await apiTokenService.createApiToken({
|
|
||||||
tokenName: 'default-valid',
|
|
||||||
type: ApiTokenType.CLIENT,
|
|
||||||
expiresAt: tomorrow,
|
|
||||||
project: '*',
|
|
||||||
environment: DEFAULT_ENV,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tokens = await apiTokenService.getAllActiveTokens();
|
|
||||||
|
|
||||||
expect(tokens.length).toBe(1);
|
|
||||||
expect(activeToken.secret).toBe(tokens[0].secret);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create client token with project list', async () => {
|
test('should create client token with project list', async () => {
|
||||||
const token = await apiTokenService.createApiToken({
|
const token = await apiTokenService.createApiToken({
|
||||||
tokenName: 'default-client',
|
tokenName: 'default-client',
|
||||||
|
95
src/test/e2e/services/edge-service.e2e.test.ts
Normal file
95
src/test/e2e/services/edge-service.e2e.test.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import dbInit, { type ITestDb } from '../helpers/database-init';
|
||||||
|
import getLogger from '../../fixtures/no-logger';
|
||||||
|
import { ApiTokenService } from '../../../lib/services/api-token-service';
|
||||||
|
import { createTestConfig } from '../../config/test-config';
|
||||||
|
import {
|
||||||
|
ApiTokenType,
|
||||||
|
type IApiToken,
|
||||||
|
} from '../../../lib/types/models/api-token';
|
||||||
|
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
|
import type ProjectService from '../../../lib/features/project/project-service';
|
||||||
|
import { createProjectService } from '../../../lib/features';
|
||||||
|
import { EdgeService, EventService } from '../../../lib/services';
|
||||||
|
import { type IUnleashStores, TEST_AUDIT_USER } from '../../../lib/types';
|
||||||
|
|
||||||
|
let db: ITestDb;
|
||||||
|
let stores: IUnleashStores;
|
||||||
|
let edgeService: EdgeService;
|
||||||
|
let projectService: ProjectService;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const config = createTestConfig({
|
||||||
|
server: { baseUriPath: '/test' },
|
||||||
|
experimental: {
|
||||||
|
flags: {
|
||||||
|
useMemoizedActiveTokens: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
db = await dbInit('api_token_service_serial', getLogger);
|
||||||
|
stores = db.stores;
|
||||||
|
const eventService = new EventService(stores, config);
|
||||||
|
const project = {
|
||||||
|
id: 'test-project',
|
||||||
|
name: 'Test Project',
|
||||||
|
description: 'Fancy',
|
||||||
|
mode: 'open' as const,
|
||||||
|
defaultStickiness: 'clientId',
|
||||||
|
};
|
||||||
|
const user = await stores.userStore.insert({
|
||||||
|
name: 'Some Name',
|
||||||
|
email: 'test@getunleash.io',
|
||||||
|
});
|
||||||
|
projectService = createProjectService(db.rawDatabase, config);
|
||||||
|
|
||||||
|
await projectService.createProject(project, user, TEST_AUDIT_USER);
|
||||||
|
|
||||||
|
const apiTokenService = new ApiTokenService(stores, config, eventService);
|
||||||
|
edgeService = new EdgeService({ apiTokenService }, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (db) {
|
||||||
|
await db.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
afterEach(async () => {
|
||||||
|
const tokens = await stores.apiTokenStore.getAll();
|
||||||
|
const deleteAll = tokens.map((t: IApiToken) =>
|
||||||
|
stores.apiTokenStore.delete(t.secret),
|
||||||
|
);
|
||||||
|
await Promise.all(deleteAll);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should only return valid tokens', async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const yesterday = subDays(now, 1);
|
||||||
|
const tomorrow = addDays(now, 1);
|
||||||
|
|
||||||
|
const expiredToken = await stores.apiTokenStore.insert({
|
||||||
|
tokenName: 'expired',
|
||||||
|
secret: 'expired-secret',
|
||||||
|
type: ApiTokenType.CLIENT,
|
||||||
|
expiresAt: yesterday,
|
||||||
|
projects: ['*'],
|
||||||
|
environment: DEFAULT_ENV,
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeToken = await stores.apiTokenStore.insert({
|
||||||
|
tokenName: 'default-valid',
|
||||||
|
secret: 'valid-secret',
|
||||||
|
type: ApiTokenType.CLIENT,
|
||||||
|
expiresAt: tomorrow,
|
||||||
|
projects: ['*'],
|
||||||
|
environment: DEFAULT_ENV,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await edgeService.getValidTokens([
|
||||||
|
activeToken.secret,
|
||||||
|
expiredToken.secret,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(response.tokens.length).toBe(1);
|
||||||
|
expect(activeToken.secret).toBe(response.tokens[0].token);
|
||||||
|
});
|
2
src/test/fixtures/fake-api-token-store.ts
vendored
2
src/test/fixtures/fake-api-token-store.ts
vendored
@ -48,7 +48,7 @@ export default class FakeApiTokenStore
|
|||||||
|
|
||||||
async getAllActive(): Promise<IApiToken[]> {
|
async getAllActive(): Promise<IApiToken[]> {
|
||||||
return this.tokens.filter(
|
return this.tokens.filter(
|
||||||
(token) => token.expiresAt === null || token.expiresAt > new Date(),
|
(token) => !token.expiresAt || token.expiresAt > new Date(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user