1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-04 00:18:40 +01:00

Create endpoint that validates tokens for edge (#2039)

* Create new endpoint

* Change edge url

* Fix snapshot
This commit is contained in:
sjaanus 2022-09-01 13:26:26 +00:00 committed by GitHub
parent 0db2c08382
commit ad546a054f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 268 additions and 0 deletions

View File

@ -111,6 +111,8 @@ import { proxyFeatureSchema } from './spec/proxy-feature-schema';
import { proxyClientSchema } from './spec/proxy-client-schema'; import { proxyClientSchema } from './spec/proxy-client-schema';
import { proxyMetricsSchema } from './spec/proxy-metrics-schema'; import { proxyMetricsSchema } from './spec/proxy-metrics-schema';
import { setUiConfigSchema } from './spec/set-ui-config-schema'; import { setUiConfigSchema } from './spec/set-ui-config-schema';
import { edgeTokenSchema } from './spec/edge-token-schema';
import { validateEdgeTokensSchema } from './spec/validate-edge-tokens-schema';
// All schemas in `openapi/spec` should be listed here. // All schemas in `openapi/spec` should be listed here.
export const schemas = { export const schemas = {
@ -221,6 +223,8 @@ export const schemas = {
proxyFeaturesSchema, proxyFeaturesSchema,
proxyFeatureSchema, proxyFeatureSchema,
proxyMetricsSchema, proxyMetricsSchema,
edgeTokenSchema,
validateEdgeTokensSchema,
}; };
// Schemas must have an $id property on the form "#/components/schemas/mySchema". // Schemas must have an $id property on the form "#/components/schemas/mySchema".

View File

@ -0,0 +1,27 @@
import { FromSchema } from 'json-schema-to-ts';
import { ApiTokenType } from '../../types/models/api-token';
export const edgeTokenSchema = {
$id: '#/components/schemas/edgeTokenSchema',
type: 'object',
additionalProperties: false,
required: ['token', 'projects', 'type'],
properties: {
projects: {
type: 'array',
items: {
type: 'string',
},
},
type: {
type: 'string',
enum: Object.values(ApiTokenType),
},
token: {
type: 'string',
},
},
components: {},
} as const;
export type EdgeTokenSchema = FromSchema<typeof edgeTokenSchema>;

View File

@ -0,0 +1,27 @@
import { FromSchema } from 'json-schema-to-ts';
import { edgeTokenSchema } from './edge-token-schema';
export const validateEdgeTokensSchema = {
$id: '#/components/schemas/validateEdgeTokensSchema',
type: 'object',
additionalProperties: false,
required: ['tokens'],
properties: {
tokens: {
type: 'array',
anyOf: [
{ items: { $ref: '#/components/schemas/edgeTokenSchema' } },
{ items: { type: 'string' } },
],
},
},
components: {
schemas: {
edgeTokenSchema,
},
},
} as const;
export type ValidateEdgeTokensSchema = FromSchema<
typeof validateEdgeTokensSchema
>;

View File

@ -77,6 +77,7 @@ const OPENAPI_TAGS = [
description: description:
'Experimental endpoints that may change or disappear at any time.', 'Experimental endpoints that may change or disappear at any time.',
}, },
{ name: 'Edge', description: 'Endpoints related to Unleash on the Edge.' },
] as const; ] as const;
// make the export mutable, so it can be used in a schema // make the export mutable, so it can be used in a schema

View File

