mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
Refactor: rename frontend api key (#1935)
* refactor: rename frontend api key * fix: api token schema tests
This commit is contained in:
parent
037b8eacd3
commit
3266e9c22a
@ -36,13 +36,13 @@ const apiAccessMiddleware = (
|
||||
try {
|
||||
const apiToken = req.header('authorization');
|
||||
const apiUser = apiTokenService.getUserForToken(apiToken);
|
||||
const { CLIENT, PROXY } = ApiTokenType;
|
||||
const { CLIENT, FRONTEND } = ApiTokenType;
|
||||
|
||||
if (apiUser) {
|
||||
if (
|
||||
(apiUser.type === CLIENT && !isClientApi(req)) ||
|
||||
(apiUser.type === PROXY && !isProxyApi(req)) ||
|
||||
(apiUser.type === PROXY && !experimental.embedProxy)
|
||||
(apiUser.type === FRONTEND && !isProxyApi(req)) ||
|
||||
(apiUser.type === FRONTEND && !experimental.embedProxy)
|
||||
) {
|
||||
res.status(403).send({ message: TOKEN_TYPE_ERROR_MESSAGE });
|
||||
return;
|
||||
|
@ -56,10 +56,10 @@ test('should set metadata', async () => {
|
||||
expect(token.projects).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should allow for embedded proxy (frontend) key', async () => {
|
||||
test('should allow for frontend key (embedded proxy)', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'proxy',
|
||||
type: 'frontend',
|
||||
project: 'default',
|
||||
metadata: {
|
||||
corsOrigins: ['*'],
|
||||
@ -68,10 +68,10 @@ test('should allow for embedded proxy (frontend) key', async () => {
|
||||
expect(token.error).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should set environment to default for proxy key', async () => {
|
||||
test('should set environment to default for frontend key', async () => {
|
||||
let token = await createApiToken.validateAsync({
|
||||
username: 'test',
|
||||
type: 'proxy',
|
||||
type: 'frontend',
|
||||
project: 'default',
|
||||
metadata: {
|
||||
corsOrigins: ['*'],
|
||||
|
@ -10,7 +10,11 @@ export const createApiToken = joi
|
||||
.string()
|
||||
.lowercase()
|
||||
.required()
|
||||
.valid(ApiTokenType.ADMIN, ApiTokenType.CLIENT, ApiTokenType.PROXY),
|
||||
.valid(
|
||||
ApiTokenType.ADMIN,
|
||||
ApiTokenType.CLIENT,
|
||||
ApiTokenType.FRONTEND,
|
||||
),
|
||||
expiresAt: joi.date().optional(),
|
||||
project: joi.when('projects', {
|
||||
not: joi.required(),
|
||||
@ -18,7 +22,7 @@ export const createApiToken = joi
|
||||
}),
|
||||
projects: joi.array().min(0).optional(),
|
||||
environment: joi.when('type', {
|
||||
is: joi.string().valid(ApiTokenType.CLIENT, ApiTokenType.PROXY),
|
||||
is: joi.string().valid(ApiTokenType.CLIENT, ApiTokenType.FRONTEND),
|
||||
then: joi.string().optional().default(DEFAULT_ENV),
|
||||
otherwise: joi.string().optional().default(ALL),
|
||||
}),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ApiTokenService } from './api-token-service';
|
||||
import { createTestConfig } from '../../test/config/test-config';
|
||||
import { IUnleashConfig } from '../server-impl';
|
||||
import { ApiTokenType } from '../types/models/api-token';
|
||||
import { ApiTokenType, IApiTokenCreate } from '../types/models/api-token';
|
||||
import FakeApiTokenStore from '../../test/fixtures/fake-api-token-store';
|
||||
import FakeEnvironmentStore from '../../test/fixtures/fake-environment-store';
|
||||
|
||||
@ -33,3 +33,37 @@ test('Should init api token', async () => {
|
||||
|
||||
expect(tokens).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("Shouldn't return frontend token when secret is undefined", async () => {
|
||||
const token: IApiTokenCreate = {
|
||||
environment: 'default',
|
||||
projects: ['*'],
|
||||
secret: '*:*:some-random-string',
|
||||
type: ApiTokenType.FRONTEND,
|
||||
username: 'front',
|
||||
expiresAt: null,
|
||||
};
|
||||
|
||||
const config: IUnleashConfig = createTestConfig({});
|
||||
const apiTokenStore = new FakeApiTokenStore();
|
||||
const environmentStore = new FakeEnvironmentStore();
|
||||
|
||||
await environmentStore.create({
|
||||
name: 'default',
|
||||
enabled: true,
|
||||
protected: true,
|
||||
type: 'test',
|
||||
sortOrder: 1,
|
||||
});
|
||||
|
||||
const apiTokenService = new ApiTokenService(
|
||||
{ apiTokenStore, environmentStore },
|
||||
config,
|
||||
);
|
||||
|
||||
await apiTokenService.createApiTokenWithProjects(token);
|
||||
await apiTokenService.fetchActiveTokens();
|
||||
|
||||
expect(apiTokenService.getUserForToken(undefined)).toEqual(undefined);
|
||||
expect(apiTokenService.getUserForToken('')).toEqual(undefined);
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import crypto from 'crypto';
|
||||
import { Logger } from '../logger';
|
||||
import { ADMIN, CLIENT, PROXY } from '../types/permissions';
|
||||
import { ADMIN, CLIENT, FRONTEND } from '../types/permissions';
|
||||
import { IUnleashStores } from '../types/stores';
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import ApiUser from '../types/api-user';
|
||||
@ -29,8 +29,8 @@ const resolveTokenPermissions = (tokenType: string) => {
|
||||
return [CLIENT];
|
||||
}
|
||||
|
||||
if (tokenType === ApiTokenType.PROXY) {
|
||||
return [PROXY];
|
||||
if (tokenType === ApiTokenType.FRONTEND) {
|
||||
return [FRONTEND];
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -69,7 +69,7 @@ export class ApiTokenService {
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchActiveTokens(): Promise<void> {
|
||||
async fetchActiveTokens(): Promise<void> {
|
||||
try {
|
||||
this.activeTokens = await this.getAllActiveTokens();
|
||||
} finally {
|
||||
@ -102,12 +102,18 @@ export class ApiTokenService {
|
||||
}
|
||||
|
||||
public getUserForToken(secret: string): ApiUser | undefined {
|
||||
if (!secret) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let token = this.activeTokens.find((t) => t.secret === secret);
|
||||
|
||||
// If the token is not found, try to find it in the legacy format with the metadata alias
|
||||
// This is to ensure that previous proxies we set up for our customers continue working
|
||||
if (!token) {
|
||||
token = this.activeTokens.find((t) => t.metadata.alias === secret);
|
||||
if (!token && secret) {
|
||||
token = this.activeTokens.find(
|
||||
(t) => t.metadata.alias && t.metadata.alias === secret,
|
||||
);
|
||||
}
|
||||
|
||||
if (token) {
|
||||
|
@ -115,6 +115,6 @@ export class ProxyService {
|
||||
}
|
||||
|
||||
private static assertExpectedTokenType({ type }: ApiUser) {
|
||||
assert(type === ApiTokenType.PROXY || type === ApiTokenType.ADMIN);
|
||||
assert(type === ApiTokenType.FRONTEND || type === ApiTokenType.ADMIN);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export const ALL = '*';
|
||||
export enum ApiTokenType {
|
||||
CLIENT = 'client',
|
||||
ADMIN = 'admin',
|
||||
PROXY = 'proxy',
|
||||
FRONTEND = 'frontend',
|
||||
}
|
||||
|
||||
export interface ILegacyApiTokenCreate {
|
||||
@ -108,9 +108,9 @@ export const validateApiToken = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (type === ApiTokenType.PROXY && environment === ALL) {
|
||||
if (type === ApiTokenType.FRONTEND && environment === ALL) {
|
||||
throw new BadDataError(
|
||||
'Proxy token cannot be scoped to all environments',
|
||||
'Frontend token cannot be scoped to all environments',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
//Special
|
||||
export const ADMIN = 'ADMIN';
|
||||
export const CLIENT = 'CLIENT';
|
||||
export const PROXY = 'PROXY';
|
||||
export const FRONTEND = 'FRONTEND';
|
||||
export const NONE = 'NONE';
|
||||
|
||||
export const CREATE_FEATURE = 'CREATE_FEATURE';
|
||||
|
@ -246,7 +246,7 @@ Object {
|
||||
"type": "string",
|
||||
},
|
||||
"type": Object {
|
||||
"description": "client, admin, proxy.",
|
||||
"description": "client, admin, frontend.",
|
||||
"type": "string",
|
||||
},
|
||||
"username": Object {
|
||||
@ -683,7 +683,7 @@ Object {
|
||||
"type": "string",
|
||||
},
|
||||
"type": Object {
|
||||
"description": "client, admin, proxy.",
|
||||
"description": "client, admin, frontend.",
|
||||
"type": "string",
|
||||
},
|
||||
"username": Object {
|
||||
|
@ -88,7 +88,7 @@ const createProject = async (id: string): Promise<void> => {
|
||||
await app.services.projectService.createProject({ id, name: id }, user);
|
||||
};
|
||||
|
||||
test('should require a proxy token or an admin token', async () => {
|
||||
test('should require a frontend token or an admin token', async () => {
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.expect('Content-Type', /json/)
|
||||
@ -117,64 +117,64 @@ test('should allow requests with an admin token', async () => {
|
||||
.expect((res) => expect(res.body).toEqual({ toggles: [] }));
|
||||
});
|
||||
|
||||
test('should not allow admin requests with a proxy token', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
test('should not allow admin requests with a frontend token', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.get('/api/admin/features')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('should not allow client requests with a proxy token', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
test('should not allow client requests with a frontend token', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.get('/api/client/features')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
test('should not allow requests with an invalid proxy token', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
test('should not allow requests with an invalid frontend token', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyToken.secret.slice(0, -1))
|
||||
.set('Authorization', frontendToken.secret.slice(0, -1))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
test('should allow requests with a proxy token', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
test('should allow requests with a frontend token', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body).toEqual({ toggles: [] }));
|
||||
});
|
||||
|
||||
test('should return 405 from unimplemented endpoints', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.post('/api/frontend')
|
||||
.send({})
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(405);
|
||||
await app.request
|
||||
.get('/api/frontend/client/features')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(405);
|
||||
await app.request
|
||||
.get('/api/frontend/health')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(405);
|
||||
await app.request
|
||||
.get('/api/frontend/internal-backstage/prometheus')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(405);
|
||||
});
|
||||
@ -183,16 +183,16 @@ test('should return 405 from unimplemented endpoints', async () => {
|
||||
test.todo('should enforce token CORS settings');
|
||||
|
||||
test('should accept client registration requests', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.post('/api/frontend/client/register')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.send({})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(400);
|
||||
await app.request
|
||||
.post('/api/frontend/client/register')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.send({
|
||||
appName: randomId(),
|
||||
instanceId: randomId(),
|
||||
@ -211,7 +211,7 @@ test('should store proxy client metrics', async () => {
|
||||
const appName = randomId();
|
||||
const instanceId = randomId();
|
||||
const featureName = randomId();
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
const adminToken = await createApiToken(ApiTokenType.ADMIN, {
|
||||
projects: ['*'],
|
||||
environment: '*',
|
||||
@ -232,7 +232,7 @@ test('should store proxy client metrics', async () => {
|
||||
});
|
||||
await app.request
|
||||
.post('/api/frontend/client/metrics')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.send({
|
||||
appName,
|
||||
instanceId,
|
||||
@ -246,7 +246,7 @@ test('should store proxy client metrics', async () => {
|
||||
.expect((res) => expect(res.text).toEqual('OK'));
|
||||
await app.request
|
||||
.post('/api/frontend/client/metrics')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.send({
|
||||
appName,
|
||||
instanceId,
|
||||
@ -282,7 +282,7 @@ test('should store proxy client metrics', async () => {
|
||||
});
|
||||
|
||||
test('should filter features by enabled/disabled', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await createFeatureToggle({
|
||||
name: 'enabledFeature1',
|
||||
enabled: true,
|
||||
@ -300,7 +300,7 @@ test('should filter features by enabled/disabled', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -324,7 +324,7 @@ test('should filter features by enabled/disabled', async () => {
|
||||
});
|
||||
|
||||
test('should filter features by strategies', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await createFeatureToggle({
|
||||
name: 'featureWithoutStrategies',
|
||||
enabled: false,
|
||||
@ -345,7 +345,7 @@ test('should filter features by strategies', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -363,7 +363,7 @@ test('should filter features by strategies', async () => {
|
||||
});
|
||||
|
||||
test('should filter features by constraints', async () => {
|
||||
const proxyToken = await createApiToken(ApiTokenType.PROXY);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await createFeatureToggle({
|
||||
name: 'featureWithAppNameA',
|
||||
enabled: true,
|
||||
@ -396,19 +396,19 @@ test('should filter features by constraints', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend?appName=a')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body.toggles).toHaveLength(2));
|
||||
await app.request
|
||||
.get('/api/frontend?appName=b')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body.toggles).toHaveLength(1));
|
||||
await app.request
|
||||
.get('/api/frontend?appName=c')
|
||||
.set('Authorization', proxyToken.secret)
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => expect(res.body.toggles).toHaveLength(0));
|
||||
@ -419,11 +419,11 @@ test('should filter features by project', async () => {
|
||||
const projectB = 'projectB';
|
||||
await createProject(projectA);
|
||||
await createProject(projectB);
|
||||
const proxyTokenDefault = await createApiToken(ApiTokenType.PROXY);
|
||||
const proxyTokenProjectA = await createApiToken(ApiTokenType.PROXY, {
|
||||
const frontendTokenDefault = await createApiToken(ApiTokenType.FRONTEND);
|
||||
const frontendTokenProjectA = await createApiToken(ApiTokenType.FRONTEND, {
|
||||
projects: [projectA],
|
||||
});
|
||||
const proxyTokenProjectAB = await createApiToken(ApiTokenType.PROXY, {
|
||||
const frontendTokenProjectAB = await createApiToken(ApiTokenType.FRONTEND, {
|
||||
projects: [projectA, projectB],
|
||||
});
|
||||
await createFeatureToggle({
|
||||
@ -445,7 +445,7 @@ test('should filter features by project', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyTokenDefault.secret)
|
||||
.set('Authorization', frontendTokenDefault.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -462,7 +462,7 @@ test('should filter features by project', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyTokenProjectA.secret)
|
||||
.set('Authorization', frontendTokenProjectA.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -479,7 +479,7 @@ test('should filter features by project', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyTokenProjectAB.secret)
|
||||
.set('Authorization', frontendTokenProjectAB.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -521,15 +521,21 @@ test('should filter features by environment', async () => {
|
||||
environmentB,
|
||||
'default',
|
||||
);
|
||||
const proxyTokenEnvironmentDefault = await createApiToken(
|
||||
ApiTokenType.PROXY,
|
||||
const frontendTokenEnvironmentDefault = await createApiToken(
|
||||
ApiTokenType.FRONTEND,
|
||||
);
|
||||
const frontendTokenEnvironmentA = await createApiToken(
|
||||
ApiTokenType.FRONTEND,
|
||||
{
|
||||
environment: environmentA,
|
||||
},
|
||||
);
|
||||
const frontendTokenEnvironmentB = await createApiToken(
|
||||
ApiTokenType.FRONTEND,
|
||||
{
|
||||
environment: environmentB,
|
||||
},
|
||||
);
|
||||
const proxyTokenEnvironmentA = await createApiToken(ApiTokenType.PROXY, {
|
||||
environment: environmentA,
|
||||
});
|
||||
const proxyTokenEnvironmentB = await createApiToken(ApiTokenType.PROXY, {
|
||||
environment: environmentB,
|
||||
});
|
||||
await createFeatureToggle({
|
||||
name: 'featureInEnvironmentDefault',
|
||||
enabled: true,
|
||||
@ -549,7 +555,7 @@ test('should filter features by environment', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyTokenEnvironmentDefault.secret)
|
||||
.set('Authorization', frontendTokenEnvironmentDefault.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -566,7 +572,7 @@ test('should filter features by environment', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyTokenEnvironmentA.secret)
|
||||
.set('Authorization', frontendTokenEnvironmentA.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
@ -583,7 +589,7 @@ test('should filter features by environment', async () => {
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', proxyTokenEnvironmentB.secret)
|
||||
.set('Authorization', frontendTokenEnvironmentB.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
|
4
src/test/fixtures/fake-api-token-store.ts
vendored
4
src/test/fixtures/fake-api-token-store.ts
vendored
@ -44,7 +44,9 @@ export default class FakeApiTokenStore
|
||||
}
|
||||
|
||||
async getAllActive(): Promise<IApiToken[]> {
|
||||
return this.tokens.filter((token) => token.expiresAt > new Date());
|
||||
return this.tokens.filter(
|
||||
(token) => token.expiresAt === null || token.expiresAt > new Date(),
|
||||
);
|
||||
}
|
||||
|
||||
async insert(newToken: IApiTokenCreate): Promise<IApiToken> {
|
||||
|
Loading…
Reference in New Issue
Block a user