mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Create endpoint that validates tokens for edge (#2039)
* Create new endpoint * Change edge url * Fix snapshot
This commit is contained in:
		
							parent
							
								
									0db2c08382
								
							
						
					
					
						commit
						ad546a054f
					
				| @ -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".
 | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								src/lib/openapi/spec/edge-token-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/lib/openapi/spec/edge-token-schema.ts
									
									
									
									
									
										Normal 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>; | ||||||
							
								
								
									
										27
									
								
								src/lib/openapi/spec/validate-edge-tokens-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/lib/openapi/spec/validate-edge-tokens-schema.ts
									
									
									
									
									
										Normal 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 | ||||||
|  | >; | ||||||
| @ -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
 | ||||||
|  | |||||||
							
								
								
									
										69
									
								
								src/lib/routes/edge-api/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/lib/routes/edge-api/index.ts
									
									
									
									
									
										Normal 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, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,3 +11,7 @@ export interface IAuthRequest< | |||||||
|     logout: () => void; |     logout: () => void; | ||||||
|     session: any; |     session: any; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export interface RequestBody<T> extends Express.Request { | ||||||
|  |     body: T; | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								src/lib/services/edge-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/lib/services/edge-service.ts
									
									
									
									
									
										Normal 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; | ||||||
| @ -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, | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| } | } | ||||||
|  | |||||||
| @ -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", | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user