@ -0,0 +1,69 @@
import { Response } from 'express';
import Controller from '../controller';
import { IUnleashConfig, IUnleashServices } from '../../types';
import { Logger } from '../../logger';
import { NONE } from '../../types/permissions';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import { RequestBody } from '../unleash-types';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import {
validateEdgeTokensSchema,
ValidateEdgeTokensSchema,
} from '../../openapi/spec/validate-edge-tokens-schema';
import EdgeService from '../../services/edge-service';
import { OpenApiService } from '../../services/openapi-service';
export default class EdgeController extends Controller {
private readonly logger: Logger;
private edgeService: EdgeService;
private openApiService: OpenApiService;
constructor(
config: IUnleashConfig,
{
edgeService,
openApiService,
}: Pick<IUnleashServices, 'edgeService' | 'openApiService'>,
) {
super(config);
this.logger = config.getLogger('edge-api/index.ts');
this.edgeService = edgeService;
this.openApiService = openApiService;
this.route({
method: 'post',
path: '/validate',
handler: this.getValidTokens,
permission: NONE,
middleware: [
this.openApiService.validPath({
tags: ['Edge'],
operationId: 'getValidTokens',
requestBody: createRequestSchema(
'validateEdgeTokensSchema',
),
responses: {
200: createResponseSchema('validateEdgeTokensSchema'),
},
}),
],
});
}
async getValidTokens(
req: RequestBody<ValidateEdgeTokensSchema>,
res: Response<ValidateEdgeTokensSchema>,
): Promise<void> {
const tokens = await this.edgeService.getValidTokens(
req.body.tokens as string[],
);
this.openApiService.respondWithValidation<ValidateEdgeTokensSchema>(
200,
res,
validateEdgeTokensSchema.$id,
tokens,
);
}
}

View File

@ -11,6 +11,7 @@ const Controller = require('./controller');
import { HealthCheckController } from './health-check'; import { HealthCheckController } from './health-check';
import ProxyController from './proxy-api'; import ProxyController from './proxy-api';
import { conditionalMiddleware } from '../middleware/conditional-middleware'; import { conditionalMiddleware } from '../middleware/conditional-middleware';
import EdgeController from './edge-api';
class IndexRouter extends Controller { class IndexRouter extends Controller {
constructor(config: IUnleashConfig, services: IUnleashServices) { constructor(config: IUnleashConfig, services: IUnleashServices) {
@ -37,6 +38,8 @@ class IndexRouter extends Controller {
new ProxyController(config, services).router, new ProxyController(config, services).router,
), ),
); );
this.use('/edge', new EdgeController(config, services).router);
} }
} }

View File

@ -11,3 +11,7 @@ export interface IAuthRequest<
logout: () => void; logout: () => void;
session: any; session: any;
} }
export interface RequestBody<T> extends Express.Request {
body: T;
}

View File

@ -0,0 +1,40 @@
import { IUnleashStores, IUnleashConfig } from '../types';
import { Logger } from '../logger';
import { IApiTokenStore } from '../types/stores/api-token-store';
import { EdgeTokenSchema } from '../openapi/spec/edge-token-schema';
import { constantTimeCompare } from '../util/constantTimeCompare';
import { ValidateEdgeTokensSchema } from '../openapi/spec/validate-edge-tokens-schema';
export default class EdgeService {
private logger: Logger;
private apiTokenStore: IApiTokenStore;
constructor(
{ apiTokenStore }: Pick<IUnleashStores, 'apiTokenStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
) {
this.logger = getLogger('lib/services/edge-service.ts');
this.apiTokenStore = apiTokenStore;
}
async getValidTokens(tokens: string[]): Promise<ValidateEdgeTokensSchema> {
const activeTokens = await this.apiTokenStore.getAllActive();
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;
}, []);
return { tokens: edgeTokens };
}
}
module.exports = EdgeService;

View File

@ -34,6 +34,7 @@ import { ClientSpecService } from './client-spec-service';
import { PlaygroundService } from './playground-service'; import { PlaygroundService } from './playground-service';
import { GroupService } from './group-service'; import { GroupService } from './group-service';
import { ProxyService } from './proxy-service'; import { ProxyService } from './proxy-service';
import EdgeService from './edge-service';
export const createServices = ( export const createServices = (
stores: IUnleashStores, stores: IUnleashStores,
config: IUnleashConfig, config: IUnleashConfig,
@ -98,6 +99,8 @@ export const createServices = (
segmentService, segmentService,
}); });
const edgeService = new EdgeService(stores, config);
return { return {
accessService, accessService,
addonService, addonService,
@ -132,6 +135,7 @@ export const createServices = (
playgroundService, playgroundService,
groupService, groupService,
proxyService, proxyService,
edgeService,
}; };
}; };

