From 06a672534df800c7034d53568377aeca5f09a238 Mon Sep 17 00:00:00 2001 From: YAEGASHI Takeshi Date: Sun, 31 Mar 2024 10:12:32 +0000 Subject: [PATCH] Add auth-backend-module-azure-easyauth-provider Signed-off-by: YAEGASHI Takeshi --- .changeset/wild-rockets-tell.md | 5 + .../.eslintrc.js | 1 + .../README.md | 5 + .../api-report.md | 40 ++++++ .../catalog-info.yaml | 10 ++ .../package.json | 48 ++++++++ .../src/authenticator.test.ts | 116 ++++++++++++++++++ .../src/authenticator.ts | 77 ++++++++++++ .../src/index.ts | 20 +++ .../src/module.test.ts | 92 ++++++++++++++ .../src/module.ts | 72 +++++++++++ .../src/resolvers.ts | 44 +++++++ .../src/types.ts | 23 ++++ yarn.lock | 22 +++- 14 files changed, 573 insertions(+), 2 deletions(-) create mode 100644 .changeset/wild-rockets-tell.md create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/.eslintrc.js create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/README.md create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/api-report.md create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/package.json create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.test.ts create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.ts create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/index.ts create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/module.test.ts create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/module.ts create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts create mode 100644 plugins/auth-backend-module-azure-easyauth-provider/src/types.ts diff --git a/.changeset/wild-rockets-tell.md b/.changeset/wild-rockets-tell.md new file mode 100644 index 0000000000..ae50a743f2 --- /dev/null +++ b/.changeset/wild-rockets-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-azure-easyauth-provider': minor +--- + +New auth backend module to add `azure-easyauth` provider. diff --git a/plugins/auth-backend-module-azure-easyauth-provider/.eslintrc.js b/plugins/auth-backend-module-azure-easyauth-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-azure-easyauth-provider/README.md b/plugins/auth-backend-module-azure-easyauth-provider/README.md new file mode 100644 index 0000000000..10be6a8af9 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/README.md @@ -0,0 +1,5 @@ +# Auth Backend Module - Azure Easy Auth Provider + +## Links + +- [The Backstage homepage](https://backstage.io) diff --git a/plugins/auth-backend-module-azure-easyauth-provider/api-report.md b/plugins/auth-backend-module-azure-easyauth-provider/api-report.md new file mode 100644 index 0000000000..3bdef66181 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/api-report.md @@ -0,0 +1,40 @@ +## API Report File for "@backstage/plugin-auth-backend-module-azure-easyauth-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Profile } from 'passport'; +import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; + +// @public (undocumented) +const authModuleAzureEasyAuthProvider: () => BackendFeature; +export default authModuleAzureEasyAuthProvider; + +// @public (undocumented) +export const azureEasyAuthAuthenticator: ProxyAuthenticator< + void, + AzureEasyAuthResult, + { + accessToken: string | undefined; + } +>; + +// @public (undocumented) +export type AzureEasyAuthResult = { + fullProfile: Profile; + accessToken?: string; +}; + +// @public (undocumented) +export namespace azureEasyAuthSignInResolvers { + const // (undocumented) + idMatchingUserEntityAnnotation: SignInResolverFactory< + AzureEasyAuthResult, + unknown + >; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml new file mode 100644 index 0000000000..174f1a52e4 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-azure-easyauth-provider + title: '@backstage/plugin-auth-backend-module-azure-easyauth-provider' + description: The azure-easyauth-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json new file mode 100644 index 0000000000..9cb1e5b72c --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -0,0 +1,48 @@ +{ + "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", + "version": "0.0.0", + "description": "The azure-easyauth-provider backend module for the auth plugin.", + "backstage": { + "role": "backend-plugin-module" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/auth-backend-module-azure-easyauth-provider" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/catalog-model": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "@types/passport": "^1.0.16", + "express": "^4.19.2", + "jose": "^5.0.0", + "passport": "^0.7.0" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^" + } +} diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.test.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.test.ts new file mode 100644 index 0000000000..1a5e4dffa4 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + azureEasyAuthAuthenticator, + ID_TOKEN_HEADER, + ACCESS_TOKEN_HEADER, +} from './authenticator'; +import { mockServices } from '@backstage/backend-test-utils'; +import { Request } from 'express'; +import { SignJWT, JWTPayload, errors as JoseErrors } from 'jose'; +import { randomBytes } from 'crypto'; + +const jwtSecret = randomBytes(48); + +async function buildJwt(claims: JWTPayload) { + return await new SignJWT(claims) + .setProtectedHeader({ alg: 'HS256' }) + .sign(jwtSecret); +} + +function mockRequest(headers?: Record) { + return { + header: (name: string) => headers?.[name], + } as unknown as Request; +} + +describe('EasyAuthAuthProvider', () => { + const ctx = azureEasyAuthAuthenticator.initialize({ + config: mockServices.rootConfig(), + }); + + describe('should succeed when', () => { + const claims = { + ver: '2.0', + oid: 'c43063d4-0650-4f3e-ba6b-307473d24dfd', + name: 'Alice Bob', + email: 'alice@bob.com', + preferred_username: 'Another name', + }; + + it('valid id_token provided', async () => { + const request = mockRequest({ + [ID_TOKEN_HEADER]: await buildJwt(claims), + }); + await expect( + azureEasyAuthAuthenticator.authenticate({ req: request }, ctx), + ).resolves.toEqual({ + result: { + fullProfile: { + provider: 'easyauth', + id: 'c43063d4-0650-4f3e-ba6b-307473d24dfd', + displayName: 'Alice Bob', + emails: [{ value: 'alice@bob.com' }], + username: 'Another name', + }, + accessToken: undefined, + }, + providerInfo: { + accessToken: undefined, + }, + }); + }); + + it('valid id_token and access_token provided', async () => { + const request = mockRequest({ + [ID_TOKEN_HEADER]: await buildJwt(claims), + [ACCESS_TOKEN_HEADER]: 'ACCESS_TOKEN', + }); + await expect( + azureEasyAuthAuthenticator.authenticate({ req: request }, ctx), + ).resolves.toMatchObject({ + result: { accessToken: 'ACCESS_TOKEN' }, + providerInfo: { accessToken: 'ACCESS_TOKEN' }, + }); + }); + }); + + describe('should fail when', () => { + it('id token is missing', async () => { + const request = mockRequest(); + await expect( + azureEasyAuthAuthenticator.authenticate({ req: request }, ctx), + ).rejects.toThrow('Missing x-ms-token-aad-id-token header'); + }); + + it('id token is invalid', async () => { + const request = mockRequest({ [ID_TOKEN_HEADER]: 'not-a-jwt' }); + await expect( + azureEasyAuthAuthenticator.authenticate({ req: request }, ctx), + ).rejects.toThrow(JoseErrors.JWTInvalid); + }); + + it('id token is v1', async () => { + const request = mockRequest({ + [ID_TOKEN_HEADER]: await buildJwt({ ver: '1.0' }), + }); + await expect( + azureEasyAuthAuthenticator.authenticate({ req: request }, ctx), + ).rejects.toThrow('id_token is not version 2.0'); + }); + }); +}); diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.ts new file mode 100644 index 0000000000..855f73073b --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/authenticator.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthenticationError } from '@backstage/errors'; +import { createProxyAuthenticator } from '@backstage/plugin-auth-node'; +import { AzureEasyAuthResult } from './types'; +import { Request } from 'express'; +import { Profile } from 'passport'; +import { decodeJwt } from 'jose'; + +export const ID_TOKEN_HEADER = 'x-ms-token-aad-id-token'; +export const ACCESS_TOKEN_HEADER = 'x-ms-token-aad-access-token'; + +/** @public */ +export const azureEasyAuthAuthenticator = createProxyAuthenticator({ + defaultProfileTransform: async (result: AzureEasyAuthResult) => { + return { + profile: { + displayName: result.fullProfile.displayName, + email: result.fullProfile.emails?.[0].value, + picture: result.fullProfile.photos?.[0].value, + }, + }; + }, + initialize() {}, + async authenticate({ req }) { + const result = await getResult(req); + return { + result, + providerInfo: { + accessToken: result.accessToken, + }, + }; + }, +}); + +async function getResult(req: Request): Promise { + const idToken = req.header(ID_TOKEN_HEADER); + const accessToken = req.header(ACCESS_TOKEN_HEADER); + if (idToken === undefined) { + throw new AuthenticationError(`Missing ${ID_TOKEN_HEADER} header`); + } + + return { + fullProfile: idTokenToProfile(idToken), + accessToken: accessToken, + }; +} + +function idTokenToProfile(idToken: string) { + const claims = decodeJwt(idToken); + + if (claims.ver !== '2.0') { + throw new Error('id_token is not version 2.0 '); + } + + return { + id: claims.oid, + displayName: claims.name, + provider: 'easyauth', + emails: [{ value: claims.email }], + username: claims.preferred_username, + } as Profile; +} diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/index.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/index.ts new file mode 100644 index 0000000000..1737aaa7cc --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { authModuleAzureEasyAuthProvider as default } from './module'; +export { azureEasyAuthAuthenticator } from './authenticator'; +export { azureEasyAuthSignInResolvers } from './resolvers'; +export type { AzureEasyAuthResult } from './types'; diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/module.test.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/module.test.ts new file mode 100644 index 0000000000..56932bc321 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/module.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import authPlugin from '@backstage/plugin-auth-backend'; +import { authModuleAzureEasyAuthProvider } from './module'; + +const rootConfig = mockServices.rootConfig.factory({ + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + auth: { + providers: { + azureEasyAuth: { + signIn: { + resolvers: [{ resolver: 'idMatchingUserEntityAnnotation' }], + }, + }, + }, + }, + }, +}); + +const features = [authPlugin, authModuleAzureEasyAuthProvider, rootConfig]; + +describe('authModuleAzureEasyAuthProvider', () => { + const env = process.env; + beforeEach(() => { + jest.resetModules(); + process.env = { ...env }; + }); + afterEach(() => { + process.env = env; + }); + + it('should fail when run outside of Azure App Services', async () => { + await expect(startTestBackend({ features })).rejects.toThrow( + 'Backstage is not running on Azure App Services', + ); + }); + + it('should fail when Azure App Services Auth is not enabled', async () => { + process.env.WEBSITE_SKU = 'Standard'; + process.env.WEBSITE_AUTH_ENABLED = 'False'; + await expect(startTestBackend({ features })).rejects.toThrow( + 'Azure App Services does not have authentication enabled', + ); + }); + + it('should fail when Azure App Services Auth is not AAD', async () => { + process.env.WEBSITE_SKU = 'Standard'; + process.env.WEBSITE_AUTH_ENABLED = 'True'; + process.env.WEBSITE_AUTH_DEFAULT_PROVIDER = 'Facebook'; + await expect(startTestBackend({ features })).rejects.toThrow( + 'Authentication provider is not Entra ID', + ); + }); + + it('should fail when Token Store not enabled', async () => { + process.env.WEBSITE_SKU = 'Standard'; + process.env.WEBSITE_AUTH_ENABLED = 'True'; + process.env.WEBSITE_AUTH_DEFAULT_PROVIDER = 'AzureActiveDirectory'; + process.env.WEBSITE_AUTH_TOKEN_STORE = 'False'; + await expect(startTestBackend({ features })).rejects.toThrow( + 'Token Store is not enabled', + ); + }); + + it('should start successfully when running in Azure App Services with AAD Auth', async () => { + process.env.WEBSITE_SKU = 'Standard'; + process.env.WEBSITE_AUTH_ENABLED = 'True'; + process.env.WEBSITE_AUTH_DEFAULT_PROVIDER = 'AzureActiveDirectory'; + process.env.WEBSITE_AUTH_TOKEN_STORE = 'True'; + await expect(startTestBackend({ features })).resolves.toBeInstanceOf( + Object, + ); + }); +}); diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/module.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/module.ts new file mode 100644 index 0000000000..4a8fffeca4 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/module.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createProxyAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { azureEasyAuthAuthenticator } from './authenticator'; +import { azureEasyAuthSignInResolvers } from './resolvers'; + +/** @public */ +export const authModuleAzureEasyAuthProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'azure-easyauth-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + validateAppServiceConfiguration(process.env); + providers.registerProvider({ + providerId: 'azureEasyAuth', + factory: createProxyAuthProviderFactory({ + authenticator: azureEasyAuthAuthenticator, + signInResolverFactories: { + ...commonSignInResolvers, + ...azureEasyAuthSignInResolvers, + }, + }), + }); + }, + }); + }, +}); + +function validateAppServiceConfiguration(env: NodeJS.ProcessEnv) { + // Based on https://github.com/AzureAD/microsoft-identity-web/blob/f7403779d1a91f4a3fec0ed0993bd82f50f299e1/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationInformation.cs#L38-L59 + // + // It's critical to validate we're really running in a correctly configured Azure App Services, + // As we rely on App Services to manage & validate the ID and Access Token headers + // Without that, this users can be trivially impersonated. + if (env.WEBSITE_SKU === undefined) { + throw new Error('Backstage is not running on Azure App Services'); + } + if (env.WEBSITE_AUTH_ENABLED?.toLowerCase() !== 'true') { + throw new Error('Azure App Services does not have authentication enabled'); + } + if ( + env.WEBSITE_AUTH_DEFAULT_PROVIDER?.toLowerCase() !== 'azureactivedirectory' + ) { + throw new Error('Authentication provider is not Entra ID'); + } + if (process.env.WEBSITE_AUTH_TOKEN_STORE?.toLowerCase() !== 'true') { + throw new Error('Token Store is not enabled'); + } +} diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts new file mode 100644 index 0000000000..e9e35420d1 --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createSignInResolverFactory, + SignInInfo, +} from '@backstage/plugin-auth-node'; +import { AzureEasyAuthResult } from './types'; + +/** @public */ +export namespace azureEasyAuthSignInResolvers { + export const idMatchingUserEntityAnnotation = createSignInResolverFactory({ + create() { + return async (info: SignInInfo, ctx) => { + const { + fullProfile: { id }, + } = info.result; + + if (!id) { + throw new Error('User profile contained no id'); + } + + return await ctx.signInWithCatalogUser({ + annotations: { + 'graph.microsoft.com/user-id': id, + }, + }); + }; + }, + }); +} diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/types.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/types.ts new file mode 100644 index 0000000000..851a31e29b --- /dev/null +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Profile } from 'passport'; + +/** @public */ +export type AzureEasyAuthResult = { + fullProfile: Profile; + accessToken?: string; +}; diff --git a/yarn.lock b/yarn.lock index 8f8116329a..9782f20576 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4793,6 +4793,24 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider": + version: 0.0.0-use.local + resolution: "@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" + "@types/passport": ^1.0.16 + express: ^4.19.2 + jose: ^5.0.0 + passport: ^0.7.0 + languageName: unknown + linkType: soft + "@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:^, @backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider" @@ -19420,7 +19438,7 @@ __metadata: languageName: node linkType: hard -"@types/passport@npm:*, @types/passport@npm:^1.0.11, @types/passport@npm:^1.0.3": +"@types/passport@npm:*, @types/passport@npm:^1.0.11, @types/passport@npm:^1.0.16, @types/passport@npm:^1.0.3": version: 1.0.16 resolution: "@types/passport@npm:1.0.16" dependencies: @@ -27856,7 +27874,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.14.0, express@npm:^4.17.1, express@npm:^4.17.3, express@npm:^4.18.1, express@npm:^4.18.2": +"express@npm:^4.14.0, express@npm:^4.17.1, express@npm:^4.17.3, express@npm:^4.18.1, express@npm:^4.18.2, express@npm:^4.19.2": version: 4.19.2 resolution: "express@npm:4.19.2" dependencies: