diff --git a/.changeset/quick-falcons-happen.md b/.changeset/quick-falcons-happen.md new file mode 100644 index 0000000000..26540aa38d --- /dev/null +++ b/.changeset/quick-falcons-happen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-todo-backend': patch +--- + +Forward authorization on todo backend requests diff --git a/plugins/todo-backend/src/service/TodoReaderService.test.ts b/plugins/todo-backend/src/service/TodoReaderService.test.ts index dacc4118d9..b79ffd1333 100644 --- a/plugins/todo-backend/src/service/TodoReaderService.test.ts +++ b/plugins/todo-backend/src/service/TodoReaderService.test.ts @@ -89,7 +89,9 @@ describe('TodoReaderService', () => { offset: 0, limit: 10, }); - expect(catalogClient.getEntityByName).toHaveBeenCalledWith(entityName); + expect(catalogClient.getEntityByName).toHaveBeenCalledWith(entityName, { + token: undefined, + }); }); it('should list, order, and filter todos', async () => { @@ -298,7 +300,9 @@ describe('TodoReaderService', () => { message: 'Entity not found, Component:default/my-component', }), ); - expect(catalogClient.getEntityByName).toHaveBeenCalledWith(entityName); + expect(catalogClient.getEntityByName).toHaveBeenCalledWith(entityName, { + token: undefined, + }); }); it('should throw if entity does not have a location', async () => { diff --git a/plugins/todo-backend/src/service/TodoReaderService.ts b/plugins/todo-backend/src/service/TodoReaderService.ts index 2f20aab0f1..b6cb258b50 100644 --- a/plugins/todo-backend/src/service/TodoReaderService.ts +++ b/plugins/todo-backend/src/service/TodoReaderService.ts @@ -56,11 +56,17 @@ export class TodoReaderService implements TodoService { this.defaultPageSize = options.defaultPageSize ?? DEFAULT_DEFAULT_PAGE_SIZE; } - async listTodos(req: ListTodosRequest): Promise { + async listTodos( + req: ListTodosRequest, + options?: { token?: string }, + ): Promise { if (!req.entity) { throw new InputError('Entity filter is required to list TODOs'); } - const entity = await this.catalogClient.getEntityByName(req.entity); + const token = options?.token; + const entity = await this.catalogClient.getEntityByName(req.entity, { + token, + }); if (!entity) { throw new NotFoundError( `Entity not found, ${serializeEntityRef(req.entity)}`, diff --git a/plugins/todo-backend/src/service/router.test.ts b/plugins/todo-backend/src/service/router.test.ts index 13574d9381..aae2878918 100644 --- a/plugins/todo-backend/src/service/router.test.ts +++ b/plugins/todo-backend/src/service/router.test.ts @@ -69,11 +69,32 @@ describe('createRouter', () => { const response = await request(app).get('/v1/todos'); expect(response.status).toEqual(200); expect(response.body).toEqual(mockListBody); - expect(mockService.listTodos).toHaveBeenCalledWith({ - entity: undefined, - offset: undefined, - limit: undefined, - }); + expect(mockService.listTodos).toHaveBeenCalledWith( + { + entity: undefined, + offset: undefined, + limit: undefined, + }, + { token: undefined }, + ); + }); + + it('forwards auth token', async () => { + mockService.listTodos.mockResolvedValueOnce(mockListBody); + + const response = await request(app) + .get('/v1/todos') + .set('Authorization', 'Bearer secret'); + expect(response.status).toEqual(200); + expect(response.body).toEqual(mockListBody); + expect(mockService.listTodos).toHaveBeenCalledWith( + { + entity: undefined, + offset: undefined, + limit: undefined, + }, + { token: 'secret' }, + ); }); it('forwards pagination query', async () => { @@ -82,11 +103,14 @@ describe('createRouter', () => { const response = await request(app).get('/v1/todos?offset=5&limit=3'); expect(response.status).toEqual(200); expect(response.body).toEqual(mockListBody); - expect(mockService.listTodos).toHaveBeenCalledWith({ - entity: undefined, - offset: 5, - limit: 3, - }); + expect(mockService.listTodos).toHaveBeenCalledWith( + { + entity: undefined, + offset: 5, + limit: 3, + }, + { token: undefined }, + ); }); it('forwards entity query', async () => { @@ -97,15 +121,18 @@ describe('createRouter', () => { ); expect(response.status).toEqual(200); expect(response.body).toEqual(mockListBody); - expect(mockService.listTodos).toHaveBeenCalledWith({ - entity: { - name: 'my-component', - kind: 'component', - namespace: 'default', + expect(mockService.listTodos).toHaveBeenCalledWith( + { + entity: { + name: 'my-component', + kind: 'component', + namespace: 'default', + }, + offset: undefined, + limit: undefined, }, - offset: undefined, - limit: undefined, - }); + { token: undefined }, + ); }); it('rejects invalid queries', async () => { diff --git a/plugins/todo-backend/src/service/router.ts b/plugins/todo-backend/src/service/router.ts index 9c06e19a20..2021505520 100644 --- a/plugins/todo-backend/src/service/router.ts +++ b/plugins/todo-backend/src/service/router.ts @@ -59,13 +59,18 @@ export async function createRouter( } } - const todos = await todoService.listTodos({ - entity, - offset, - limit, - orderBy, - filters, - }); + const todos = await todoService.listTodos( + { + entity, + offset, + limit, + orderBy, + filters, + }, + { + token: getBearerToken(req.headers.authorization), + }, + ); res.json(todos); }); @@ -157,3 +162,7 @@ export function parseFilterParam( return filters; } + +function getBearerToken(header?: string): string | undefined { + return header?.match(/Bearer\s+(\S+)/i)?.[1]; +} diff --git a/plugins/todo-backend/src/service/types.ts b/plugins/todo-backend/src/service/types.ts index 9216fe733d..d6e2d517e8 100644 --- a/plugins/todo-backend/src/service/types.ts +++ b/plugins/todo-backend/src/service/types.ts @@ -42,5 +42,8 @@ export type ListTodosResponse = { }; export interface TodoService { - listTodos(req: ListTodosRequest): Promise; + listTodos( + req: ListTodosRequest, + options?: { token?: string }, + ): Promise; }