use the IdentityApi properly
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -18,64 +18,47 @@ yarn --cwd packages/backend add @backstage/plugin-user-settings-backend
|
||||
|
||||
```ts
|
||||
// packages/backend/src/plugins/userSettings.ts
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
createRouter,
|
||||
createUserSettingsStore,
|
||||
} from '@backstage/plugin-user-settings-backend';
|
||||
import { Router } from 'express';
|
||||
|
||||
import { createRouter } from '@backstage/plugin-user-settings-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
database,
|
||||
discovery,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
const identity = IdentityClient.create({
|
||||
discovery,
|
||||
issuer: await discovery.getExternalBaseUrl('auth'),
|
||||
});
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment) {
|
||||
return await createRouter({
|
||||
userSettingsStore: await createUserSettingsStore(database),
|
||||
identity,
|
||||
database: env.database,
|
||||
identity: env.identity,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
3. Add the new routes to your backend by modifying the
|
||||
`packages/backend/src/index.ts`:
|
||||
3. Add the new routes to your backend by modifying `packages/backend/src/index.ts`:
|
||||
|
||||
```diff
|
||||
// packages/backend/src/index.ts
|
||||
+ import userSettings from './plugins/userSettings';
|
||||
async function main() {
|
||||
+ const userSettingsEnv = useHotMemoize(module, () =>
|
||||
+ createEnv('userSettings'),
|
||||
+ );
|
||||
const apiRouter = Router();
|
||||
+ apiRouter.use('/user-settings', await userSettings(userSettingsEnv));
|
||||
// packages/backend/src/index.ts
|
||||
+import userSettings from './plugins/userSettings';
|
||||
async function main() {
|
||||
+ const userSettingsEnv = useHotMemoize(module, () => createEnv('userSettings'));
|
||||
const apiRouter = Router();
|
||||
+ apiRouter.use('/user-settings', await userSettings(userSettingsEnv));
|
||||
}
|
||||
```
|
||||
|
||||
## Setup app
|
||||
|
||||
To make use of the user settings backend, replace the `WebStorage` with the
|
||||
`PersistentStorage` by using the `storageApiRef`.
|
||||
`UserSettingsStorage` by using the `storageApiRef`.
|
||||
|
||||
```diff
|
||||
// packages/app/src/apis.ts
|
||||
import {
|
||||
AnyApiFactory,
|
||||
createApiFactory,
|
||||
// packages/app/src/apis.ts
|
||||
import {
|
||||
AnyApiFactory,
|
||||
createApiFactory,
|
||||
+ discoveryApiRef,
|
||||
+ fetchApiRef
|
||||
errorApiRef,
|
||||
errorApiRef,
|
||||
+ storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
+ import { PersistentStorage } from '@backstage/core-app-api';
|
||||
} from '@backstage/core-plugin-api';
|
||||
+import { UserSettingsStorage } from '@backstage/core-app-api';
|
||||
|
||||
export const apis: AnyApiFactory[] = [
|
||||
export const apis: AnyApiFactory[] = [
|
||||
+ createApiFactory({
|
||||
+ api: storageApiRef,
|
||||
+ deps: {
|
||||
@@ -84,9 +67,9 @@ export const apis: AnyApiFactory[] = [
|
||||
+ fetchApi: fetchApiRef,
|
||||
+ },
|
||||
+ factory: ({ discoveryApi, errorApi, fetchApi }) =>
|
||||
+ PersistentStorage.create({ discoveryApi, errorApi, fetchApi }),
|
||||
+ UserSettingsStorage.create({ discoveryApi, errorApi, fetchApi }),
|
||||
+ }),
|
||||
];
|
||||
];
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
```ts
|
||||
import express from 'express';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
|
||||
// @public
|
||||
@@ -15,7 +15,7 @@ export interface RouterOptions {
|
||||
// (undocumented)
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
identity: IdentityClient;
|
||||
identity: IdentityApi;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
BackstageIdentityResponse,
|
||||
IdentityApi,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { UserSettingsStore } from '../database/UserSettingsStore';
|
||||
@@ -26,9 +29,12 @@ describe('createRouter', () => {
|
||||
set: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
const authenticateMock = jest.fn();
|
||||
const identityClient: jest.Mocked<Partial<IdentityClient>> = {
|
||||
authenticate: authenticateMock,
|
||||
const getIdentityMock = jest.fn<
|
||||
Promise<BackstageIdentityResponse | undefined>,
|
||||
any
|
||||
>();
|
||||
const identityApi: jest.Mocked<Partial<IdentityApi>> = {
|
||||
getIdentity: getIdentityMock,
|
||||
};
|
||||
|
||||
let app: express.Express;
|
||||
@@ -36,7 +42,7 @@ describe('createRouter', () => {
|
||||
beforeEach(async () => {
|
||||
const router = await createRouterInternal({
|
||||
userSettingsStore,
|
||||
identity: identityClient as IdentityClient,
|
||||
identity: identityApi as IdentityApi,
|
||||
});
|
||||
|
||||
app = express().use(router);
|
||||
@@ -49,8 +55,13 @@ describe('createRouter', () => {
|
||||
describe('GET /buckets/:bucket/keys/:key', () => {
|
||||
it('returns ok', async () => {
|
||||
const setting = { bucket: 'my-bucket', key: 'my-key', value: 'a' };
|
||||
authenticateMock.mockResolvedValue({
|
||||
identity: { userEntityRef: 'user-1' },
|
||||
getIdentityMock.mockResolvedValue({
|
||||
token: 'a',
|
||||
identity: {
|
||||
type: 'user',
|
||||
ownershipEntityRefs: [],
|
||||
userEntityRef: 'user-1',
|
||||
},
|
||||
});
|
||||
|
||||
userSettingsStore.get.mockResolvedValue(setting);
|
||||
@@ -62,7 +73,7 @@ describe('createRouter', () => {
|
||||
expect(responses.status).toEqual(200);
|
||||
expect(responses.body).toEqual(setting);
|
||||
|
||||
expect(authenticateMock).toHaveBeenCalledWith('foo');
|
||||
expect(getIdentityMock).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.get).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.get).toHaveBeenCalledWith({
|
||||
userEntityRef: 'user-1',
|
||||
@@ -83,8 +94,13 @@ describe('createRouter', () => {
|
||||
|
||||
describe('DELETE /buckets/:bucket/keys/:key', () => {
|
||||
it('returns ok', async () => {
|
||||
authenticateMock.mockResolvedValue({
|
||||
identity: { userEntityRef: 'user-1' },
|
||||
getIdentityMock.mockResolvedValue({
|
||||
token: 'a',
|
||||
identity: {
|
||||
type: 'user',
|
||||
ownershipEntityRefs: [],
|
||||
userEntityRef: 'user-1',
|
||||
},
|
||||
});
|
||||
|
||||
userSettingsStore.delete.mockResolvedValue();
|
||||
@@ -95,7 +111,7 @@ describe('createRouter', () => {
|
||||
|
||||
expect(responses.status).toEqual(204);
|
||||
|
||||
expect(authenticateMock).toHaveBeenCalledWith('foo');
|
||||
expect(getIdentityMock).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.delete).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.delete).toHaveBeenCalledWith({
|
||||
userEntityRef: 'user-1',
|
||||
@@ -117,8 +133,13 @@ describe('createRouter', () => {
|
||||
describe('PUT /buckets/:bucket/keys/:key', () => {
|
||||
it('returns ok', async () => {
|
||||
const setting = { bucket: 'my-bucket', key: 'my-key', value: 'a' };
|
||||
authenticateMock.mockResolvedValue({
|
||||
identity: { userEntityRef: 'user-1' },
|
||||
getIdentityMock.mockResolvedValue({
|
||||
token: 'a',
|
||||
identity: {
|
||||
type: 'user',
|
||||
ownershipEntityRefs: [],
|
||||
userEntityRef: 'user-1',
|
||||
},
|
||||
});
|
||||
|
||||
userSettingsStore.set.mockResolvedValue();
|
||||
@@ -132,7 +153,7 @@ describe('createRouter', () => {
|
||||
expect(responses.status).toEqual(200);
|
||||
expect(responses.body).toEqual(setting);
|
||||
|
||||
expect(authenticateMock).toHaveBeenCalledWith('foo');
|
||||
expect(getIdentityMock).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.set).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.set).toHaveBeenCalledWith({
|
||||
userEntityRef: 'user-1',
|
||||
@@ -149,8 +170,13 @@ describe('createRouter', () => {
|
||||
});
|
||||
|
||||
it('returns an error if the value is not given', async () => {
|
||||
authenticateMock.mockResolvedValue({
|
||||
identity: { userEntityRef: 'user-1' },
|
||||
getIdentityMock.mockResolvedValue({
|
||||
token: 'a',
|
||||
identity: {
|
||||
type: 'user',
|
||||
ownershipEntityRefs: [],
|
||||
userEntityRef: 'user-1',
|
||||
},
|
||||
});
|
||||
|
||||
const responses = await request(app)
|
||||
@@ -160,7 +186,7 @@ describe('createRouter', () => {
|
||||
|
||||
expect(responses.status).toEqual(400);
|
||||
|
||||
expect(authenticateMock).toHaveBeenCalledWith('foo');
|
||||
expect(getIdentityMock).toHaveBeenCalledTimes(1);
|
||||
expect(userSettingsStore.set).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
|
||||
import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { AuthenticationError, InputError } from '@backstage/errors';
|
||||
import {
|
||||
getBearerTokenFromAuthorizationHeader,
|
||||
IdentityClient,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import express, { Request } from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { DatabaseUserSettingsStore } from '../database/DatabaseUserSettingsStore';
|
||||
@@ -30,7 +27,7 @@ import { UserSettingsStore } from '../database/UserSettingsStore';
|
||||
*/
|
||||
export interface RouterOptions {
|
||||
database: PluginDatabaseManager;
|
||||
identity: IdentityClient;
|
||||
identity: IdentityApi;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +49,7 @@ export async function createRouter(
|
||||
}
|
||||
|
||||
export async function createRouterInternal(options: {
|
||||
identity: IdentityClient;
|
||||
identity: IdentityApi;
|
||||
userSettingsStore: UserSettingsStore;
|
||||
}): Promise<express.Router> {
|
||||
const router = Router();
|
||||
@@ -62,18 +59,13 @@ export async function createRouterInternal(options: {
|
||||
* Helper method to extract the userEntityRef from the request.
|
||||
*/
|
||||
const getUserEntityRef = async (req: Request): Promise<string> => {
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
req.header('authorization'),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
// throws an AuthenticationError in case the token exists but is invalid
|
||||
const identity = await options.identity.getIdentity({ request: req });
|
||||
if (!identity) {
|
||||
throw new AuthenticationError(`Missing token in 'authorization' header`);
|
||||
}
|
||||
|
||||
// throws an AuthenticationError in case the token is invalid
|
||||
const user = await options.identity.authenticate(token);
|
||||
|
||||
return user.identity.userEntityRef;
|
||||
return identity.identity.userEntityRef;
|
||||
};
|
||||
|
||||
// get a single value
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
|
||||
import { createServiceBuilder, useHotMemoize } from '@backstage/backend-common';
|
||||
import { IdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { Router } from 'express';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import { Server } from 'http';
|
||||
import Knex from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
@@ -44,11 +43,19 @@ export async function startStandaloneServer(
|
||||
|
||||
logger.debug('Starting application server...');
|
||||
|
||||
const identityMock = {
|
||||
authenticate: async token => ({
|
||||
identity: { userEntityRef: token ?? 'user:default/john_doe' },
|
||||
}),
|
||||
} as IdentityClient;
|
||||
const identityMock: IdentityApi = {
|
||||
async getIdentity({ request }) {
|
||||
const token = request.headers.authorization?.split(' ')[1];
|
||||
return {
|
||||
identity: {
|
||||
type: 'user',
|
||||
ownershipEntityRefs: [],
|
||||
userEntityRef: token || 'user:default/john_doe',
|
||||
},
|
||||
token: token || 'no-token',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const router = await createRouterInternal({
|
||||
userSettingsStore: await DatabaseUserSettingsStore.create({
|
||||
@@ -57,18 +64,9 @@ export async function startStandaloneServer(
|
||||
identity: identityMock,
|
||||
});
|
||||
|
||||
// set a custom authorization header to simplify the development
|
||||
const authWrapper = Router();
|
||||
authWrapper.use((req, _res, next) => {
|
||||
req.headers.authorization =
|
||||
req.headers.authorization ?? 'Bearer user:default/john_doe';
|
||||
next();
|
||||
});
|
||||
authWrapper.use(router);
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/user-settings', authWrapper);
|
||||
.addRouter('/user-settings', router);
|
||||
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
|
||||
Reference in New Issue
Block a user