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);
|
||||
}
|
||||
|
||||
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> {
|
||||
await this.db<ITokenUserRow>(TOKEN_USERS_TABLE).insert(
|
||||
{ user_id: userId, secret },
|
||||
|
@ -185,10 +185,6 @@ export class ApiTokenService {
|
||||
return this.store.getAll();
|
||||
}
|
||||
|
||||
public async getAllActiveTokens(): Promise<IApiToken[]> {
|
||||
return this.store.getAllActive();
|
||||
}
|
||||
|
||||
private async initApiTokens(tokens: ILegacyApiTokenCreate[]) {
|
||||
const tokenCount = await this.store.count();
|
||||
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 { EdgeTokenSchema } from '../openapi/spec/edge-token-schema';
|
||||
import { constantTimeCompare } from '../util/constantTimeCompare';
|
||||
import type { ValidatedEdgeTokensSchema } from '../openapi/spec/validated-edge-tokens-schema';
|
||||
import type { ApiTokenService } from './api-token-service';
|
||||
import metricsHelper from '../util/metrics-helper';
|
||||
@ -12,21 +11,17 @@ export default class EdgeService {
|
||||
|
||||
private apiTokenService: ApiTokenService;
|
||||
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
private timer: Function;
|
||||
|
||||
constructor(
|
||||
{ apiTokenService }: { apiTokenService: ApiTokenService },
|
||||
{
|
||||
getLogger,
|
||||
flagResolver,
|
||||
eventBus,
|
||||
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'eventBus'>,
|
||||
) {
|
||||
this.logger = getLogger('lib/services/edge-service.ts');
|
||||
this.apiTokenService = apiTokenService;
|
||||
this.flagResolver = flagResolver;
|
||||
this.timer = (functionName: string) =>
|
||||
metricsHelper.wrapTimer(eventBus, FUNCTION_TIME, {
|
||||
className: 'EdgeService',
|
||||
@ -35,49 +30,23 @@ export default class EdgeService {
|
||||
}
|
||||
|
||||
async getValidTokens(tokens: string[]): Promise<ValidatedEdgeTokensSchema> {
|
||||
if (this.flagResolver.isEnabled('checkEdgeValidTokensFromCache')) {
|
||||
const stopTimer = this.timer('validateTokensWithCache');
|
||||
// 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: EdgeTokenSchema[] = [];
|
||||
for (const token of tokens) {
|
||||
const found =
|
||||
await this.apiTokenService.getTokenWithCache(token);
|
||||
if (found) {
|
||||
validatedTokens.push({
|
||||
token: token,
|
||||
type: found.type,
|
||||
projects: found.projects,
|
||||
});
|
||||
}
|
||||
const stopTimer = this.timer('validateTokensWithCache');
|
||||
// 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: EdgeTokenSchema[] = [];
|
||||
for (const token of tokens) {
|
||||
const found = await this.apiTokenService.getTokenWithCache(token);
|
||||
if (found) {
|
||||
validatedTokens.push({
|
||||
token: token,
|
||||
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();
|
||||
}
|
||||
|
||||
public async getAllActiveTokens(): Promise<PublicSignupTokenSchema[]> {
|
||||
return this.store.getAllActive();
|
||||
}
|
||||
|
||||
public async validate(secret: string): Promise<boolean> {
|
||||
return this.store.isValid(secret);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import type { IPublicSignupTokenCreate } from '../models/public-signup-token';
|
||||
|
||||
export interface IPublicSignupTokenStore
|
||||
extends Store<PublicSignupTokenSchema, string> {
|
||||
getAllActive(): Promise<PublicSignupTokenSchema[]>;
|
||||
insert(
|
||||
newToken: IPublicSignupTokenCreate,
|
||||
): Promise<PublicSignupTokenSchema>;
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
type IApiToken,
|
||||
} from '../../../lib/types/models/api-token';
|
||||
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 { createProjectService } from '../../../lib/features';
|
||||
import { EventService } from '../../../lib/services';
|
||||
@ -133,33 +133,6 @@ test('should update expiry of token', async () => {
|
||||
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 () => {
|
||||
const token = await apiTokenService.createApiToken({
|
||||
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[]> {
|
||||
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