View File

@ -30,6 +30,7 @@ import { ClientSpecService } from '../services/client-spec-service';
import { PlaygroundService } from 'lib/services/playground-service'; import { PlaygroundService } from 'lib/services/playground-service';
import { GroupService } from '../services/group-service'; import { GroupService } from '../services/group-service';
import { ProxyService } from '../services/proxy-service'; import { ProxyService } from '../services/proxy-service';
import EdgeService from '../services/edge-service';
export interface IUnleashServices { export interface IUnleashServices {
accessService: AccessService; accessService: AccessService;
@ -65,4 +66,5 @@ export interface IUnleashServices {
clientSpecService: ClientSpecService; clientSpecService: ClientSpecService;
playgroundService: PlaygroundService; playgroundService: PlaygroundService;
proxyService: ProxyService; proxyService: ProxyService;
edgeService: EdgeService;
} }

View File

@ -758,6 +758,34 @@ Object {
}, },
], ],
}, },
"edgeTokenSchema": Object {
"additionalProperties": false,
"properties": Object {
"projects": Object {
"items": Object {
"type": "string",
},
"type": "array",
},
"token": Object {
"type": "string",
},
"type": Object {
"enum": Array [
"client",
"admin",
"frontend",
],
"type": "string",
},
},
"required": Array [
"token",
"projects",
"type",
],
"type": "object",
},
"emailSchema": Object { "emailSchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
@ -3133,6 +3161,30 @@ Object {
}, },
"type": "array", "type": "array",
}, },
"validateEdgeTokensSchema": Object {
"additionalProperties": false,
"properties": Object {
"tokens": Object {
"anyOf": Array [
Object {
"items": Object {
"$ref": "#/components/schemas/edgeTokenSchema",
},
},
Object {
"items": Object {
"type": "string",
},
},
],
"type": "array",
},
},
"required": Array [
"tokens",
],
"type": "object",
},
"validatePasswordSchema": Object { "validatePasswordSchema": Object {
"additionalProperties": false, "additionalProperties": false,
"properties": Object { "properties": Object {
@ -6837,6 +6889,37 @@ If the provided project does not exist, the list of events will be empty.",
], ],
}, },
}, },
"/edge/validate": Object {
"post": Object {
"operationId": "getValidTokens",
"requestBody": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/validateEdgeTokensSchema",
},
},
},
"description": "validateEdgeTokensSchema",
"required": true,
},
"responses": Object {
"200": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/validateEdgeTokensSchema",
},
},
},
"description": "validateEdgeTokensSchema",
},
},
"tags": Array [
"Edge",
],
},
},
"/health": Object { "/health": Object {
"get": Object { "get": Object {
"operationId": "getHealth", "operationId": "getHealth",
@ -6907,6 +6990,10 @@ If the provided project does not exist, the list of events will be empty.",
"description": "Create, update, and delete [context fields](https://docs.getunleash.io/user_guide/unleash_context) that Unleash is aware of.", "description": "Create, update, and delete [context fields](https://docs.getunleash.io/user_guide/unleash_context) that Unleash is aware of.",
"name": "Context", "name": "Context",
}, },
Object {
"description": "Endpoints related to Unleash on the Edge.",
"name": "Edge",
},
Object { Object {
"description": "Create, update, delete, enable or disable [environments](https://docs.getunleash.io/user_guide/environments) for this Unleash instance.", "description": "Create, update, delete, enable or disable [environments](https://docs.getunleash.io/user_guide/environments) for this Unleash instance.",
"name": "Environments", "name": "Environments",