mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: make edge use token's cache (#6893)
## About the changes This PR removes the feature flag `queryMissingTokens` that was fully rolled out. It introduces a new way of checking edgeValidTokens controlled by the flag `checkEdgeValidTokensFromCache` that relies in the cached data but hits the DB if needed. The assumption is that most of the times edge will find tokens in the cache, except for a few cases in which a new token is queried. From all tokens we expect at most one to hit the DB and in this case querying a single token should be better than querying all the tokens.
This commit is contained in:
parent
ff6297d338
commit
126b78896e
@ -194,10 +194,12 @@ export class ApiTokenStore implements IApiTokenStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(key: string): Promise<IApiToken> {
|
async get(key: string): Promise<IApiToken> {
|
||||||
|
const stopTimer = this.timer('get-by-secret');
|
||||||
const row = await this.makeTokenProjectQuery().where(
|
const row = await this.makeTokenProjectQuery().where(
|
||||||
'tokens.secret',
|
'tokens.secret',
|
||||||
key,
|
key,
|
||||||
);
|
);
|
||||||
|
stopTimer();
|
||||||
return toTokens(row)[0];
|
return toTokens(row)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export const scheduleServices = async (
|
|||||||
apiTokenService.fetchActiveTokens.bind(apiTokenService),
|
apiTokenService.fetchActiveTokens.bind(apiTokenService),
|
||||||
minutesToMilliseconds(1),
|
minutesToMilliseconds(1),
|
||||||
'fetchActiveTokens',
|
'fetchActiveTokens',
|
||||||
0,
|
0, // no jitter, we need tokens at startup
|
||||||
);
|
);
|
||||||
|
|
||||||
schedulerService.schedule(
|
schedulerService.schedule(
|
||||||
|
@ -2,7 +2,6 @@ import { ApiTokenType } from '../types/models/api-token';
|
|||||||
import type { IUnleashConfig } from '../types/option';
|
import type { IUnleashConfig } from '../types/option';
|
||||||
import type { IApiRequest, IAuthRequest } from '../routes/unleash-types';
|
import type { IApiRequest, IAuthRequest } from '../routes/unleash-types';
|
||||||
import type { IUnleashServices } from '../server-impl';
|
import type { IUnleashServices } from '../server-impl';
|
||||||
import type { IFlagContext } from '../types';
|
|
||||||
|
|
||||||
const isClientApi = ({ path }) => {
|
const isClientApi = ({ path }) => {
|
||||||
return path && path.indexOf('/api/client') > -1;
|
return path && path.indexOf('/api/client') > -1;
|
||||||
@ -27,20 +26,6 @@ const isProxyApi = ({ path }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextFrom = (
|
|
||||||
req: IAuthRequest<any, any, any, any> | IApiRequest<any, any, any, any>,
|
|
||||||
): IFlagContext | undefined => {
|
|
||||||
// this is what we'd get from edge:
|
|
||||||
// req_path: '/api/client/features',
|
|
||||||
// req_user_agent: 'unleash-edge-16.0.4'
|
|
||||||
return {
|
|
||||||
reqPath: req.path,
|
|
||||||
reqUserAgent: req.get ? req.get('User-Agent') ?? '' : '',
|
|
||||||
reqAppName:
|
|
||||||
req.headers?.['unleash-appname'] ?? req.query?.appName ?? '',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TOKEN_TYPE_ERROR_MESSAGE =
|
export const TOKEN_TYPE_ERROR_MESSAGE =
|
||||||
'invalid token: expected a different token type for this endpoint';
|
'invalid token: expected a different token type for this endpoint';
|
||||||
|
|
||||||
@ -70,10 +55,7 @@ const apiAccessMiddleware = (
|
|||||||
const apiToken = req.header('authorization');
|
const apiToken = req.header('authorization');
|
||||||
if (!apiToken?.startsWith('user:')) {
|
if (!apiToken?.startsWith('user:')) {
|
||||||
const apiUser = apiToken
|
const apiUser = apiToken
|
||||||
? await apiTokenService.getUserForToken(
|
? await apiTokenService.getUserForToken(apiToken)
|
||||||
apiToken,
|
|
||||||
contextFrom(req),
|
|
||||||
)
|
|
||||||
: undefined;
|
: undefined;
|
||||||
const { CLIENT, FRONTEND } = ApiTokenType;
|
const { CLIENT, FRONTEND } = ApiTokenType;
|
||||||
|
|
||||||
|
@ -197,17 +197,17 @@ test('getUserForToken should get a user with admin token user id and token name'
|
|||||||
expect(user!.internalAdminTokenUserId).toBe(ADMIN_TOKEN_USER.id);
|
expect(user!.internalAdminTokenUserId).toBe(ADMIN_TOKEN_USER.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When token is added by another instance', () => {
|
describe('API token getTokenWithCache', () => {
|
||||||
const setup = (options?: IUnleashOptions) => {
|
const token: IApiTokenCreate = {
|
||||||
const token: IApiTokenCreate = {
|
environment: 'default',
|
||||||
environment: 'default',
|
projects: ['*'],
|
||||||
projects: ['*'],
|
secret: '*:*:some-random-string',
|
||||||
secret: '*:*:some-random-string',
|
type: ApiTokenType.CLIENT,
|
||||||
type: ApiTokenType.CLIENT,
|
tokenName: 'new-token-by-another-instance',
|
||||||
tokenName: 'new-token-by-another-instance',
|
expiresAt: undefined,
|
||||||
expiresAt: undefined,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
const setup = (options?: IUnleashOptions) => {
|
||||||
const config: IUnleashConfig = createTestConfig(options);
|
const config: IUnleashConfig = createTestConfig(options);
|
||||||
const apiTokenStore = new FakeApiTokenStore();
|
const apiTokenStore = new FakeApiTokenStore();
|
||||||
const environmentStore = new FakeEnvironmentStore();
|
const environmentStore = new FakeEnvironmentStore();
|
||||||
@ -220,60 +220,43 @@ describe('When token is added by another instance', () => {
|
|||||||
return {
|
return {
|
||||||
apiTokenService,
|
apiTokenService,
|
||||||
apiTokenStore,
|
apiTokenStore,
|
||||||
token,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
test('should not return the token when query db flag is disabled', async () => {
|
|
||||||
const { apiTokenService, apiTokenStore, token } = setup();
|
|
||||||
|
|
||||||
// simulate this token being inserted by another instance (apiTokenService does not know about it)
|
test('should return the token and perform only one db query', async () => {
|
||||||
|
const { apiTokenService, apiTokenStore } = setup();
|
||||||
|
const apiTokenStoreGet = jest.spyOn(apiTokenStore, 'get');
|
||||||
|
|
||||||
|
// valid token not present in cache (could be inserted by another instance)
|
||||||
apiTokenStore.insert(token);
|
apiTokenStore.insert(token);
|
||||||
|
|
||||||
const found = await apiTokenService.getUserForToken(token.secret);
|
for (let i = 0; i < 5; i++) {
|
||||||
expect(found).toBeUndefined();
|
const found = await apiTokenService.getTokenWithCache(token.secret);
|
||||||
});
|
expect(found).toBeDefined();
|
||||||
|
expect(found?.tokenName).toBe(token.tokenName);
|
||||||
test('should return the token when query db flag is enabled', async () => {
|
expect(found?.createdAt).toBeDefined();
|
||||||
const { apiTokenService, apiTokenStore, token } = setup({
|
}
|
||||||
experimental: {
|
expect(apiTokenStoreGet).toHaveBeenCalledTimes(1);
|
||||||
flags: {
|
|
||||||
queryMissingTokens: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// simulate this token being inserted by another instance (apiTokenService does not know about it)
|
|
||||||
apiTokenStore.insert(token);
|
|
||||||
|
|
||||||
const found = await apiTokenService.getUserForToken(token.secret);
|
|
||||||
expect(found).toBeDefined();
|
|
||||||
expect(found?.username).toBe(token.tokenName);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should query the db only once for invalid tokens', async () => {
|
test('should query the db only once for invalid tokens', async () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
const { apiTokenService, apiTokenStore } = setup({
|
const { apiTokenService, apiTokenStore } = setup();
|
||||||
experimental: {
|
|
||||||
flags: {
|
|
||||||
queryMissingTokens: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const apiTokenStoreGet = jest.spyOn(apiTokenStore, 'get');
|
const apiTokenStoreGet = jest.spyOn(apiTokenStore, 'get');
|
||||||
|
|
||||||
const invalidToken = 'invalid-token';
|
const invalidToken = 'invalid-token';
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
expect(
|
expect(
|
||||||
await apiTokenService.getUserForToken(invalidToken),
|
await apiTokenService.getTokenWithCache(invalidToken),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
}
|
}
|
||||||
expect(apiTokenStoreGet).toHaveBeenCalledTimes(1);
|
expect(apiTokenStoreGet).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
// after more than 5 minutes we should be able to query again
|
// after more than 5 minutes we should be able to query again
|
||||||
jest.advanceTimersByTime(minutesToMilliseconds(6));
|
jest.advanceTimersByTime(minutesToMilliseconds(6));
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
expect(
|
expect(
|
||||||
await apiTokenService.getUserForToken(invalidToken),
|
await apiTokenService.getTokenWithCache(invalidToken),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
}
|
}
|
||||||
expect(apiTokenStoreGet).toHaveBeenCalledTimes(2);
|
expect(apiTokenStoreGet).toHaveBeenCalledTimes(2);
|
||||||
|
@ -25,13 +25,14 @@ import {
|
|||||||
ApiTokenDeletedEvent,
|
ApiTokenDeletedEvent,
|
||||||
ApiTokenUpdatedEvent,
|
ApiTokenUpdatedEvent,
|
||||||
type IAuditUser,
|
type IAuditUser,
|
||||||
type IFlagContext,
|
|
||||||
type IFlagResolver,
|
type IFlagResolver,
|
||||||
SYSTEM_USER_AUDIT,
|
SYSTEM_USER_AUDIT,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { omitKeys } from '../util';
|
import { omitKeys } from '../util';
|
||||||
import type EventService from '../features/events/event-service';
|
import type EventService from '../features/events/event-service';
|
||||||
import { addMinutes, isPast } from 'date-fns';
|
import { addMinutes, isPast } from 'date-fns';
|
||||||
|
import metricsHelper from '../util/metrics-helper';
|
||||||
|
import { FUNCTION_TIME } from '../metric-events';
|
||||||
|
|
||||||
const resolveTokenPermissions = (tokenType: string) => {
|
const resolveTokenPermissions = (tokenType: string) => {
|
||||||
if (tokenType === ApiTokenType.ADMIN) {
|
if (tokenType === ApiTokenType.ADMIN) {
|
||||||
@ -60,14 +61,14 @@ export class ApiTokenService {
|
|||||||
|
|
||||||
private queryAfter = new Map<string, Date>();
|
private queryAfter = new Map<string, Date>();
|
||||||
|
|
||||||
private initialized = false;
|
|
||||||
|
|
||||||
private eventService: EventService;
|
private eventService: EventService;
|
||||||
|
|
||||||
private lastSeenSecrets: Set<string> = new Set<string>();
|
private lastSeenSecrets: Set<string> = new Set<string>();
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
private timer: Function;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
apiTokenStore,
|
apiTokenStore,
|
||||||
@ -75,7 +76,7 @@ export class ApiTokenService {
|
|||||||
}: Pick<IUnleashStores, 'apiTokenStore' | 'environmentStore'>,
|
}: Pick<IUnleashStores, 'apiTokenStore' | 'environmentStore'>,
|
||||||
config: Pick<
|
config: Pick<
|
||||||
IUnleashConfig,
|
IUnleashConfig,
|
||||||
'getLogger' | 'authentication' | 'flagResolver'
|
'getLogger' | 'authentication' | 'flagResolver' | 'eventBus'
|
||||||
>,
|
>,
|
||||||
eventService: EventService,
|
eventService: EventService,
|
||||||
) {
|
) {
|
||||||
@ -94,18 +95,21 @@ export class ApiTokenService {
|
|||||||
this.initApiTokens(config.authentication.initApiTokens),
|
this.initApiTokens(config.authentication.initApiTokens),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
this.timer = (functionName: string) =>
|
||||||
|
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
|
||||||
|
className: 'ApiTokenService',
|
||||||
|
functionName,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executed by a scheduler to refresh all active tokens
|
* Called by a scheduler without jitter to refresh all active tokens
|
||||||
*/
|
*/
|
||||||
async fetchActiveTokens(): Promise<void> {
|
async fetchActiveTokens(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.activeTokens = await this.store.getAllActive();
|
this.activeTokens = await this.store.getAllActive();
|
||||||
this.initialized = true;
|
} catch (e) {
|
||||||
} finally {
|
this.logger.warn('Failed to fetch active tokens', e);
|
||||||
// biome-ignore lint/correctness/noUnsafeFinally: We ignored this for eslint. Leaving this here for now, server-impl test fails without it
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,50 +117,7 @@ export class ApiTokenService {
|
|||||||
return this.store.get(secret);
|
return this.store.get(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateLastSeen(): Promise<void> {
|
async getTokenWithCache(secret: string): Promise<IApiToken | undefined> {
|
||||||
if (this.lastSeenSecrets.size > 0) {
|
|
||||||
const toStore = [...this.lastSeenSecrets];
|
|
||||||
this.lastSeenSecrets = new Set<string>();
|
|
||||||
await this.store.markSeenAt(toStore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllTokens(): Promise<IApiToken[]> {
|
|
||||||
return this.store.getAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllActiveTokens(): Promise<IApiToken[]> {
|
|
||||||
if (this.flagResolver.isEnabled('useMemoizedActiveTokens')) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
// unlikely this will happen but nice to have a fail safe
|
|
||||||
this.logger.info('Fetching active tokens before initialized');
|
|
||||||
await this.fetchActiveTokens();
|
|
||||||
}
|
|
||||||
return this.activeTokens;
|
|
||||||
} else {
|
|
||||||
return this.store.getAllActive();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async initApiTokens(tokens: ILegacyApiTokenCreate[]) {
|
|
||||||
const tokenCount = await this.store.count();
|
|
||||||
if (tokenCount > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const createAll = tokens
|
|
||||||
.map(mapLegacyTokenWithSecret)
|
|
||||||
.map((t) => this.insertNewApiToken(t, SYSTEM_USER_AUDIT));
|
|
||||||
await Promise.all(createAll);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error('Unable to create initial Admin API tokens');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getUserForToken(
|
|
||||||
secret: string,
|
|
||||||
flagContext?: IFlagContext, // temporarily added, expected from the middleware
|
|
||||||
): Promise<IApiUser | undefined> {
|
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -178,11 +139,7 @@ export class ApiTokenService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nextAllowedQuery = this.queryAfter.get(secret) ?? 0;
|
const nextAllowedQuery = this.queryAfter.get(secret) ?? 0;
|
||||||
if (
|
if (!token && isPast(nextAllowedQuery)) {
|
||||||
!token &&
|
|
||||||
isPast(nextAllowedQuery) &&
|
|
||||||
this.flagResolver.isEnabled('queryMissingTokens', flagContext)
|
|
||||||
) {
|
|
||||||
if (this.queryAfter.size > 1000) {
|
if (this.queryAfter.size > 1000) {
|
||||||
// establish a max limit for queryAfter size to prevent memory leak
|
// establish a max limit for queryAfter size to prevent memory leak
|
||||||
this.queryAfter.clear();
|
this.queryAfter.clear();
|
||||||
@ -190,12 +147,52 @@ export class ApiTokenService {
|
|||||||
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
|
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
|
||||||
this.queryAfter.set(secret, addMinutes(new Date(), 5));
|
this.queryAfter.set(secret, addMinutes(new Date(), 5));
|
||||||
|
|
||||||
|
const stopCacheTimer = this.timer('getTokenWithCache.query');
|
||||||
token = await this.store.get(secret);
|
token = await this.store.get(secret);
|
||||||
if (token) {
|
if (token) {
|
||||||
this.activeTokens.push(token);
|
this.activeTokens.push(token);
|
||||||
}
|
}
|
||||||
|
stopCacheTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLastSeen(): Promise<void> {
|
||||||
|
if (this.lastSeenSecrets.size > 0) {
|
||||||
|
const toStore = [...this.lastSeenSecrets];
|
||||||
|
this.lastSeenSecrets = new Set<string>();
|
||||||
|
await this.store.markSeenAt(toStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAllTokens(): Promise<IApiToken[]> {
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const createAll = tokens
|
||||||
|
.map(mapLegacyTokenWithSecret)
|
||||||
|
.map((t) => this.insertNewApiToken(t, SYSTEM_USER_AUDIT));
|
||||||
|
await Promise.all(createAll);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('Unable to create initial Admin API tokens');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUserForToken(
|
||||||
|
secret: string,
|
||||||
|
): Promise<IApiUser | undefined> {
|
||||||
|
const token = await this.getTokenWithCache(secret);
|
||||||
if (token) {
|
if (token) {
|
||||||
this.lastSeenSecrets.add(token.secret);
|
this.lastSeenSecrets.add(token.secret);
|
||||||
const apiUser: IApiUser = new ApiUser({
|
const apiUser: IApiUser = new ApiUser({
|
||||||
|
@ -1,39 +1,83 @@
|
|||||||
import type { IUnleashConfig } from '../types';
|
import type { IFlagResolver, 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 { 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 { FUNCTION_TIME } from '../metric-events';
|
||||||
|
|
||||||
export default class EdgeService {
|
export default class EdgeService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
private apiTokenService: ApiTokenService;
|
private apiTokenService: ApiTokenService;
|
||||||
|
|
||||||
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
private timer: Function;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ apiTokenService }: { apiTokenService: ApiTokenService },
|
{ apiTokenService }: { apiTokenService: ApiTokenService },
|
||||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
{
|
||||||
|
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) =>
|
||||||
|
metricsHelper.wrapTimer(eventBus, FUNCTION_TIME, {
|
||||||
|
className: 'EdgeService',
|
||||||
|
functionName,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getValidTokens(tokens: string[]): Promise<ValidatedEdgeTokensSchema> {
|
async getValidTokens(tokens: string[]): Promise<ValidatedEdgeTokensSchema> {
|
||||||
const activeTokens = await this.apiTokenService.getAllActiveTokens();
|
if (this.flagResolver.isEnabled('checkEdgeValidTokensFromCache')) {
|
||||||
const edgeTokens = tokens.reduce((result: EdgeTokenSchema[], token) => {
|
const stopTimer = this.timer('validateTokensWithCache');
|
||||||
const dbToken = activeTokens.find((activeToken) =>
|
// new behavior: use cached tokens when possible
|
||||||
constantTimeCompare(activeToken.secret, token),
|
// use the db to fetch the missing ones
|
||||||
);
|
// cache stores both missing and active so we don't hammer the db
|
||||||
if (dbToken) {
|
const validatedTokens: EdgeTokenSchema[] = [];
|
||||||
result.push({
|
for (const token of tokens) {
|
||||||
token: token,
|
const found =
|
||||||
type: dbToken.type,
|
await this.apiTokenService.getTokenWithCache(token);
|
||||||
projects: dbToken.projects,
|
if (found) {
|
||||||
});
|
validatedTokens.push({
|
||||||
|
token: token,
|
||||||
|
type: found.type,
|
||||||
|
projects: found.projects,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
stopTimer();
|
||||||
}, []);
|
return { tokens: validatedTokens };
|
||||||
return { tokens: edgeTokens };
|
} 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 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ export type IFlagKey =
|
|||||||
| 'displayTrafficDataUsage'
|
| 'displayTrafficDataUsage'
|
||||||
| 'useMemoizedActiveTokens'
|
| 'useMemoizedActiveTokens'
|
||||||
| 'queryMissingTokens'
|
| 'queryMissingTokens'
|
||||||
|
| 'checkEdgeValidTokensFromCache'
|
||||||
| 'userAccessUIEnabled'
|
| 'userAccessUIEnabled'
|
||||||
| 'disableUpdateMaxRevisionId'
|
| 'disableUpdateMaxRevisionId'
|
||||||
| 'disablePublishUnannouncedEvents'
|
| 'disablePublishUnannouncedEvents'
|
||||||
|
Loading…
Reference in New Issue
Block a user