diff --git a/.changeset/rare-feet-melt.md b/.changeset/rare-feet-melt.md new file mode 100644 index 0000000000..b28b09a29d --- /dev/null +++ b/.changeset/rare-feet-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings-backend': patch +--- + +Replaced usage of the deprecated identity service with the new HTTP auth service for the new backend system. diff --git a/plugins/user-settings-backend/api-report.md b/plugins/user-settings-backend/api-report.md index f8bf571fa7..06dad6a82a 100644 --- a/plugins/user-settings-backend/api-report.md +++ b/plugins/user-settings-backend/api-report.md @@ -3,9 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { DatabaseService } from '@backstage/backend-plugin-api'; import express from 'express'; import { IdentityApi } from '@backstage/plugin-auth-node'; -import { PluginDatabaseManager } from '@backstage/backend-common'; import { SignalsService } from '@backstage/plugin-signals-node'; // @public @@ -14,7 +14,7 @@ export function createRouter(options: RouterOptions): Promise; // @public (undocumented) export interface RouterOptions { // (undocumented) - database: PluginDatabaseManager; + database: DatabaseService; // (undocumented) identity: IdentityApi; // (undocumented) diff --git a/plugins/user-settings-backend/src/alpha.ts b/plugins/user-settings-backend/src/alpha.ts index 86180c8699..ea24389e46 100644 --- a/plugins/user-settings-backend/src/alpha.ts +++ b/plugins/user-settings-backend/src/alpha.ts @@ -18,8 +18,9 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { createRouter } from './service/router'; +import { createRouterInternal } from './service/router'; import { signalsServiceRef } from '@backstage/plugin-signals-node'; +import { DatabaseUserSettingsStore } from './database/DatabaseUserSettingsStore'; /** * The user settings backend plugin. @@ -32,12 +33,17 @@ export default createBackendPlugin({ env.registerInit({ deps: { database: coreServices.database, - identity: coreServices.identity, + httpAuth: coreServices.httpAuth, httpRouter: coreServices.httpRouter, signals: signalsServiceRef, }, - async init({ database, identity, httpRouter, signals }) { - httpRouter.use(await createRouter({ database, identity, signals })); + async init({ database, httpAuth, httpRouter, signals }) { + const userSettingsStore = await DatabaseUserSettingsStore.create({ + database, + }); + httpRouter.use( + await createRouterInternal({ userSettingsStore, httpAuth, signals }), + ); }, }); }, diff --git a/plugins/user-settings-backend/src/service/router.test.ts b/plugins/user-settings-backend/src/service/router.test.ts index 27375c6333..90b6fb587f 100644 --- a/plugins/user-settings-backend/src/service/router.test.ts +++ b/plugins/user-settings-backend/src/service/router.test.ts @@ -14,15 +14,12 @@ * limitations under the License. */ -import { - BackstageIdentityResponse, - IdentityApi, -} from '@backstage/plugin-auth-node'; import express from 'express'; import request from 'supertest'; import { UserSettingsStore } from '../database/UserSettingsStore'; import { createRouterInternal } from './router'; import { SignalsService } from '@backstage/plugin-signals-node'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; describe('createRouter', () => { const userSettingsStore: jest.Mocked = { @@ -30,13 +27,6 @@ describe('createRouter', () => { set: jest.fn(), delete: jest.fn(), }; - const getIdentityMock = jest.fn< - Promise, - any - >(); - const identityApi: jest.Mocked> = { - getIdentity: getIdentityMock, - }; const signalService: jest.Mocked = { publish: jest.fn(), }; @@ -46,7 +36,7 @@ describe('createRouter', () => { beforeEach(async () => { const router = await createRouterInternal({ userSettingsStore, - identity: identityApi as IdentityApi, + httpAuth: mockServices.httpAuth(), signals: signalService as SignalsService, }); @@ -57,40 +47,33 @@ describe('createRouter', () => { jest.resetAllMocks(); }); + const mockUserRef = mockCredentials.user().principal.userEntityRef; + describe('GET /buckets/:bucket/keys/:key', () => { it('returns ok', async () => { const setting = { bucket: 'my-bucket', key: 'my-key', value: 'a' }; - getIdentityMock.mockResolvedValue({ - token: 'a', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'user-1', - }, - }); userSettingsStore.get.mockResolvedValue(setting); - const responses = await request(app) - .get('/buckets/my-bucket/keys/my-key') - .set('Authorization', 'Bearer foo'); + const responses = await request(app).get( + '/buckets/my-bucket/keys/my-key', + ); expect(responses.status).toEqual(200); expect(responses.body).toEqual(setting); - expect(getIdentityMock).toHaveBeenCalledTimes(1); expect(userSettingsStore.get).toHaveBeenCalledTimes(1); expect(userSettingsStore.get).toHaveBeenCalledWith({ - userEntityRef: 'user-1', + userEntityRef: mockUserRef, bucket: 'my-bucket', key: 'my-key', }); }); it('returns an error if the Authorization header is missing', async () => { - const responses = await request(app).get( - '/buckets/my-bucket/keys/my-key', - ); + const responses = await request(app) + .get('/buckets/my-bucket/keys/my-key') + .set('Authorization', mockCredentials.none.header()); expect(responses.status).toEqual(401); expect(userSettingsStore.get).not.toHaveBeenCalled(); @@ -99,41 +82,31 @@ describe('createRouter', () => { describe('DELETE /buckets/:bucket/keys/:key', () => { it('returns ok', async () => { - getIdentityMock.mockResolvedValue({ - token: 'a', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'user-1', - }, - }); - userSettingsStore.delete.mockResolvedValue(); - const responses = await request(app) - .delete('/buckets/my-bucket/keys/my-key') - .set('Authorization', 'Bearer foo'); + const responses = await request(app).delete( + '/buckets/my-bucket/keys/my-key', + ); expect(responses.status).toEqual(204); - expect(getIdentityMock).toHaveBeenCalledTimes(1); expect(userSettingsStore.delete).toHaveBeenCalledTimes(1); expect(userSettingsStore.delete).toHaveBeenCalledWith({ - userEntityRef: 'user-1', + userEntityRef: mockUserRef, bucket: 'my-bucket', key: 'my-key', }); expect(signalService.publish).toHaveBeenCalledWith({ - recipients: { type: 'user', entityRef: 'user-1' }, + recipients: { type: 'user', entityRef: mockUserRef }, channel: `user-settings`, message: { type: 'key-deleted', key: 'my-key' }, }); }); it('returns an error if the Authorization header is missing', async () => { - const responses = await request(app).delete( - '/buckets/my-bucket/keys/my-key', - ); + const responses = await request(app) + .delete('/buckets/my-bucket/keys/my-key') + .set('Authorization', mockCredentials.none.header()); expect(responses.status).toEqual(401); expect(userSettingsStore.delete).not.toHaveBeenCalled(); @@ -143,72 +116,51 @@ describe('createRouter', () => { describe('PUT /buckets/:bucket/keys/:key', () => { it('returns ok', async () => { const setting = { bucket: 'my-bucket', key: 'my-key', value: 'a' }; - getIdentityMock.mockResolvedValue({ - token: 'a', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'user-1', - }, - }); userSettingsStore.set.mockResolvedValue(); userSettingsStore.get.mockResolvedValue(setting); const responses = await request(app) .put('/buckets/my-bucket/keys/my-key') - .set('Authorization', 'Bearer foo') .send({ value: 'a' }); expect(responses.status).toEqual(200); expect(responses.body).toEqual(setting); - expect(getIdentityMock).toHaveBeenCalledTimes(1); expect(userSettingsStore.set).toHaveBeenCalledTimes(1); expect(userSettingsStore.set).toHaveBeenCalledWith({ - userEntityRef: 'user-1', + userEntityRef: mockUserRef, bucket: 'my-bucket', key: 'my-key', value: 'a', }); expect(userSettingsStore.get).toHaveBeenCalledTimes(1); expect(userSettingsStore.get).toHaveBeenCalledWith({ - userEntityRef: 'user-1', + userEntityRef: mockUserRef, bucket: 'my-bucket', key: 'my-key', }); expect(signalService.publish).toHaveBeenCalledWith({ - recipients: { type: 'user', entityRef: 'user-1' }, + recipients: { type: 'user', entityRef: mockUserRef }, channel: `user-settings`, message: { type: 'key-changed', key: 'my-key' }, }); }); it('returns an error if the value is not given', async () => { - getIdentityMock.mockResolvedValue({ - token: 'a', - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'user-1', - }, - }); - const responses = await request(app) .put('/buckets/my-bucket/keys/my-key') - .set('Authorization', 'Bearer foo') .send({}); expect(responses.status).toEqual(400); - expect(getIdentityMock).toHaveBeenCalledTimes(1); expect(userSettingsStore.set).not.toHaveBeenCalled(); }); it('returns an error if the Authorization header is missing', async () => { - const responses = await request(app).get( - '/buckets/my-bucket/keys/my-key', - ); + const responses = await request(app) + .get('/buckets/my-bucket/keys/my-key') + .set('Authorization', mockCredentials.none.header()); expect(responses.status).toEqual(401); expect(userSettingsStore.get).not.toHaveBeenCalled(); diff --git a/plugins/user-settings-backend/src/service/router.ts b/plugins/user-settings-backend/src/service/router.ts index b38e536354..3efb7da0f9 100644 --- a/plugins/user-settings-backend/src/service/router.ts +++ b/plugins/user-settings-backend/src/service/router.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common'; +import { errorHandler } from '@backstage/backend-common'; import { AuthenticationError, InputError } from '@backstage/errors'; import { IdentityApi } from '@backstage/plugin-auth-node'; import express, { Request } from 'express'; @@ -23,12 +23,16 @@ import { DatabaseUserSettingsStore } from '../database/DatabaseUserSettingsStore import { UserSettingsStore } from '../database/UserSettingsStore'; import { SignalsService } from '@backstage/plugin-signals-node'; import { UserSettingsSignal } from '@backstage/plugin-user-settings-common'; +import { + DatabaseService, + HttpAuthService, +} from '@backstage/backend-plugin-api'; /** * @public */ export interface RouterOptions { - database: PluginDatabaseManager; + database: DatabaseService; identity: IdentityApi; signals?: SignalsService; } @@ -52,11 +56,19 @@ export async function createRouter( }); } -export async function createRouterInternal(options: { - identity: IdentityApi; - userSettingsStore: UserSettingsStore; - signals?: SignalsService; -}): Promise { +export async function createRouterInternal( + options: + | { + identity: IdentityApi; + userSettingsStore: UserSettingsStore; + signals?: SignalsService; + } + | { + httpAuth: HttpAuthService; + userSettingsStore: UserSettingsStore; + signals?: SignalsService; + }, +): Promise { const router = Router(); router.use(express.json()); @@ -64,6 +76,13 @@ export async function createRouterInternal(options: { * Helper method to extract the userEntityRef from the request. */ const getUserEntityRef = async (req: Request): Promise => { + if ('httpAuth' in options) { + const credentials = await options.httpAuth.credentials(req, { + allow: ['user'], + }); + return credentials.principal.userEntityRef; + } + // throws an AuthenticationError in case the token exists but is invalid const identity = await options.identity.getIdentity({ request: req }); if (!identity) {