From 762d9e7a41e783d20fca3dea040540b6a0930add Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:22:04 +1000 Subject: [PATCH 1/6] chore: Update GitHub authentication configuration across all environments - Update GitHub authentication provider settings in both development and production environments - Implement GitHub authentication provider in the backend module - Update App.tsx file to include additional GitHub authentication provider details --- app-config.example.yaml | 17 ++++ app-config.production.yaml | 10 +++ packages/app/src/App.tsx | 17 +++- packages/backend/package.json | 2 +- packages/backend/src/index.ts | 2 +- yarn.lock | 164 +++++++++++++++++++++++++++++++++- 6 files changed, 208 insertions(+), 4 deletions(-) diff --git a/app-config.example.yaml b/app-config.example.yaml index 6bfe5e6..0e4eda1 100644 --- a/app-config.example.yaml +++ b/app-config.example.yaml @@ -20,7 +20,24 @@ catalog: target: ../../examples/all.yaml - type: file target: ../../examples/acme-corp.yaml +## Uncomment the following to enable Plausible analytics in development mode # plausible: # enabled: true # dataDomain: backstage.localhost # sourceUrl: http://plausible.localhost/js/script.js + +## Uncomment the following to enable GitHub authentication in development mode +# auth: +# environment: development +# providers: +# guest: +# dangerouslyAllowOutsideDevelopment: true +# github: +# development: +# clientId: ${GITHUB_CLIENT_ID} +# clientSecret: ${GITHUB_CLIENT_SECRET} +# signIn: +# resolvers: +# # Matches the GitHub username with the Backstage user entity name. +# # See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers. +# - resolver: usernameMatchingUserEntityName diff --git a/app-config.production.yaml b/app-config.production.yaml index faa6ac5..88d2c1c 100644 --- a/app-config.production.yaml +++ b/app-config.production.yaml @@ -17,9 +17,19 @@ backend: credentials: true auth: + environment: production providers: guest: dangerouslyAllowOutsideDevelopment: true + github: + production: + clientId: ${GITHUB_CLIENT_ID} + clientSecret: ${GITHUB_CLIENT_SECRET} + signIn: + resolvers: + # Matches the GitHub username with the Backstage user entity name. + # See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers. + - resolver: usernameMatchingUserEntityName catalog: # Overrides the default list locations from app-config.yaml as these contain example data. # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index bff2d1a..bdb55a0 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -42,6 +42,7 @@ import { backstageTheme } from './theme'; import { HomePage } from './components/home/HomePage'; import CssBaseline from '@mui/material/CssBaseline'; import { PlausibleAnalytics } from '@internal/backstage-plugin-plausible'; +import { githubAuthApiRef } from '@backstage/core-plugin-api'; const app = createApp({ apis, @@ -63,7 +64,21 @@ const app = createApp({ }); }, components: { - SignInPage: props => , + SignInPage: props => ( + + ), }, themes: [ { diff --git a/packages/backend/package.json b/packages/backend/package.json index 99dbb4b..0d4655e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -22,7 +22,7 @@ "@backstage/config": "^1.2.0", "@backstage/plugin-app-backend": "^0.3.70", "@backstage/plugin-auth-backend": "^0.22.8", - "@backstage/plugin-auth-backend-module-github-provider": "^0.1.18", + "@backstage/plugin-auth-backend-module-github-provider": "^0.1.19", "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.7", "@backstage/plugin-auth-node": "^0.4.16", "@backstage/plugin-catalog-backend": "^1.23.2", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 1c442a8..ee8cb83 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -19,7 +19,7 @@ backend.add(import('@backstage/plugin-techdocs-backend/alpha')); 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 +backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); // catalog plugin backend.add(import('@backstage/plugin-catalog-backend/alpha')); diff --git a/yarn.lock b/yarn.lock index 9d6428c..1145cc4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2884,6 +2884,82 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-common@npm:^0.23.3": + version: 0.23.3 + resolution: "@backstage/backend-common@npm:0.23.3" + dependencies: + "@aws-sdk/abort-controller": "npm:^3.347.0" + "@aws-sdk/client-codecommit": "npm:^3.350.0" + "@aws-sdk/client-s3": "npm:^3.350.0" + "@aws-sdk/credential-providers": "npm:^3.350.0" + "@aws-sdk/types": "npm:^3.347.0" + "@backstage/backend-dev-utils": "npm:^0.1.4" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/cli-common": "npm:^0.1.14" + "@backstage/config": "npm:^1.2.0" + "@backstage/config-loader": "npm:^1.8.1" + "@backstage/errors": "npm:^1.2.4" + "@backstage/integration": "npm:^1.13.0" + "@backstage/integration-aws-node": "npm:^0.1.12" + "@backstage/plugin-auth-node": "npm:^0.4.17" + "@backstage/types": "npm:^1.1.1" + "@google-cloud/storage": "npm:^7.0.0" + "@keyv/memcache": "npm:^1.3.5" + "@keyv/redis": "npm:^2.5.3" + "@kubernetes/client-node": "npm:0.20.0" + "@manypkg/get-packages": "npm:^1.1.3" + "@octokit/rest": "npm:^19.0.3" + "@types/cors": "npm:^2.8.6" + "@types/dockerode": "npm:^3.3.0" + "@types/express": "npm:^4.17.6" + "@types/luxon": "npm:^3.0.0" + "@types/webpack-env": "npm:^1.15.2" + archiver: "npm:^6.0.0" + base64-stream: "npm:^1.0.0" + compression: "npm:^1.7.4" + concat-stream: "npm:^2.0.0" + cors: "npm:^2.8.5" + dockerode: "npm:^4.0.0" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + fs-extra: "npm:^11.2.0" + git-url-parse: "npm:^14.0.0" + helmet: "npm:^6.0.0" + isomorphic-git: "npm:^1.23.0" + jose: "npm:^5.0.0" + keyv: "npm:^4.5.2" + knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" + logform: "npm:^2.3.2" + luxon: "npm:^3.0.0" + minimatch: "npm:^9.0.0" + minimist: "npm:^1.2.5" + morgan: "npm:^1.10.0" + mysql2: "npm:^3.0.0" + node-fetch: "npm:^2.6.7" + node-forge: "npm:^1.3.1" + p-limit: "npm:^3.1.0" + path-to-regexp: "npm:^6.2.1" + pg: "npm:^8.11.3" + raw-body: "npm:^2.4.1" + selfsigned: "npm:^2.0.0" + stoppable: "npm:^1.1.0" + tar: "npm:^6.1.12" + triple-beam: "npm:^1.4.1" + uuid: "npm:^9.0.0" + winston: "npm:^3.2.1" + winston-transport: "npm:^4.5.0" + yauzl: "npm:^3.0.0" + yn: "npm:^4.0.0" + peerDependencies: + pg-connection-string: ^2.3.0 + peerDependenciesMeta: + pg-connection-string: + optional: true + checksum: 10c0/8bc8d0564719f5c7d734a6b9388b099630ee8ea76ab815f04ef0cfe9541714883c0a0b9a0d8b559c3ba3830b855773b2ac9fce95d959006f67f893c940454370 + languageName: node + linkType: hard + "@backstage/backend-defaults@npm:^0.3.3": version: 0.3.3 resolution: "@backstage/backend-defaults@npm:0.3.3" @@ -3005,6 +3081,25 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-plugin-api@npm:^0.7.0": + version: 0.7.0 + resolution: "@backstage/backend-plugin-api@npm:0.7.0" + dependencies: + "@backstage/cli-common": "npm:^0.1.14" + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/plugin-auth-node": "npm:^0.4.17" + "@backstage/plugin-permission-common": "npm:^0.8.0" + "@backstage/types": "npm:^1.1.1" + "@types/express": "npm:^4.17.6" + "@types/luxon": "npm:^3.0.0" + express: "npm:^4.17.1" + knex: "npm:^3.0.0" + luxon: "npm:^3.0.0" + checksum: 10c0/b8620d7b3683b53b3b3fa094a73a2391e18d7df8c4df713882ff8d2c1daf0278536fde8611e86bed2eba95f41b24e15c91109b2c556aa49b2bafb9ef4c74e468 + languageName: node + linkType: hard + "@backstage/backend-tasks@npm:^0.5.26": version: 0.5.26 resolution: "@backstage/backend-tasks@npm:0.5.26" @@ -3478,6 +3573,23 @@ __metadata: languageName: node linkType: hard +"@backstage/integration@npm:^1.13.0": + version: 1.13.0 + resolution: "@backstage/integration@npm:1.13.0" + dependencies: + "@azure/identity": "npm:^4.0.0" + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@octokit/auth-app": "npm:^4.0.0" + "@octokit/rest": "npm:^19.0.3" + cross-fetch: "npm:^4.0.0" + git-url-parse: "npm:^14.0.0" + lodash: "npm:^4.17.21" + luxon: "npm:^3.0.0" + checksum: 10c0/ed41933c707e6eaf895baea14d44fd6690e072d25f42e50a8fc32267633030046e7a6399ada1e3d6b7d2002c08c0fa8575580b7f07b0c3e47df5963b0d519d50 + languageName: node + linkType: hard + "@backstage/plugin-api-docs@npm:^0.11.6": version: 0.11.6 resolution: "@backstage/plugin-api-docs@npm:0.11.6" @@ -3647,6 +3759,17 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-auth-backend-module-github-provider@npm:^0.1.19": + version: 0.1.19 + resolution: "@backstage/plugin-auth-backend-module-github-provider@npm:0.1.19" + dependencies: + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/plugin-auth-node": "npm:^0.4.17" + passport-github2: "npm:^0.1.12" + checksum: 10c0/5df71eef3ca2e0d524c88350a51908111669f9b3f1126de8264f6c4bb1609582c628cd8bc86d4042763ffb7b1194d7f83fc846caa9c54bf0bba8d9815f7ef8ce + languageName: node + linkType: hard + "@backstage/plugin-auth-backend-module-gitlab-provider@npm:^0.1.18": version: 0.1.18 resolution: "@backstage/plugin-auth-backend-module-gitlab-provider@npm:0.1.18" @@ -3856,6 +3979,31 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-auth-node@npm:^0.4.17": + version: 0.4.17 + resolution: "@backstage/plugin-auth-node@npm:0.4.17" + dependencies: + "@backstage/backend-common": "npm:^0.23.3" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/catalog-client": "npm:^1.6.5" + "@backstage/catalog-model": "npm:^1.5.0" + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/types": "npm:^1.1.1" + "@types/express": "npm:*" + "@types/passport": "npm:^1.0.3" + express: "npm:^4.17.1" + jose: "npm:^5.0.0" + lodash: "npm:^4.17.21" + node-fetch: "npm:^2.6.7" + passport: "npm:^0.7.0" + winston: "npm:^3.2.1" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.21.4" + checksum: 10c0/b4b081afc89fa24deda831787309180657ba49e93310cd03adb4931f7a7cd5d03d2d74b95f247fc22cf8f0b95b435c1942377c021eebc7534758155cad04686b + languageName: node + linkType: hard + "@backstage/plugin-auth-react@npm:^0.1.3": version: 0.1.3 resolution: "@backstage/plugin-auth-react@npm:0.1.3" @@ -4236,6 +4384,20 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-permission-common@npm:^0.8.0": + version: 0.8.0 + resolution: "@backstage/plugin-permission-common@npm:0.8.0" + dependencies: + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/types": "npm:^1.1.1" + cross-fetch: "npm:^4.0.0" + uuid: "npm:^9.0.0" + zod: "npm:^3.22.4" + checksum: 10c0/976516a308ca9fd3d626be3e3c14d8d171f35fd27badc87c3178de49f5603cd71ffb6e0a750fef6deeafeeab07ea0a66bae930068b5315949e522f2b517e7e82 + languageName: node + linkType: hard + "@backstage/plugin-permission-node@npm:^0.7.32": version: 0.7.32 resolution: "@backstage/plugin-permission-node@npm:0.7.32" @@ -13960,7 +14122,7 @@ __metadata: "@backstage/config": "npm:^1.2.0" "@backstage/plugin-app-backend": "npm:^0.3.70" "@backstage/plugin-auth-backend": "npm:^0.22.8" - "@backstage/plugin-auth-backend-module-github-provider": "npm:^0.1.18" + "@backstage/plugin-auth-backend-module-github-provider": "npm:^0.1.19" "@backstage/plugin-auth-backend-module-guest-provider": "npm:^0.1.7" "@backstage/plugin-auth-node": "npm:^0.4.16" "@backstage/plugin-catalog-backend": "npm:^1.23.2" From c79257a499bd5b84be01eac0e0862ebb4cf1e50b Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:51:01 +1000 Subject: [PATCH 2/6] feat: Update GitHub authentication implementation across backend packages - Introduce github authentication in backend - Add new dependencies for backend functionality - Implement github authentication logic in `auth.ts` --- packages/backend/package.json | 2 + packages/backend/src/auth.ts | 72 +++++++++++++++++++++++++++++++++++ packages/backend/src/index.ts | 3 +- yarn.lock | 2 + 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/auth.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index 0d4655e..b5c0ba6 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -18,7 +18,9 @@ "dependencies": { "@backstage/backend-common": "^0.23.2", "@backstage/backend-defaults": "^0.3.3", + "@backstage/backend-plugin-api": "^0.7.0", "@backstage/backend-tasks": "^0.5.26", + "@backstage/catalog-model": "^1.5.0", "@backstage/config": "^1.2.0", "@backstage/plugin-app-backend": "^0.3.70", "@backstage/plugin-auth-backend": "^0.22.8", diff --git a/packages/backend/src/auth.ts b/packages/backend/src/auth.ts new file mode 100644 index 0000000..e5bcfc5 --- /dev/null +++ b/packages/backend/src/auth.ts @@ -0,0 +1,72 @@ +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-node'; +import { + stringifyEntityRef, + DEFAULT_NAMESPACE, +} from '@backstage/catalog-model'; +import { githubAuthenticator } from '@backstage/plugin-auth-backend-module-github-provider'; +import { + authProvidersExtensionPoint, + BackstageSignInResult, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; + +export const githubAuth = createBackendModule({ + pluginId: 'auth', + moduleId: 'github', + register(reg) { + reg.registerInit({ + deps: { providers: authProvidersExtensionPoint }, + async init({ providers }) { + providers.registerProvider({ + // This ID must match the actual provider config, e.g. addressing + // auth.providers.github means that this must be "github". + providerId: 'github', + // Use createProxyAuthProviderFactory instead if it's one of the proxy + // based providers rather than an OAuth based one + factory: createOAuthProviderFactory({ + authenticator: githubAuthenticator, + async signInResolver( + { profile }, + ctx, + ): Promise { + if (!profile.email) { + throw new Error( + 'Login failed, user profile does not contain an email', + ); + } + + // Split the email into the local part and the domain. + const [localPart, domain] = profile.email.split('@'); + + // Next we verify the email domain. It is recommended to include this + // kind of check if you don't look up the user in an external service. + if (domain !== 'johnnyhuy.com') { + throw new Error( + `Login failed, '${profile.email}' does not belong to the expected domain`, + ); + } + + // By using `stringifyEntityRef` we ensure that the reference is formatted correctly + const userEntity = stringifyEntityRef({ + kind: 'User', + name: localPart, + namespace: DEFAULT_NAMESPACE, + }); + + return ctx.issueToken({ + claims: { + sub: userEntity, + ent: [userEntity], + }, + }); + }, + }), + }); + }, + }); + }, +}); diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index ee8cb83..8424914 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -7,6 +7,7 @@ */ import { createBackend } from '@backstage/backend-defaults'; +import { githubAuth } from './auth'; const backend = createBackend(); @@ -19,7 +20,7 @@ backend.add(import('@backstage/plugin-techdocs-backend/alpha')); 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')); -backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); +backend.add(githubAuth); // catalog plugin backend.add(import('@backstage/plugin-catalog-backend/alpha')); diff --git a/yarn.lock b/yarn.lock index 1145cc4..f030d85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14117,7 +14117,9 @@ __metadata: dependencies: "@backstage/backend-common": "npm:^0.23.2" "@backstage/backend-defaults": "npm:^0.3.3" + "@backstage/backend-plugin-api": "npm:^0.7.0" "@backstage/backend-tasks": "npm:^0.5.26" + "@backstage/catalog-model": "npm:^1.5.0" "@backstage/cli": "npm:^0.26.10" "@backstage/config": "npm:^1.2.0" "@backstage/plugin-app-backend": "npm:^0.3.70" From d5c93744ae7231f955fd93b45b3b17151f6c5565 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Sat, 27 Jul 2024 15:49:09 +1000 Subject: [PATCH 3/6] Implement authentication strategies and configurations - Documented authentication strategies in `auth.md` - Updated `signInResolver` in `auth.ts` to fetch emails and organizations from GitHub API --- docs/auth.md | 56 ++++++++++++++++++++++++++++++++++++ packages/backend/src/auth.ts | 42 ++++++++++++++++++++------- 2 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 docs/auth.md diff --git a/docs/auth.md b/docs/auth.md new file mode 100644 index 0000000..dff7db7 --- /dev/null +++ b/docs/auth.md @@ -0,0 +1,56 @@ +# Auth + +Backstage provides a number of authentication strategies out of the box. These can be configured in the `app-config.yaml` file. + +Refer to the [Backstage documentation](https://backstage.io/docs/auth/) for more information. + +Since the [new backend](https://backstage.io/docs/backend-system/building-backends/index/) was introduced, authentication plugins have been abstracted away into a separate package. To reference the new authentication plugins, use the following import: + +```typescript title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +``` + +Refer to [Migrating to New Auth Services](https://backstage.io/docs/tutorials/auth-service-migration#migrating-the-backend) documentation for more information. + +## This Project + +This project will use GitHub and Guest authentication strategies. + +### GitHub + +To enable GitHub authentication, add the following configuration to the `app-config.yaml` file: + +```diff +auth: ++ environment: development + providers: ++ github: ++ development: ++ clientId: ++ clientSecret: ++ additionalScopes: ++ - user:email ++ - read:org ++ signIn: ++ resolvers: ++ - resolver: emailMatchingUserEntityProfileEmail +``` + +Replace `` and `` with the values from your GitHub OAuth application. + +See [Backstage documentation](https://backstage.io/docs/auth/github/provider#resolvers) for more resolvers. + +Note that the `environment` key is set to `development` to allow for the use of GitHub authentication in a local development environment. This can be changed to `production` for a production environment. + +### Guest + +To enable Guest authentication, add the following configuration to the `app-config.yaml` file: + +```diff +auth: + providers: ++ guest: ++ dangerouslyAllowOutsideDevelopment: true +``` + +This will allow users to access the Backstage application without authenticating. diff --git a/packages/backend/src/auth.ts b/packages/backend/src/auth.ts index e5bcfc5..1998aea 100644 --- a/packages/backend/src/auth.ts +++ b/packages/backend/src/auth.ts @@ -1,8 +1,4 @@ import { createBackendModule } from '@backstage/backend-plugin-api'; -import { - EntityProvider, - EntityProviderConnection, -} from '@backstage/plugin-catalog-node'; import { stringifyEntityRef, DEFAULT_NAMESPACE, @@ -30,14 +26,40 @@ export const githubAuth = createBackendModule({ factory: createOAuthProviderFactory({ authenticator: githubAuthenticator, async signInResolver( - { profile }, + fullProfile, ctx, ): Promise { - if (!profile.email) { - throw new Error( - 'Login failed, user profile does not contain an email', - ); - } + const { profile } = fullProfile; + // if (!profile.email) { + // throw new Error( + // 'Login failed, user profile does not contain an email', + // ); + // } + + const emailResponse = await fetch( + 'https://api.github.com/user/emails', + { + headers: { + Authorization: `Bearer ${fullProfile.result.session.accessToken}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + const emails = await emailResponse.json(); + + // Fetch user's organizations + const orgsResponse = await fetch( + 'https://api.github.com/user/orgs', + { + headers: { + Authorization: `Bearer ${fullProfile.result.session.accessToken}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + const orgs = await orgsResponse.json(); // Split the email into the local part and the domain. const [localPart, domain] = profile.email.split('@'); From a866b77dd37ddcac7c2a2e298718ba7447c24a5b Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Sat, 27 Jul 2024 16:05:22 +1000 Subject: [PATCH 4/6] Update user authentication process - Refactor authentication process for GitHub login - Update signInResolver to handle username extraction and validation properly - Simplify user creation process by using username from authentication result --- docs/auth.md | 60 ++++++++++++++++++++++++++++++++++-- packages/backend/src/auth.ts | 48 +++-------------------------- 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/docs/auth.md b/docs/auth.md index dff7db7..e86905b 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -28,9 +28,6 @@ auth: + development: + clientId: + clientSecret: -+ additionalScopes: -+ - user:email -+ - read:org + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail @@ -42,6 +39,63 @@ See [Backstage documentation](https://backstage.io/docs/auth/github/provider#res Note that the `environment` key is set to `development` to allow for the use of GitHub authentication in a local development environment. This can be changed to `production` for a production environment. +#### Additional Scopes + +The `additionalScopes` key is used to request additional permissions from the user. In this case, we are requesting the `user:email` and `read:org` scopes. + +```diff +auth: ++ environment: development + providers: ++ github: ++ development: ++ clientId: ++ clientSecret: ++ additionalScopes: ++ - user:email ++ - read:org ++ signIn: ++ resolvers: ++ - resolver: emailMatchingUserEntityProfileEmail +``` + +We can then query the users email and organisations using the fetch method: + +```typescript +createOAuthProviderFactory({ + authenticator: githubAuthenticator, + async signInResolver( + {profile, result}, + ctx, + ): Promise { + const emailResponse = await fetch( + 'https://api.github.com/user/emails', + { + headers: { + Authorization: `Bearer ${result.session.accessToken}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + const emails = await emailResponse.json(); + + // Fetch user's organizations + const orgsResponse = await fetch( + 'https://api.github.com/user/orgs', + { + headers: { + Authorization: `Bearer ${result.session.accessToken}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + const orgs = await orgsResponse.json(); + }) +}) +``` + ### Guest To enable Guest authentication, add the following configuration to the `app-config.yaml` file: diff --git a/packages/backend/src/auth.ts b/packages/backend/src/auth.ts index 1998aea..1bd5535 100644 --- a/packages/backend/src/auth.ts +++ b/packages/backend/src/auth.ts @@ -26,56 +26,16 @@ export const githubAuth = createBackendModule({ factory: createOAuthProviderFactory({ authenticator: githubAuthenticator, async signInResolver( - fullProfile, + { result }, ctx, ): Promise { - const { profile } = fullProfile; - // if (!profile.email) { - // throw new Error( - // 'Login failed, user profile does not contain an email', - // ); - // } - - const emailResponse = await fetch( - 'https://api.github.com/user/emails', - { - headers: { - Authorization: `Bearer ${fullProfile.result.session.accessToken}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - }, - ); - const emails = await emailResponse.json(); - - // Fetch user's organizations - const orgsResponse = await fetch( - 'https://api.github.com/user/orgs', - { - headers: { - Authorization: `Bearer ${fullProfile.result.session.accessToken}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - }, - ); - const orgs = await orgsResponse.json(); - - // Split the email into the local part and the domain. - const [localPart, domain] = profile.email.split('@'); - - // Next we verify the email domain. It is recommended to include this - // kind of check if you don't look up the user in an external service. - if (domain !== 'johnnyhuy.com') { - throw new Error( - `Login failed, '${profile.email}' does not belong to the expected domain`, - ); + if (!result.fullProfile.username) { + throw new Error('Username not found in profile'); } - // By using `stringifyEntityRef` we ensure that the reference is formatted correctly const userEntity = stringifyEntityRef({ kind: 'User', - name: localPart, + name: result.fullProfile.username, namespace: DEFAULT_NAMESPACE, }); From 651acf72d5dbbf0ef02fe66cca37331856d73ae1 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Sun, 28 Jul 2024 23:40:13 +1000 Subject: [PATCH 5/6] Improve testing for GitHub authentication in backend. - Added necessary devDependencies to package.json - Created auth.test.ts for testing githubAuth function - Implemented tests for starting and completing GitHub auth flow --- packages/backend/package.json | 7 +- packages/backend/src/auth.test.ts | 166 ++++++ yarn.lock | 926 +++++++++++++++++++++++++++++- 3 files changed, 1073 insertions(+), 26 deletions(-) create mode 100644 packages/backend/src/auth.test.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index b5c0ba6..61c656a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -48,11 +48,16 @@ "winston": "^3.2.1" }, "devDependencies": { + "@backstage/backend-test-utils": "^0.4.4", "@backstage/cli": "^0.26.10", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", - "@types/luxon": "^2.0.4" + "@types/luxon": "^2.0.4", + "@types/supertest": "^6.0.2", + "jose": "^5.6.3", + "msw": "^2.3.4", + "supertest": "^7.0.0" }, "files": [ "dist" diff --git a/packages/backend/src/auth.test.ts b/packages/backend/src/auth.test.ts new file mode 100644 index 0000000..7296bc7 --- /dev/null +++ b/packages/backend/src/auth.test.ts @@ -0,0 +1,166 @@ +/* eslint-disable @typescript-eslint/no-shadow */ +import request from 'supertest'; +import { decodeOAuthState } from '@backstage/plugin-auth-node'; +import { setupServer } from 'msw/node'; +import { http, HttpResponse } from 'msw'; +import { + mockServices, + registerMswTestHooks, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { Server } from 'http'; +import { githubAuth } from './auth'; + +let accessToken: string; + +const githubApiUrl = 'https://api.github.com'; +const githubAuthUrl = 'https://github.com/login/oauth'; + +const handlers = [ + http.get(`${githubAuthUrl}/authorize`, ({ request }) => { + const url = new URL(request.url); + const callbackUrl = new URL(url.searchParams.get('redirect_uri')!); + callbackUrl.searchParams.set('code', 'github_auth_code'); + callbackUrl.searchParams.set('state', url.searchParams.get('state')!); + return HttpResponse.json(null, { + status: 302, + headers: { + location: callbackUrl.toString(), + }, + }); + }), + http.post(`${githubAuthUrl}/access_token`, () => { + accessToken = 'github_access_token'; + return HttpResponse.json({ + access_token: accessToken, + token_type: 'bearer', + scope: 'read:user', + }); + }), + http.get(`${githubApiUrl}/user`, ({ request }) => { + if (request.headers.get('Authorization') === `token ${accessToken}`) { + return HttpResponse.json({ + login: 'octocat', + name: 'Octocat', + email: 'octocat@github.com', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + }); + } + return new HttpResponse(null, { status: 401 }); + }), +]; + +const mswServer = setupServer(...handlers); +registerMswTestHooks(mswServer); + +describe('githubAuth', () => { + let backstageServer: Server; + let appUrl: string; + + beforeAll(async () => { + mswServer.listen(); + const backend = await startTestBackend({ + features: [ + githubAuth, + import('@backstage/plugin-auth-backend'), + mockServices.rootConfig.factory({ + data: { + app: { baseUrl: 'http://localhost' }, + auth: { + providers: { + github: { + development: { + clientId: 'github-client-id', + clientSecret: 'github-client-secret', + }, + }, + }, + }, + }, + }), + ], + }); + + backstageServer = backend.server; + const port = backend.server.port(); + appUrl = `http://localhost:${port}`; + }); + + beforeEach(async () => { + jest.clearAllMocks(); + }); + + afterEach(() => { + mswServer.resetHandlers(); + }); + + afterAll(() => { + backstageServer.close(); + mswServer.close(); + }); + + it('should start the GitHub auth flow', async () => { + // Arrange + const agent = request.agent(backstageServer); + + // Act + const startResponse = await agent.get( + '/api/auth/github/start?env=development', + ); + const startUrl = new URL(startResponse.get('location')!); + const expected = Object.fromEntries(startUrl.searchParams); + const state = decodeOAuthState(startUrl.searchParams.get('state')!); + + // Assert + expect(startResponse.status).toEqual(302); + expect(startUrl.origin).toBe('https://github.com'); + expect(startUrl.pathname).toBe('/login/oauth/authorize'); + expect(expected).toEqual({ + client_id: 'github-client-id', + redirect_uri: `${appUrl}/api/auth/github/handler/frame`, + response_type: 'code', + scope: 'read:user', + state: expect.any(String), + }); + expect(state.env).toEqual('development'); + expect(state.nonce).toBeDefined(); + expect(state.scope).toEqual('read:user'); + }); + + it('should complete the GitHub auth flow', async () => { + const agent = request.agent(backstageServer); + + // Start the auth flow + const startResponse = await agent.get( + '/api/auth/github/start?env=development', + ); + expect(startResponse.status).toBe(302); + const test = await agent.get( + '/api/auth/github/handler/frame?code=github_auth_code&state=6e6f6e63653d70595572584f37374363597a786f57414c2532465042325125334425334426656e763d646576656c6f706d656e742673636f70653d7265616425334175736572', + ); + expect(test.text).toContain('test'); + expect(startResponse.status).toBe(302); + + const startUrl = new URL(startResponse.get('location')!); + const state = startUrl.searchParams.get('state')!; + const decodedState = decodeOAuthState(state); + const nonce = decodedState.nonce; + + // Set the nonce cookie + agent.jar.setCookie(`github-nonce=${nonce}; Path=/; HttpOnly`); + + // Simulate the OAuth callback + const callbackUrl = new URL(`${appUrl}/api/auth/github/handler/frame`); + callbackUrl.searchParams.set('code', 'github_auth_code'); + callbackUrl.searchParams.set('state', state); + + const handlerResponse = await agent.get( + callbackUrl.href.replace(callbackUrl.origin, ''), + ); + expect(handlerResponse.status).toBe(200); + + expect(handlerResponse.text).toContain( + encodeURIComponent(`"accessToken":"${accessToken}"`), + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index f030d85..0144ca1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2808,6 +2808,52 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-app-api@npm:^0.8.0": + version: 0.8.0 + resolution: "@backstage/backend-app-api@npm:0.8.0" + dependencies: + "@backstage/backend-common": "npm:^0.23.3" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/backend-tasks": "npm:^0.5.27" + "@backstage/cli-common": "npm:^0.1.14" + "@backstage/cli-node": "npm:^0.2.7" + "@backstage/config": "npm:^1.2.0" + "@backstage/config-loader": "npm:^1.8.1" + "@backstage/errors": "npm:^1.2.4" + "@backstage/plugin-auth-node": "npm:^0.4.17" + "@backstage/plugin-permission-node": "npm:^0.8.0" + "@backstage/types": "npm:^1.1.1" + "@manypkg/get-packages": "npm:^1.1.3" + "@types/cors": "npm:^2.8.6" + "@types/express": "npm:^4.17.6" + compression: "npm:^1.7.4" + cookie: "npm:^0.6.0" + cors: "npm:^2.8.5" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + fs-extra: "npm:^11.2.0" + helmet: "npm:^6.0.0" + jose: "npm:^5.0.0" + knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" + logform: "npm:^2.3.2" + luxon: "npm:^3.0.0" + minimatch: "npm:^9.0.0" + minimist: "npm:^1.2.5" + morgan: "npm:^1.10.0" + node-fetch: "npm:^2.6.7" + node-forge: "npm:^1.3.1" + path-to-regexp: "npm:^6.2.1" + selfsigned: "npm:^2.0.0" + stoppable: "npm:^1.1.0" + triple-beam: "npm:^1.4.1" + uuid: "npm:^9.0.0" + winston: "npm:^3.2.1" + winston-transport: "npm:^4.5.0" + checksum: 10c0/ff52ba3c2d1c7b14b0cdc34e930cfa593f558d064490b6062d6acbe3ca2e5eacf9542e4a848996149f99b587119ad59cd9a5d9c3be1afbd2621dc329246c230a + languageName: node + linkType: hard + "@backstage/backend-common@npm:^0.23.2": version: 0.23.2 resolution: "@backstage/backend-common@npm:0.23.2" @@ -3036,6 +3082,82 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-defaults@npm:^0.4.0": + version: 0.4.1 + resolution: "@backstage/backend-defaults@npm:0.4.1" + dependencies: + "@aws-sdk/abort-controller": "npm:^3.347.0" + "@aws-sdk/client-codecommit": "npm:^3.350.0" + "@aws-sdk/client-s3": "npm:^3.350.0" + "@aws-sdk/credential-providers": "npm:^3.350.0" + "@aws-sdk/types": "npm:^3.347.0" + "@backstage/backend-app-api": "npm:^0.8.0" + "@backstage/backend-common": "npm:^0.23.3" + "@backstage/backend-dev-utils": "npm:^0.1.4" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/cli-common": "npm:^0.1.14" + "@backstage/config": "npm:^1.2.0" + "@backstage/config-loader": "npm:^1.8.1" + "@backstage/errors": "npm:^1.2.4" + "@backstage/integration": "npm:^1.13.0" + "@backstage/integration-aws-node": "npm:^0.1.12" + "@backstage/plugin-auth-node": "npm:^0.4.17" + "@backstage/plugin-events-node": "npm:^0.3.8" + "@backstage/plugin-permission-node": "npm:^0.8.0" + "@backstage/types": "npm:^1.1.1" + "@google-cloud/storage": "npm:^7.0.0" + "@keyv/memcache": "npm:^1.3.5" + "@keyv/redis": "npm:^2.5.3" + "@manypkg/get-packages": "npm:^1.1.3" + "@octokit/rest": "npm:^19.0.3" + "@opentelemetry/api": "npm:^1.3.0" + "@types/cors": "npm:^2.8.6" + "@types/express": "npm:^4.17.6" + archiver: "npm:^6.0.0" + base64-stream: "npm:^1.0.0" + better-sqlite3: "npm:^11.0.0" + compression: "npm:^1.7.4" + concat-stream: "npm:^2.0.0" + cookie: "npm:^0.6.0" + cors: "npm:^2.8.5" + cron: "npm:^3.0.0" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + fs-extra: "npm:^11.2.0" + git-url-parse: "npm:^14.0.0" + helmet: "npm:^6.0.0" + isomorphic-git: "npm:^1.23.0" + jose: "npm:^5.0.0" + keyv: "npm:^4.5.2" + knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" + logform: "npm:^2.3.2" + luxon: "npm:^3.0.0" + minimatch: "npm:^9.0.0" + minimist: "npm:^1.2.5" + morgan: "npm:^1.10.0" + mysql2: "npm:^3.0.0" + node-fetch: "npm:^2.6.7" + node-forge: "npm:^1.3.1" + p-limit: "npm:^3.1.0" + path-to-regexp: "npm:^6.2.1" + pg: "npm:^8.11.3" + pg-connection-string: "npm:^2.3.0" + raw-body: "npm:^2.4.1" + selfsigned: "npm:^2.0.0" + stoppable: "npm:^1.1.0" + tar: "npm:^6.1.12" + triple-beam: "npm:^1.4.1" + uuid: "npm:^9.0.0" + winston: "npm:^3.2.1" + winston-transport: "npm:^4.5.0" + yauzl: "npm:^3.0.0" + yn: "npm:^4.0.0" + zod: "npm:^3.22.4" + checksum: 10c0/351cb03492865c1d6669c0c4a5d107d4f86e06179c65108913052c94a45681d3389935cebdffc7f546d5bca90ca3f86886f68dfd15c3684f42563e06b19560ee + languageName: node + linkType: hard + "@backstage/backend-dev-utils@npm:^0.1.4": version: 0.1.4 resolution: "@backstage/backend-dev-utils@npm:0.1.4" @@ -3121,6 +3243,62 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-tasks@npm:^0.5.27": + version: 0.5.27 + resolution: "@backstage/backend-tasks@npm:0.5.27" + dependencies: + "@backstage/backend-common": "npm:^0.23.3" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/types": "npm:^1.1.1" + "@opentelemetry/api": "npm:^1.3.0" + "@types/luxon": "npm:^3.0.0" + cron: "npm:^3.0.0" + knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" + luxon: "npm:^3.0.0" + uuid: "npm:^9.0.0" + zod: "npm:^3.22.4" + checksum: 10c0/d57a9166e37ae8b8fd0e11e6f4b0528b42fa2e669353815a124fe33634eac89655c33e2a67a55411ad3e899aeecd44e5ebb5e0dfc4b0fc9325b5f237674c4cd3 + languageName: node + linkType: hard + +"@backstage/backend-test-utils@npm:^0.4.4": + version: 0.4.4 + resolution: "@backstage/backend-test-utils@npm:0.4.4" + dependencies: + "@backstage/backend-app-api": "npm:^0.8.0" + "@backstage/backend-defaults": "npm:^0.4.0" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/plugin-auth-node": "npm:^0.4.17" + "@backstage/plugin-events-node": "npm:^0.3.8" + "@backstage/types": "npm:^1.1.1" + "@keyv/memcache": "npm:^1.3.5" + "@keyv/redis": "npm:^2.5.3" + "@types/keyv": "npm:^4.2.0" + better-sqlite3: "npm:^11.0.0" + cookie: "npm:^0.6.0" + express: "npm:^4.17.1" + fs-extra: "npm:^11.0.0" + keyv: "npm:^4.5.2" + knex: "npm:^3.0.0" + msw: "npm:^1.0.0" + mysql2: "npm:^3.0.0" + pg: "npm:^8.11.3" + pg-connection-string: "npm:^2.3.0" + testcontainers: "npm:^10.0.0" + textextensions: "npm:^5.16.0" + uuid: "npm:^9.0.0" + yn: "npm:^4.0.0" + peerDependencies: + "@types/jest": "*" + checksum: 10c0/50010dac59eb3c6cc3af1768ff6b83b37dd5c195b3d409b1305667d836578d6cef0e4142dacaf8aed5aec9c62c8a7e2fb14ba0039980ac242be95670f29889c0 + languageName: node + linkType: hard + "@backstage/catalog-client@npm:^1.6.5": version: 1.6.5 resolution: "@backstage/catalog-client@npm:1.6.5" @@ -3168,6 +3346,22 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-node@npm:^0.2.7": + version: 0.2.7 + resolution: "@backstage/cli-node@npm:0.2.7" + dependencies: + "@backstage/cli-common": "npm:^0.1.14" + "@backstage/errors": "npm:^1.2.4" + "@backstage/types": "npm:^1.1.1" + "@manypkg/get-packages": "npm:^1.1.3" + "@yarnpkg/parsers": "npm:^3.0.0" + fs-extra: "npm:^11.2.0" + semver: "npm:^7.5.3" + zod: "npm:^3.22.4" + checksum: 10c0/d2af77627825a9eaf391069cc86f980b700fcf8b49cedf85b103e81ca3cc33be4915a812ce9ec5f145c9500cd6d2c40cec13a7fee40d7c56e1fc6284d001a5a1 + languageName: node + linkType: hard + "@backstage/cli@npm:^0.26.10": version: 0.26.10 resolution: "@backstage/cli@npm:0.26.10" @@ -4252,6 +4446,15 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-events-node@npm:^0.3.8": + version: 0.3.8 + resolution: "@backstage/plugin-events-node@npm:0.3.8" + dependencies: + "@backstage/backend-plugin-api": "npm:^0.7.0" + checksum: 10c0/18d36c003eb62b67dc5accc09230651d97c84d6906e2cfa209a1e3b3f9a78b1b9822abeab2028b6bf46fb83ca3ff73f96408c0751d77a48f89f1cd5549ca808d + languageName: node + linkType: hard + "@backstage/plugin-home-react@npm:^0.1.14": version: 0.1.14 resolution: "@backstage/plugin-home-react@npm:0.1.14" @@ -4417,6 +4620,25 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-permission-node@npm:^0.8.0": + version: 0.8.0 + resolution: "@backstage/plugin-permission-node@npm:0.8.0" + dependencies: + "@backstage/backend-common": "npm:^0.23.3" + "@backstage/backend-plugin-api": "npm:^0.7.0" + "@backstage/config": "npm:^1.2.0" + "@backstage/errors": "npm:^1.2.4" + "@backstage/plugin-auth-node": "npm:^0.4.17" + "@backstage/plugin-permission-common": "npm:^0.8.0" + "@types/express": "npm:^4.17.6" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.20.4" + checksum: 10c0/c2a68660aa98c3762f5869e13ed5999506ac2c1d41e9f880597d297e7af0c73739dd506ad658aba02a9d4ff7b15750fe25cdb3d153da47baec770891c4c4ccdb + languageName: node + linkType: hard + "@backstage/plugin-permission-react@npm:^0.4.23": version: 0.4.23 resolution: "@backstage/plugin-permission-react@npm:0.4.23" @@ -5227,6 +5449,34 @@ __metadata: languageName: node linkType: hard +"@bundled-es-modules/cookie@npm:^2.0.0": + version: 2.0.0 + resolution: "@bundled-es-modules/cookie@npm:2.0.0" + dependencies: + cookie: "npm:^0.5.0" + checksum: 10c0/0655dd331b35d7b5b6dd2301c3bcfb7233018c0e3235a40ced1d53f00463ab92dc01f0091f153812867bc0ef0f8e0a157a30acb16e8d7ef149702bf8db9fe7a6 + languageName: node + linkType: hard + +"@bundled-es-modules/statuses@npm:^1.0.1": + version: 1.0.1 + resolution: "@bundled-es-modules/statuses@npm:1.0.1" + dependencies: + statuses: "npm:^2.0.1" + checksum: 10c0/c1a8ede3efa8da61ccda4b98e773582a9733edfbeeee569d4630785f8e018766202edb190a754a3ec7a7f6bd738e857829affc2fdb676b6dab4db1bb44e62785 + languageName: node + linkType: hard + +"@bundled-es-modules/tough-cookie@npm:^0.1.6": + version: 0.1.6 + resolution: "@bundled-es-modules/tough-cookie@npm:0.1.6" + dependencies: + "@types/tough-cookie": "npm:^4.0.5" + tough-cookie: "npm:^4.1.4" + checksum: 10c0/28bcac878bff6b34719ba3aa8341e9924772ee55de5487680ebe784981ec9fccb70ed5d46f563e2404855a04de606f9e56aa4202842d4f5835bc04a4fe820571 + languageName: node + linkType: hard + "@changesets/types@npm:^4.0.1": version: 4.1.0 resolution: "@changesets/types@npm:4.1.0" @@ -6297,6 +6547,53 @@ __metadata: languageName: node linkType: hard +"@inquirer/confirm@npm:^3.0.0": + version: 3.1.17 + resolution: "@inquirer/confirm@npm:3.1.17" + dependencies: + "@inquirer/core": "npm:^9.0.5" + "@inquirer/type": "npm:^1.5.1" + checksum: 10c0/45758e10691359bae5c5bc5e2ea3d24843be9b1c8180aa47725ec1b20c5e8601b3efa30f5a941595a47dde157d29cc233219ab9ddc486935d510f7479d731fe7 + languageName: node + linkType: hard + +"@inquirer/core@npm:^9.0.5": + version: 9.0.5 + resolution: "@inquirer/core@npm:9.0.5" + dependencies: + "@inquirer/figures": "npm:^1.0.5" + "@inquirer/type": "npm:^1.5.1" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^20.14.11" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + cli-spinners: "npm:^2.9.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^1.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/5cbf43b61213933c0704f80d867f2c854cae00f9c8b4304fa6d9332eb0d143952e65286ccc3fae48bd15095ea85486b556ae95151e7a05a87b770f1867f8257e + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.5": + version: 1.0.5 + resolution: "@inquirer/figures@npm:1.0.5" + checksum: 10c0/ec9ba23db42cb33fa18eb919abf2a18e750e739e64c1883ce4a98345cd5711c60cac12d1faf56a859f52d387deb221c8d3dfe60344ee07955a9a262f8b821fe3 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.5.1": + version: 1.5.1 + resolution: "@inquirer/type@npm:1.5.1" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10c0/a4fa548179210b55102c05bb7f475bb757385fb5ccbc7f8f20b8020d9f3acb75d544f26292b35ebb8b7b5ebac54ecb503d238058aea4a34f2b47b78c8c63020e + languageName: node + linkType: hard + "@internal/backstage-plugin-plausible@npm:^0.1.0, @internal/backstage-plugin-plausible@workspace:plugins/plausible": version: 0.0.0-use.local resolution: "@internal/backstage-plugin-plausible@workspace:plugins/plausible" @@ -7227,6 +7524,20 @@ __metadata: languageName: node linkType: hard +"@mswjs/interceptors@npm:^0.29.0": + version: 0.29.1 + resolution: "@mswjs/interceptors@npm:0.29.1" + dependencies: + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/logger": "npm:^0.3.0" + "@open-draft/until": "npm:^2.0.0" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.2.1" + strict-event-emitter: "npm:^0.5.1" + checksum: 10c0/816660a17b0e89e6e6955072b96882b5807c8c9faa316eab27104e8ba80e8e7d78b1862af42e1044156a5ae3ae2071289dc9211ecdc8fd5f7078d8c8a8a7caa3 + languageName: node + linkType: hard + "@mui/base@npm:5.0.0-beta.40, @mui/base@npm:^5.0.0-beta.40": version: 5.0.0-beta.40 resolution: "@mui/base@npm:5.0.0-beta.40" @@ -8511,6 +8822,23 @@ __metadata: languageName: node linkType: hard +"@open-draft/deferred-promise@npm:^2.2.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 10c0/eafc1b1d0fc8edb5e1c753c5e0f3293410b40dde2f92688211a54806d4136887051f39b98c1950370be258483deac9dfd17cf8b96557553765198ef2547e4549 + languageName: node + linkType: hard + +"@open-draft/logger@npm:^0.3.0": + version: 0.3.0 + resolution: "@open-draft/logger@npm:0.3.0" + dependencies: + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.0" + checksum: 10c0/90010647b22e9693c16258f4f9adb034824d1771d3baa313057b9a37797f571181005bc50415a934eaf7c891d90ff71dcd7a9d5048b0b6bb438f31bef2c7c5c1 + languageName: node + linkType: hard + "@open-draft/until@npm:^1.0.3": version: 1.0.3 resolution: "@open-draft/until@npm:1.0.3" @@ -8518,6 +8846,13 @@ __metadata: languageName: node linkType: hard +"@open-draft/until@npm:^2.0.0, @open-draft/until@npm:^2.1.0": + version: 2.1.0 + resolution: "@open-draft/until@npm:2.1.0" + checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf + languageName: node + linkType: hard + "@openapi-contrib/openapi-schema-to-json-schema@npm:~3.2.0": version: 3.2.0 resolution: "@openapi-contrib/openapi-schema-to-json-schema@npm:3.2.0" @@ -11764,6 +12099,20 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 + languageName: node + linkType: hard + +"@types/cookiejar@npm:^2.1.5": + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10c0/af38c3d84aebb3ccc6e46fb6afeeaac80fb26e63a487dd4db5a8b87e6ad3d4b845ba1116b2ae90d6f886290a36200fa433d8b1f6fe19c47da6b81872ce9a2764 + languageName: node + linkType: hard + "@types/cors@npm:^2.8.6": version: 2.8.17 resolution: "@types/cors@npm:2.8.17" @@ -11803,6 +12152,17 @@ __metadata: languageName: node linkType: hard +"@types/dockerode@npm:^3.3.29": + version: 3.3.31 + resolution: "@types/dockerode@npm:3.3.31" + dependencies: + "@types/docker-modem": "npm:*" + "@types/node": "npm:*" + "@types/ssh2": "npm:*" + checksum: 10c0/e0b85edcb7065c24d6d8140b90ea3be128451d4ef25d44fe07ef653e931f3ff4ce86ba0fbb0d7037f51296eb5055d17d8d3ac373028c9e13861b3baf249578d8 + languageName: node + linkType: hard + "@types/dompurify@npm:^2.1.0": version: 2.4.0 resolution: "@types/dompurify@npm:2.4.0" @@ -12029,6 +12389,15 @@ __metadata: languageName: node linkType: hard +"@types/keyv@npm:^4.2.0": + version: 4.2.0 + resolution: "@types/keyv@npm:4.2.0" + dependencies: + keyv: "npm:*" + checksum: 10c0/ad626918f1843035b732b582263890a67d73dc3ff80da97e51fbe0ae3f2fe7a1ada2eef1bd89605c5fb739444110e696c0e0703d9b49a842a2f924c6e9164faa + languageName: node + linkType: hard + "@types/long@npm:^4.0.0": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -12066,6 +12435,13 @@ __metadata: languageName: node linkType: hard +"@types/methods@npm:^1.1.4": + version: 1.1.4 + resolution: "@types/methods@npm:1.1.4" + checksum: 10c0/a78534d79c300718298bfff92facd07bf38429c66191f640c1db4c9cff1e36f819304298a96f7536b6512bfc398e5c3e6b831405e138cd774b88ad7be78d682a + languageName: node + linkType: hard + "@types/mime@npm:^1": version: 1.3.5 resolution: "@types/mime@npm:1.3.5" @@ -12103,6 +12479,15 @@ __metadata: languageName: node linkType: hard +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/944730fd7b398c5078de3c3d4d0afeec8584283bc694da1803fdfca14149ea385e18b1b774326f1601baf53898ce6d121a952c51eb62d188ef6fcc41f725c0dc + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.5.0": version: 2.6.11 resolution: "@types/node-fetch@npm:2.6.11" @@ -12154,6 +12539,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.14.11": + version: 20.14.12 + resolution: "@types/node@npm:20.14.12" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/59bc5fa11fdd23fd517f859063118f54a1ab53d3399ef63c926f8902429d7453abc0db22ef4b0a6110026b6ab81b6472fee894e1d235c24b01a0b3e10cfae0bb + languageName: node + linkType: hard + "@types/node@npm:^20.14.9": version: 20.14.9 resolution: "@types/node@npm:20.14.9" @@ -12399,7 +12793,16 @@ __metadata: resolution: "@types/sockjs@npm:0.3.36" dependencies: "@types/node": "npm:*" - checksum: 10c0/b20b7820ee813f22de4f2ce98bdd12c68c930e016a8912b1ed967595ac0d8a4cbbff44f4d486dd97f77f5927e7b5725bdac7472c9ec5b27f53a5a13179f0612f + checksum: 10c0/b20b7820ee813f22de4f2ce98bdd12c68c930e016a8912b1ed967595ac0d8a4cbbff44f4d486dd97f77f5927e7b5725bdac7472c9ec5b27f53a5a13179f0612f + languageName: node + linkType: hard + +"@types/ssh2-streams@npm:*": + version: 0.1.12 + resolution: "@types/ssh2-streams@npm:0.1.12" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/6c860066e76391c937723b9f8c3953208737be5adf33b5584d7817ec90913094f2ca578e1d47717182f1d62cb5ca8e83fdec0241d73bf064221e3a2b2d132f0e languageName: node linkType: hard @@ -12412,6 +12815,16 @@ __metadata: languageName: node linkType: hard +"@types/ssh2@npm:^0.5.48": + version: 0.5.52 + resolution: "@types/ssh2@npm:0.5.52" + dependencies: + "@types/node": "npm:*" + "@types/ssh2-streams": "npm:*" + checksum: 10c0/95c52fd3438dedae6a59ca87b6558cb36568db6b9144c6c8a28c168739e04c51e27c02908aae14950b7b5020e1c40fea039b1203ae2734c356a40a050fd51c84 + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -12419,6 +12832,13 @@ __metadata: languageName: node linkType: hard +"@types/statuses@npm:^2.0.4": + version: 2.0.5 + resolution: "@types/statuses@npm:2.0.5" + checksum: 10c0/4dacec0b29483a44be902a022a11a22b339de7a6e7b2059daa4f7add10cb6dbcc28d02d2a416fe9687e48d335906bf983065391836d4e7c847e55ddef4de8fad + languageName: node + linkType: hard + "@types/styled-jsx@npm:^2.2.8": version: 2.2.9 resolution: "@types/styled-jsx@npm:2.2.9" @@ -12428,6 +12848,28 @@ __metadata: languageName: node linkType: hard +"@types/superagent@npm:^8.1.0": + version: 8.1.8 + resolution: "@types/superagent@npm:8.1.8" + dependencies: + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10c0/c5fa8fe48e63445317d2e056c93c373a14cd916ac7b6e5a084f8cdecc70419683c89e3245ad47ff3d1f33406cfdc23117e3877651b184257adcd3063b7037feb + languageName: node + linkType: hard + +"@types/supertest@npm:^6.0.2": + version: 6.0.2 + resolution: "@types/supertest@npm:6.0.2" + dependencies: + "@types/methods": "npm:^1.1.4" + "@types/superagent": "npm:^8.1.0" + checksum: 10c0/44a28f9b35b65800f4c7bcc23748e71c925098aa74ea504d14c98385c36d00de2a4f5aca15d7dc4514bc80533e0af21f985a4ab9f5f317c7266e9e75836aef39 + languageName: node + linkType: hard + "@types/tern@npm:*": version: 0.23.9 resolution: "@types/tern@npm:0.23.9" @@ -12437,7 +12879,7 @@ __metadata: languageName: node linkType: hard -"@types/tough-cookie@npm:*": +"@types/tough-cookie@npm:*, @types/tough-cookie@npm:^4.0.5": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 @@ -12495,6 +12937,13 @@ __metadata: languageName: node linkType: hard +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 10c0/8d8f53363f360f38135301a06b596c295433ad01debd082078c33c6ed98b05a5c8fe8853a88265432126096084f4a135ec1564e3daad631b83296905509f90b3 + languageName: node + linkType: hard + "@types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.3": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" @@ -13306,7 +13755,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -13489,6 +13938,42 @@ __metadata: languageName: node linkType: hard +"archiver-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "archiver-utils@npm:2.1.0" + dependencies: + glob: "npm:^7.1.4" + graceful-fs: "npm:^4.2.0" + lazystream: "npm:^1.0.0" + lodash.defaults: "npm:^4.2.0" + lodash.difference: "npm:^4.5.0" + lodash.flatten: "npm:^4.4.0" + lodash.isplainobject: "npm:^4.0.6" + lodash.union: "npm:^4.6.0" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^2.0.0" + checksum: 10c0/6ea5b02e440f3099aff58b18dd384f84ecfe18632e81d26c1011fe7dfdb80ade43d7a06cbf048ef0e9ee0f2c87a80cb24c0f0ac5e3a2c4d67641d6f0d6e36ece + languageName: node + linkType: hard + +"archiver-utils@npm:^3.0.4": + version: 3.0.4 + resolution: "archiver-utils@npm:3.0.4" + dependencies: + glob: "npm:^7.2.3" + graceful-fs: "npm:^4.2.0" + lazystream: "npm:^1.0.0" + lodash.defaults: "npm:^4.2.0" + lodash.difference: "npm:^4.5.0" + lodash.flatten: "npm:^4.4.0" + lodash.isplainobject: "npm:^4.0.6" + lodash.union: "npm:^4.6.0" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/9bb7e271e95ff33bdbdcd6f69f8860e0aeed3fcba352a74f51a626d1c32b404f20e3185d5214f171b24a692471d01702f43874d1a4f0d2e5f57bd0834bc54c14 + languageName: node + linkType: hard + "archiver-utils@npm:^4.0.1": version: 4.0.1 resolution: "archiver-utils@npm:4.0.1" @@ -13503,6 +13988,21 @@ __metadata: languageName: node linkType: hard +"archiver@npm:^5.3.2": + version: 5.3.2 + resolution: "archiver@npm:5.3.2" + dependencies: + archiver-utils: "npm:^2.1.0" + async: "npm:^3.2.4" + buffer-crc32: "npm:^0.2.1" + readable-stream: "npm:^3.6.0" + readdir-glob: "npm:^1.1.2" + tar-stream: "npm:^2.2.0" + zip-stream: "npm:^4.1.0" + checksum: 10c0/973384d749b3fa96f44ceda1603a65aaa3f24a267230d69a4df9d7b607d38d3ebc6c18c358af76eb06345b6b331ccb9eca07bd079430226b5afce95de22dfade + languageName: node + linkType: hard + "archiver@npm:^6.0.0": version: 6.0.2 resolution: "archiver@npm:6.0.2" @@ -13749,7 +14249,7 @@ __metadata: languageName: node linkType: hard -"asap@npm:^2.0.3": +"asap@npm:^2.0.0, asap@npm:^2.0.3": version: 2.0.6 resolution: "asap@npm:2.0.6" checksum: 10c0/c6d5e39fe1f15e4b87677460bd66b66050cd14c772269cee6688824c1410a08ab20254bb6784f9afb75af9144a9f9a7692d49547f4d19d715aeb7c0318f3136d @@ -14119,6 +14619,7 @@ __metadata: "@backstage/backend-defaults": "npm:^0.3.3" "@backstage/backend-plugin-api": "npm:^0.7.0" "@backstage/backend-tasks": "npm:^0.5.26" + "@backstage/backend-test-utils": "npm:^0.4.4" "@backstage/catalog-model": "npm:^1.5.0" "@backstage/cli": "npm:^0.26.10" "@backstage/config": "npm:^1.2.0" @@ -14144,11 +14645,15 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/express-serve-static-core": "npm:^4.17.5" "@types/luxon": "npm:^2.0.4" + "@types/supertest": "npm:^6.0.2" app: "link:../app" better-sqlite3: "npm:^9.0.0" dockerode: "npm:^3.3.1" + jose: "npm:^5.6.3" + msw: "npm:^2.3.4" node-gyp: "npm:^9.0.0" pg: "npm:^8.11.3" + supertest: "npm:^7.0.0" winston: "npm:^3.2.1" languageName: unknown linkType: soft @@ -14167,6 +14672,13 @@ __metadata: languageName: node linkType: hard +"bare-events@npm:^2.0.0": + version: 2.4.2 + resolution: "bare-events@npm:2.4.2" + checksum: 10c0/09fa923061f31f815e83504e2ed4a8ba87732a01db40a7fae703dbb7eef7f05d99264b5e186074cbe9698213990d1af564c62cca07a5ff88baea8099ad9a6303 + languageName: node + linkType: hard + "bare-events@npm:^2.2.0": version: 2.3.1 resolution: "bare-events@npm:2.3.1" @@ -14174,6 +14686,42 @@ __metadata: languageName: node linkType: hard +"bare-fs@npm:^2.1.1": + version: 2.3.1 + resolution: "bare-fs@npm:2.3.1" + dependencies: + bare-events: "npm:^2.0.0" + bare-path: "npm:^2.0.0" + bare-stream: "npm:^2.0.0" + checksum: 10c0/820979ad3dd8693076ba08af842e41b5119fcca63f4324b8f28d96b96050cd260085dffd1169dc644f20746fadb4cf4368b317f2fa2db4e40890921ceb557581 + languageName: node + linkType: hard + +"bare-os@npm:^2.1.0": + version: 2.4.0 + resolution: "bare-os@npm:2.4.0" + checksum: 10c0/85615522fd8309d3815d3bef227623f008fac34e037459294a7e24bb2b51ea125597274b8aa7e7038f82de89c15e2148fef299eece40ec3ea33797a357c4f2bb + languageName: node + linkType: hard + +"bare-path@npm:^2.0.0, bare-path@npm:^2.1.0": + version: 2.1.3 + resolution: "bare-path@npm:2.1.3" + dependencies: + bare-os: "npm:^2.1.0" + checksum: 10c0/35587e177fc8fa5b13fb90bac8779b5ce49c99016d221ddaefe2232d02bd4295d79b941e14ae19fda75ec42a6fe5fb66c07d83ae7ec11462178e66b7be65ca74 + languageName: node + linkType: hard + +"bare-stream@npm:^2.0.0": + version: 2.1.3 + resolution: "bare-stream@npm:2.1.3" + dependencies: + streamx: "npm:^2.18.0" + checksum: 10c0/8703b1d80318496ea560483943d5f425a160ded8d3d75659571842caf5f374f52668809bc1e39b032af14df7210973995efaf273f8c35986bef697380ef4674a + languageName: node + linkType: hard + "base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -14227,6 +14775,17 @@ __metadata: languageName: node linkType: hard +"better-sqlite3@npm:^11.0.0": + version: 11.1.2 + resolution: "better-sqlite3@npm:11.1.2" + dependencies: + bindings: "npm:^1.5.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10c0/d4f4d7e56fc854fdf794781fc99fe5c0f7f373d635769d8ad1b7b22278bd91cdcdfe03360cbc63b51ab208b86a05465281a2b4c55065d6c3e911274f6f1e07cb + languageName: node + linkType: hard + "better-sqlite3@npm:^9.0.0": version: 9.6.0 resolution: "better-sqlite3@npm:9.6.0" @@ -14524,7 +15083,7 @@ __metadata: languageName: node linkType: hard -"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:~0.2.3": +"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" checksum: 10c0/cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 @@ -15066,7 +15625,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 @@ -15080,6 +15639,13 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + "client-only@npm:^0.0.1": version: 0.0.1 resolution: "client-only@npm:0.0.1" @@ -15424,6 +15990,25 @@ __metadata: languageName: node linkType: hard +"component-emitter@npm:^1.3.0": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10c0/e4900b1b790b5e76b8d71b328da41482118c0f3523a516a41be598dc2785a07fd721098d9bf6e22d89b19f4fa4e1025160dc00317ea111633a3e4f75c2b86032 + languageName: node + linkType: hard + +"compress-commons@npm:^4.1.2": + version: 4.1.2 + resolution: "compress-commons@npm:4.1.2" + dependencies: + buffer-crc32: "npm:^0.2.13" + crc32-stream: "npm:^4.0.2" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10c0/e5fa03cb374ed89028e20226c70481e87286240392d5c6856f4e7fef40605c1892748648e20ed56597d390d76513b1b9bb4dbd658a1bbff41c9fa60107c74d3f + languageName: node + linkType: hard + "compress-commons@npm:^5.0.1": version: 5.0.3 resolution: "compress-commons@npm:5.0.3" @@ -15749,6 +16334,20 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 10c0/c01ca3ef8d7b8187bae434434582288681273b5a9ed27521d4d7f9f7928fe0c920df0decd9f9d3bbd2d14ac432b8c8cf42b98b3bdd5bfe0e6edddeebebe8b61d + languageName: node + linkType: hard + +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: 10c0/2dae55611c6e1678f34d93984cbd4bda58f4fe3e5247cc4993f4a305cd19c913bbaf325086ed952e892108115073a747596453d3dc1c34947f47f731818b8ad1 + languageName: node + linkType: hard + "copy-to-clipboard@npm:^3.2.0, copy-to-clipboard@npm:^3.3.1": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -15875,6 +16474,16 @@ __metadata: languageName: node linkType: hard +"crc32-stream@npm:^4.0.2": + version: 4.0.3 + resolution: "crc32-stream@npm:4.0.3" + dependencies: + crc-32: "npm:^1.2.0" + readable-stream: "npm:^3.4.0" + checksum: 10c0/127b0c66a947c54db37054fca86085722140644d3a75ebc61d4477bad19304d2936386b0461e8ee9e1c24b00e804cd7c2e205180e5bcb4632d20eccd60533bc4 + languageName: node + linkType: hard + "crc32-stream@npm:^5.0.0": version: 5.0.1 resolution: "crc32-stream@npm:5.0.1" @@ -16532,7 +17141,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5": version: 4.3.5 resolution: "debug@npm:4.3.5" dependencies: @@ -16895,6 +17504,16 @@ __metadata: languageName: node linkType: hard +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: "npm:^2.0.0" + wrappy: "npm:1" + checksum: 10c0/8a870ed42eade9a397e6141fe5c025148a59ed52f1f28b1db5de216b4d57f0af7a257070c3af7ce3d5508c1ce9dd5009028a76f4b2cc9370dc56551d2355fad8 + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -16959,6 +17578,15 @@ __metadata: languageName: node linkType: hard +"docker-compose@npm:^0.24.8": + version: 0.24.8 + resolution: "docker-compose@npm:0.24.8" + dependencies: + yaml: "npm:^2.2.2" + checksum: 10c0/1494389e554fed8aabf9fef24210a641cd2442028b1462d7f68186919f5e75045f7bfb4ccaf47c94ed879dcb63e4d82885c389399f531550c4b244920740b2b3 + languageName: node + linkType: hard + "docker-modem@npm:^3.0.0": version: 3.0.8 resolution: "docker-modem@npm:3.0.8" @@ -16983,7 +17611,7 @@ __metadata: languageName: node linkType: hard -"dockerode@npm:^3.3.1": +"dockerode@npm:^3.3.1, dockerode@npm:^3.3.5": version: 3.3.5 resolution: "dockerode@npm:3.3.5" dependencies: @@ -18494,7 +19122,7 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:^2.0.6, fast-safe-stringify@npm:^2.0.7": +"fast-safe-stringify@npm:^2.0.6, fast-safe-stringify@npm:^2.0.7, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d @@ -18897,6 +19525,17 @@ __metadata: languageName: node linkType: hard +"formidable@npm:^3.5.1": + version: 3.5.1 + resolution: "formidable@npm:3.5.1" + dependencies: + dezalgo: "npm:^1.0.4" + hexoid: "npm:^1.0.0" + once: "npm:^1.4.0" + checksum: 10c0/c02fa1a027876dd9fc5abde16e9c537bec41bc652b362d0b3fd26daaae0615b9a472129c29b328a130d11f543f676acd1b13e6f28f3adc1b088cdaea9fb9e054 + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -19215,7 +19854,7 @@ __metadata: languageName: node linkType: hard -"get-port@npm:5.1.1": +"get-port@npm:5.1.1, get-port@npm:^5.1.1": version: 5.1.1 resolution: "get-port@npm:5.1.1" checksum: 10c0/2873877a469b24e6d5e0be490724a17edb39fafc795d1d662e7bea951ca649713b4a50117a473f9d162312cb0e946597bd0e049ed2f866e79e576e8e213d3d1c @@ -19414,7 +20053,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7": +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -19919,6 +20558,13 @@ __metadata: languageName: node linkType: hard +"headers-polyfill@npm:^4.0.2": + version: 4.0.3 + resolution: "headers-polyfill@npm:4.0.3" + checksum: 10c0/53e85b2c6385f8d411945fb890c5369f1469ce8aa32a6e8d28196df38568148de640c81cf88cbc7c67767103dd9acba48f4f891982da63178fc6e34560022afe + languageName: node + linkType: hard + "helmet@npm:^6.0.0": version: 6.2.0 resolution: "helmet@npm:6.2.0" @@ -19926,6 +20572,13 @@ __metadata: languageName: node linkType: hard +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 10c0/9c45e8ba676b9eb88455631ebceec4c829a8374a583410dc735472ab9808bf11339fcd074633c3fa30e420901b894d8a92ffd5e2e21eddd41149546e05a91f69 + languageName: node + linkType: hard + "hey-listen@npm:^1.0.8": version: 1.0.8 resolution: "hey-listen@npm:1.0.8" @@ -21904,6 +22557,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^5.6.3": + version: 5.6.3 + resolution: "jose@npm:5.6.3" + checksum: 10c0/c33d8d37b86a17b1f720c667a1208248171b602375131828f645fff0d71a2fbe9b5e2a47c3fd9b7c3e41f3bf25761398150d1f158a2ee1b616ac90cf56f5e862 + languageName: node + linkType: hard + "js-base64@npm:^3.6.0": version: 3.7.7 resolution: "js-base64@npm:3.7.7" @@ -22507,7 +23167,7 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0, keyv@npm:^4.5.2, keyv@npm:^4.5.3": +"keyv@npm:*, keyv@npm:^4.0.0, keyv@npm:^4.5.2, keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" dependencies: @@ -22975,6 +23635,20 @@ __metadata: languageName: node linkType: hard +"lodash.difference@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.difference@npm:4.5.0" + checksum: 10c0/5d52859218a7df427547ff1fadbc397879709fe6c788b037df7d6d92b676122c92bd35ec85d364edb596b65dfc6573132f420c9b4ee22bb6b9600cd454c90637 + languageName: node + linkType: hard + +"lodash.flatten@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.flatten@npm:4.4.0" + checksum: 10c0/97e8f0d6b61fe4723c02ad0c6e67e51784c4a2c48f56ef283483e556ad01594cf9cec9c773e177bbbdbdb5d19e99b09d2487cb6b6e5dc405c2693e93b125bd3a + languageName: node + linkType: hard + "lodash.flattendeep@npm:^4.0.0": version: 4.4.0 resolution: "lodash.flattendeep@npm:4.4.0" @@ -23080,6 +23754,13 @@ __metadata: languageName: node linkType: hard +"lodash.union@npm:^4.6.0": + version: 4.6.0 + resolution: "lodash.union@npm:4.6.0" + checksum: 10c0/6da7f72d1facd472f6090b49eefff984c9f9179e13172039c0debca6851d21d37d83c7ad5c43af23bd220f184cd80e6897e8e3206509fae491f9068b02ae6319 + languageName: node + linkType: hard + "lodash.uniq@npm:^4.5.0": version: 4.5.0 resolution: "lodash.uniq@npm:4.5.0" @@ -23733,7 +24414,7 @@ __metadata: languageName: node linkType: hard -"methods@npm:^1.0.0, methods@npm:~1.1.2": +"methods@npm:^1.0.0, methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 @@ -24116,6 +24797,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:2.6.0": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10c0/a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + "mime@npm:^3.0.0": version: 3.0.0 resolution: "mime@npm:3.0.0" @@ -24535,6 +25225,38 @@ __metadata: languageName: node linkType: hard +"msw@npm:^2.3.4": + version: 2.3.4 + resolution: "msw@npm:2.3.4" + dependencies: + "@bundled-es-modules/cookie": "npm:^2.0.0" + "@bundled-es-modules/statuses": "npm:^1.0.1" + "@bundled-es-modules/tough-cookie": "npm:^0.1.6" + "@inquirer/confirm": "npm:^3.0.0" + "@mswjs/interceptors": "npm:^0.29.0" + "@open-draft/until": "npm:^2.1.0" + "@types/cookie": "npm:^0.6.0" + "@types/statuses": "npm:^2.0.4" + chalk: "npm:^4.1.2" + graphql: "npm:^16.8.1" + headers-polyfill: "npm:^4.0.2" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.2" + path-to-regexp: "npm:^6.2.0" + strict-event-emitter: "npm:^0.5.1" + type-fest: "npm:^4.9.0" + yargs: "npm:^17.7.2" + peerDependencies: + typescript: ">= 4.7.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 10c0/e9a5a1de80f1688c1dd5c4de86c0192891568912d0f98500b552ecfe233d80559941d2bd563b3f7a792919a8a5fd380e222461f4b2a331ab1ab65fcb5fe30a97 + languageName: node + linkType: hard + "multer@npm:^1.4.5-lts.1": version: 1.4.5-lts.1 resolution: "multer@npm:1.4.5-lts.1" @@ -24816,7 +25538,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -25682,7 +26404,7 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.2.1, outvariant@npm:^1.4.0": +"outvariant@npm:^1.2.1, outvariant@npm:^1.4.0, outvariant@npm:^1.4.2": version: 1.4.3 resolution: "outvariant@npm:1.4.3" checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c @@ -27203,6 +27925,26 @@ __metadata: languageName: node linkType: hard +"proper-lockfile@npm:^4.1.2": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: "npm:^4.2.4" + retry: "npm:^0.12.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/2f265dbad15897a43110a02dae55105c04d356ec4ed560723dcb9f0d34bc4fb2f13f79bb930e7561be10278e2314db5aca2527d5d3dcbbdee5e6b331d1571f6d + languageName: node + linkType: hard + +"properties-reader@npm:^2.3.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: "npm:^1.0.4" + checksum: 10c0/f665057e3a9076c643ba1198afcc71703eda227a59913252f7ff9467ece8d29c0cf8bf14bf1abcaef71570840c32a4e257e6c39b7550451bbff1a777efcf5667 + languageName: node + linkType: hard + "property-expr@npm:^2.0.5": version: 2.0.6 resolution: "property-expr@npm:2.0.6" @@ -27349,6 +28091,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.11.0": + version: 6.12.3 + resolution: "qs@npm:6.12.3" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10c0/243ddcc8f49dab78fc51041f7f64c500b47c671c45a101a8aca565d8537cb562921da7ef1a831b4a7051596ec88bb35a0d5e25a240025e8b32c6bfb69f00bf2f + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -28176,7 +28927,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.6, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.6, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -29430,7 +30181,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 @@ -29795,7 +30546,17 @@ __metadata: languageName: node linkType: hard -"ssh2@npm:^1.11.0, ssh2@npm:^1.15.0": +"ssh-remote-port-forward@npm:^1.0.4": + version: 1.0.4 + resolution: "ssh-remote-port-forward@npm:1.0.4" + dependencies: + "@types/ssh2": "npm:^0.5.48" + ssh2: "npm:^1.4.0" + checksum: 10c0/33a441af12817577ea30d089b03c19f980d2fb2370933123a35026dc6be40f2dfce067e4dfc173e23d745464537ff647aa1bb7469be5571cc21f7cdb25181c09 + languageName: node + linkType: hard + +"ssh2@npm:^1.11.0, ssh2@npm:^1.15.0, ssh2@npm:^1.4.0": version: 1.15.0 resolution: "ssh2@npm:1.15.0" dependencies: @@ -29927,7 +30688,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1": +"statuses@npm:2.0.1, statuses@npm:^2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 @@ -30020,7 +30781,7 @@ __metadata: languageName: node linkType: hard -"streamx@npm:^2.15.0": +"streamx@npm:^2.15.0, streamx@npm:^2.18.0": version: 2.18.0 resolution: "streamx@npm:2.18.0" dependencies: @@ -30051,6 +30812,13 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.5.1": + version: 0.5.1 + resolution: "strict-event-emitter@npm:0.5.1" + checksum: 10c0/f5228a6e6b6393c57f52f62e673cfe3be3294b35d6f7842fc24b172ae0a6e6c209fa83241d0e433fc267c503bc2f4ffdbe41a9990ff8ffd5ac425ec0489417f7 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -30370,6 +31138,33 @@ __metadata: languageName: node linkType: hard +"superagent@npm:^9.0.1": + version: 9.0.2 + resolution: "superagent@npm:9.0.2" + dependencies: + component-emitter: "npm:^1.3.0" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.4" + fast-safe-stringify: "npm:^2.1.1" + form-data: "npm:^4.0.0" + formidable: "npm:^3.5.1" + methods: "npm:^1.1.2" + mime: "npm:2.6.0" + qs: "npm:^6.11.0" + checksum: 10c0/bfe7522ce9554552bed03c0e71949038e54626dd7be627f1033d92aae5b46d90afc42f8fc0dcda481eebf371a30b702414e438ea51251be6ab7bfbd60086d147 + languageName: node + linkType: hard + +"supertest@npm:^7.0.0": + version: 7.0.0 + resolution: "supertest@npm:7.0.0" + dependencies: + methods: "npm:^1.1.2" + superagent: "npm:^9.0.1" + checksum: 10c0/f0b10a1d292e6156fab16efdbb90d8cb1df54367667ae4108a6da67b81058d35182720dd9a3b4b2f538b14729dc8633741e6242724f1a0ccfde5197341ea96ec + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -30554,6 +31349,23 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^3.0.6": + version: 3.0.6 + resolution: "tar-fs@npm:3.0.6" + dependencies: + bare-fs: "npm:^2.1.1" + bare-path: "npm:^2.1.0" + pump: "npm:^3.0.0" + tar-stream: "npm:^3.1.5" + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: 10c0/207b7c0f193495668bd9dbad09a0108ce4ffcfec5bce2133f90988cdda5c81fad83c99f963d01e47b565196594f7a17dbd063ae55b97b36268fcc843975278ee + languageName: node + linkType: hard + "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -30566,7 +31378,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.0.0, tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": +"tar-stream@npm:^2.0.0, tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -30579,7 +31391,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^3.0.0": +"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5": version: 3.1.7 resolution: "tar-stream@npm:3.1.7" dependencies: @@ -30701,6 +31513,29 @@ __metadata: languageName: node linkType: hard +"testcontainers@npm:^10.0.0": + version: 10.10.4 + resolution: "testcontainers@npm:10.10.4" + dependencies: + "@balena/dockerignore": "npm:^1.0.2" + "@types/dockerode": "npm:^3.3.29" + archiver: "npm:^5.3.2" + async-lock: "npm:^1.4.1" + byline: "npm:^5.0.0" + debug: "npm:^4.3.5" + docker-compose: "npm:^0.24.8" + dockerode: "npm:^3.3.5" + get-port: "npm:^5.1.1" + node-fetch: "npm:^2.7.0" + proper-lockfile: "npm:^4.1.2" + properties-reader: "npm:^2.3.0" + ssh-remote-port-forward: "npm:^1.0.4" + tar-fs: "npm:^3.0.6" + tmp: "npm:^0.2.3" + checksum: 10c0/7186c728cb2c07692546e886a98c56036a3ec9537cfed41b809ef936b499522426b6fd143516a4d93994a9015875fe1c6263265578547f582f02a9e816848d8e + languageName: node + linkType: hard + "text-decoder@npm:^1.1.0": version: 1.1.0 resolution: "text-decoder@npm:1.1.0" @@ -30731,6 +31566,13 @@ __metadata: languageName: node linkType: hard +"textextensions@npm:^5.16.0": + version: 5.16.0 + resolution: "textextensions@npm:5.16.0" + checksum: 10c0/bc90dc60272c3ffb76eeff86dc1decab9535ba8da6a00efe2a005763d0305cb445db9ac35970538c59b89bf41820c3d19394f46c6b7346d520b9ae16fe47be80 + languageName: node + linkType: hard + "thenify-all@npm:^1.0.0": version: 1.6.0 resolution: "thenify-all@npm:1.6.0" @@ -30844,7 +31686,7 @@ __metadata: languageName: node linkType: hard -"tmp@npm:~0.2.1": +"tmp@npm:^0.2.3, tmp@npm:~0.2.1": version: 0.2.3 resolution: "tmp@npm:0.2.3" checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125 @@ -30919,7 +31761,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.0.0, tough-cookie@npm:^4.1.2": +"tough-cookie@npm:^4.0.0, tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.4": version: 4.1.4 resolution: "tough-cookie@npm:4.1.4" dependencies: @@ -31316,6 +32158,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.9.0": + version: 4.23.0 + resolution: "type-fest@npm:4.23.0" + checksum: 10c0/c42bb14e99329ab37983d1f188e307bf0cc705a23807d9b2268d8fb2ae781d610ac6e2058dde8f9ea2b1b8ddc77ceb578d157fa81f69f8f70aef1d42fb002996 + languageName: node + linkType: hard + "type-is@npm:^1.6.4, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -32672,7 +33521,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.0.1": +"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" dependencies: @@ -32934,6 +33783,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.2": + version: 2.5.0 + resolution: "yaml@npm:2.5.0" + bin: + yaml: bin.mjs + checksum: 10c0/771a1df083c8217cf04ef49f87244ae2dd7d7457094425e793b8f056159f167602ce172aa32d6bca21f787d24ec724aee3cecde938f6643564117bd151452631 + languageName: node + linkType: hard + "yargs-parser@npm:20.2.4": version: 20.2.4 resolution: "yargs-parser@npm:20.2.4" @@ -33026,6 +33884,13 @@ __metadata: languageName: node linkType: hard +"yoctocolors-cjs@npm:^2.1.2": + version: 2.1.2 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f + languageName: node + linkType: hard + "yup@npm:^1.0.0": version: 1.4.0 resolution: "yup@npm:1.4.0" @@ -33052,6 +33917,17 @@ __metadata: languageName: node linkType: hard +"zip-stream@npm:^4.1.0": + version: 4.1.1 + resolution: "zip-stream@npm:4.1.1" + dependencies: + archiver-utils: "npm:^3.0.4" + compress-commons: "npm:^4.1.2" + readable-stream: "npm:^3.6.0" + checksum: 10c0/38f91ca116a38561cf184c29e035e9453b12c30eaf574e0993107a4a5331882b58c9a7f7b97f63910664028089fbde3296d0b3682d1ccb2ad96929e68f1b2b89 + languageName: node + linkType: hard + "zip-stream@npm:^5.0.1": version: 5.0.2 resolution: "zip-stream@npm:5.0.2" From 3ef56a55d25c0987c3f0d5418220789b141c6b50 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Mon, 29 Jul 2024 06:17:47 +1000 Subject: [PATCH 6/6] refactor: Refactor authentication test suite for clarity - Refactor auth.test.ts for improved code quality and readability - Update setupServer scope in describe block - Streamline test process and assertions in auth test file --- packages/backend/src/auth.test.ts | 150 +++++++++--------------------- 1 file changed, 42 insertions(+), 108 deletions(-) diff --git a/packages/backend/src/auth.test.ts b/packages/backend/src/auth.test.ts index 7296bc7..7432a3f 100644 --- a/packages/backend/src/auth.test.ts +++ b/packages/backend/src/auth.test.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/no-shadow */ import request from 'supertest'; -import { decodeOAuthState } from '@backstage/plugin-auth-node'; import { setupServer } from 'msw/node'; import { http, HttpResponse } from 'msw'; import { @@ -11,61 +9,48 @@ import { import { Server } from 'http'; import { githubAuth } from './auth'; -let accessToken: string; - -const githubApiUrl = 'https://api.github.com'; -const githubAuthUrl = 'https://github.com/login/oauth'; +describe('githubAuth', () => { + let server: Server; + let baseUrl: string; + + const mockGithubAuth = setupServer( + // Mock GitHub's OAuth endpoints + http.get('https://github.com/login/oauth/authorize', params => { + const url = new URL(params.request.url); + const callbackUrl = new URL(url.searchParams.get('redirect_uri')!); + callbackUrl.searchParams.set('code', 'github_auth_code'); + callbackUrl.searchParams.set('state', url.searchParams.get('state')!); + return HttpResponse.redirect(callbackUrl.toString()); + }), + + http.post('https://github.com/login/oauth/access_token', () => { + return HttpResponse.json({ + access_token: 'github_access_token', + token_type: 'bearer', + scope: 'read:user', + }); + }), -const handlers = [ - http.get(`${githubAuthUrl}/authorize`, ({ request }) => { - const url = new URL(request.url); - const callbackUrl = new URL(url.searchParams.get('redirect_uri')!); - callbackUrl.searchParams.set('code', 'github_auth_code'); - callbackUrl.searchParams.set('state', url.searchParams.get('state')!); - return HttpResponse.json(null, { - status: 302, - headers: { - location: callbackUrl.toString(), - }, - }); - }), - http.post(`${githubAuthUrl}/access_token`, () => { - accessToken = 'github_access_token'; - return HttpResponse.json({ - access_token: accessToken, - token_type: 'bearer', - scope: 'read:user', - }); - }), - http.get(`${githubApiUrl}/user`, ({ request }) => { - if (request.headers.get('Authorization') === `token ${accessToken}`) { + http.get('https://api.github.com/user', () => { return HttpResponse.json({ login: 'octocat', name: 'Octocat', email: 'octocat@github.com', avatar_url: 'https://github.com/images/error/octocat_happy.gif', }); - } - return new HttpResponse(null, { status: 401 }); - }), -]; + }), + ); -const mswServer = setupServer(...handlers); -registerMswTestHooks(mswServer); - -describe('githubAuth', () => { - let backstageServer: Server; - let appUrl: string; + registerMswTestHooks(mockGithubAuth); beforeAll(async () => { - mswServer.listen(); const backend = await startTestBackend({ features: [ githubAuth, import('@backstage/plugin-auth-backend'), mockServices.rootConfig.factory({ data: { - app: { baseUrl: 'http://localhost' }, + app: { baseUrl: 'http://localhost:3000' }, auth: { providers: { github: { @@ -81,86 +66,35 @@ describe('githubAuth', () => { ], }); - backstageServer = backend.server; - const port = backend.server.port(); - appUrl = `http://localhost:${port}`; - }); - - beforeEach(async () => { - jest.clearAllMocks(); - }); - - afterEach(() => { - mswServer.resetHandlers(); + server = backend.server; + baseUrl = `http://localhost:${backend.server.port()}`; + mockGithubAuth.listen(); }); afterAll(() => { - backstageServer.close(); - mswServer.close(); - }); - - it('should start the GitHub auth flow', async () => { - // Arrange - const agent = request.agent(backstageServer); - - // Act - const startResponse = await agent.get( - '/api/auth/github/start?env=development', - ); - const startUrl = new URL(startResponse.get('location')!); - const expected = Object.fromEntries(startUrl.searchParams); - const state = decodeOAuthState(startUrl.searchParams.get('state')!); - - // Assert - expect(startResponse.status).toEqual(302); - expect(startUrl.origin).toBe('https://github.com'); - expect(startUrl.pathname).toBe('/login/oauth/authorize'); - expect(expected).toEqual({ - client_id: 'github-client-id', - redirect_uri: `${appUrl}/api/auth/github/handler/frame`, - response_type: 'code', - scope: 'read:user', - state: expect.any(String), - }); - expect(state.env).toEqual('development'); - expect(state.nonce).toBeDefined(); - expect(state.scope).toEqual('read:user'); + server.close(); }); it('should complete the GitHub auth flow', async () => { - const agent = request.agent(backstageServer); - // Start the auth flow - const startResponse = await agent.get( - '/api/auth/github/start?env=development', - ); - expect(startResponse.status).toBe(302); - const test = await agent.get( - '/api/auth/github/handler/frame?code=github_auth_code&state=6e6f6e63653d70595572584f37374363597a786f57414c2532465042325125334425334426656e763d646576656c6f706d656e742673636f70653d7265616425334175736572', - ); - expect(test.text).toContain('test'); - expect(startResponse.status).toBe(302); - - const startUrl = new URL(startResponse.get('location')!); - const state = startUrl.searchParams.get('state')!; - const decodedState = decodeOAuthState(state); - const nonce = decodedState.nonce; + const startResponse = await request(server) + .get('/api/auth/github/start?env=development') + .expect(302); - // Set the nonce cookie - agent.jar.setCookie(`github-nonce=${nonce}; Path=/; HttpOnly`); + const startUrl = new URL(startResponse.header.location); + expect(startUrl.searchParams.get('response_type')).not.toBeNull(); + expect(startUrl.searchParams.get('redirect_uri')).not.toBeNull(); // Simulate the OAuth callback - const callbackUrl = new URL(`${appUrl}/api/auth/github/handler/frame`); + const callbackUrl = new URL('/api/auth/github/handler/frame', baseUrl); + callbackUrl.searchParams.set('env', 'development'); callbackUrl.searchParams.set('code', 'github_auth_code'); - callbackUrl.searchParams.set('state', state); + callbackUrl.searchParams.set('state', 'mock_state'); - const handlerResponse = await agent.get( - callbackUrl.href.replace(callbackUrl.origin, ''), + const handlerResponse = await request(server).get( + callbackUrl.pathname + callbackUrl.search, ); - expect(handlerResponse.status).toBe(200); - expect(handlerResponse.text).toContain( - encodeURIComponent(`"accessToken":"${accessToken}"`), - ); + expect(handlerResponse.status).toBe(200); }); });