Skip to content

Commit

Permalink
chore: remove flag to validate edge tokens from cache
Browse files Browse the repository at this point in the history
  • Loading branch information
gastonfournier committed May 21, 2024
1 parent 850f69f commit b935c6d
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 94 deletions.
1 change: 1 addition & 0 deletions src/lib/db/api-token-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export class ApiTokenStore implements IApiTokenStore {
.where('expires_at', 'IS', null)
.orWhere('expires_at', '>', 'now()');
stopTimer();
console.log(rows);
return toTokens(rows);
}

Expand Down
9 changes: 0 additions & 9 deletions src/lib/db/public-signup-token-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
8 changes: 4 additions & 4 deletions src/lib/services/api-token-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export class ApiTokenService {
if (!secret) {
return undefined;
}
console.log(
'Active tokens (with cache)',
this.activeTokens.map((t) => t.secret),
);

let token = this.activeTokens.find(
(activeToken) =>
Expand Down Expand Up @@ -185,10 +189,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) {
Expand Down
63 changes: 16 additions & 47 deletions src/lib/services/edge-service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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',
Expand All @@ -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 };
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/lib/services/public-signup-token-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/types/stores/public-signup-token-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand All @@ -14,6 +13,7 @@ export interface IPublicSignupTokenStore
secret: string,
value: { expiresAt?: Date; enabled?: boolean },
): Promise<PublicSignupTokenSchema>;
PublicSignup;
delete(secret: string): Promise<void>;
count(): Promise<number>;
}
29 changes: 1 addition & 28 deletions src/test/e2e/services/api-token-service.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
Expand Down
95 changes: 95 additions & 0 deletions src/test/e2e/services/edge-service.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 1 addition & 1 deletion src/test/fixtures/fake-api-token-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
}

Expand Down

0 comments on commit b935c6d

Please sign in to comment.