From 20667b20d495fc3a748e84ccd51002f687c3c3be Mon Sep 17 00:00:00 2001 From: Zach Hammer Date: Sat, 24 Aug 2024 20:23:49 -0400 Subject: [PATCH] feat(awards): support new backend system (#61) BREAKING CHANGE: The awards-backend plugin will no longer be able to run on the old plugin system. See the [migration guide](https://backstage.io/docs/backend-system/building-backends/migrating/) for more info. --- app-config.yaml | 2 +- package.json | 2 +- packages/app/src/App.tsx | 4 +- packages/backend/package.json | 5 + packages/backend/src/index.ts | 154 ++++--------- packages/backend/src/plugins/app.ts | 14 -- packages/backend/src/plugins/auth.ts | 107 --------- packages/backend/src/plugins/awards.ts | 16 -- packages/backend/src/plugins/catalog.ts | 14 -- packages/backend/src/plugins/proxy.ts | 13 -- packages/backend/src/plugins/scaffolder.ts | 22 -- packages/backend/src/plugins/search.ts | 66 ------ packages/backend/src/plugins/techdocs.ts | 51 ----- plugins/awards-backend/README.md | 44 +--- plugins/awards-backend/dev/index.ts | 13 ++ plugins/awards-backend/package.json | 4 + plugins/awards-backend/src/awards.ts | 6 +- plugins/awards-backend/src/notifier.test.ts | 16 +- plugins/awards-backend/src/notifier.ts | 13 +- plugins/awards-backend/src/plugin.ts | 35 ++- plugins/awards-backend/src/run.ts | 21 -- .../awards-backend/src/service/router.test.ts | 122 ++++------ plugins/awards-backend/src/service/router.ts | 77 +++---- .../src/service/standaloneServer.ts | 69 ------ yarn.lock | 212 +++++++++++++++++- 25 files changed, 393 insertions(+), 709 deletions(-) delete mode 100644 packages/backend/src/plugins/app.ts delete mode 100644 packages/backend/src/plugins/auth.ts delete mode 100644 packages/backend/src/plugins/awards.ts delete mode 100644 packages/backend/src/plugins/catalog.ts delete mode 100644 packages/backend/src/plugins/proxy.ts delete mode 100644 packages/backend/src/plugins/scaffolder.ts delete mode 100644 packages/backend/src/plugins/search.ts delete mode 100644 packages/backend/src/plugins/techdocs.ts create mode 100644 plugins/awards-backend/dev/index.ts delete mode 100644 plugins/awards-backend/src/run.ts delete mode 100644 plugins/awards-backend/src/service/standaloneServer.ts diff --git a/app-config.yaml b/app-config.yaml index 253cf3d..ebc89b3 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -33,7 +33,7 @@ techdocs: auth: environment: development providers: - 'dummy-auth': {} + guest: {} catalog: rules: diff --git a/package.json b/package.json index 4e75b7e..5ac94d9 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "concurrently": "^8.0.0", "conventional-changelog-conventionalcommits": "^6.1.0", "lerna": "^7.3.0", - "node-gyp": "^9.0.0", + "node-gyp": "^10.0.0", "prettier": "^2.3.2", "prettier-plugin-organize-imports": "^3.2.4", "typescript": "~5.2.0" diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index f5db78d..495c1c9 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -31,7 +31,7 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { AlertDisplay, OAuthRequestDialog, - ProxiedSignInPage, + SignInPage, } from '@backstage/core-components'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; @@ -41,7 +41,7 @@ import { AwardsPage } from '@seatgeek/backstage-plugin-awards'; const app = createApp({ apis, components: { - SignInPage: props => , + SignInPage: props => , }, bindRoutes({ bind }) { bind(catalogPlugin.externalRoutes, { diff --git a/packages/backend/package.json b/packages/backend/package.json index fe4f9cd..295e0fe 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -17,15 +17,20 @@ }, "dependencies": { "@backstage/backend-common": "^0.24.0", + "@backstage/backend-defaults": "^0.4.3", "@backstage/backend-tasks": "^0.6.0", "@backstage/catalog-client": "^1.6.6", "@backstage/catalog-model": "^1.6.0", "@backstage/config": "^1.2.0", "@backstage/plugin-app-backend": "^0.3.72", "@backstage/plugin-auth-backend": "^0.22.10", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", "@backstage/plugin-auth-node": "^0.5.0", "@backstage/plugin-catalog-backend": "^1.25.0", + "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", + "@backstage/plugin-permission-backend": "^0.5.47", + "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", "@backstage/plugin-permission-common": "^0.8.1", "@backstage/plugin-permission-node": "^0.8.1", "@backstage/plugin-proxy-backend": "^0.5.4", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 8517b92..05d3fa6 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,114 +1,40 @@ -/* - * Hi! - * - * Note that this is an EXAMPLE Backstage backend. Please check the README. - * - * Happy hacking! - */ - -import { - CacheManager, - DatabaseManager, - HostDiscovery, - ServerTokenManager, - createServiceBuilder, - getRootLogger, - loadBackendConfig, - notFoundHandler, - useHotMemoize, -} from '@backstage/backend-common'; -import { TaskScheduler } from '@backstage/backend-tasks'; -import { Config } from '@backstage/config'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import Router from 'express-promise-router'; -import app from './plugins/app'; -import auth from './plugins/auth'; -import awards from './plugins/awards'; -import catalog from './plugins/catalog'; -import proxy from './plugins/proxy'; -import scaffolder from './plugins/scaffolder'; -import search from './plugins/search'; -import techdocs from './plugins/techdocs'; -import { PluginEnvironment } from './types'; - -function makeCreateEnv(config: Config) { - const root = getRootLogger(); - const discovery = HostDiscovery.fromConfig(config); - const cacheManager = CacheManager.fromConfig(config); - const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); - const tokenManager = ServerTokenManager.noop(); - const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); - - const identity = DefaultIdentityClient.create({ - discovery, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - - return (plugin: string): PluginEnvironment => { - const logger = root.child({ type: 'plugin', plugin }); - const database = databaseManager.forPlugin(plugin); - const cache = cacheManager.forPlugin(plugin); - const scheduler = taskScheduler.forPlugin(plugin); - return { - logger, - database, - cache, - config, - discovery, - tokenManager, - scheduler, - reader: undefined as any, - permissions, - identity, - }; - }; -} - -async function main() { - const config = await loadBackendConfig({ - argv: process.argv, - logger: getRootLogger(), - }); - const createEnv = makeCreateEnv(config); - - const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); - const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); - const authEnv = useHotMemoize(module, () => createEnv('auth')); - const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); - const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - const awardsEnv = useHotMemoize(module, () => createEnv('awards')); - - const apiRouter = Router(); - apiRouter.use('/catalog', await catalog(catalogEnv)); - apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); - apiRouter.use('/auth', await auth(authEnv)); - apiRouter.use('/techdocs', await techdocs(techdocsEnv)); - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - apiRouter.use('/awards', await awards(awardsEnv)); - - // Add backends ABOVE this line; this 404 handler is the catch-all fallback - apiRouter.use(notFoundHandler()); - - const service = createServiceBuilder(module) - .loadConfig(config) - .addRouter('/api', apiRouter) - .addRouter('', await app(appEnv)); - - await service.start().catch(err => { - console.log(err); - process.exit(1); - }); -} - -module.hot?.accept(); -main().catch(error => { - console.error('Backend failed to start up', error); - process.exit(1); -}); +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-proxy-backend/alpha')); +backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); +backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +// auth plugin +backend.add(import('@backstage/plugin-auth-backend')); +// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +// See https://backstage.io/docs/auth/guest/provider + +// catalog plugin +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +backend.add( + import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), +); + +// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors +backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +// permission plugin +backend.add(import('@backstage/plugin-permission-backend/alpha')); +// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy +backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), +); + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +// search collators +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +backend.add(import('@seatgeek/backstage-plugin-awards-backend')); +backend.start(); diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts deleted file mode 100644 index 7c37f68..0000000 --- a/packages/backend/src/plugins/app.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createRouter } from '@backstage/plugin-app-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - appPackageName: 'app', - }); -} diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts deleted file mode 100644 index 8dcc222..0000000 --- a/packages/backend/src/plugins/auth.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { stringifyEntityRef } from '@backstage/catalog-model'; -import { - createAuthProviderIntegration, - createRouter, -} from '@backstage/plugin-auth-backend'; -import { - AuthProviderRouteHandlers, - AuthResolverContext, - SignInResolver, - prepareBackstageIdentityResponse, -} from '@backstage/plugin-auth-node'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -// a "dummy" auth provider for the local demo site used with -// proxy sign in so that the user is always logged in as guest. -// https://backstage.io/docs/auth/#sign-in-with-proxy-providers -export class DummyAuthProvider implements AuthProviderRouteHandlers { - private readonly resolverContext: AuthResolverContext; - private readonly signInResolver: SignInResolver<{}>; - - constructor(options: { - resolverContext: AuthResolverContext; - signInResolver: SignInResolver<{}>; - }) { - this.resolverContext = options.resolverContext; - this.signInResolver = options.signInResolver; - } - - async frameHandler(): Promise { - return; - } - - async refresh(_: any, res: any): Promise { - const profile = {}; - - const backstageSignInResult = await this.signInResolver( - { - profile, - result: {}, - }, - this.resolverContext, - ); - - res.json({ - providerInfo: {}, - backstageIdentity: prepareBackstageIdentityResponse( - backstageSignInResult, - ), - profile, - }); - } - - async start(): Promise { - return; - } -} - -// "dummy" auth provider integration that doesn't talk to -// any external auth providers and lets the provided signIn -// resolver do all the work -export const dummyAuth = createAuthProviderIntegration({ - create(options: { - signIn: { - resolver: SignInResolver<{}>; - }; - }) { - return ({ resolverContext }) => { - const signInResolver = options.signIn.resolver; - return new DummyAuthProvider({ - resolverContext, - signInResolver, - }); - }; - }, -}); - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - // "dummy" sign in resolver that always signs - // into the user "guest" - 'dummy-auth': dummyAuth.create({ - signIn: { - async resolver(_, ctx) { - const user = await ctx.findCatalogUser({ - entityRef: 'user:default/guest', - }); - return ctx.issueToken({ - claims: { - sub: stringifyEntityRef(user.entity), - ent: [stringifyEntityRef(user.entity)], - }, - }); - }, - }, - }), - }, - }); -} diff --git a/packages/backend/src/plugins/awards.ts b/packages/backend/src/plugins/awards.ts deleted file mode 100644 index 89e93e8..0000000 --- a/packages/backend/src/plugins/awards.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createRouter } from '@seatgeek/backstage-plugin-awards-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - database: env.database, - identity: env.identity, - config: env.config, - discovery: env.discovery, - tokenManager: env.tokenManager, - }); -} diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts deleted file mode 100644 index 4decdca..0000000 --- a/packages/backend/src/plugins/catalog.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts deleted file mode 100644 index 54ec393..0000000 --- a/packages/backend/src/plugins/proxy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createRouter } from '@backstage/plugin-proxy-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - discovery: env.discovery, - }); -} diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts deleted file mode 100644 index a12fee2..0000000 --- a/packages/backend/src/plugins/scaffolder.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CatalogClient } from '@backstage/catalog-client'; -import { createRouter } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import type { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const catalogClient = new CatalogClient({ - discoveryApi: env.discovery, - }); - - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - reader: env.reader, - catalogClient, - identity: env.identity, - permissions: env.permissions, - }); -} diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts deleted file mode 100644 index 8cdb6c3..0000000 --- a/packages/backend/src/plugins/search.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useHotCleanup } from '@backstage/backend-common'; -import { createRouter } from '@backstage/plugin-search-backend'; -import { DefaultCatalogCollatorFactory } from '@backstage/plugin-search-backend-module-catalog'; -import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs'; -import { - IndexBuilder, - LunrSearchEngine, -} from '@backstage/plugin-search-backend-node'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - // Initialize a connection to a search engine. - const searchEngine = new LunrSearchEngine({ - logger: env.logger, - }); - const indexBuilder = new IndexBuilder({ - logger: env.logger, - searchEngine, - }); - - const schedule = env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 10 }, - timeout: { minutes: 15 }, - // A 3 second delay gives the backend server a chance to initialize before - // any collators are executed, which may attempt requests against the API. - initialDelay: { seconds: 3 }, - }); - - // Collators are responsible for gathering documents known to plugins. This - // collator gathers entities from the software catalog. - indexBuilder.addCollator({ - schedule, - factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - tokenManager: env.tokenManager, - }), - }); - - // collator gathers entities from techdocs. - indexBuilder.addCollator({ - schedule, - factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { - discovery: env.discovery, - logger: env.logger, - tokenManager: env.tokenManager, - }), - }); - - // The scheduler controls when documents are gathered from collators and sent - // to the search engine for indexing. - const { scheduler } = await indexBuilder.build(); - scheduler.start(); - - useHotCleanup(module, () => scheduler.stop()); - - return await createRouter({ - engine: indexBuilder.getSearchEngine(), - types: indexBuilder.getDocumentTypes(), - permissions: env.permissions, - config: env.config, - logger: env.logger, - }); -} diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts deleted file mode 100644 index be8bb0c..0000000 --- a/packages/backend/src/plugins/techdocs.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { DockerContainerRunner } from '@backstage/backend-common'; -import { - createRouter, - Generators, - Preparers, - Publisher, -} from '@backstage/plugin-techdocs-backend'; -import Docker from 'dockerode'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - // Preparers are responsible for fetching source files for documentation. - const preparers = await Preparers.fromConfig(env.config, { - logger: env.logger, - reader: env.reader, - }); - - // Docker client (conditionally) used by the generators, based on techdocs.generators config. - const dockerClient = new Docker(); - const containerRunner = new DockerContainerRunner({ dockerClient }); - - // Generators are used for generating documentation sites. - const generators = await Generators.fromConfig(env.config, { - logger: env.logger, - containerRunner, - }); - - // Publisher is used for - // 1. Publishing generated files to storage - // 2. Fetching files from storage and passing them to TechDocs frontend. - const publisher = await Publisher.fromConfig(env.config, { - logger: env.logger, - discovery: env.discovery, - }); - - // checks if the publisher is working and logs the result - await publisher.getReadiness(); - - return await createRouter({ - preparers, - generators, - publisher, - logger: env.logger, - config: env.config, - discovery: env.discovery, - cache: env.cache, - }); -} diff --git a/plugins/awards-backend/README.md b/plugins/awards-backend/README.md index c3ee7c3..31260c3 100644 --- a/plugins/awards-backend/README.md +++ b/plugins/awards-backend/README.md @@ -14,48 +14,8 @@ Currently we support only `SQLite` and `PostgreSQL` databases. Install the @seatgeek/backstage-plugin-awards-backend package in your backend package: -```shell -# From your Backstage root directory -yarn add --cwd packages/backend @seatgeek/backstage-plugin-awards-backend -``` - -Then create a plugin entry inside `packages/src/plugins/awards.ts` in your -Backstage root with the following content: - -```typescript -import { createRouter } from '@seatgeek/backstage-plugin-awards-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - database: env.database, - identity: env.identity, - }); -} -``` - -Import the plugin inside `packages/backend/src/index.ts` in your Backstage root: - -```typescript -// Other imports here -import awards from './plugins/awards'; - -function makeCreateEnv(config: Config) { - // Lots of code here. Add the following line before the router is instantiated. - const awardsEnv = useHotMemoize(module, () => createEnv('awards')); - - const apiRouter = Router(); - // Several apiRouter.use() statements here. - // Add the route for /awards as the last one before the notFoundHandler() is - // setup. - apiRouter.use('/awards', await awards(awardsEnv)); - - apiRouter.use(notFoundHandler()); -} +```ts +backend.add(import('@seatgeek/backstage-plugin-awards-backend')); ``` ## Configuration diff --git a/plugins/awards-backend/dev/index.ts b/plugins/awards-backend/dev/index.ts new file mode 100644 index 0000000..4bedf2e --- /dev/null +++ b/plugins/awards-backend/dev/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright SeatGeek + * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. + */ +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +backend.add(import('../src')); + +backend.start(); diff --git a/plugins/awards-backend/package.json b/plugins/awards-backend/package.json index dc284a8..206efbf 100644 --- a/plugins/awards-backend/package.json +++ b/plugins/awards-backend/package.json @@ -29,7 +29,9 @@ "dependencies": { "@aws-sdk/client-s3": "^3.521.0", "@backstage/backend-common": "^0.24.0", + "@backstage/backend-defaults": "0.4.3", "@backstage/backend-plugin-api": "^0.8.0", + "@backstage/backend-test-utils": "^0.5.0", "@backstage/catalog-client": "^1.6.6", "@backstage/catalog-model": "^1.6.0", "@backstage/config": "^1.2.0", @@ -56,6 +58,8 @@ }, "devDependencies": { "@backstage/cli": "^0.27.0", + "@backstage/plugin-auth-backend": "^0.22.10", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", "@types/lodash": "^4.17.7", "@types/supertest": "^2.0.12", "better-sqlite3": "^9.1.1", diff --git a/plugins/awards-backend/src/awards.ts b/plugins/awards-backend/src/awards.ts index f66c2a6..6d24ff5 100644 --- a/plugins/awards-backend/src/awards.ts +++ b/plugins/awards-backend/src/awards.ts @@ -2,13 +2,13 @@ * Copyright SeatGeek * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { NotFoundError } from '@backstage/errors'; import { Award, AwardInput } from '@seatgeek/backstage-plugin-awards-common'; import { Storage } from '@tweedegolf/storage-abstraction'; import sizeOf from 'image-size'; import { Readable } from 'stream'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { AwardsStore } from './database/awards'; import { AwardsNotifier } from './notifier'; @@ -26,7 +26,7 @@ const mimetypeByExtension = Object.fromEntries( export class Awards { private readonly db: AwardsStore; - private readonly logger: Logger; + private readonly logger: LoggerService; private readonly notifier: AwardsNotifier; private readonly storage: Storage; @@ -34,7 +34,7 @@ export class Awards { db: AwardsStore, notifier: AwardsNotifier, storage: Storage, - logger: Logger, + logger: LoggerService, ) { this.db = db; this.notifier = notifier; diff --git a/plugins/awards-backend/src/notifier.test.ts b/plugins/awards-backend/src/notifier.test.ts index 71da2ec..21cd653 100644 --- a/plugins/awards-backend/src/notifier.test.ts +++ b/plugins/awards-backend/src/notifier.test.ts @@ -2,7 +2,7 @@ * Copyright SeatGeek * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. */ -import { TokenManager } from '@backstage/backend-common'; +import { AuthService } from '@backstage/backend-plugin-api'; import { CatalogClient, CatalogRequestOptions, @@ -24,10 +24,10 @@ function makeUser(ref: string): UserEntity { } describe('MultiAwardsNotifier', () => { - const tokenManager: jest.Mocked = { - getToken: jest.fn().mockResolvedValue({ token: 'token' }), - authenticate: jest.fn(), - }; + const authService: jest.Mocked = { + getPluginRequestToken: jest.fn().mockResolvedValue({ token: 'token' }), + getOwnServiceCredentials: jest.fn().mockResolvedValue({}), + } as unknown as jest.Mocked; const catalogClient: jest.Mocked = { getEntitiesByRefs: jest .fn() @@ -64,7 +64,7 @@ describe('MultiAwardsNotifier', () => { const notifier = new MultiAwardsNotifier( [gateway], catalogClient, - tokenManager, + authService, ); await notifier.notifyNewRecipients(award, ['user:default/jeff-buckley']); @@ -82,11 +82,11 @@ describe('MultiAwardsNotifier', () => { }); it('does not fetch from the catalog if there are no gateways', async () => { - const notifier = new MultiAwardsNotifier([], catalogClient, tokenManager); + const notifier = new MultiAwardsNotifier([], catalogClient, authService); await notifier.notifyNewRecipients(award, ['user:default/jeff-buckley']); - expect(tokenManager.getToken).not.toHaveBeenCalled(); + expect(authService.getPluginRequestToken).not.toHaveBeenCalled(); expect(catalogClient.getEntitiesByRefs).not.toHaveBeenCalled(); }); }); diff --git a/plugins/awards-backend/src/notifier.ts b/plugins/awards-backend/src/notifier.ts index 05b6341..50d0050 100644 --- a/plugins/awards-backend/src/notifier.ts +++ b/plugins/awards-backend/src/notifier.ts @@ -2,7 +2,7 @@ * Copyright SeatGeek * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. */ -import { TokenManager } from '@backstage/backend-common'; +import { AuthService } from '@backstage/backend-plugin-api'; import { CatalogClient } from '@backstage/catalog-client'; import { isUserEntity } from '@backstage/catalog-model'; import { Award } from '@seatgeek/backstage-plugin-awards-common'; @@ -22,16 +22,16 @@ export interface AwardsNotifier { export class MultiAwardsNotifier implements AwardsNotifier { private readonly notificationsGateways: NotificationsGateway[]; private readonly catalogClient: CatalogClient; - private readonly tokenManager: TokenManager; + private readonly auth: AuthService; constructor( notificationsGateways: NotificationsGateway[], catalogClient: CatalogClient, - tokenManager: TokenManager, + auth: AuthService, ) { this.notificationsGateways = notificationsGateways; this.catalogClient = catalogClient; - this.tokenManager = tokenManager; + this.auth = auth; } addNotificationsGateway(gateway: NotificationsGateway) { @@ -46,7 +46,10 @@ export class MultiAwardsNotifier implements AwardsNotifier { return; } - const token = await this.tokenManager.getToken(); + const token = await this.auth.getPluginRequestToken({ + onBehalfOf: await this.auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); const resp = await this.catalogClient.getEntitiesByRefs( { entityRefs: newRecipients, diff --git a/plugins/awards-backend/src/plugin.ts b/plugins/awards-backend/src/plugin.ts index 74a368a..fa15a16 100644 --- a/plugins/awards-backend/src/plugin.ts +++ b/plugins/awards-backend/src/plugin.ts @@ -2,7 +2,6 @@ * Copyright SeatGeek * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. */ -import { loggerToWinstonLogger } from '@backstage/backend-common'; import { coreServices, createBackendPlugin, @@ -14,33 +13,45 @@ export const awardsPlugin = createBackendPlugin({ register(env) { env.registerInit({ deps: { + auth: coreServices.auth, config: coreServices.rootConfig, database: coreServices.database, - identity: coreServices.identity, - logger: coreServices.logger, - httpRouter: coreServices.httpRouter, discovery: coreServices.discovery, - tokenManager: coreServices.tokenManager, + httpAuth: coreServices.httpAuth, + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + userInfo: coreServices.userInfo, }, async init({ + auth, config, database, - identity, - logger, - httpRouter, discovery, - tokenManager, + httpAuth, + httpRouter, + logger, + userInfo, }) { httpRouter.use( await createRouter({ + auth, config, database, - identity, - logger: loggerToWinstonLogger(logger), discovery, - tokenManager, + httpAuth, + logger, + userInfo, }), ); + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + // Award logos are rendered on the frontend as img tags, so they need to be public. + httpRouter.addAuthPolicy({ + path: '/logos/:id', + allow: 'unauthenticated', + }); }, }); }, diff --git a/plugins/awards-backend/src/run.ts b/plugins/awards-backend/src/run.ts deleted file mode 100644 index 2262a91..0000000 --- a/plugins/awards-backend/src/run.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright SeatGeek - * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. - */ -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/awards-backend/src/service/router.test.ts b/plugins/awards-backend/src/service/router.test.ts index 4d51a65..6ce00f1 100644 --- a/plugins/awards-backend/src/service/router.test.ts +++ b/plugins/awards-backend/src/service/router.test.ts @@ -2,99 +2,57 @@ * Copyright SeatGeek * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. */ -import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; import { - BackstageIdentityResponse, - IdentityApiGetIdentityRequest, -} from '@backstage/plugin-auth-node'; + mockServices, + startTestBackend, + TestDatabaseId, + TestDatabases, +} from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; import { StorageType } from '@tweedegolf/storage-abstraction'; -import express from 'express'; -import { Knex } from 'knex'; import request from 'supertest'; -import { createRouter, getStorageClient } from './router'; +import { awardsPlugin } from '../plugin'; +import { getStorageClient } from './router'; -// Good references for this: -// https://github.com/backstage/backstage/blob/0c930f8df1f2acb4a0af400e8e31cae354973af4/plugins/tech-insights-backend/src/service/router.test.ts -// https://github.com/backstage/backstage/blob/0c930f8df1f2acb4a0af400e8e31cae354973af4/plugins/playlist-backend/src/service/router.test.ts#L112 describe('backend router', () => { - let app: express.Express; - let db: Knex; - - const getIdentity = jest.fn(); - getIdentity.mockImplementation( - async ({ - request: _request, - }: IdentityApiGetIdentityRequest): Promise< - BackstageIdentityResponse | undefined - > => { - return { - identity: { - userEntityRef: 'user:default/guest', - ownershipEntityRefs: [], - type: 'user', - }, - token: 'token', - }; - }, - ); - - beforeEach(async () => { - // Need to do this to avoid using @backstage/backend-test-utils and run into - // a conflict with the knex import from the plugin. - // This means that the tests will run only on SQLite. - const createDatabaseManager = async () => - DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', + const databases = TestDatabases.create({ + ids: ['SQLITE_3'], + }); + async function makeTestServer(databaseId: TestDatabaseId) { + const knex = await databases.init(databaseId); + const { server } = await startTestBackend({ + features: [ + awardsPlugin, + mockServices.rootConfig.factory({ + data: { + awards: { + storage: { + s3: { + bucket: 'awards-bucket', + region: 'us-east-1', + }, + }, }, }, }), - ).forPlugin('awards'); - const dbm = await createDatabaseManager(); - db = await dbm.getClient(); - const router = await createRouter({ - config: new ConfigReader({ - awards: { - storage: { - s3: { - region: 'us-east-1', - bucket: 'awards-bucket', - }, - }, - }, - }), - logger: getVoidLogger(), - identity: { getIdentity }, - database: dbm, - discovery: { - getBaseUrl: jest.fn().mockResolvedValue('/'), - getExternalBaseUrl: jest.fn().mockResolvedValue('/'), - }, - tokenManager: { - authenticate: jest.fn(), - getToken: jest.fn().mockResolvedValue({ token: 'token' }), - }, + mockServices.database.mock({ + getClient: async () => knex, + }).factory, + ], }); - app = express().use(router); - - jest.resetAllMocks(); - }); - - afterEach(async () => { - await db.destroy(); - }); + return server; + } describe('GET /health', () => { - it('returns ok', async () => { - const response = await request(app).get('/health'); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'ok' }); - }); + it.each(databases.eachSupportedId())( + 'returns ok for %s', + async databaseId => { + const server = await makeTestServer(databaseId); + const response = await request(server).get('/api/awards/health'); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }, + ); }); }); diff --git a/plugins/awards-backend/src/service/router.ts b/plugins/awards-backend/src/service/router.ts index d76b2ff..d794ae9 100644 --- a/plugins/awards-backend/src/service/router.ts +++ b/plugins/awards-backend/src/service/router.ts @@ -2,33 +2,45 @@ * Copyright SeatGeek * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. */ +import { errorHandler } from '@backstage/backend-common'; import { - PluginDatabaseManager, - TokenManager, - errorHandler, -} from '@backstage/backend-common'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; + AuthService, + DatabaseService, + DiscoveryService, + HttpAuthService, + LoggerService, + RootConfigService, + UserInfoService, +} from '@backstage/backend-plugin-api'; import { CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import { AuthenticationError } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; import { Storage, StorageType } from '@tweedegolf/storage-abstraction'; import express from 'express'; import fileUpload from 'express-fileupload'; import Router from 'express-promise-router'; -import { Logger } from 'winston'; import { Awards } from '../awards'; import { DatabaseAwardsStore } from '../database/awards'; import { SlackNotificationsGateway } from '../notifications/notifications'; import { MultiAwardsNotifier } from '../notifier'; export interface RouterOptions { - identity: IdentityApi; - database: PluginDatabaseManager; - logger: Logger; - config: Config; + auth: AuthService; + config: RootConfigService; + database: DatabaseService; discovery: DiscoveryService; - tokenManager: TokenManager; + httpAuth: HttpAuthService; + logger: LoggerService; + userInfo: UserInfoService; +} + +function userInfoGetter( + httpAuth: HttpAuthService, + userInfoService: UserInfoService, +): (request: any) => Promise { + return async (request: any): Promise => { + const creds = await httpAuth.credentials(request, { allow: ['user'] }); + return (await userInfoService.getUserInfo(creds)).userEntityRef; + }; } function buildS3Adapter(config: Config): Storage { @@ -112,16 +124,13 @@ export function getStorageClient(config: Config): Storage { export async function createRouter( options: RouterOptions, ): Promise { - const { config, database, identity, logger } = options; + const { config, database, logger, userInfo, httpAuth } = options; + const getUserRef = userInfoGetter(httpAuth, userInfo); const catalogClient = new CatalogClient({ discoveryApi: options.discovery, }); - const notifier = new MultiAwardsNotifier( - [], - catalogClient, - options.tokenManager, - ); + const notifier = new MultiAwardsNotifier([], catalogClient, options.auth); const slack = SlackNotificationsGateway.fromConfig(config); if (slack) { notifier.addNotificationsGateway(slack); @@ -142,9 +151,9 @@ export async function createRouter( router.get('/', async (request, response) => { // Protecting the request - await getUserRef(identity, request); + await getUserRef(request); - logger.debug(request.query); + logger.debug(JSON.stringify(request.query)); const { uid, name, owners, recipients } = request.query; let uidQuery: string = ''; @@ -180,7 +189,7 @@ export async function createRouter( router.get('/:uid', async (request, response) => { // Protecting the request - await getUserRef(identity, request); + await getUserRef(request); const uid = request.params.uid; // TODO: validate uuid parameter @@ -190,7 +199,7 @@ export async function createRouter( }); router.put('/:uid', async (request, response) => { - const userRef = await getUserRef(identity, request); + const userRef = await getUserRef(request); const uid = request.params.uid; // TODO: validate uuid parameter @@ -202,7 +211,7 @@ export async function createRouter( }); router.delete('/:uid', async (request, response) => { - const userRef = await getUserRef(identity, request); + const userRef = await getUserRef(request); const uid = request.params.uid; // TODO: validate uuid parameter @@ -213,7 +222,7 @@ export async function createRouter( }); router.post('/', async (request, response) => { - await getUserRef(identity, request); + await getUserRef(request); const award = await awardsApp.create(request.body); @@ -221,7 +230,7 @@ export async function createRouter( }); router.post('/logos', async (request, response) => { - await getUserRef(identity, request); + await getUserRef(request); if (!request.files) { response.status(400).send('No files were uploaded.'); @@ -259,19 +268,3 @@ export async function createRouter( router.use(errorHandler()); return router; } - -async function getUserRef( - identity: IdentityApi, - request: any, -): Promise { - const user = await identity.getIdentity({ request: request }); - if (!user) { - throw new AuthenticationError('Unauthorized'); - } - const userIdentity = await identity.getIdentity({ request: request }); - const userIdRef = userIdentity?.identity.userEntityRef; - if (userIdRef === undefined) { - throw new AuthenticationError('No user entity ref'); - } - return userIdRef; -} diff --git a/plugins/awards-backend/src/service/standaloneServer.ts b/plugins/awards-backend/src/service/standaloneServer.ts deleted file mode 100644 index 00d0b87..0000000 --- a/plugins/awards-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright SeatGeek - * Licensed under the terms of the Apache-2.0 license. See LICENSE file in project root for terms. - */ -import { - DatabaseManager, - HostDiscovery, - ServerTokenManager, - createServiceBuilder, - loadBackendConfig, -} from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'awards-backend' }); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - - const manager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ); - const database = manager.forPlugin('awards'); - - const identity = DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }); - - const router = await createRouter({ - database, - identity, - logger, - config, - discovery, - tokenManager: ServerTokenManager.fromConfig(config, { - logger, - }), - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/awards', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/yarn.lock b/yarn.lock index 40ea89c..3ecbf4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3565,7 +3565,7 @@ yauzl "^3.0.0" yn "^4.0.0" -"@backstage/backend-defaults@^0.4.2": +"@backstage/backend-defaults@^0.4.2", "@backstage/backend-defaults@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@backstage/backend-defaults/-/backend-defaults-0.4.3.tgz#9078d176ae0fd1df77d2073286ea943c0fbfbee7" integrity sha512-/xmUY506bGt9JLYcW9h5HeSMFTkS4ZtSHVM5EEsTsb704qrWwGDtfKpp92o+fQt2yZS4XIigskQUkCX1SB5FHQ== @@ -4309,6 +4309,18 @@ google-auth-library "^9.0.0" passport-google-oauth20 "^2.0.0" +"@backstage/plugin-auth-backend-module-guest-provider@^0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@backstage/plugin-auth-backend-module-guest-provider/-/plugin-auth-backend-module-guest-provider-0.1.9.tgz#b81773a30a018bba55add827e2663faa0b974675" + integrity sha512-PO2S5Bz8P2Tdw7VP9P2gHx25iCgqX9Q/RfGOHq6+HeTCr0Dvp7M4uW5HBQiVBK1kjNL6Z0KsG0XTbvVXYh+Kiw== + dependencies: + "@backstage/backend-common" "^0.24.0" + "@backstage/backend-plugin-api" "^0.8.0" + "@backstage/catalog-model" "^1.6.0" + "@backstage/errors" "^1.2.4" + "@backstage/plugin-auth-node" "^0.5.0" + passport-oauth2 "^1.7.0" + "@backstage/plugin-auth-backend-module-microsoft-provider@^0.1.18": version "0.1.18" resolved "https://registry.yarnpkg.com/@backstage/plugin-auth-backend-module-microsoft-provider/-/plugin-auth-backend-module-microsoft-provider-0.1.18.tgz#24dd2bcf44a6aae702c987df99bef76eaaedac4f" @@ -4481,6 +4493,15 @@ "@backstage/integration" "^1.14.0" cross-fetch "^4.0.0" +"@backstage/plugin-catalog-backend-module-logs@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@backstage/plugin-catalog-backend-module-logs/-/plugin-catalog-backend-module-logs-0.0.2.tgz#28e51f4f7bc2afb54abef24c0b510dcfd98a5e7f" + integrity sha512-s2JQKEJ7EFBXd/2JSvW+/E0Dsezp7Mt8l6EDfTOpueuUlh7gBA41YwpMv7W9+o3ou1X19AtdFbOeEUdwrvHwKQ== + dependencies: + "@backstage/backend-plugin-api" "^0.8.0" + "@backstage/plugin-catalog-backend" "^1.25.0" + "@backstage/plugin-events-node" "^0.3.9" + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model@^0.1.21": version "0.1.21" resolved "https://registry.yarnpkg.com/@backstage/plugin-catalog-backend-module-scaffolder-entity-model/-/plugin-catalog-backend-module-scaffolder-entity-model-0.1.21.tgz#972af10299fdb34eead2f61e5d4b21e48a1c93f4" @@ -4721,6 +4742,37 @@ qs "^6.10.1" react-use "^17.2.4" +"@backstage/plugin-permission-backend-module-allow-all-policy@^0.1.20": + version "0.1.20" + resolved "https://registry.yarnpkg.com/@backstage/plugin-permission-backend-module-allow-all-policy/-/plugin-permission-backend-module-allow-all-policy-0.1.20.tgz#c94e502ddca08d5df08e228b6d22e6d70df3a3f7" + integrity sha512-R+qmgZi367rcAW04CPUAKDZYwxFR4qrVR/CYEKaQrwbKC55Hr2gMWoU6IBmJ1birIUg3AlVW5OvnsVLfEZut0A== + dependencies: + "@backstage/backend-plugin-api" "^0.8.0" + "@backstage/plugin-auth-node" "^0.5.0" + "@backstage/plugin-permission-common" "^0.8.1" + "@backstage/plugin-permission-node" "^0.8.1" + +"@backstage/plugin-permission-backend@^0.5.47": + version "0.5.47" + resolved "https://registry.yarnpkg.com/@backstage/plugin-permission-backend/-/plugin-permission-backend-0.5.47.tgz#a5e0e03989565de0a871b15c2068841512f16ec1" + integrity sha512-N6EedZajrRlVdqQs0FouPDghodBKC26khquD6YbftJBpBNaNBHQOV2eq6aVGFpM7UXn6q8NfNQQi0wLUeT0gaw== + dependencies: + "@backstage/backend-common" "^0.24.0" + "@backstage/backend-plugin-api" "^0.8.0" + "@backstage/config" "^1.2.0" + "@backstage/errors" "^1.2.4" + "@backstage/plugin-auth-node" "^0.5.0" + "@backstage/plugin-permission-common" "^0.8.1" + "@backstage/plugin-permission-node" "^0.8.1" + "@types/express" "*" + dataloader "^2.0.0" + express "^4.17.1" + express-promise-router "^4.1.0" + lodash "^4.17.21" + node-fetch "^2.7.0" + yn "^4.0.0" + zod "^3.22.4" + "@backstage/plugin-permission-common@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@backstage/plugin-permission-common/-/plugin-permission-common-0.8.1.tgz#797a2e9c26076cf52d69556acdd8e50bc02d522c" @@ -7319,6 +7371,17 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/agent@^2.0.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.2.2.tgz#967604918e62f620a648c7975461c9c9e74fc5d5" + integrity sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^10.0.1" + socks-proxy-agent "^8.0.3" + "@npmcli/arborist@^6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-6.5.0.tgz#ee24ecc56e4c387d78c3bce66918b386df6bd560" @@ -11811,7 +11874,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@*", "@types/react-dom@<18.0.0": +"@types/react-dom@*", "@types/react-dom@<18.0.0", "@types/react-dom@^17": version "17.0.25" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.25.tgz#e0e5b3571e1069625b3a3da2b279379aa33a0cb5" integrity sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA== @@ -12591,6 +12654,13 @@ agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" +agent-base@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + agentkeepalive@^4.1.4, agentkeepalive@^4.2.1: version "4.5.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" @@ -13804,6 +13874,24 @@ cacache@^17.0.0, cacache@^17.0.4, cacache@^17.1.3: tar "^6.1.11" unique-filename "^3.0.0" +cacache@^18.0.0: + version "18.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.4.tgz#4601d7578dadb59c66044e157d02a3314682d6a5" + integrity sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ== + dependencies: + "@npmcli/fs" "^3.1.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + cache-content-type@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" @@ -18703,6 +18791,14 @@ ioredis@^5.3.2: redis-parser "^3.0.0" standard-as-callback "^2.1.0" +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + ip-regex@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -19177,6 +19273,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -19793,6 +19894,11 @@ js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.6.1, js-yaml@^3.8.3: argparse "^1.0.7" esprima "^4.0.0" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -21130,6 +21236,24 @@ make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1, make-fetch-happen@^11.1.1: socks-proxy-agent "^7.0.0" ssri "^10.0.0" +make-fetch-happen@^13.0.0: + version "13.0.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36" + integrity sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA== + dependencies: + "@npmcli/agent" "^2.0.0" + cacache "^18.0.0" + http-cache-semantics "^4.1.1" + is-lambda "^1.0.1" + minipass "^7.0.2" + minipass-fetch "^3.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + proc-log "^4.2.0" + promise-retry "^2.0.1" + ssri "^10.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -21931,6 +22055,13 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-2.0.1.tgz#1621bc77e12258a12c60d34e2276ec5c20680863" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== + dependencies: + minipass "^7.0.3" + minipass-fetch@^2.0.3: version "2.1.2" resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" @@ -22004,7 +22135,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== -minipass@^7.1.2: +minipass@^7.0.2, minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== @@ -22345,6 +22476,22 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== +node-gyp@^10.0.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.2.0.tgz#80101c4aa4f7ab225f13fcc8daaaac4eb1a8dd86" + integrity sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + glob "^10.3.10" + graceful-fs "^4.2.6" + make-fetch-happen "^13.0.0" + nopt "^7.0.0" + proc-log "^4.1.0" + semver "^7.3.5" + tar "^6.2.1" + which "^4.0.0" + node-gyp@^9.0.0, node-gyp@^9.4.0: version "9.4.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" @@ -22815,6 +22962,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth@0.10.x: + version "0.10.0" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" + integrity sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q== + oauth@0.9.x: version "0.9.15" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" @@ -23551,6 +23703,17 @@ passport-oauth2@1.x.x, passport-oauth2@^1.1.2, passport-oauth2@^1.4.0, passport- uid2 "0.0.x" utils-merge "1.x.x" +passport-oauth2@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8" + integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== + dependencies: + base64url "3.x.x" + oauth "0.10.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + passport-oauth@1.0.0, passport-oauth@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-oauth/-/passport-oauth-1.0.0.tgz#90aff63387540f02089af28cdad39ea7f80d77df" @@ -24327,6 +24490,11 @@ proc-log@^3.0.0: resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== +proc-log@^4.1.0, proc-log@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034" + integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -26272,6 +26440,15 @@ socks-proxy-agent@^7.0.0: debug "^4.3.3" socks "^2.6.2" +socks-proxy-agent@^8.0.3: + version "8.0.4" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz#9071dca17af95f483300316f4b063578fa0db08c" + integrity sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== + dependencies: + agent-base "^7.1.1" + debug "^4.3.4" + socks "^2.8.3" + socks@^2.6.2: version "2.7.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" @@ -26280,6 +26457,14 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" +socks@^2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + sonic-boom@^0.7.5: version "0.7.7" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-0.7.7.tgz#d921de887428208bfa07b0ae32c278de043f350a" @@ -26451,7 +26636,7 @@ split@^1.0.1: dependencies: through "2" -sprintf-js@^1.1.2: +sprintf-js@^1.1.2, sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== @@ -27185,6 +27370,18 @@ tar@^6.1.11, tar@^6.1.12, tar@^6.1.13, tar@^6.1.15, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + tarn@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" @@ -28734,6 +28931,13 @@ which@^3.0.0, which@^3.0.1: dependencies: isexe "^2.0.0" +which@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" + integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== + dependencies: + isexe "^3.1.1" + wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"